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)
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 "root-dev-ino.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 ();
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;
53 chopt
->group_name
= 0;
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. */
67 uint_to_string (unsigned int n
)
69 char buf
[UINT_MAX_DECIMAL_DIGITS
+ 1];
70 char *p
= buf
+ sizeof buf
;
75 *--p
= '0' + (n
% 10);
76 while ((n
/= 10) != 0);
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. */
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. */
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. */
108 describe_change (const char *file
, enum Change_status changed
,
109 char const *user
, char const *group
)
113 int spec_allocated
= 0;
115 if (changed
== CH_NOT_APPLIED
)
117 printf (_("neither symbolic link %s nor referent has been changed\n"),
126 spec
= xmalloc (strlen (user
) + 1 + strlen (group
) + 1);
127 stpcpy (stpcpy (stpcpy (spec
, user
), ":"), group
);
132 spec
= (char *) user
;
137 spec
= (char *) group
;
144 ? _("changed ownership of %s to %s\n")
145 : _("changed group of %s to %s\n"));
149 ? _("failed to change ownership of %s to %s\n")
150 : _("failed to change group of %s to %s\n"));
152 case CH_NO_CHANGE_REQUESTED
:
154 ? _("ownership of %s retained as %s\n")
155 : _("group of %s retained as %s\n"));
161 printf (fmt
, quote (file
), 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. */
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
;
182 /* This is the second time we've seen this directory. */
183 if (ent
->fts_info
== FTS_DP
)
186 switch (ent
->fts_info
)
189 error (0, ent
->fts_errno
, _("cannot access %s"), quote (file_full_name
));
193 error (0, ent
->fts_errno
, _("%s"), quote (file_full_name
));
197 error (0, ent
->fts_errno
, _("cannot read directory %s"),
198 quote (file_full_name
));
205 if (ROOT_DEV_INO_CHECK (chopt
->root_dev_ino
, file_stats
))
207 ROOT_DEV_INO_WARN (file_full_name
);
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
;
220 int symlink_changed
= 1;
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
));
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
)
251 fail
= chown (file
, new_uid
, new_gid
);
255 if (chopt
->verbosity
== V_high
256 || (chopt
->verbosity
== V_changes_only
&& !fail
))
258 enum Change_status ch_status
= (! symlink_changed
261 ? CH_FAILED
: CH_SUCCEEDED
));
262 describe_change (file_full_name
, ch_status
,
263 chopt
->user_name
, chopt
->group_name
);
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
));
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
);
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
)
315 FTS
*fts
= xfts_open (files
, bit_flags
, NULL
);
321 ent
= fts_read (fts
);
326 /* FIXME: try to give a better message */
327 error (0, errno
, _("fts_read failed"));
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. */