.
[coreutils.git] / src / chown-core.c
blobba2c5c3df7f4177a589057b88430bd03a4bcbae8
1 /* chown-core.c -- core functions for changing ownership.
2 Copyright (C) 2000, 2002, 2003 Free Software Foundation.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 /* Extracted from chown.c/chgrp.c and librarified by Jim Meyering. */
20 #include <config.h>
21 #include <stdio.h>
22 #include <sys/types.h>
23 #include <pwd.h>
24 #include <grp.h>
26 #include "system.h"
27 #include "error.h"
28 #include "xfts.h"
29 #include "lchown.h"
30 #include "quote.h"
31 #include "root-dev-ino.h"
32 #include "savedir.h"
33 #include "chown-core.h"
35 /* The number of decimal digits required to represent the largest value of
36 type `unsigned int'. This is enough for an 8-byte unsigned int type. */
37 #define UINT_MAX_DECIMAL_DIGITS 20
39 #ifndef _POSIX_VERSION
40 struct group *getgrnam ();
41 struct group *getgrgid ();
42 #endif
44 void
45 chopt_init (struct Chown_option *chopt)
47 chopt->verbosity = V_off;
48 chopt->root_dev_ino = NULL;
49 chopt->affect_symlink_referent = false;
50 chopt->recurse = false;
51 chopt->force_silent = false;
52 chopt->user_name = 0;
53 chopt->group_name = 0;
56 void
57 chopt_free (struct Chown_option *chopt)
59 /* Deliberately do not free chopt->user_name or ->group_name.
60 They're not always allocated. */
63 /* Convert N to a string, and return a pointer to that string in memory
64 allocated from the heap. */
66 static char *
67 uint_to_string (unsigned int n)
69 char buf[UINT_MAX_DECIMAL_DIGITS + 1];
70 char *p = buf + sizeof buf;
72 *--p = '\0';
75 *--p = '0' + (n % 10);
76 while ((n /= 10) != 0);
78 return xstrdup (p);
81 /* Convert the numeric group-id, GID, to a string stored in xmalloc'd memory,
82 and return it. If there's no corresponding group name, use the decimal
83 representation of the ID. */
85 char *
86 gid_to_name (gid_t gid)
88 struct group *grp = getgrgid (gid);
89 return grp ? xstrdup (grp->gr_name) : uint_to_string (gid);
92 /* Convert the numeric user-id, UID, to a string stored in xmalloc'd memory,
93 and return it. If there's no corresponding user name, use the decimal
94 representation of the ID. */
96 char *
97 uid_to_name (uid_t uid)
99 struct passwd *pwd = getpwuid (uid);
100 return pwd ? xstrdup (pwd->pw_name) : uint_to_string (uid);
103 /* Tell the user how/if the user and group of FILE have been changed.
104 If USER is NULL, give the group-oriented messages.
105 CHANGED describes what (if anything) has happened. */
107 static void
108 describe_change (const char *file, enum Change_status changed,
109 char const *user, char const *group)
111 const char *fmt;
112 char *spec;
113 int spec_allocated = 0;
115 if (changed == CH_NOT_APPLIED)
117 printf (_("neither symbolic link %s nor referent has been changed\n"),
118 quote (file));
119 return;
122 if (user)
124 if (group)
126 spec = xmalloc (strlen (user) + 1 + strlen (group) + 1);
127 stpcpy (stpcpy (stpcpy (spec, user), ":"), group);
128 spec_allocated = 1;
130 else
132 spec = (char *) user;
135 else
137 spec = (char *) group;
140 switch (changed)
142 case CH_SUCCEEDED:
143 fmt = (user
144 ? _("changed ownership of %s to %s\n")
145 : _("changed group of %s to %s\n"));
146 break;
147 case CH_FAILED:
148 fmt = (user
149 ? _("failed to change ownership of %s to %s\n")
150 : _("failed to change group of %s to %s\n"));
151 break;
152 case CH_NO_CHANGE_REQUESTED:
153 fmt = (user
154 ? _("ownership of %s retained as %s\n")
155 : _("group of %s retained as %s\n"));
156 break;
157 default:
158 abort ();
161 printf (fmt, quote (file), spec);
163 if (spec_allocated)
164 free (spec);
167 /* Change the owner and/or group of the file specified by FTS and ENT
168 to UID and/or GID as appropriate.
169 FIXME: describe old_uid and old_gid.
170 CHOPT specifies additional options.
171 Return nonzero upon error, zero otherwise. */
172 static int
173 change_file_owner (FTS *fts, FTSENT *ent,
174 uid_t uid, gid_t gid,
175 uid_t old_uid, gid_t old_gid,
176 struct Chown_option const *chopt)
178 const char *file_full_name = ent->fts_path;
179 struct stat *file_stats = ent->fts_statp;
180 int errors = 0;
182 /* This is the second time we've seen this directory. */
183 if (ent->fts_info == FTS_DP)
184 return 0;
186 switch (ent->fts_info)
188 case FTS_NS:
189 error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
190 return 1;
192 case FTS_ERR:
193 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
194 return 1;
196 case FTS_DNR:
197 error (0, ent->fts_errno, _("cannot read directory %s"),
198 quote (file_full_name));
199 return 1;
201 default:
202 break;
205 if (ROOT_DEV_INO_CHECK (chopt->root_dev_ino, file_stats))
207 ROOT_DEV_INO_WARN (file_full_name);
208 return 1;
211 if ((old_uid == (uid_t) -1 || file_stats->st_uid == old_uid)
212 && (old_gid == (gid_t) -1 || file_stats->st_gid == old_gid))
214 uid_t new_uid = (uid == (uid_t) -1 ? file_stats->st_uid : uid);
215 gid_t new_gid = (gid == (gid_t) -1 ? file_stats->st_gid : gid);
216 if (new_uid != file_stats->st_uid || new_gid != file_stats->st_gid)
218 const char *file = ent->fts_accpath;
219 int fail;
220 int symlink_changed = 1;
221 int saved_errno;
223 if (S_ISLNK (file_stats->st_mode))
225 if (chopt->affect_symlink_referent)
227 /* Applying chown to a symlink and expecting it to affect
228 the referent is not portable. So instead, open the
229 file and use fchown on the resulting descriptor. */
230 int fd = open (file, O_RDONLY | O_NONBLOCK | O_NOCTTY);
231 fail = (fd == -1 ? 1 : fchown (fd, new_uid, new_gid));
232 if (fd != -1)
233 close (fd);
235 else
237 bool is_command_line_argument = (ent->fts_level == 1);
238 fail = lchown (file, new_uid, new_gid);
240 /* Ignore the failure if it's due to lack of support (ENOSYS)
241 and this is not a command line argument. */
242 if (!is_command_line_argument && fail && errno == ENOSYS)
244 fail = 0;
245 symlink_changed = 0;
249 else
251 fail = chown (file, new_uid, new_gid);
253 saved_errno = errno;
255 if (chopt->verbosity == V_high
256 || (chopt->verbosity == V_changes_only && !fail))
258 enum Change_status ch_status = (! symlink_changed
259 ? CH_NOT_APPLIED
260 : (fail
261 ? CH_FAILED : CH_SUCCEEDED));
262 describe_change (file_full_name, ch_status,
263 chopt->user_name, chopt->group_name);
266 if (fail)
268 if ( ! chopt->force_silent)
269 error (0, saved_errno, (uid != (uid_t) -1
270 ? _("changing ownership of %s")
271 : _("changing group of %s")),
272 quote (file_full_name));
273 errors = 1;
275 else
277 /* The change succeeded. On some systems (e.g., Linux-2.4.x),
278 the chown function resets the `special' permission bits.
279 Do *not* restore those bits; doing so would open a window in
280 which a malicious user, M, could subvert a chown command run
281 by some other user and operating on files in a directory
282 where M has write access. */
285 else if (chopt->verbosity == V_high)
287 describe_change (file_full_name, CH_NO_CHANGE_REQUESTED,
288 chopt->user_name, chopt->group_name);
292 if ( ! chopt->recurse)
293 fts_set (fts, ent, FTS_SKIP);
295 return errors;
298 /* Change the owner and/or group of the specified FILES.
299 BIT_FLAGS specifies how to treat each symlink-to-directory
300 that is encountered during a recursive traversal.
301 CHOPT specifies additional options.
302 If UID is not -1, then change the owner id of each file to UID.
303 If GID is not -1, then change the group id of each file to GID.
304 If REQUIRED_UID and/or REQUIRED_GID is not -1, then change only
305 files with user ID and group ID that match the non-(-1) value(s).
306 Return nonzero upon error, zero otherwise. */
308 chown_files (char **files, int bit_flags,
309 uid_t uid, gid_t gid,
310 uid_t required_uid, gid_t required_gid,
311 struct Chown_option const *chopt)
313 int fail = 0;
315 FTS *fts = xfts_open (files, bit_flags, NULL);
317 while (1)
319 FTSENT *ent;
321 ent = fts_read (fts);
322 if (ent == NULL)
324 if (errno != 0)
326 /* FIXME: try to give a better message */
327 error (0, errno, _("fts_read failed"));
328 fail = 1;
330 break;
333 fail |= change_file_owner (fts, ent, uid, gid,
334 required_uid, required_gid, chopt);
337 /* Ignore failure, since the only way it can do so is in failing to
338 return to the original directory, and since we're about to exit,
339 that doesn't matter. */
340 fts_close (fts);
342 return fail;