| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| #include <config.h> |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <pwd.h> |
| #include <grp.h> |
|
|
| #include "system.h" |
| #include "assure.h" |
| #include "chown-core.h" |
| #include "ignore-value.h" |
| #include "root-dev-ino.h" |
| #include "xfts.h" |
|
|
| #define FTSENT_IS_DIRECTORY(E) \ |
| ((E)->fts_info == FTS_D \ |
| || (E)->fts_info == FTS_DC \ |
| || (E)->fts_info == FTS_DP \ |
| || (E)->fts_info == FTS_DNR) |
|
|
| enum RCH_status |
| { |
| |
| RC_ok = 2, |
|
|
| |
| RC_excluded, |
|
|
| |
| RC_inode_changed, |
|
|
| |
| |
| RC_do_ordinary_chown, |
|
|
| |
| RC_error |
| }; |
|
|
| extern void |
| chopt_init (struct Chown_option *chopt) |
| { |
| chopt->verbosity = V_off; |
| chopt->root_dev_ino = nullptr; |
| chopt->affect_symlink_referent = true; |
| chopt->recurse = false; |
| chopt->force_silent = false; |
| chopt->user_name = nullptr; |
| chopt->group_name = nullptr; |
| } |
|
|
| extern void |
| chopt_free (struct Chown_option *chopt) |
| { |
| free (chopt->user_name); |
| free (chopt->group_name); |
| } |
|
|
| |
| |
|
|
| static char * |
| uid_to_str (uid_t uid) |
| { |
| char buf[INT_BUFSIZE_BOUND (intmax_t)]; |
| return xstrdup (TYPE_SIGNED (uid_t) ? imaxtostr (uid, buf) |
| : umaxtostr (uid, buf)); |
| } |
|
|
| |
| |
|
|
| static char * |
| gid_to_str (gid_t gid) |
| { |
| char buf[INT_BUFSIZE_BOUND (intmax_t)]; |
| return xstrdup (TYPE_SIGNED (gid_t) ? imaxtostr (gid, buf) |
| : umaxtostr (gid, buf)); |
| } |
|
|
| |
| |
| |
|
|
| extern char * |
| gid_to_name (gid_t gid) |
| { |
| struct group *grp = getgrgid (gid); |
| return grp ? xstrdup (grp->gr_name) : gid_to_str (gid); |
| } |
|
|
| |
| |
| |
|
|
| extern char * |
| uid_to_name (uid_t uid) |
| { |
| struct passwd *pwd = getpwuid (uid); |
| return pwd ? xstrdup (pwd->pw_name) : uid_to_str (uid); |
| } |
|
|
| |
|
|
| static char * |
| user_group_str (char const *user, char const *group) |
| { |
| char *spec = nullptr; |
|
|
| if (user) |
| { |
| if (group) |
| { |
| spec = xmalloc (strlen (user) + 1 + strlen (group) + 1); |
| stpcpy (stpcpy (stpcpy (spec, user), ":"), group); |
| } |
| else |
| { |
| spec = xstrdup (user); |
| } |
| } |
| else if (group) |
| { |
| spec = xstrdup (group); |
| } |
|
|
| return spec; |
| } |
|
|
| |
| |
| |
|
|
| static void |
| describe_change (char const *file, enum Change_status changed, |
| char const *old_user, char const *old_group, |
| char const *user, char const *group) |
| { |
| char const *fmt; |
| char *old_spec; |
| char *spec; |
|
|
| if (changed == CH_NOT_APPLIED) |
| { |
| printf (_("neither symbolic link %s nor referent has been changed\n"), |
| quoteaf (file)); |
| return; |
| } |
|
|
| spec = user_group_str (user, group); |
| old_spec = user_group_str (user ? old_user : nullptr, |
| group ? old_group : nullptr); |
|
|
| switch (changed) |
| { |
| case CH_SUCCEEDED: |
| fmt = (user ? _("changed ownership of %s from %s to %s\n") |
| : group ? _("changed group of %s from %s to %s\n") |
| : _("no change to ownership of %s\n")); |
| break; |
| case CH_FAILED: |
| if (old_spec) |
| { |
| fmt = (user ? _("failed to change ownership of %s from %s to %s\n") |
| : group ? _("failed to change group of %s from %s to %s\n") |
| : _("failed to change ownership of %s\n")); |
| } |
| else |
| { |
| fmt = (user ? _("failed to change ownership of %s to %s\n") |
| : group ? _("failed to change group of %s to %s\n") |
| : _("failed to change ownership of %s\n")); |
| free (old_spec); |
| old_spec = spec; |
| spec = nullptr; |
| } |
| break; |
| case CH_NO_CHANGE_REQUESTED: |
| fmt = (user ? _("ownership of %s retained as %s\n") |
| : group ? _("group of %s retained as %s\n") |
| : _("ownership of %s retained\n")); |
| break; |
| case CH_NOT_APPLIED: |
| default: |
| affirm (false); |
| } |
|
|
| printf (fmt, quoteaf (file), old_spec, spec); |
|
|
| free (old_spec); |
| free (spec); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| static enum RCH_status |
| restricted_chown (int cwd_fd, char const *file, |
| struct stat const *orig_st, |
| uid_t uid, gid_t gid, |
| uid_t required_uid, gid_t required_gid) |
| { |
| enum RCH_status status = RC_ok; |
| struct stat st; |
| int open_flags = O_NONBLOCK | O_NOCTTY; |
| int fd; |
|
|
| if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1) |
| return RC_do_ordinary_chown; |
|
|
| if (! S_ISREG (orig_st->st_mode)) |
| { |
| if (S_ISDIR (orig_st->st_mode)) |
| open_flags |= O_DIRECTORY; |
| else |
| return RC_do_ordinary_chown; |
| } |
|
|
| fd = openat (cwd_fd, file, O_RDONLY | open_flags); |
| if (! (0 <= fd |
| || (errno == EACCES && S_ISREG (orig_st->st_mode) |
| && 0 <= (fd = openat (cwd_fd, file, O_WRONLY | open_flags))))) |
| return (errno == EACCES ? RC_do_ordinary_chown : RC_error); |
|
|
| if (fstat (fd, &st) != 0) |
| status = RC_error; |
| else if (! psame_inode (orig_st, &st)) |
| status = RC_inode_changed; |
| else if ((required_uid == (uid_t) -1 || required_uid == st.st_uid) |
| && (required_gid == (gid_t) -1 || required_gid == st.st_gid)) |
| { |
| #if HAVE_FCHOWN |
| if (fchown (fd, uid, gid) == 0) |
| return close (fd) < 0 ? RC_error : RC_ok; |
| #endif |
| status = RC_error; |
| } |
|
|
| int saved_errno = errno; |
| close (fd); |
| errno = saved_errno; |
| return status; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static bool |
| change_file_owner (FTS *fts, FTSENT *ent, |
| uid_t uid, gid_t gid, |
| uid_t required_uid, gid_t required_gid, |
| struct Chown_option const *chopt) |
| { |
| char const *file_full_name = ent->fts_path; |
| char const *file = ent->fts_accpath; |
| struct stat const *file_stats; |
| struct stat stat_buf; |
| bool ok = true; |
| bool do_chown; |
| bool symlink_changed = true; |
|
|
| switch (ent->fts_info) |
| { |
| case FTS_D: |
| if (chopt->recurse) |
| { |
| if (ROOT_DEV_INO_CHECK (chopt->root_dev_ino, ent->fts_statp)) |
| { |
| |
| |
| ROOT_DEV_INO_WARN (file_full_name); |
| |
| fts_set (fts, ent, FTS_SKIP); |
| |
| ignore_value (fts_read (fts)); |
| return false; |
| } |
| return true; |
| } |
| break; |
|
|
| case FTS_DP: |
| if (! chopt->recurse) |
| return true; |
| break; |
|
|
| case FTS_NS: |
| |
| |
| |
| |
| |
| |
| |
| if (ent->fts_level == 0 && ent->fts_number == 0) |
| { |
| ent->fts_number = 1; |
| fts_set (fts, ent, FTS_AGAIN); |
| return true; |
| } |
| if (! chopt->force_silent) |
| error (0, ent->fts_errno, _("cannot access %s"), |
| quoteaf (file_full_name)); |
| ok = false; |
| break; |
|
|
| case FTS_ERR: |
| if (! chopt->force_silent) |
| error (0, ent->fts_errno, "%s", quotef (file_full_name)); |
| ok = false; |
| break; |
|
|
| case FTS_DNR: |
| if (! chopt->force_silent) |
| error (0, ent->fts_errno, _("cannot read directory %s"), |
| quoteaf (file_full_name)); |
| ok = false; |
| break; |
|
|
| case FTS_DC: |
| if (cycle_warning_required (fts, ent)) |
| { |
| emit_cycle_warning (file_full_name); |
| return false; |
| } |
| break; |
|
|
| default: |
| break; |
| } |
|
|
| if (!ok) |
| { |
| do_chown = false; |
| file_stats = nullptr; |
| } |
| else if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1 |
| && chopt->verbosity == V_off |
| && ! chopt->root_dev_ino |
| && ! chopt->affect_symlink_referent) |
| { |
| do_chown = true; |
| file_stats = ent->fts_statp; |
| } |
| else |
| { |
| file_stats = ent->fts_statp; |
|
|
| |
| |
| if (chopt->affect_symlink_referent && S_ISLNK (file_stats->st_mode)) |
| { |
| if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0) |
| { |
| if (! chopt->force_silent) |
| error (0, errno, _("cannot dereference %s"), |
| quoteaf (file_full_name)); |
| ok = false; |
| } |
|
|
| file_stats = &stat_buf; |
| } |
|
|
| do_chown = (ok |
| && (required_uid == (uid_t) -1 |
| || required_uid == file_stats->st_uid) |
| && (required_gid == (gid_t) -1 |
| || required_gid == file_stats->st_gid)); |
| } |
|
|
| |
| if (ok |
| && FTSENT_IS_DIRECTORY (ent) |
| && ROOT_DEV_INO_CHECK (chopt->root_dev_ino, file_stats)) |
| { |
| ROOT_DEV_INO_WARN (file_full_name); |
| return false; |
| } |
|
|
| if (do_chown) |
| { |
| if ( ! chopt->affect_symlink_referent) |
| { |
| ok = (lchownat (fts->fts_cwd_fd, file, uid, gid) == 0); |
|
|
| |
| |
| |
| if (!ok && is_ENOTSUP (errno)) |
| { |
| ok = true; |
| symlink_changed = false; |
| } |
| } |
| else |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| enum RCH_status err |
| = restricted_chown (fts->fts_cwd_fd, file, file_stats, uid, gid, |
| required_uid, required_gid); |
| switch (err) |
| { |
| case RC_ok: |
| break; |
|
|
| case RC_do_ordinary_chown: |
| ok = (chownat (fts->fts_cwd_fd, file, uid, gid) == 0); |
| break; |
|
|
| case RC_error: |
| ok = false; |
| break; |
|
|
| case RC_inode_changed: |
| |
| case RC_excluded: |
| do_chown = false; |
| ok = false; |
| break; |
|
|
| default: |
| unreachable (); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| if (do_chown && !ok && ! chopt->force_silent) |
| error (0, errno, (uid != (uid_t) -1 |
| ? _("changing ownership of %s") |
| : _("changing group of %s")), |
| quoteaf (file_full_name)); |
| } |
|
|
| if (chopt->verbosity != V_off) |
| { |
| bool changed = |
| ((do_chown && ok && symlink_changed) |
| && ! ((uid == (uid_t) -1 || uid == file_stats->st_uid) |
| && (gid == (gid_t) -1 || gid == file_stats->st_gid))); |
|
|
| if (changed || chopt->verbosity == V_high) |
| { |
| enum Change_status ch_status = |
| (!ok ? CH_FAILED |
| : !symlink_changed ? CH_NOT_APPLIED |
| : !changed ? CH_NO_CHANGE_REQUESTED |
| : CH_SUCCEEDED); |
| char *old_usr = (file_stats |
| ? uid_to_name (file_stats->st_uid) : nullptr); |
| char *old_grp = (file_stats |
| ? gid_to_name (file_stats->st_gid) : nullptr); |
| char *new_usr = chopt->user_name |
| ? chopt->user_name : uid != -1 |
| ? uid_to_str (uid) : nullptr; |
| char *new_grp = chopt->group_name |
| ? chopt->group_name : gid != -1 |
| ? gid_to_str (gid) : nullptr; |
| describe_change (file_full_name, ch_status, |
| old_usr, old_grp, |
| new_usr, new_grp); |
| free (old_usr); |
| free (old_grp); |
| if (new_usr != chopt->user_name) |
| free (new_usr); |
| if (new_grp != chopt->group_name) |
| free (new_grp); |
| } |
| } |
|
|
| if ( ! chopt->recurse) |
| fts_set (fts, ent, FTS_SKIP); |
|
|
| return ok; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| extern bool |
| 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) |
| { |
| bool ok = true; |
|
|
| |
| int stat_flags = ((required_uid != (uid_t) -1 || required_gid != (gid_t) -1 |
| || chopt->affect_symlink_referent |
| || chopt->verbosity != V_off) |
| ? 0 |
| : FTS_NOSTAT); |
|
|
| FTS *fts = xfts_open (files, bit_flags | stat_flags, nullptr); |
|
|
| while (true) |
| { |
| FTSENT *ent; |
|
|
| ent = fts_read (fts); |
| if (ent == nullptr) |
| { |
| if (errno != 0) |
| { |
| |
| if (! chopt->force_silent) |
| error (0, errno, _("fts_read failed")); |
| ok = false; |
| } |
| break; |
| } |
|
|
| ok &= change_file_owner (fts, ent, uid, gid, |
| required_uid, required_gid, chopt); |
| } |
|
|
| if (fts_close (fts) != 0) |
| { |
| error (0, errno, _("fts_close failed")); |
| ok = false; |
| } |
|
|
| return ok; |
| } |
|
|