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)
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. */
22 #include <sys/types.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 ();
45 chopt_init (struct Chown_option
*chopt
)
47 chopt
->verbosity
= V_off
;
48 chopt
->dereference
= DEREF_NEVER
;
50 chopt
->force_silent
= 0;
52 chopt
->group_name
= 0;
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. */
66 uint_to_string (unsigned int n
)
68 char buf
[UINT_MAX_DECIMAL_DIGITS
+ 1];
69 char *p
= buf
+ sizeof buf
;
74 *--p
= '0' + (n
% 10);
75 while ((n
/= 10) != 0);
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. */
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. */
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. */
107 describe_change (const char *file
, enum Change_status changed
,
108 char const *user
, char const *group
)
112 int spec_allocated
= 0;
114 if (changed
== CH_NOT_APPLIED
)
116 printf (_("neither symbolic link %s nor referent has been changed\n"),
125 spec
= xmalloc (strlen (user
) + 1 + strlen (group
) + 1);
126 stpcpy (stpcpy (stpcpy (spec
, user
), ":"), group
);
131 spec
= (char *) user
;
136 spec
= (char *) group
;
143 ? _("changed ownership of %s to %s\n")
144 : _("changed group of %s to %s\n"));
148 ? _("failed to change ownership of %s to %s\n")
149 : _("failed to change group of %s to %s\n"));
151 case CH_NO_CHANGE_REQUESTED
:
153 ? _("ownership of %s retained as %s\n")
154 : _("group of %s retained as %s\n"));
160 printf (fmt
, quote (file
), 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. */
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'. */
182 name_space
= savedir (dir
);
183 if (name_space
== NULL
)
185 if (chopt
->force_silent
== 0)
186 error (0, errno
, "%s", quote (dir
));
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
);
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
,
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
;
232 if (lstat (file
, &file_stats
))
234 if (chopt
->force_silent
== 0)
235 error (0, errno
, _("failed to get attributes of %s"), quote (file
));
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
));
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. */
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
)
272 int symlink_changed
= 1;
274 int called_lchown
= 0;
278 if (chopt
->dereference
== DEREF_NEVER
)
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
)
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
));
307 fail
= chown (file
, new_uid
, new_gid
);
311 if (chopt
->verbosity
== V_high
312 || (chopt
->verbosity
== V_changes_only
&& !fail
))
314 enum Change_status ch_status
= (! symlink_changed
317 ? CH_FAILED
: CH_SUCCEEDED
));
318 describe_change (file
, ch_status
,
319 chopt
->user_name
, chopt
->group_name
);
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")),
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),
342 if (chmod (file
, file_stats
.st_mode
))
344 error (0, saved_errno
,
345 _("unable to restore permissions of %s"),
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
);