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