diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/chown-core.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/chown-core.c b/src/chown-core.c new file mode 100644 index 000000000..73568c2c4 --- /dev/null +++ b/src/chown-core.c @@ -0,0 +1,252 @@ +/* chown-core.c -- core functions for changing ownership. + Copyright (C) 2000 Free Software Foundation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* Extracted from chown.c/chgrp.c and librarified by Jim Meyering. */ + +#include <config.h> +#include <stdio.h> +#include <sys/types.h> + +#include "system.h" +#include "error.h" +#include "lchown.h" +#include "quote.h" +#include "savedir.h" +#include "chown-core.h" + +void +chopt_init (struct Chown_option *chopt) +{ + chopt->verbosity = V_off; + chopt->change_symlinks = 1; + chopt->recurse = 0; + chopt->force_silent = 0; + chopt->user_name = 0; + chopt->group_name = 0; +} + +/* Tell the user how/if the user and group of FILE have been changed. + If USER is NULL, give the group-oriented messages. + CHANGED describes what (if anything) has happened. */ + +static void +describe_change (const char *file, enum Change_status changed, + char const *user, char const *group) +{ + const char *fmt; + + if (changed == CH_NOT_APPLIED) + { + printf (_("neither symbolic link %s nor referent has been changed\n"), + quote (file)); + return; + } + + if (user == NULL) + { + switch (changed) + { + case CH_SUCCEEDED: + fmt = _("group of %s changed to %s\n"); + break; + case CH_FAILED: + fmt = _("failed to change group of %s to %s\n"); + break; + case CH_NO_CHANGE_REQUESTED: + fmt = _("group of %s retained as %s\n"); + break; + default: + abort (); + } + printf (fmt, quote (file), group); + } + else + { + switch (changed) + { + case CH_SUCCEEDED: + fmt = _("ownership of %s changed to "); + break; + case CH_FAILED: + fmt = _("failed to change ownership of %s to "); + break; + case CH_NO_CHANGE_REQUESTED: + fmt = _("ownership of %s retained as "); + break; + default: + abort (); + } + printf (fmt, file); + if (group) + printf ("%s.%s\n", user, group); + else + printf ("%s\n", user); + } +} + +/* Recursively change the ownership of the files in directory DIR + to UID USER and GID GROUP, according to the options specified by CHOPT. + STATP points to the results of lstat on DIR. + Return 0 if successful, 1 if errors occurred. */ + +static int +change_dir_owner (const char *dir, uid_t user, gid_t group, + uid_t old_user, gid_t old_group, + const struct stat *statp, + struct Chown_option const *chopt) +{ + char *name_space, *namep; + char *path; /* Full path of each entry to process. */ + unsigned dirlength; /* Length of `dir' and '\0'. */ + unsigned filelength; /* Length of each pathname to process. */ + unsigned pathlength; /* Bytes allocated for `path'. */ + int errors = 0; + + name_space = savedir (dir, statp->st_size); + if (name_space == NULL) + { + if (chopt->force_silent == 0) + error (0, errno, "%s", quote (dir)); + return 1; + } + + dirlength = strlen (dir) + 1; /* + 1 is for the trailing '/'. */ + pathlength = dirlength + 1; + /* Give `path' a dummy value; it will be reallocated before first use. */ + path = xmalloc (pathlength); + strcpy (path, dir); + path[dirlength - 1] = '/'; + + for (namep = name_space; *namep; namep += filelength - dirlength) + { + filelength = dirlength + strlen (namep) + 1; + if (filelength > pathlength) + { + pathlength = filelength * 2; + path = xrealloc (path, pathlength); + } + strcpy (path + dirlength, namep); + errors |= change_file_owner (0, path, user, group, old_user, old_group, + chopt); + } + free (path); + free (name_space); + return errors; +} + +/* Change the ownership of FILE to UID USER and GID GROUP + provided it presently has UID OLDUSER and GID OLDGROUP. + Honor the options specified by CHOPT. + If FILE is a directory and -R is given, recurse. + Return 0 if successful, 1 if errors occurred. */ + +int +change_file_owner (int cmdline_arg, const char *file, uid_t user, gid_t group, + uid_t old_user, gid_t old_group, + struct Chown_option const *chopt) +{ + struct stat file_stats; + uid_t newuser; + gid_t newgroup; + int errors = 0; + + if (lstat (file, &file_stats)) + { + if (chopt->force_silent == 0) + error (0, errno, _("getting attributes of %s"), quote (file)); + return 1; + } + + if ((old_user == (uid_t) -1 || file_stats.st_uid == old_user) && + (old_group == (gid_t) -1 || file_stats.st_gid == old_group)) + { + newuser = user == (uid_t) -1 ? file_stats.st_uid : user; + newgroup = group == (gid_t) -1 ? file_stats.st_gid : group; + if (newuser != file_stats.st_uid || newgroup != file_stats.st_gid) + { + int fail; + int symlink_changed = 1; + int saved_errno; + + if (S_ISLNK (file_stats.st_mode) && chopt->change_symlinks) + { + fail = lchown (file, newuser, newgroup); + + /* Ignore the failure if it's due to lack of support (ENOSYS) + and this is not a command line argument. */ + if (!cmdline_arg && fail && errno == ENOSYS) + { + fail = 0; + symlink_changed = 0; + } + } + else + { + fail = chown (file, newuser, newgroup); + } + saved_errno = errno; + + if (chopt->verbosity == V_high + || (chopt->verbosity == V_changes_only && !fail)) + { + enum Change_status ch_status = (! symlink_changed + ? CH_NOT_APPLIED + : (fail + ? CH_FAILED : CH_SUCCEEDED)); + describe_change (file, ch_status, + chopt->user_name, chopt->group_name); + } + + if (fail) + { + if (chopt->force_silent == 0) + error (0, saved_errno, _("changing ownership of %s"), + quote (file)); + errors = 1; + } + else + { + /* The change succeeded. On some systems, the chown function + resets the `special' permission bits. When run by a + `privileged' user, this program must ensure that at least + the set-uid and set-group ones are still set. */ + if (file_stats.st_mode & ~S_IRWXUGO + /* If this is a symlink and we changed *it*, then skip it. */ + && ! (S_ISLNK (file_stats.st_mode) && chopt->change_symlinks)) + { + if (chmod (file, file_stats.st_mode)) + { + error (0, saved_errno, + _("unable to restore permissions of %s"), + quote (file)); + fail = 1; + } + } + } + } + else if (chopt->verbosity == V_high) + { + describe_change (file, CH_NO_CHANGE_REQUESTED, + chopt->user_name, chopt->group_name); + } + } + + if (chopt->recurse && S_ISDIR (file_stats.st_mode)) + errors |= change_dir_owner (file, user, group, + old_user, old_group, &file_stats, chopt); + return errors; +} |