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