*** empty log message ***
[coreutils.git] / src / chown-core.c
blobb38efaa895d4d4ff1191c840599998b791cd572d
1 /* chown-core.c -- core functions for changing ownership.
2 Copyright (C) 2000, 2002 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 "lchown.h"
29 #include "quote.h"
30 #include "savedir.h"
31 #include "chown-core.h"
33 /* The number of decimal digits required to represent the largest value of
34 type `unsigned int'. This is enough for an 8-byte unsigned int type. */
35 #define UINT_MAX_DECIMAL_DIGITS 20
37 #ifndef _POSIX_VERSION
38 struct group *getgrnam ();
39 struct group *getgrgid ();
40 #endif
42 int lstat ();
44 void
45 chopt_init (struct Chown_option *chopt)
47 chopt->verbosity = V_off;
48 chopt->dereference = DEREF_NEVER;
49 chopt->recurse = 0;
50 chopt->force_silent = 0;
51 chopt->user_name = 0;
52 chopt->group_name = 0;
55 void
56 chopt_free (struct Chown_option *chopt)
58 /* Deliberately do not free chopt->user_name or ->group_name.
59 They're not always allocated. */
62 /* Convert N to a string, and return a pointer to that string in memory
63 allocated from the heap. */
65 static char *
66 uint_to_string (unsigned int n)
68 char buf[UINT_MAX_DECIMAL_DIGITS + 1];
69 char *p = buf + sizeof buf;
71 *--p = '\0';
74 *--p = '0' + (n % 10);
75 while ((n /= 10) != 0);
77 return xstrdup (p);
80 /* Convert the numeric group-id, GID, to a string stored in xmalloc'd memory,
81 and return it. If there's no corresponding group name, use the decimal
82 representation of the ID. */
84 char *
85 gid_to_name (gid_t gid)
87 struct group *grp = getgrgid (gid);
88 return grp ? xstrdup (grp->gr_name) : uint_to_string (gid);
91 /* Convert the numeric user-id, UID, to a string stored in xmalloc'd memory,
92 and return it. If there's no corresponding user name, use the decimal
93 representation of the ID. */
95 char *
96 uid_to_name (uid_t uid)
98 struct passwd *pwd = getpwuid (uid);
99 return pwd ? xstrdup (pwd->pw_name) : uint_to_string (uid);
102 /* Tell the user how/if the user and group of FILE have been changed.
103 If USER is NULL, give the group-oriented messages.
104 CHANGED describes what (if anything) has happened. */
106 static void
107 describe_change (const char *file, enum Change_status changed,
108 char const *user, char const *group)
110 const char *fmt;
111 char *spec;
112 int spec_allocated = 0;
114 if (changed == CH_NOT_APPLIED)
116 printf (_("neither symbolic link %s nor referent has been changed\n"),
117 quote (file));
118 return;
121 if (user)
123 if (group)
125 spec = xmalloc (strlen (user) + 1 + strlen (group) + 1);
126 stpcpy (stpcpy (stpcpy (spec, user), ":"), group);
127 spec_allocated = 1;
129 else
131 spec = (char *) user;
134 else
136 spec = (char *) group;
139 switch (changed)
141 case CH_SUCCEEDED:
142 fmt = (user
143 ? _("changed ownership of %s to %s\n")
144 : _("changed group of %s to %s\n"));
145 break;
146 case CH_FAILED:
147 fmt = (user
148 ? _("failed to change ownership of %s to %s\n")
149 : _("failed to change group of %s to %s\n"));
150 break;
151 case CH_NO_CHANGE_REQUESTED:
152 fmt = (user
153 ? _("ownership of %s retained as %s\n")
154 : _("group of %s retained as %s\n"));
155 break;
156 default:
157 abort ();
160 printf (fmt, quote (file), spec);
162 if (spec_allocated)
163 free (spec);
166 /* Recursively change the ownership of the files in directory DIR to user-id,
167 UID, and group-id, GID, according to the options specified by CHOPT.
168 Return 0 if successful, 1 if errors occurred. */
170 static int
171 change_dir_owner (const char *dir, uid_t uid, gid_t gid,
172 uid_t old_uid, gid_t old_gid,
173 struct Chown_option const *chopt)
175 char *name_space, *namep;
176 char *path; /* Full path of each entry to process. */
177 unsigned dirlength; /* Length of `dir' and '\0'. */
178 unsigned filelength; /* Length of each pathname to process. */
179 unsigned pathlength; /* Bytes allocated for `path'. */
180 int errors = 0;
182 name_space = savedir (dir);
183 if (name_space == NULL)
185 if (chopt->force_silent == 0)
186 error (0, errno, "%s", quote (dir));
187 return 1;
190 dirlength = strlen (dir) + 1; /* + 1 is for the trailing '/'. */
191 pathlength = dirlength + 1;
192 /* Give `path' a dummy value; it will be reallocated before first use. */
193 path = xmalloc (pathlength);
194 strcpy (path, dir);
195 path[dirlength - 1] = '/';
197 for (namep = name_space; *namep; namep += filelength - dirlength)
199 filelength = dirlength + strlen (namep) + 1;
200 if (filelength > pathlength)
202 pathlength = filelength * 2;
203 path = xrealloc (path, pathlength);
205 strcpy (path + dirlength, namep);
206 errors |= change_file_owner (0, path, uid, gid, old_uid, old_gid,
207 chopt);
209 free (path);
210 free (name_space);
211 return errors;
214 /* Change the ownership of FILE to user-id, UID, and group-id, GID,
215 provided it presently has owner OLD_UID and group OLD_GID.
216 Honor the options specified by CHOPT.
217 If FILE is a directory and -R is given, recurse.
218 Return 0 if successful, 1 if errors occurred. */
221 change_file_owner (int cmdline_arg, const char *file, uid_t uid, gid_t gid,
222 uid_t old_uid, gid_t old_gid,
223 struct Chown_option const *chopt)
225 struct stat file_stats;
226 uid_t new_uid;
227 gid_t new_gid;
228 int errors = 0;
229 int is_symlink;
230 int is_directory;
232 if (lstat (file, &file_stats))
234 if (chopt->force_silent == 0)
235 error (0, errno, _("failed to get attributes of %s"), quote (file));
236 return 1;
239 /* If it's a symlink and we're dereferencing, then use stat
240 to get the attributes of the referent. */
241 if (S_ISLNK (file_stats.st_mode))
243 if (chopt->dereference == DEREF_ALWAYS
244 && stat (file, &file_stats))
246 if (chopt->force_silent == 0)
247 error (0, errno, _("failed to get attributes of %s"), quote (file));
248 return 1;
251 is_symlink = 1;
253 /* With -R, don't traverse through symlinks-to-directories.
254 But of course, this will all change with POSIX's new
255 -H, -L, -P options. */
256 is_directory = 0;
258 else
260 is_symlink = 0;
261 is_directory = S_ISDIR (file_stats.st_mode);
264 if ((old_uid == (uid_t) -1 || file_stats.st_uid == old_uid)
265 && (old_gid == (gid_t) -1 || file_stats.st_gid == old_gid))
267 new_uid = (uid == (uid_t) -1 ? file_stats.st_uid : uid);
268 new_gid = (gid == (gid_t) -1 ? file_stats.st_gid : gid);
269 if (new_uid != file_stats.st_uid || new_gid != file_stats.st_gid)
271 int fail;
272 int symlink_changed = 1;
273 int saved_errno;
274 int called_lchown = 0;
276 if (is_symlink)
278 if (chopt->dereference == DEREF_NEVER)
280 called_lchown = 1;
281 fail = lchown (file, new_uid, new_gid);
283 /* Ignore the failure if it's due to lack of support (ENOSYS)
284 and this is not a command line argument. */
285 if (!cmdline_arg && fail && errno == ENOSYS)
287 fail = 0;
288 symlink_changed = 0;
291 else if (chopt->dereference == DEREF_ALWAYS)
293 /* Applying chown to a symlink and expecting it to affect
294 the referent is not portable. So instead, open the
295 file and use fchown on the resulting descriptor. */
296 int fd = open (file, O_RDONLY | O_NONBLOCK | O_NOCTTY);
297 fail = (fd == -1 ? 1 : fchown (fd, new_uid, new_gid));
299 else
301 /* FIXME */
302 abort ();
305 else
307 fail = chown (file, new_uid, new_gid);
309 saved_errno = errno;
311 if (chopt->verbosity == V_high
312 || (chopt->verbosity == V_changes_only && !fail))
314 enum Change_status ch_status = (! symlink_changed
315 ? CH_NOT_APPLIED
316 : (fail
317 ? CH_FAILED : CH_SUCCEEDED));
318 describe_change (file, ch_status,
319 chopt->user_name, chopt->group_name);
322 if (fail)
324 if (chopt->force_silent == 0)
325 error (0, saved_errno, (uid != (uid_t) -1
326 ? _("changing ownership of %s")
327 : _("changing group of %s")),
328 quote (file));
329 errors = 1;
331 else
333 /* The change succeeded. On some systems, the chown function
334 resets the `special' permission bits. When run by a
335 `privileged' user, this program must ensure that at least
336 the set-uid and set-group ones are still set. */
337 if (file_stats.st_mode & ~(S_IFMT | S_IRWXUGO)
338 /* If we called lchown above (which means this is a symlink),
339 then skip it. */
340 && ! called_lchown)
342 if (chmod (file, file_stats.st_mode))
344 error (0, saved_errno,
345 _("unable to restore permissions of %s"),
346 quote (file));
347 fail = 1;
352 else if (chopt->verbosity == V_high)
354 describe_change (file, CH_NO_CHANGE_REQUESTED,
355 chopt->user_name, chopt->group_name);
359 if (chopt->recurse && is_directory)
360 errors |= change_dir_owner (file, uid, gid, old_uid, old_gid, chopt);
361 return errors;