From 9c81925d00e1ec140ad8c040c904160bb2ea69b2 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Wed, 15 Oct 2003 13:56:28 +0000 Subject: Rewrite to iterate through hierarchies using fts rather than via explicit recursion. Include "fts_.h" (change_file_owner): Rewrite to use FTS* and FTSENT* and to operate on a single file at a time. (chown_files): New function. --- src/chown-core.c | 231 ++++++++++++++++++++++++++----------------------------- 1 file changed, 108 insertions(+), 123 deletions(-) (limited to 'src/chown-core.c') diff --git a/src/chown-core.c b/src/chown-core.c index 1ec46f15c..25b273bf5 100644 --- a/src/chown-core.c +++ b/src/chown-core.c @@ -25,6 +25,7 @@ #include "system.h" #include "error.h" +#include "fts_.h" #include "lchown.h" #include "quote.h" #include "savedir.h" @@ -39,15 +40,13 @@ struct group *getgrnam (); struct group *getgrgid (); #endif -int lstat (); - void chopt_init (struct Chown_option *chopt) { chopt->verbosity = V_off; - chopt->dereference = DEREF_NEVER; - chopt->recurse = 0; - chopt->force_silent = 0; + chopt->affect_symlink_referent = false; + chopt->recurse = false; + chopt->force_silent = false; chopt->user_name = 0; chopt->group_name = 0; } @@ -163,130 +162,40 @@ describe_change (const char *file, enum Change_status changed, free (spec); } -/* Recursively change the ownership of the files in directory DIR to user-id, - UID, and group-id, GID, according to the options specified by CHOPT. - Return 0 if successful, 1 if errors occurred. */ - +/* Change the owner and/or group of the file specified by FTS and ENT + to UID and/or GID as appropriate. + FIXME: describe old_uid and old_gid. + CHOPT specifies additional options. + Return nonzero upon error, zero otherwise. */ static int -change_dir_owner (const char *dir, uid_t uid, gid_t gid, - uid_t old_uid, gid_t old_gid, - 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); - 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, uid, gid, old_uid, old_gid, - chopt); - } - free (path); - free (name_space); - return errors; -} - -/* Change the ownership of FILE to user-id, UID, and group-id, GID, - provided it presently has owner OLD_UID and group OLD_GID. - 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 uid, gid_t gid, +change_file_owner (FTS *fts, FTSENT *ent, + uid_t uid, gid_t gid, uid_t old_uid, gid_t old_gid, struct Chown_option const *chopt) { - struct stat file_stats; - uid_t new_uid; - gid_t new_gid; + struct stat *file_stats = ent->fts_statp; int errors = 0; - int is_symlink; - int is_directory; - if (lstat (file, &file_stats)) - { - if (chopt->force_silent == 0) - error (0, errno, _("failed to get attributes of %s"), quote (file)); - return 1; - } + /* This is the second time we've seen this directory. */ + if (ent->fts_info == FTS_DP) + return 0; - /* If it's a symlink and we're dereferencing, then use stat - to get the attributes of the referent. */ - if (S_ISLNK (file_stats.st_mode)) + if ((old_uid == (uid_t) -1 || file_stats->st_uid == old_uid) + && (old_gid == (gid_t) -1 || file_stats->st_gid == old_gid)) { - if (chopt->dereference == DEREF_ALWAYS - && stat (file, &file_stats)) - { - if (chopt->force_silent == 0) - error (0, errno, _("failed to get attributes of %s"), quote (file)); - return 1; - } - - is_symlink = 1; - - /* With -R, don't traverse through symlinks-to-directories. - But of course, this will all change with POSIX's new - -H, -L, -P options. */ - is_directory = 0; - } - else - { - is_symlink = 0; - is_directory = S_ISDIR (file_stats.st_mode); - } - - if ((old_uid == (uid_t) -1 || file_stats.st_uid == old_uid) - && (old_gid == (gid_t) -1 || file_stats.st_gid == old_gid)) - { - new_uid = (uid == (uid_t) -1 ? file_stats.st_uid : uid); - new_gid = (gid == (gid_t) -1 ? file_stats.st_gid : gid); - if (new_uid != file_stats.st_uid || new_gid != file_stats.st_gid) + const char *file_full_name = ent->fts_path; + uid_t new_uid = (uid == (uid_t) -1 ? file_stats->st_uid : uid); + gid_t new_gid = (gid == (gid_t) -1 ? file_stats->st_gid : gid); + if (new_uid != file_stats->st_uid || new_gid != file_stats->st_gid) { + const char *file = ent->fts_accpath; int fail; int symlink_changed = 1; int saved_errno; - if (is_symlink) + if (S_ISLNK (file_stats->st_mode)) { - if (chopt->dereference == DEREF_NEVER) - { - fail = lchown (file, new_uid, new_gid); - - /* 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 if (chopt->dereference == DEREF_ALWAYS) + if (chopt->affect_symlink_referent) { /* Applying chown to a symlink and expecting it to affect the referent is not portable. So instead, open the @@ -298,8 +207,16 @@ change_file_owner (int cmdline_arg, const char *file, uid_t uid, gid_t gid, } else { - /* FIXME */ - abort (); + bool is_command_line_argument = (ent->fts_level == 1); + fail = lchown (file, new_uid, new_gid); + + /* Ignore the failure if it's due to lack of support (ENOSYS) + and this is not a command line argument. */ + if (!is_command_line_argument && fail && errno == ENOSYS) + { + fail = 0; + symlink_changed = 0; + } } } else @@ -315,17 +232,17 @@ change_file_owner (int cmdline_arg, const char *file, uid_t uid, gid_t gid, ? CH_NOT_APPLIED : (fail ? CH_FAILED : CH_SUCCEEDED)); - describe_change (file, ch_status, + describe_change (file_full_name, ch_status, chopt->user_name, chopt->group_name); } if (fail) { - if (chopt->force_silent == 0) + if ( ! chopt->force_silent) error (0, saved_errno, (uid != (uid_t) -1 ? _("changing ownership of %s") : _("changing group of %s")), - quote (file)); + quote (file_full_name)); errors = 1; } else @@ -340,12 +257,80 @@ change_file_owner (int cmdline_arg, const char *file, uid_t uid, gid_t gid, } else if (chopt->verbosity == V_high) { - describe_change (file, CH_NO_CHANGE_REQUESTED, + describe_change (file_full_name, CH_NO_CHANGE_REQUESTED, chopt->user_name, chopt->group_name); } } - if (chopt->recurse && is_directory) - errors |= change_dir_owner (file, uid, gid, old_uid, old_gid, chopt); + if ( ! chopt->recurse) + fts_set (fts, ent, FTS_SKIP); + return errors; } + +/* Change the owner and/or group of the specified FILES. + BIT_FLAGS specifies how to treat each symlink-to-directory + that is encountered during a recursive traversal. + CHOPT specifies additional options. + If UID is not -1, then change the owner id of each file to UID. + If GID is not -1, then change the group id of each file to GID. + If REQUIRED_UID and/or REQUIRED_GID is not -1, then change only + files with user ID and group ID that match the non-(-1) value(s). + Return nonzero upon error, zero otherwise. */ +int +chown_files (char **files, int bit_flags, + uid_t uid, gid_t gid, + uid_t required_uid, gid_t required_gid, + struct Chown_option const *chopt) +{ + int fail = 0; + + FTS *fts = fts_open (files, bit_flags, NULL); + if (fts == NULL) + { + /* This can fail in three ways: out of memory, invalid bit_flags, + and one of the FILES is an empty string. + We could try to decipher that errno==EINVAL means invalid + bit_flags and errno==ENOENT, but that seems wrong. Ideally + fts_open would return a proper error indicator. For now, + we'll presume that the bit_flags are valid and just check for + empty strings. */ + bool invalid_arg = false; + for (; *files; ++files) + { + if (**files == '\0') + invalid_arg = true; + } + if (invalid_arg) + error (EXIT_FAILURE, 0, _("invalid argument: %s"), quote ("")); + else + xalloc_die (); + } + + while (1) + { + FTSENT *ent; + + ent = fts_read (fts); + if (ent == NULL) + { + if (errno != 0) + { + /* FIXME: try to give a better message */ + error (0, errno, _("fts_read failed")); + fail = 1; + } + break; + } + + fail |= change_file_owner (fts, ent, uid, gid, + required_uid, required_gid, chopt); + } + + /* Ignore failure, since the only way it can do so is in failing to + return to the original directory, and since we're about to exit, + that doesn't matter. */ + fts_close (fts); + + return fail; +} -- cgit v1.2.3-54-g00ecf