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