- Make sure we don't write out any extraneous mode bits as part of
[rsync/rsync-patches.git] / fake-super.diff
1 Depends-On-Patch: acls.diff
2 Depends-On-Patch: xattrs.diff
3
4 This patch adds a new option:  --fake-super, which tells rsync to copy in a
5 fake super-user mode that stores various file attributes in an extended-
6 attribute value instead of as real file-system attributes.  The items
7 affected are:
8
9   mode  the real mode of the file always has the special-permission bits
10         cleared (u-s,g-s,o-t) and full owner access is always enabled
11         (u+rw for files and u+rwx for directories).  The former makes
12         the files safe if the user and/or group info was not really
13         preserved, and the latter ensures that our fake-super process
14         can always read & write & scan the files and directories.
15
16   rdev  devices and special files are created as zero-length normal
17         files (with all the attributes preserved in the xattr-stat).
18
19   uid   the real owner will be the executor of the receiving rsync.
20
21   gid   the real group will be the default group of the executor.
22
23 The --fake-super option only affects the side where the option is used.  To
24 affect the remote side of a remote-shell connection, specify an rsync path:
25
26   rsync -av --rsync-path='rsync --fake-super' /src/ host:/dest/
27
28 The --fake-super option affects both sides of a local copy, so if you want
29 to affect only one side or the other, you'll need to turn the copy into a
30 remote copy to/from localhost.  However, it's always safe to copy from some
31 non-fake-super files into some fake-super files using a normal local copy
32 since the non-fake source files will just have their normal attributes.
33
34 A daemon can set "fake super = yes" in the rsync.conf file for any module
35 that you'd like to be able to preserve all attributes without having it
36 run as root (the client cannot affect this setting on the daemon).
37
38 After applying this patch, run these commands for a successful build:
39
40     ./prepare-source
41     ./configure --enable-xattr-support
42     make
43
44 or, if you want ACL support too:
45
46     ./prepare-source
47     ./configure --enable-acl-support --enable-xattr-support
48     make
49
50 --- old/backup.c
51 +++ new/backup.c
52 @@ -129,7 +129,7 @@ static int make_bak_dir(char *fullpath)
53                 if (p >= rel) {
54                         /* Try to transfer the directory settings of the
55                          * actual dir that the files are coming from. */
56 -                       if (do_stat(rel, &sx.st) < 0) {
57 +                       if (x_stat(rel, &sx.st, NULL) < 0) {
58                                 rsyserr(FERROR, errno,
59                                         "make_bak_dir stat %s failed",
60                                         full_fname(rel));
61 @@ -200,7 +200,7 @@ static int keep_backup(char *fname)
62         int ret_code;
63  
64         /* return if no file to keep */
65 -       if (do_lstat(fname, &sx.st) < 0)
66 +       if (x_lstat(fname, &sx.st, NULL) < 0)
67                 return 1;
68  #ifdef SUPPORT_ACLS
69         sx.acc_acl = sx.def_acl = NULL;
70 --- old/clientserver.c
71 +++ new/clientserver.c
72 @@ -625,6 +625,11 @@ static int rsync_module(int f_in, int f_
73         ret = parse_arguments(&argc, (const char ***) &argv, 0);
74         quiet = 0; /* Don't let someone try to be tricky. */
75  
76 +       if (lp_fake_super(i))
77 +               am_root = -1;
78 +       else if (am_root < 0) /* Treat --fake-super from client as --super. */
79 +               am_root = 2;
80 +
81         if (filesfrom_fd == 0)
82                 filesfrom_fd = f_in;
83  
84 --- old/flist.c
85 +++ new/flist.c
86 @@ -181,7 +181,7 @@ static int readlink_stat(const char *pat
87         }
88         return 0;
89  #else
90 -       return do_stat(path, stp);
91 +       return x_stat(path, stp, NULL);
92  #endif
93  }
94  
95 @@ -189,17 +189,17 @@ int link_stat(const char *path, STRUCT_S
96  {
97  #ifdef SUPPORT_LINKS
98         if (copy_links)
99 -               return do_stat(path, stp);
100 -       if (do_lstat(path, stp) < 0)
101 +               return x_stat(path, stp, NULL);
102 +       if (x_lstat(path, stp, NULL) < 0)
103                 return -1;
104         if (follow_dirlinks && S_ISLNK(stp->st_mode)) {
105                 STRUCT_STAT st;
106 -               if (do_stat(path, &st) == 0 && S_ISDIR(st.st_mode))
107 +               if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode))
108                         *stp = st;
109         }
110         return 0;
111  #else
112 -       return do_stat(path, stp);
113 +       return x_stat(path, stp, NULL);
114  #endif
115  }
116  
117 @@ -234,26 +234,6 @@ static int is_excluded(char *fname, int 
118         return 0;
119  }
120  
121 -static int to_wire_mode(mode_t mode)
122 -{
123 -#ifdef SUPPORT_LINKS
124 -#if _S_IFLNK != 0120000
125 -       if (S_ISLNK(mode))
126 -               return (mode & ~(_S_IFMT)) | 0120000;
127 -#endif
128 -#endif
129 -       return mode;
130 -}
131 -
132 -static mode_t from_wire_mode(int mode)
133 -{
134 -#if _S_IFLNK != 0120000
135 -       if ((mode & (_S_IFMT)) == 0120000)
136 -               return (mode & ~(_S_IFMT)) | _S_IFLNK;
137 -#endif
138 -       return mode;
139 -}
140 -
141  static void send_directory(int f, struct file_list *flist,
142                            char *fbuf, int len);
143  
144 @@ -793,7 +773,7 @@ struct file_struct *make_file(char *fnam
145                 if (save_errno == ENOENT) {
146  #ifdef SUPPORT_LINKS
147                         /* Avoid "vanished" error if symlink points nowhere. */
148 -                       if (copy_links && do_lstat(thisname, &st) == 0
149 +                       if (copy_links && x_lstat(thisname, &st, NULL) == 0
150                             && S_ISLNK(st.st_mode)) {
151                                 io_error |= IOERR_GENERAL;
152                                 rprintf(FERROR, "symlink has no referent: %s\n",
153 @@ -963,7 +943,7 @@ struct file_struct *make_file(char *fnam
154                 int save_mode = file->mode;
155                 file->mode = S_IFDIR; /* Find a directory with our name. */
156                 if (flist_find(the_file_list, file) >= 0
157 -                   && do_stat(thisname, &st2) == 0 && S_ISDIR(st2.st_mode)) {
158 +                   && x_stat(thisname, &st2, NULL) == 0 && S_ISDIR(st2.st_mode)) {
159                         file->modtime = st2.st_mtime;
160                         file->length = st2.st_size;
161                         file->mode = st2.st_mode;
162 --- old/loadparm.c
163 +++ new/loadparm.c
164 @@ -150,6 +150,7 @@ typedef struct
165         int syslog_facility;
166         int timeout;
167  
168 +       BOOL fake_super;
169         BOOL ignore_errors;
170         BOOL ignore_nonreadable;
171         BOOL list;
172 @@ -197,6 +198,7 @@ static service sDefault =
173   /* syslog_facility; */                LOG_DAEMON,
174   /* timeout; */                        0,
175  
176 + /* fake_super; */             False,
177   /* ignore_errors; */          False,
178   /* ignore_nonreadable; */     False,
179   /* list; */                   True,
180 @@ -298,6 +300,7 @@ static struct parm_struct parm_table[] =
181   {"dont compress",     P_STRING, P_LOCAL, &sDefault.dont_compress,     NULL,0},
182   {"exclude from",      P_STRING, P_LOCAL, &sDefault.exclude_from,      NULL,0},
183   {"exclude",           P_STRING, P_LOCAL, &sDefault.exclude,           NULL,0},
184 + {"fake super",        P_BOOL,   P_LOCAL, &sDefault.fake_super,        NULL,0},
185   {"filter",            P_STRING, P_LOCAL, &sDefault.filter,            NULL,0},
186   {"gid",               P_STRING, P_LOCAL, &sDefault.gid,               NULL,0},
187   {"hosts allow",       P_STRING, P_LOCAL, &sDefault.hosts_allow,       NULL,0},
188 @@ -412,6 +415,7 @@ FN_LOCAL_INTEGER(lp_max_connections, max
189  FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity)
190  FN_LOCAL_INTEGER(lp_timeout, timeout)
191  
192 +FN_LOCAL_BOOL(lp_fake_super, fake_super)
193  FN_LOCAL_BOOL(lp_ignore_errors, ignore_errors)
194  FN_LOCAL_BOOL(lp_ignore_nonreadable, ignore_nonreadable)
195  FN_LOCAL_BOOL(lp_list, list)
196 @@ -816,7 +820,7 @@ BOOL lp_load(char *pszFname, int globals
197  
198         if (pszFname)
199             pstrcpy(n2,pszFname);
200 -       else if (am_server && !am_root)
201 +       else if (am_server && am_root <= 0)
202             pstrcpy(n2,RSYNCD_USERCONF);
203         else
204             pstrcpy(n2,RSYNCD_SYSCONF);
205 --- old/options.c
206 +++ new/options.c
207 @@ -73,7 +73,7 @@ int protocol_version = PROTOCOL_VERSION;
208  int sparse_files = 0;
209  int do_compression = 0;
210  int def_compress_level = Z_DEFAULT_COMPRESSION;
211 -int am_root = 0;
212 +int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */
213  int am_server = 0;
214  int am_sender = 0;
215  int am_generator = 0;
216 @@ -329,6 +329,9 @@ void usage(enum logcode F)
217    rprintf(F," -t, --times                 preserve times\n");
218    rprintf(F," -O, --omit-dir-times        omit directories when preserving times\n");
219    rprintf(F,"     --super                 receiver attempts super-user activities\n");
220 +#ifdef SUPPORT_XATTRS
221 +  rprintf(F,"     --fake-super            store/recover privileged attrs using xattrs\n");
222 +#endif
223    rprintf(F," -S, --sparse                handle sparse files efficiently\n");
224    rprintf(F," -n, --dry-run               show what would have been transferred\n");
225    rprintf(F," -W, --whole-file            copy files whole (without rsync algorithm)\n");
226 @@ -453,6 +456,7 @@ static struct poptOption long_options[] 
227    {"modify-window",    0,  POPT_ARG_INT,    &modify_window, OPT_MODIFY_WINDOW, 0, 0 },
228    {"super",            0,  POPT_ARG_VAL,    &am_root, 2, 0, 0 },
229    {"no-super",         0,  POPT_ARG_VAL,    &am_root, 0, 0, 0 },
230 +  {"fake-super",       0,  POPT_ARG_VAL,    &am_root, -1, 0, 0 },
231    {"owner",           'o', POPT_ARG_VAL,    &preserve_uid, 1, 0, 0 },
232    {"no-owner",         0,  POPT_ARG_VAL,    &preserve_uid, 0, 0, 0 },
233    {"no-o",             0,  POPT_ARG_VAL,    &preserve_uid, 0, 0, 0 },
234 @@ -1178,6 +1182,14 @@ int parse_arguments(int *argc, const cha
235         }
236  #endif
237  
238 +#ifndef SUPPORT_XATTRS
239 +       if (am_root < 0) {
240 +               snprintf(err_buf, sizeof err_buf,
241 +                        "--fake-super requires an rsync with extended attributes enabled\n");
242 +               return 0;
243 +       }
244 +#endif
245 +
246         if (write_batch && read_batch) {
247                 snprintf(err_buf, sizeof err_buf,
248                         "--write-batch and --read-batch can not be used together\n");
249 --- old/receiver.c
250 +++ new/receiver.c
251 @@ -528,7 +528,7 @@ int recv_files(int f_in, struct file_lis
252                 if (fd1 == -1) {
253                         st.st_mode = 0;
254                         st.st_size = 0;
255 -               } else if (do_fstat(fd1,&st) != 0) {
256 +               } else if (x_fstat(fd1, &st, NULL) != 0) {
257                         rsyserr(FERROR, errno, "fstat %s failed",
258                                 full_fname(fnamecmp));
259                         discard_receive_data(f_in, file->length);
260 --- old/rsync.c
261 +++ new/rsync.c
262 @@ -49,7 +49,6 @@ extern int preserve_gid;
263  extern int inplace;
264  extern int keep_dirlinks;
265  extern int make_backups;
266 -extern mode_t orig_umask;
267  extern struct stats stats;
268  extern struct chmod_mode_struct *daemon_chmod_modes;
269  
270 @@ -197,7 +196,9 @@ int set_file_attrs(char *fname, struct f
271                                         (long)sxp->st.st_gid, (long)file->gid);
272                         }
273                 }
274 -               if (do_lchown(fname,
275 +               if (am_root < 0) {
276 +                       ;
277 +               } else if (do_lchown(fname,
278                     change_uid ? file->uid : sxp->st.st_uid,
279                     change_gid ? file->gid : sxp->st.st_gid) != 0) {
280                         /* shouldn't have attempted to change uid or gid
281 @@ -206,7 +207,7 @@ int set_file_attrs(char *fname, struct f
282                             change_uid ? "chown" : "chgrp",
283                             full_fname(fname));
284                         goto cleanup;
285 -               }
286 +               } else
287                 /* a lchown had been done - we have to re-stat if the
288                  * destination had the setuid or setgid bits set due
289                  * to the side effect of the chown call */
290 @@ -223,6 +224,8 @@ int set_file_attrs(char *fname, struct f
291  #ifdef SUPPORT_XATTRS
292         if (preserve_xattrs && set_xattr(fname, file, sxp) == 0)
293                 updated = 1;
294 +       if (am_root < 0)
295 +               set_stat_xattr(fname, file);
296  #endif
297  #ifdef SUPPORT_ACLS
298         /* It's OK to call set_acl() now, even for a dir, as the generator
299 @@ -237,7 +240,7 @@ int set_file_attrs(char *fname, struct f
300  
301  #ifdef HAVE_CHMOD
302         if ((sxp->st.st_mode & CHMOD_BITS) != (new_mode & CHMOD_BITS)) {
303 -               int ret = do_chmod(fname, new_mode);
304 +               int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
305                 if (ret < 0) {
306                         rsyserr(FERROR, errno,
307                                 "failed to set permissions on %s",
308 --- old/rsync.h
309 +++ new/rsync.h
310 @@ -707,6 +707,12 @@ typedef struct {
311  
312  #include "proto.h"
313  
314 +#ifndef SUPPORT_XATTRS
315 +#define x_stat(fn,fst,xst) do_stat(fn,fst)
316 +#define x_lstat(fn,fst,xst) do_lstat(fn,fst)
317 +#define x_fstat(fd,fst,xst) do_fstat(fd,fst)
318 +#endif
319 +
320  /* We have replacement versions of these if they're missing. */
321  #ifndef HAVE_ASPRINTF
322  int asprintf(char **ptr, const char *format, ...);
323 @@ -924,3 +930,23 @@ int inet_pton(int af, const char *src, v
324  #ifdef MAINTAINER_MODE
325  const char *get_panic_action(void);
326  #endif
327 +
328 +static inline int to_wire_mode(mode_t mode)
329 +{
330 +#ifdef SUPPORT_LINKS
331 +#if _S_IFLNK != 0120000
332 +       if (S_ISLNK(mode))
333 +               return (mode & ~(_S_IFMT)) | 0120000;
334 +#endif
335 +#endif
336 +       return mode;
337 +}
338 +
339 +static inline mode_t from_wire_mode(int mode)
340 +{
341 +#if _S_IFLNK != 0120000
342 +       if ((mode & (_S_IFMT)) == 0120000)
343 +               return (mode & ~(_S_IFMT)) | _S_IFLNK;
344 +#endif
345 +       return mode;
346 +}
347 --- old/rsync.yo
348 +++ new/rsync.yo
349 @@ -333,6 +333,7 @@ to the detailed description below for a 
350   -t, --times                 preserve times
351   -O, --omit-dir-times        omit directories when preserving times
352       --super                 receiver attempts super-user activities
353 +     --fake-super            store/recover privileged attrs using xattrs
354   -S, --sparse                handle sparse files efficiently
355   -n, --dry-run               show what would have been transferred
356   -W, --whole-file            copy files whole (without rsync algorithm)
357 @@ -846,7 +847,7 @@ permission value can be applied to the f
358  dit(bf(-o, --owner)) This option causes rsync to set the owner of the
359  destination file to be the same as the source file, but only if the
360  receiving rsync is being run as the super-user (see also the bf(--super)
361 -option to force rsync to attempt super-user activities).
362 +and bf(--fake-super) options).
363  Without this option, the owner is set to the invoking user on the
364  receiving side.
365  
366 @@ -869,7 +870,7 @@ default, but may fall back to using the 
367  dit(bf(--devices)) This option causes rsync to transfer character and
368  block device files to the remote system to recreate these devices.
369  This option has no effect if the receiving rsync is not run as the
370 -super-user and bf(--super) is not specified.
371 +super-user (see also the bf(--super) and bf(--fake-super) options).
372  
373  dit(bf(--specials)) This option causes rsync to transfer special files
374  such as named sockets and fifos.
375 @@ -899,6 +900,33 @@ also for ensuring that you will get erro
376  being running as the super-user.  To turn off super-user activities, the
377  super-user can use bf(--no-super).
378  
379 +dit(bf(--fake-super)) When this option is enabled, rsync simulates
380 +super-user activities by saving/restoring the privileged attributes via a
381 +special extended attribute that is attached to each file (as needed).  This
382 +includes the file's owner and group (if it is not the default), the file's
383 +device info (device & special files are created as empty text files), and
384 +any permission bits that we won't allow to be set on the real file (e.g.
385 +the real file gets u-s,g-s,o-t for safety) or that would limit the owner's
386 +access (since the real super-user can always access/change a file or
387 +directory, the files we create can always be accessed/changed by the
388 +creating user).
389 +
390 +The bf(--fake-super) option only affects the side where the option is used.
391 +To affect the remote side of a remote-shell connection, specify an rsync
392 +path:
393 +
394 +quote(tt(  rsync -av --rsync-path="rsync --fake-super" /src/ host:/dest/))
395 +
396 +Since there is only one "side" in a local copy, this option affects both
397 +the sending and recieving of files.  You'll need to specify a copy using
398 +"localhost" if you need to avoid this.  Note, however, that it is always
399 +safe to copy from some non-fake-super files into some fake-super files
400 +using a local bf(--fake-super) command because the non-fake source files
401 +will just have their normal attributes.
402 +
403 +See also the "fake super" setting in the daemon's rsyncd.conf file.
404 +This option is overridden by both bf(--super) and bf(--no-super).
405 +
406  dit(bf(-S, --sparse)) Try to handle sparse files efficiently so they take
407  up less space on the destination.  Conflicts with bf(--inplace) because it's
408  not possible to overwrite data in a sparse fashion.
409 --- old/rsyncd.conf.yo
410 +++ new/rsyncd.conf.yo
411 @@ -226,6 +226,11 @@ file transfers to and from that module s
412  was run as root. This complements the "uid" option. The default is gid -2,
413  which is normally the group "nobody".
414  
415 +dit(bf(fake super)) Setting "fake super = yes" for a module causes the
416 +daemon side to behave as if the bf(--fake-user) command-line option had
417 +been specified.  This allows the full attributes of a file to be stored
418 +without having to have the daemon actually running as root.
419 +
420  dit(bf(filter)) The "filter" option allows you to specify a space-separated
421  list of filter rules that the daemon will not allow to be read or written.
422  This is only superficially equivalent to the client specifying these
423 --- old/syscall.c
424 +++ new/syscall.c
425 @@ -28,6 +28,7 @@
426  #endif
427  
428  extern int dry_run;
429 +extern int am_root;
430  extern int read_only;
431  extern int list_only;
432  extern int preserve_perms;
433 @@ -79,6 +80,15 @@ int do_mknod(char *pathname, mode_t mode
434  {
435         if (dry_run) return 0;
436         RETURN_ERROR_IF_RO_OR_LO;
437 +
438 +       /* For --fake-super, we create a normal file with mode 0600. */
439 +       if (am_root < 0) {
440 +               int fd = open(pathname, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR|S_IRUSR);
441 +               if (fd < 0 || close(fd) < 0)
442 +                       return -1;
443 +               return 0;
444 +       }
445 +
446  #if !defined MKNOD_CREATES_FIFOS && defined HAVE_MKFIFO
447         if (S_ISFIFO(mode))
448                 return mkfifo(pathname, mode);
449 --- old/t_unsafe.c
450 +++ new/t_unsafe.c
451 @@ -24,7 +24,11 @@
452  
453  #include "rsync.h"
454  
455 -int dry_run, read_only, list_only, verbose;
456 +int dry_run = 0;
457 +int am_root = 0;
458 +int read_only = 0;
459 +int list_only = 0;
460 +int verbose = 0;
461  int preserve_perms = 0;
462  
463  int
464 --- old/tls.c
465 +++ new/tls.c
466 @@ -39,6 +39,7 @@
467  
468  /* These are to make syscall.o shut up. */
469  int dry_run = 0;
470 +int am_root = 0; /* TODO: add option to set this to -1. */
471  int read_only = 1;
472  int list_only = 0;
473  int preserve_perms = 0;
474 --- old/trimslash.c
475 +++ new/trimslash.c
476 @@ -23,6 +23,7 @@
477  
478  /* These are to make syscall.o shut up. */
479  int dry_run = 0;
480 +int am_root = 0;
481  int read_only = 1;
482  int list_only = 0;
483  int preserve_perms = 0;
484 --- old/xattr.c
485 +++ new/xattr.c
486 @@ -28,11 +28,15 @@
487  extern int dry_run;
488  extern int read_only;
489  extern int list_only;
490 +extern int am_root;
491 +extern mode_t orig_umask;
492  extern unsigned int file_struct_len;
493  
494  #define RSYNC_XAL_INITIAL 5
495  #define RSYNC_XAL_LIST_INITIAL 100
496  
497 +#define FAKE_XATTR "user.rsync%stat"
498 +
499  typedef struct {
500         char *name;
501         char *datum;
502 @@ -132,9 +136,15 @@ static int rsync_xal_get(const char *fna
503         if (name_size == 0)
504                 return 0;
505         for (left = name_size, name = namebuf; left > 0 ; left -= len, name += len) {
506 -               rsync_xa *rxas = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL);
507 +               rsync_xa *rxas;
508  
509                 len = strlen(name) + 1;
510 +               if (am_root < 0 && len == sizeof FAKE_XATTR
511 +                && name[10] == '%' && strcmp(name, FAKE_XATTR) == 0)
512 +                       continue;
513 +
514 +               rxas = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL);
515 +
516                 datum_size = sys_lgetxattr(fname, name, NULL, 0);
517                 if (datum_size < 0) {
518                         if (errno == ENOTSUP)
519 @@ -287,10 +297,19 @@ void receive_xattr(struct file_struct *f
520                                 out_of_memory("receive_xattr");
521                         read_buf(f, ptr, name_len);
522                         read_buf(f, ptr + name_len, datum_len);
523 +
524 +                       if (am_root < 0 && name_len == sizeof FAKE_XATTR
525 +                        && ptr[10] == '%' && strcmp(ptr, FAKE_XATTR) == 0) {
526 +                               free(ptr);
527 +                               temp_xattr.count--;
528 +                               continue;
529 +                       }
530 +
531                         rxa->name_len = name_len;
532                         rxa->datum_len = datum_len;
533                         rxa->name = ptr;
534                         rxa->datum = ptr + name_len;
535 +
536  #ifdef HAVE_OSX_XATTRS
537                         if (strncmp(rxa->name, UNIQUE_PREFIX, UPRE_LEN) == 0) {
538                                 rxa->name_len -= UPRE_LEN;
539 @@ -372,4 +391,146 @@ int set_xattr(const char *fname, const s
540         return rsync_xal_set(fname, lst + ndx); /* TODO:  This needs to return 1 if no xattrs changed! */
541  }
542  
543 +int get_stat_xattr(const char *fname, int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
544 +{
545 +       int mode, rdev_major, rdev_minor, uid, gid, len;
546 +       char buf[256];
547 +
548 +       if (am_root >= 0 || IS_DEVICE(fst->st_mode) || IS_SPECIAL(fst->st_mode))
549 +               return -1;
550 +
551 +       if (xst)
552 +               *xst = *fst;
553 +       else
554 +               xst = fst;
555 +       if (fname) {
556 +               fd = -1;
557 +               len = sys_lgetxattr(fname, FAKE_XATTR, buf, sizeof buf - 1);
558 +       } else {
559 +               fname = "fd";
560 +               len = sys_fgetxattr(fd, FAKE_XATTR, buf, sizeof buf - 1);
561 +       }
562 +       if (len >= (int)sizeof buf) {
563 +               len = -1;
564 +               errno = ERANGE;
565 +       }
566 +       if (len < 0) {
567 +               if (errno == ENOTSUP || errno == ENOATTR)
568 +                       return -1;
569 +               if (errno == EPERM && S_ISLNK(fst->st_mode)) {
570 +                       xst->st_uid = 0;
571 +                       xst->st_gid = 0;
572 +                       return 0;
573 +               }
574 +               rsyserr(FERROR, errno, "failed to read xattr %s for %s",
575 +                       FAKE_XATTR, full_fname(fname));
576 +               return -1;
577 +       }
578 +       buf[len] = '\0';
579 +
580 +       if (sscanf(buf, "%o %d,%d %d:%d",
581 +                  &mode, &rdev_major, &rdev_minor, &uid, &gid) != 5) {
582 +               rprintf(FERROR, "Corrupt %s xattr attached to %s: \"%s\"\n",
583 +                       FAKE_XATTR, full_fname(fname), buf);
584 +               exit_cleanup(RERR_FILEIO);
585 +       }
586 +
587 +       xst->st_mode = from_wire_mode(mode);
588 +       xst->st_rdev = MAKEDEV(rdev_major, rdev_minor);
589 +       xst->st_uid = uid;
590 +       xst->st_gid = gid;
591 +
592 +       return 0;
593 +}
594 +
595 +int set_stat_xattr(const char *fname, struct file_struct *file)
596 +{
597 +       STRUCT_STAT fst, xst;
598 +       dev_t rdev;
599 +       mode_t mode;
600 +
601 +       if (dry_run)
602 +               return 0;
603 +
604 +       if (read_only || list_only) {
605 +               rsyserr(FERROR, EROFS, "failed to write xattr %s for %s",
606 +                       FAKE_XATTR, full_fname(fname));
607 +               return -1;
608 +       }
609 +
610 +       if (x_lstat(fname, &fst, &xst) < 0) {
611 +               rsyserr(FERROR, errno, "failed to re-stat %s",
612 +                       full_fname(fname));
613 +               return -1;
614 +       }
615 +
616 +       if (IS_DEVICE(file->mode) || IS_SPECIAL(file->mode))
617 +               rdev = file->u.rdev;
618 +       else
619 +               rdev = 0;
620 +
621 +       /* Dump the special permissions and enable full owner access. */
622 +       mode = (fst.st_mode & ~CHMOD_BITS) | (file->mode & ACCESSPERMS)
623 +            | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
624 +       if (fst.st_mode != mode)
625 +               do_chmod(fname, mode);
626 +       if (!IS_DEVICE(fst.st_mode) && !IS_SPECIAL(fst.st_mode))
627 +               fst.st_rdev = 0; /* just in case */
628 +
629 +       if (mode == file->mode && fst.st_rdev == rdev
630 +        && fst.st_uid == file->uid && fst.st_gid == file->gid) {
631 +               /* xst.st_mode will be 0 if there's no current stat xattr */
632 +               if (xst.st_mode && sys_lremovexattr(fname, FAKE_XATTR) < 0) {
633 +                       rsyserr(FERROR, errno,
634 +                               "delete of stat xattr failed for %s",
635 +                               full_fname(fname));
636 +                       return -1;
637 +               }
638 +               return 0;
639 +       }
640 +
641 +       if (xst.st_mode != file->mode || xst.st_rdev != rdev
642 +        || xst.st_uid != file->uid || xst.st_gid != file->gid) {
643 +               char buf[256];
644 +               int len = snprintf(buf, sizeof buf, "%o %u,%u %u:%u",
645 +                       to_wire_mode(file->mode) & (_S_IFMT|CHMOD_BITS),
646 +                       (int)major(rdev), (int)minor(rdev),
647 +                       (int)file->uid, (int)file->gid);
648 +               if (sys_lsetxattr(fname, FAKE_XATTR, buf, len, 0) < 0) {
649 +                       if (errno == EPERM && S_ISLNK(fst.st_mode))
650 +                               return 0;
651 +                       rsyserr(FERROR, errno,
652 +                               "failed to write xattr %s for %s",
653 +                               FAKE_XATTR, full_fname(fname));
654 +                       return -1;
655 +               }
656 +       }
657 +
658 +       return 0;
659 +}
660 +
661 +int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
662 +{
663 +       int ret = do_stat(fname, fst);
664 +       if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
665 +               xst->st_mode = 0;
666 +       return ret;
667 +}
668 +
669 +int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
670 +{
671 +       int ret = do_lstat(fname, fst);
672 +       if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
673 +               xst->st_mode = 0;
674 +       return ret;
675 +}
676 +
677 +int x_fstat(int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
678 +{
679 +       int ret = do_fstat(fd, fst);
680 +       if ((ret < 0 || get_stat_xattr(NULL, fd, fst, xst) < 0) && xst)
681 +               xst->st_mode = 0;
682 +       return ret;
683 +}
684 +
685  #endif /* SUPPORT_XATTRS */