Adding the --fake-super option.
authorWayne Davison <wayned@samba.org>
Tue, 24 Apr 2007 07:32:44 +0000 (07:32 +0000)
committerWayne Davison <wayned@samba.org>
Tue, 24 Apr 2007 07:32:44 +0000 (07:32 +0000)
17 files changed:
Makefile.in
NEWS
backup.c
clientserver.c
flist.c
loadparm.c
options.c
rsync.c
rsync.h
rsync.yo
rsyncd.conf.yo
syscall.c
t_unsafe.c
testsuite/devices.test
tls.c
trimslash.c
xattrs.c

index 0764c3f..969a552 100644 (file)
@@ -41,7 +41,7 @@ popt_OBJS=popt/findme.o  popt/popt.o  popt/poptconfig.o \
        popt/popthelp.o popt/poptparse.o
 OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@
 
-TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o
+TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o
 
 # Programs we must have to run the test cases
 CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
diff --git a/NEWS b/NEWS
index eae08d9..8825ffd 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -11,9 +11,9 @@ Changes since 2.6.9:
       option.  Prior versions used to output too many creation events for
       matching items.
 
-    - The code that waits for a child pid now handles being interrupted by
-      a signal.  This fixes a problem with the pre-xfer exec function not
-      being able to get the exit status from the script.
+    - The code that waits for a child pid now handles being interrupted by a
+      signal.  This fixes a problem with the pre-xfer exec function not being
+      able to get the exit status from the script.
 
     - A negated filter rule now sends the negation option when sending the
       filter rules.
@@ -41,12 +41,11 @@ Changes since 2.6.9:
 
     - The default --delete algorithm is now --delete-during when talking to a
       3.x rsync.  This is a faster scan than using --delete-before (which is
-      the default when talking to older rsync versions), and is compatible
-      with the new incremental recursion mode.
+      the default when talking to older rsync versions), and is compatible with
+      the new incremental recursion mode.
 
-    - Added the --delete-delay option, which is a more efficient way to
-      delete files at the end of the transfer without needing a separate
-      delete pass.
+    - Added the --delete-delay option, which is a more efficient way to delete
+      files at the end of the transfer without needing a separate delete pass.
 
     - Added the --acls (-A) option to preserve Access Control Lists.  This is
       an improved version of the prior patch that was available.  (If you need
@@ -58,22 +57,25 @@ Changes since 2.6.9:
       to have backward compatibility with old, patched versions, the new
       xattrs.diff patch that will add that.)
 
-    - You may specify --max-delete=0 to a 3.0.0 client as long as the
-      receiving side is at least version 3.0.0.  This means that you
-      can pull from an older rsync with this option, but pushing to an
-      older rsync will generate an error.  *Be sure to never specify a 0
-      value to an older rsync client, or it will be silently ignored.*
+    - Added the --fake-super option that allows a non-super user to preserve
+      all attributes of a file by using a special extended-attribute idiom.
+      There is also an analogous "fake super" option for an rsync daemon.
+
+    - You may specify --max-delete=0 to a 3.0.0 client as long as the receiving
+      side is at least version 3.0.0.  This means that you can pull from an
+      older rsync with this option, but pushing to an older rsync will generate
+      an error.  *Be sure to never specify a 0 value to an older rsync client,
+      or it will be silently ignored.*
 
     - The --hard-link option now uses less memory on both the sending and
-      receiving side for all protocol versions.  For protocol 30, the use
-      of a hashtable on the sending side allows us to more efficiently
-      convey to the receiver what files are linked together.  This reduces
-      the amount of data sent over the socket by a considerable margin
-      (rather than adding more data), and limits the in-memory storage of
-      the device+inode information to just the sending side for the new
-      protocol 30, or to the receiving side when speaking an older protocol
-      (note that older rsync versions kept the device+inode information on
-      both sides).
+      receiving side for all protocol versions.  For protocol 30, the use of a
+      hashtable on the sending side allows us to more efficiently convey to the
+      receiver what files are linked together.  This reduces the amount of data
+      sent over the socket by a considerable margin (rather than adding more
+      data), and limits the in-memory storage of the device+inode information
+      to just the sending side for the new protocol 30, or to the receiving
+      side when speaking an older protocol (note that older rsync versions kept
+      the device+inode information on both sides).
 
   INTERNAL:
 
index 49985fa..7189a5c 100644 (file)
--- a/backup.c
+++ b/backup.c
@@ -127,7 +127,7 @@ static int make_bak_dir(char *fullpath)
                if (p >= rel) {
                        /* Try to transfer the directory settings of the
                         * actual dir that the files are coming from. */
-                       if (do_stat(rel, &sx.st) < 0) {
+                       if (x_stat(rel, &sx.st, NULL) < 0) {
                                rsyserr(FERROR, errno,
                                        "make_bak_dir stat %s failed",
                                        full_fname(rel));
@@ -200,7 +200,7 @@ static int keep_backup(const char *fname)
        int ret_code;
 
        /* return if no file to keep */
-       if (do_lstat(fname, &sx.st) < 0)
+       if (x_lstat(fname, &sx.st, NULL) < 0)
                return 1;
 #ifdef SUPPORT_ACLS
        sx.acc_acl = sx.def_acl = NULL;
index 95c1601..7c1b81f 100644 (file)
@@ -629,6 +629,11 @@ static int rsync_module(int f_in, int f_out, int i, char *addr, char *host)
        if (lp_ignore_errors(module_id))
                ignore_errors = 1;
 
+       if (lp_fake_super(i))
+               am_root = -1;
+       else if (am_root < 0) /* Treat --fake-super from client as --super. */
+               am_root = 2;
+
        if (filesfrom_fd == 0)
                filesfrom_fd = f_in;
 
diff --git a/flist.c b/flist.c
index 9bdf89c..eb94d00 100644 (file)
--- a/flist.c
+++ b/flist.c
@@ -197,12 +197,12 @@ static int readlink_stat(const char *path, STRUCT_STAT *stp, char *linkbuf)
                                rprintf(FINFO,"copying unsafe symlink \"%s\" -> \"%s\"\n",
                                        path, linkbuf);
                        }
-                       return do_stat(path, stp);
+                       return x_stat(path, stp, NULL);
                }
        }
        return 0;
 #else
-       return do_stat(path, stp);
+       return x_stat(path, stp, NULL);
 #endif
 }
 
@@ -210,17 +210,17 @@ int link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks)
 {
 #ifdef SUPPORT_LINKS
        if (copy_links)
-               return do_stat(path, stp);
-       if (do_lstat(path, stp) < 0)
+               return x_stat(path, stp, NULL);
+       if (x_lstat(path, stp, NULL) < 0)
                return -1;
        if (follow_dirlinks && S_ISLNK(stp->st_mode)) {
                STRUCT_STAT st;
-               if (do_stat(path, &st) == 0 && S_ISDIR(st.st_mode))
+               if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode))
                        *stp = st;
        }
        return 0;
 #else
-       return do_stat(path, stp);
+       return x_stat(path, stp, NULL);
 #endif
 }
 
@@ -255,26 +255,6 @@ static int is_excluded(char *fname, int is_dir, int filter_level)
        return 0;
 }
 
-static int to_wire_mode(mode_t mode)
-{
-#ifdef SUPPORT_LINKS
-#if _S_IFLNK != 0120000
-       if (S_ISLNK(mode))
-               return (mode & ~(_S_IFMT)) | 0120000;
-#endif
-#endif
-       return mode;
-}
-
-static mode_t from_wire_mode(int mode)
-{
-#if _S_IFLNK != 0120000
-       if ((mode & (_S_IFMT)) == 0120000)
-               return (mode & ~(_S_IFMT)) | _S_IFLNK;
-#endif
-       return mode;
-}
-
 static void send_directory(int f, struct file_list *flist, int ndx,
                           char *fbuf, int len, int flags);
 
@@ -970,7 +950,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
                if (save_errno == ENOENT) {
 #ifdef SUPPORT_LINKS
                        /* Avoid "vanished" error if symlink points nowhere. */
-                       if (copy_links && do_lstat(thisname, &st) == 0
+                       if (copy_links && x_lstat(thisname, &st, NULL) == 0
                            && S_ISLNK(st.st_mode)) {
                                io_error |= IOERR_GENERAL;
                                rprintf(FERROR, "symlink has no referent: %s\n",
@@ -1142,7 +1122,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
                int save_mode = file->mode;
                file->mode = S_IFDIR; /* Find a directory with our name. */
                if (flist_find(dir_flist, file) >= 0
-                   && do_stat(thisname, &st2) == 0 && S_ISDIR(st2.st_mode)) {
+                   && x_stat(thisname, &st2, NULL) == 0 && S_ISDIR(st2.st_mode)) {
                        file->modtime = st2.st_mtime;
                        file->len32 = 0;
                        file->mode = st2.st_mode;
index d82c205..be3dc63 100644 (file)
@@ -149,6 +149,7 @@ typedef struct
        int syslog_facility;
        int timeout;
 
+       BOOL fake_super;
        BOOL ignore_errors;
        BOOL ignore_nonreadable;
        BOOL list;
@@ -196,6 +197,7 @@ static service sDefault =
  /* syslog_facility; */                LOG_DAEMON,
  /* timeout; */                        0,
 
+ /* fake_super; */             False,
  /* ignore_errors; */          False,
  /* ignore_nonreadable; */     False,
  /* list; */                   True,
@@ -297,6 +299,7 @@ static struct parm_struct parm_table[] =
  {"dont compress",     P_STRING, P_LOCAL, &sDefault.dont_compress,     NULL,0},
  {"exclude from",      P_STRING, P_LOCAL, &sDefault.exclude_from,      NULL,0},
  {"exclude",           P_STRING, P_LOCAL, &sDefault.exclude,           NULL,0},
+ {"fake super",        P_BOOL,   P_LOCAL, &sDefault.fake_super,        NULL,0},
  {"filter",            P_STRING, P_LOCAL, &sDefault.filter,            NULL,0},
  {"gid",               P_STRING, P_LOCAL, &sDefault.gid,               NULL,0},
  {"hosts allow",       P_STRING, P_LOCAL, &sDefault.hosts_allow,       NULL,0},
@@ -411,6 +414,7 @@ FN_LOCAL_INTEGER(lp_max_connections, max_connections)
 FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity)
 FN_LOCAL_INTEGER(lp_timeout, timeout)
 
+FN_LOCAL_BOOL(lp_fake_super, fake_super)
 FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
 FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
 FN_LOCAL_BOOL(lp_list, list)
@@ -814,7 +818,7 @@ BOOL lp_load(char *pszFname, int globals_only)
 
        if (pszFname)
            pstrcpy(n2,pszFname);
-       else if (am_server && !am_root)
+       else if (am_server && am_root <= 0)
            pstrcpy(n2,RSYNCD_USERCONF);
        else
            pstrcpy(n2,RSYNCD_SYSCONF);
index f860a49..f3d1083 100644 (file)
--- a/options.c
+++ b/options.c
@@ -72,7 +72,7 @@ int protocol_version = PROTOCOL_VERSION;
 int sparse_files = 0;
 int do_compression = 0;
 int def_compress_level = Z_DEFAULT_COMPRESSION;
-int am_root = 0;
+int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */
 int am_server = 0;
 int am_sender = 0;
 int am_generator = 0;
@@ -328,6 +328,9 @@ void usage(enum logcode F)
   rprintf(F," -t, --times                 preserve times\n");
   rprintf(F," -O, --omit-dir-times        omit directories when preserving times\n");
   rprintf(F,"     --super                 receiver attempts super-user activities\n");
+#ifdef SUPPORT_XATTRS
+  rprintf(F,"     --fake-super            store/recover privileged attrs using xattrs\n");
+#endif
   rprintf(F," -S, --sparse                handle sparse files efficiently\n");
   rprintf(F," -n, --dry-run               show what would have been transferred\n");
   rprintf(F," -W, --whole-file            copy files whole (without rsync algorithm)\n");
@@ -457,6 +460,7 @@ static struct poptOption long_options[] = {
   {"modify-window",    0,  POPT_ARG_INT,    &modify_window, OPT_MODIFY_WINDOW, 0, 0 },
   {"super",            0,  POPT_ARG_VAL,    &am_root, 2, 0, 0 },
   {"no-super",         0,  POPT_ARG_VAL,    &am_root, 0, 0, 0 },
+  {"fake-super",       0,  POPT_ARG_VAL,    &am_root, -1, 0, 0 },
   {"owner",           'o', POPT_ARG_VAL,    &preserve_uid, 1, 0, 0 },
   {"no-owner",         0,  POPT_ARG_VAL,    &preserve_uid, 0, 0, 0 },
   {"no-o",             0,  POPT_ARG_VAL,    &preserve_uid, 0, 0, 0 },
@@ -1189,6 +1193,14 @@ int parse_arguments(int *argc, const char ***argv, int frommain)
        }
 #endif
 
+#ifndef SUPPORT_XATTRS
+       if (am_root < 0) {
+               snprintf(err_buf, sizeof err_buf,
+                        "--fake-super requires an rsync with extended attributes enabled\n");
+               return 0;
+       }
+#endif
+
        if (write_batch && read_batch) {
                snprintf(err_buf, sizeof err_buf,
                        "--write-batch and --read-batch can not be used together\n");
diff --git a/rsync.c b/rsync.c
index 676e658..1eda740 100644 (file)
--- a/rsync.c
+++ b/rsync.c
@@ -257,6 +257,8 @@ int set_file_attrs(const char *fname, struct file_struct *file, statx *sxp,
 #ifdef SUPPORT_XATTRS
        if (preserve_xattrs && fnamecmp)
                set_xattr(fname, file, fnamecmp, sxp);
+       if (am_root < 0)
+               set_stat_xattr(fname, file);
 #endif
 
        if (!preserve_times || (S_ISDIR(sxp->st.st_mode) && omit_dir_times))
@@ -296,7 +298,9 @@ int set_file_attrs(const char *fname, struct file_struct *file, statx *sxp,
                                        (long)sxp->st.st_gid, (long)F_GID(file));
                        }
                }
-               if (do_lchown(fname,
+               if (am_root < 0) {
+                       ;
+               } else if (do_lchown(fname,
                    change_uid ? F_UID(file) : sxp->st.st_uid,
                    change_gid ? F_GID(file) : sxp->st.st_gid) != 0) {
                        /* shouldn't have attempted to change uid or gid
@@ -305,7 +309,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, statx *sxp,
                            change_uid ? "chown" : "chgrp",
                            full_fname(fname));
                        goto cleanup;
-               }
+               } else
                /* a lchown had been done - we have to re-stat if the
                 * destination had the setuid or setgid bits set due
                 * to the side effect of the chown call */
@@ -332,7 +336,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, statx *sxp,
 
 #ifdef HAVE_CHMOD
        if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
-               int ret = do_chmod(fname, new_mode);
+               int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
                if (ret < 0) {
                        rsyserr(FERROR, errno,
                                "failed to set permissions on %s",
diff --git a/rsync.h b/rsync.h
index 4717e35..53989bd 100644 (file)
--- a/rsync.h
+++ b/rsync.h
@@ -811,6 +811,12 @@ typedef struct {
 
 #include "proto.h"
 
+#ifndef SUPPORT_XATTRS
+#define x_stat(fn,fst,xst) do_stat(fn,fst)
+#define x_lstat(fn,fst,xst) do_lstat(fn,fst)
+#define x_fstat(fd,fst,xst) do_fstat(fd,fst)
+#endif
+
 /* We have replacement versions of these if they're missing. */
 #ifndef HAVE_ASPRINTF
 int asprintf(char **ptr, const char *format, ...);
@@ -1029,6 +1035,26 @@ int inet_pton(int af, const char *src, void *dst);
 const char *get_panic_action(void);
 #endif
 
+static inline int to_wire_mode(mode_t mode)
+{
+#ifdef SUPPORT_LINKS
+#if _S_IFLNK != 0120000
+       if (S_ISLNK(mode))
+               return (mode & ~(_S_IFMT)) | 0120000;
+#endif
+#endif
+       return mode;
+}
+
+static inline mode_t from_wire_mode(int mode)
+{
+#if _S_IFLNK != 0120000
+       if ((mode & (_S_IFMT)) == 0120000)
+               return (mode & ~(_S_IFMT)) | _S_IFLNK;
+#endif
+       return mode;
+}
+
 static inline int
 isDigit(const char *ptr)
 {
index e9d1e20..fd229d6 100644 (file)
--- a/rsync.yo
+++ b/rsync.yo
@@ -333,6 +333,7 @@ to the detailed description below for a complete description.  verb(
  -t, --times                 preserve times
  -O, --omit-dir-times        omit directories when preserving times
      --super                 receiver attempts super-user activities
+     --fake-super            store/recover privileged attrs using xattrs
  -S, --sparse                handle sparse files efficiently
  -n, --dry-run               show what would have been transferred
  -W, --whole-file            copy files whole (without rsync algorithm)
@@ -865,7 +866,7 @@ permission value can be applied to the files in the transfer.
 dit(bf(-o, --owner)) This option causes rsync to set the owner of the
 destination file to be the same as the source file, but only if the
 receiving rsync is being run as the super-user (see also the bf(--super)
-option to force rsync to attempt super-user activities).
+and bf(--fake-super) options).
 Without this option, the owner is set to the invoking user on the
 receiving side.
 
@@ -888,7 +889,7 @@ default, but may fall back to using the ID number in some circumstances
 dit(bf(--devices)) This option causes rsync to transfer character and
 block device files to the remote system to recreate these devices.
 This option has no effect if the receiving rsync is not run as the
-super-user and bf(--super) is not specified.
+super-user (see also the bf(--super) and bf(--fake-super) options).
 
 dit(bf(--specials)) This option causes rsync to transfer special files
 such as named sockets and fifos.
@@ -918,6 +919,34 @@ also for ensuring that you will get errors if the receiving side isn't
 being running as the super-user.  To turn off super-user activities, the
 super-user can use bf(--no-super).
 
+dit(bf(--fake-super)) When this option is enabled, rsync simulates
+super-user activities by saving/restoring the privileged attributes via a
+special extended attribute that is attached to each file (as needed).  This
+includes the file's owner and group (if it is not the default), the file's
+device info (device & special files are created as empty text files), and
+any permission bits that we won't allow to be set on the real file (e.g.
+the real file gets u-s,g-s,o-t for safety) or that would limit the owner's
+access (since the real super-user can always access/change a file or
+directory, the files we create can always be accessed/changed by the
+creating user).
+
+The bf(--fake-super) option only affects the side where the option is used.
+To affect the remote side of a remote-shell connection, specify an rsync
+path:
+
+quote(tt(  rsync -av --rsync-path="rsync --fake-super" /src/ host:/dest/))
+
+Since there is only one "side" in a local copy, this option affects both
+the sending and recieving of files.  You'll need to specify a copy using
+"localhost" if you need to avoid this.  Note, however, that it is always
+safe to copy from some non-fake-super files into some fake-super files
+using a local bf(--fake-super) command because the non-fake source files
+will just have their normal attributes.
+
+This option is overridden by both bf(--super) and bf(--no-super).
+
+See also the "fake super" setting in the daemon's rsyncd.conf file.
+
 dit(bf(-S, --sparse)) Try to handle sparse files efficiently so they take
 up less space on the destination.  Conflicts with bf(--inplace) because it's
 not possible to overwrite data in a sparse fashion.
index 5b7cc9b..8029114 100644 (file)
@@ -226,6 +226,11 @@ file transfers to and from that module should take place as when the daemon
 was run as root. This complements the "uid" option. The default is gid -2,
 which is normally the group "nobody".
 
+dit(bf(fake super)) Setting "fake super = yes" for a module causes the
+daemon side to behave as if the bf(--fake-user) command-line option had
+been specified.  This allows the full attributes of a file to be stored
+without having to have the daemon actually running as root.
+
 dit(bf(filter)) The "filter" option allows you to specify a space-separated
 list of filter rules that the daemon will not allow to be read or written.
 This is only superficially equivalent to the client specifying these
index 20ad131..ebcb900 100644 (file)
--- a/syscall.c
+++ b/syscall.c
@@ -27,6 +27,7 @@
 #endif
 
 extern int dry_run;
+extern int am_root;
 extern int read_only;
 extern int list_only;
 extern int preserve_perms;
@@ -78,6 +79,15 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
 {
        if (dry_run) return 0;
        RETURN_ERROR_IF_RO_OR_LO;
+
+       /* For --fake-super, we create a normal file with mode 0600. */
+       if (am_root < 0) {
+               int fd = open(pathname, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR);
+               if (fd < 0 || close(fd) < 0)
+                       return -1;
+               return 0;
+       }
+
 #if !defined MKNOD_CREATES_FIFOS && defined HAVE_MKFIFO
        if (S_ISFIFO(mode))
                return mkfifo(pathname, mode);
index a08dd71..fa79fb7 100644 (file)
 
 #include "rsync.h"
 
-int dry_run, read_only, list_only, verbose;
+int dry_run = 0;
+int am_root = 0;
+int read_only = 0;
+int list_only = 0;
+int verbose = 0;
 int preserve_perms = 0;
 
 int
index 1c34477..b25fa6e 100644 (file)
@@ -21,7 +21,41 @@ case `id -u` in
        echo "Let's try re-running the script under fakeroot..."
        exec /usr/bin/fakeroot /bin/sh $RUNSHFLAGS "$0"
     fi
-    test_skipped "Rsync won't copy devices unless we're root"
+
+    # If we can't test the real device code, try to test --fake-super instead
+    $RSYNC --version | grep ", xattrs" >/dev/null || test_skipped "Rsync needs root/fakeroot/--fake-super for device tests"
+    RSYNC="$RSYNC --fake-super"
+    export RSYNC_FAKE_SUPER=1
+    case "`xattr 2>&1`" in
+    *--list:*)
+       mknod() {
+           fn="$1"
+           case "$2" in
+           p) mode=10644 ;;
+           c) mode=20644 ;;
+           b) mode=60644 ;;
+           esac
+           maj="${3:-0}"
+           min="${4:-0}"
+           touch "$fn"
+           xattr -s 'rsync.%stat' "$mode $maj,$min 0:0" "$fn"
+       }
+       ;;
+    *)
+       mknod() {
+           fn="$1"
+           case "$2" in
+           p) mode=10644 ;;
+           c) mode=20644 ;;
+           b) mode=60644 ;;
+           esac
+           maj="${3:-0}"
+           min="${4:-0}"
+           touch "$fn"
+           setfattr -n 'user.rsync.%stat' -v "$mode $maj,$min 0:0" "$fn"
+       }
+       ;;
+    esac
     ;;
 esac
 
@@ -29,14 +63,14 @@ esac
 
 mkdir "$fromdir"
 mkdir "$todir"
-mknod "$fromdir/char" c 41 67  || test_skipped "Can't create char device node unless root"
-mknod "$fromdir/char2" c 42 68  || test_skipped "Can't create char device node unless root"
-mknod "$fromdir/char3" c 42 69  || test_skipped "Can't create char device node unless root"
-mknod "$fromdir/block" b 42 69 || test_skipped "Can't create block device node unless root"
-mknod "$fromdir/block2" b 42 73 || test_skipped "Can't create block device node unless root"
-mknod "$fromdir/block3" b 105 73 || test_skipped "Can't create block device node unless root"
+mknod "$fromdir/char" c 41 67  || test_skipped "Can't create char device node"
+mknod "$fromdir/char2" c 42 68  || test_skipped "Can't create char device node"
+mknod "$fromdir/char3" c 42 69  || test_skipped "Can't create char device node"
+mknod "$fromdir/block" b 42 69 || test_skipped "Can't create block device node"
+mknod "$fromdir/block2" b 42 73 || test_skipped "Can't create block device node"
+mknod "$fromdir/block3" b 105 73 || test_skipped "Can't create block device node"
 ln "$fromdir/block3" "$fromdir/block2.5" || echo "Skipping hard-linked device test..."
-mkfifo "$fromdir/fifo" || test_skipped "Can't run mkfifo"
+mkfifo "$fromdir/fifo" || mknod "$fromdir/fifo" p || test_skipped "Can't run mkfifo"
 touch -r "$fromdir/block" "$fromdir/block2"
 
 $RSYNC -ai "$fromdir/block" "$todir/block2" \
@@ -76,9 +110,8 @@ cD+++++++++ char2
 cD+++++++++ char3
 cS+++++++++ fifo
 EOT
-if test ! -b "$fromdir/block2.5"; then
-    sed -e '/block2\.5/d' \
-       <"$chkfile" >"$chkfile.new"
+if test ! -r "$fromdir/block2.5"; then
+    sed -e '/block2\.5/d' <"$chkfile" >"$chkfile.new"
     mv "$chkfile.new" "$chkfile"
 fi
 diff $diffopt "$chkfile" "$outfile" || test_fail "test 4 failed"
diff --git a/tls.c b/tls.c
index cd4e26d..8a38819 100644 (file)
--- a/tls.c
+++ b/tls.c
  * change. */
 
 #include "rsync.h"
+#include "lib/sysxattrs.h"
 
 #define PROGRAM "tls"
 
 /* These are to make syscall.o shut up. */
 int dry_run = 0;
+int am_root = 0;
 int read_only = 1;
 int list_only = 0;
 int preserve_perms = 0;
 
+#ifdef HAVE_LINUX_XATTRS
+#define XSTAT_ATTR "user.rsync.%stat"
+#else
+#define XSTAT_ATTR "rsync.%stat"
+#endif
+
+static int stat_xattr(const char *fname, STRUCT_STAT *fst)
+{
+       int mode, rdev_major, rdev_minor, uid, gid, len;
+       char buf[256];
+
+       if (am_root >= 0 || IS_DEVICE(fst->st_mode) || IS_SPECIAL(fst->st_mode))
+               return -1;
+
+       len = sys_lgetxattr(fname, XSTAT_ATTR, buf, sizeof buf - 1);
+       if (len >= (int)sizeof buf) {
+               len = -1;
+               errno = ERANGE;
+       }
+       if (len < 0) {
+               if (errno == ENOTSUP || errno == ENOATTR)
+                       return -1;
+               if (errno == EPERM && S_ISLNK(fst->st_mode)) {
+                       fst->st_uid = 0;
+                       fst->st_gid = 0;
+                       return 0;
+               }
+               fprintf(stderr, "failed to read xattr %s for %s: %s\n",
+                       XSTAT_ATTR, fname, strerror(errno));
+               return -1;
+       }
+       buf[len] = '\0';
+
+       if (sscanf(buf, "%o %d,%d %d:%d",
+                  &mode, &rdev_major, &rdev_minor, &uid, &gid) != 5) {
+               fprintf(stderr, "Corrupt %s xattr attached to %s: \"%s\"\n",
+                       XSTAT_ATTR, fname, buf);
+               exit(1);
+       }
+
+       fst->st_mode = from_wire_mode(mode);
+       fst->st_rdev = MAKEDEV(rdev_major, rdev_minor);
+       fst->st_uid = uid;
+       fst->st_gid = gid;
+
+       return 0;
+}
+
 static void failed(char const *what, char const *where)
 {
        fprintf(stderr, PROGRAM ": %s %s: %s\n",
@@ -60,6 +110,8 @@ static void list_file(const char *fname)
 
        if (do_lstat(fname, &buf) < 0)
                failed("stat", fname);
+       if (am_root < 0)
+               stat_xattr(fname, &buf);
 
        /* The size of anything but a regular file is probably not
         * worth thinking about. */
@@ -125,9 +177,11 @@ main(int argc, char *argv[])
                return 1;
        }
 
-       for (argv++; *argv; argv++) {
+       if (getenv("RSYNC_FAKE_SUPER"))
+               am_root = -1;
+
+       for (argv++; *argv; argv++)
                list_file(*argv);
-       }
 
        return 0;
 }
index bb5f1d8..6ab9638 100644 (file)
@@ -22,6 +22,7 @@
 
 /* These are to make syscall.o shut up. */
 int dry_run = 0;
+int am_root = 0;
 int read_only = 1;
 int list_only = 0;
 int preserve_perms = 0;
index e4d8dde..f662cf7 100644 (file)
--- a/xattrs.c
+++ b/xattrs.c
@@ -53,11 +53,16 @@ extern int checksum_seed;
 #define SPRE_LEN ((int)sizeof SYSTEM_PREFIX - 1)
 
 #ifdef HAVE_LINUX_XATTRS
-#define RPRE_LEN 0
+#define MIGHT_NEED_RPRE (am_root < 0)
+#define RSYNC_PREFIX USER_PREFIX "rsync."
 #else
+#define MIGHT_NEED_RPRE am_root
 #define RSYNC_PREFIX "rsync."
-#define RPRE_LEN ((int)sizeof RSYNC_PREFIX - 1)
 #endif
+#define RPRE_LEN ((int)sizeof RSYNC_PREFIX - 1)
+
+#define XSTAT_ATTR RSYNC_PREFIX "%stat"
+#define XSTAT_LEN ((int)sizeof XSTAT_ATTR - 1)
 
 typedef struct {
        char *datum, *name;
@@ -218,6 +223,10 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
                        continue;
 #endif
 
+               if (am_root < 0 && name_len == XSTAT_LEN + 1
+                && name[RPRE_LEN] == '%' && strcmp(name, XSTAT_ATTR) == 0)
+                       continue;
+
                datum_len = name_len; /* Pass extra size to get_xattr_data() */
                if (!(ptr = get_xattr_data(fname, name, &datum_len, 0)))
                        return -1;
@@ -236,6 +245,14 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
                } else
                        name_offset = datum_len;
 
+#ifdef HAVE_LINUX_XATTRS
+               if (am_root < 0 && name_len > RPRE_LEN
+                && HAS_PREFIX(name, RSYNC_PREFIX)) {
+                       name += RPRE_LEN;
+                       name_len -= RPRE_LEN;
+               }
+#endif
+
                rxas = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL);
                rxas->name = ptr + name_offset;
                memcpy(rxas->name, name, name_len);
@@ -576,13 +593,9 @@ void receive_xattr(struct file_struct *file, int f)
                size_t name_len = read_varint(f);
                size_t datum_len = read_varint(f);
                size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + MAX_DIGEST_LEN : datum_len;
-#ifdef HAVE_LINUX_XATTRS
-               size_t extra_len = 0;
-#else
-               size_t extra_len = am_root ? RPRE_LEN : 0;
+               size_t extra_len = MIGHT_NEED_RPRE ? RPRE_LEN : 0;
                if (dget_len + extra_len < dget_len)
                        out_of_memory("receive_xattr"); /* overflow */
-#endif
                if (dget_len + extra_len + name_len < dget_len)
                        out_of_memory("receive_xattr"); /* overflow */
                ptr = new_array(char, dget_len + extra_len + name_len);
@@ -598,9 +611,14 @@ void receive_xattr(struct file_struct *file, int f)
                }
 #ifdef HAVE_LINUX_XATTRS
                /* Non-root can only save the user namespace. */
-               if (!am_root && !HAS_PREFIX(name, USER_PREFIX)) {
-                       free(ptr);
-                       continue;
+                       if (am_root <= 0 && !HAS_PREFIX(name, USER_PREFIX)) {
+                               if (!am_root) {
+                                       free(ptr);
+                                       continue;
+                               }
+                               name -= RPRE_LEN;
+                               name_len += RPRE_LEN;
+                               memcpy(name, RSYNC_PREFIX, RPRE_LEN);
                }
 #else
                /* This OS only has a user namespace, so we either
@@ -618,6 +636,11 @@ void receive_xattr(struct file_struct *file, int f)
                        continue;
                }
 #endif
+               if (am_root < 0 && name_len == XSTAT_LEN + 1
+                && name[RPRE_LEN] == '%' && strcmp(name, XSTAT_ATTR) == 0) {
+                       free(ptr);
+                       continue;
+               }
                rxa = EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, 1);
                rxa->name = name;
                rxa->datum = ptr;
@@ -772,4 +795,150 @@ int set_xattr(const char *fname, const struct file_struct *file,
        return rsync_xal_set(fname, lst + ndx, fnamecmp, sxp);
 }
 
+int get_stat_xattr(const char *fname, int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
+{
+       int mode, rdev_major, rdev_minor, uid, gid, len;
+       char buf[256];
+
+       if (am_root >= 0 || IS_DEVICE(fst->st_mode) || IS_SPECIAL(fst->st_mode))
+               return -1;
+
+       if (xst)
+               *xst = *fst;
+       else
+               xst = fst;
+       if (fname) {
+               fd = -1;
+               len = sys_lgetxattr(fname, XSTAT_ATTR, buf, sizeof buf - 1);
+       } else {
+               fname = "fd";
+               len = sys_fgetxattr(fd, XSTAT_ATTR, buf, sizeof buf - 1);
+       }
+       if (len >= (int)sizeof buf) {
+               len = -1;
+               errno = ERANGE;
+       }
+       if (len < 0) {
+               if (errno == ENOTSUP || errno == ENOATTR)
+                       return -1;
+               if (errno == EPERM && S_ISLNK(fst->st_mode)) {
+                       xst->st_uid = 0;
+                       xst->st_gid = 0;
+                       return 0;
+               }
+               rsyserr(FERROR, errno, "failed to read xattr %s for %s",
+                       XSTAT_ATTR, full_fname(fname));
+               return -1;
+       }
+       buf[len] = '\0';
+
+       if (sscanf(buf, "%o %d,%d %d:%d",
+                  &mode, &rdev_major, &rdev_minor, &uid, &gid) != 5) {
+               rprintf(FERROR, "Corrupt %s xattr attached to %s: \"%s\"\n",
+                       XSTAT_ATTR, full_fname(fname), buf);
+               exit_cleanup(RERR_FILEIO);
+       }
+
+       xst->st_mode = from_wire_mode(mode);
+       xst->st_rdev = MAKEDEV(rdev_major, rdev_minor);
+       xst->st_uid = uid;
+       xst->st_gid = gid;
+
+       return 0;
+}
+
+int set_stat_xattr(const char *fname, struct file_struct *file)
+{
+       STRUCT_STAT fst, xst;
+       dev_t rdev;
+       mode_t mode, fmode;
+
+       if (dry_run)
+               return 0;
+
+       if (read_only || list_only) {
+               rsyserr(FERROR, EROFS, "failed to write xattr %s for %s",
+                       XSTAT_ATTR, full_fname(fname));
+               return -1;
+       }
+
+       if (x_lstat(fname, &fst, &xst) < 0) {
+               rsyserr(FERROR, errno, "failed to re-stat %s",
+                       full_fname(fname));
+               return -1;
+       }
+
+       fst.st_mode &= (_S_IFMT | CHMOD_BITS);
+       fmode = file->mode & (_S_IFMT | CHMOD_BITS);
+
+       if (IS_DEVICE(fmode) || IS_SPECIAL(fmode)) {
+               uint32 *devp = F_RDEV_P(file);
+               rdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
+       } else
+               rdev = 0;
+
+       /* Dump the special permissions and enable full owner access. */
+       mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
+            | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
+       if (fst.st_mode != mode)
+               do_chmod(fname, mode);
+       if (!IS_DEVICE(fst.st_mode) && !IS_SPECIAL(fst.st_mode))
+               fst.st_rdev = 0; /* just in case */
+
+       if (mode == fmode && fst.st_rdev == rdev
+        && fst.st_uid == F_UID(file) && fst.st_gid == F_GID(file)) {
+               /* xst.st_mode will be 0 if there's no current stat xattr */
+               if (xst.st_mode && sys_lremovexattr(fname, XSTAT_ATTR) < 0) {
+                       rsyserr(FERROR, errno,
+                               "delete of stat xattr failed for %s",
+                               full_fname(fname));
+                       return -1;
+               }
+               return 0;
+       }
+
+       if (xst.st_mode != fmode || xst.st_rdev != rdev
+        || xst.st_uid != F_UID(file) || xst.st_gid != F_GID(file)) {
+               char buf[256];
+               int len = snprintf(buf, sizeof buf, "%o %u,%u %u:%u",
+                       to_wire_mode(fmode),
+                       (int)major(rdev), (int)minor(rdev),
+                       (int)F_UID(file), (int)F_GID(file));
+               if (sys_lsetxattr(fname, XSTAT_ATTR, buf, len) < 0) {
+                       if (errno == EPERM && S_ISLNK(fst.st_mode))
+                               return 0;
+                       rsyserr(FERROR, errno,
+                               "failed to write xattr %s for %s",
+                               XSTAT_ATTR, full_fname(fname));
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
+{
+       int ret = do_stat(fname, fst);
+       if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
+               xst->st_mode = 0;
+       return ret;
+}
+
+int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
+{
+       int ret = do_lstat(fname, fst);
+       if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
+               xst->st_mode = 0;
+       return ret;
+}
+
+int x_fstat(int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
+{
+       int ret = do_fstat(fd, fst);
+       if ((ret < 0 || get_stat_xattr(NULL, fd, fst, xst) < 0) && xst)
+               xst->st_mode = 0;
+       return ret;
+}
+
 #endif /* SUPPORT_XATTRS */