- Make sure that we don't write an ACL in read-only or list-only modes.
[rsync/rsync-patches.git] / fake-super.diff
CommitLineData
c44efccb
WD
1Depends-On-Patch: acls.diff
2Depends-On-Patch: xattrs.diff
3
4This patch adds a new option: --fake-super, which tells rsync to copy in a
5fake super-user mode that stores various file attributes in an extended-
6attribute value instead of as real file-system attributes. The items
7affected are:
8
7ac2aef2
WD
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.
c44efccb 15
7ac2aef2
WD
16 rdev devices and special files are created as zero-length normal
17 files (with all the attributes preserved in the xattr-stat).
c44efccb 18
7ac2aef2 19 uid the real owner will be the executor of the receiving rsync.
c44efccb 20
7ac2aef2 21 gid the real group will be the default group of the executor.
c44efccb
WD
22
23The --fake-super option only affects the side where the option is used. To
24affect 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
7ac2aef2
WD
28The --fake-super option affects both sides of a local copy, so if you want
29to affect only one side or the other, you'll need to turn the copy into a
30remote copy to/from localhost. However, it's always safe to copy from some
31non-fake-super files into some fake-super files using a normal local copy
32since the non-fake source files will just have their normal attributes.
33
34A daemon can set "fake super = yes" in the rsync.conf file for any module
35that you'd like to be able to preserve all attributes without having it
36run as root (the client cannot affect this setting on the daemon).
c44efccb
WD
37
38After applying this patch, run these commands for a successful build:
39
40 ./prepare-source
1e883fdf
WD
41 ./configure --enable-xattr-support
42 make
43
44or, if you want ACL support too:
45
46 ./prepare-source
47 ./configure --enable-acl-support --enable-xattr-support
c44efccb
WD
48 make
49
e66b9b1f
WD
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;
c44efccb 63
e66b9b1f
WD
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;
c44efccb
WD
70--- old/clientserver.c
71+++ new/clientserver.c
611f9f3c
WD
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. */
c44efccb 75
611f9f3c 76+ if (lp_fake_super(i))
c44efccb 77+ am_root = -1;
611f9f3c
WD
78+ else if (am_root < 0) /* Treat --fake-super from client as --super. */
79+ am_root = 2;
c44efccb 80+
611f9f3c
WD
81 if (filesfrom_fd == 0)
82 filesfrom_fd = f_in;
83
e66b9b1f
WD
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;
c44efccb
WD
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");
7ac2aef2 213+ rprintf(F," --fake-super store/recover privileged attrs using xattrs\n");
c44efccb
WD
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 },
e66b9b1f
WD
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);
c44efccb
WD
236--- old/rsync.c
237+++ new/rsync.c
35d8f76e
WD
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
c44efccb
WD
247 (long)sxp->st.st_gid, (long)file->gid);
248 }
249 }
3ae9c1ee
WD
250- if (do_lchown(fname,
251+ if (am_root < 0)
252+ ;
253+ else if (do_lchown(fname,
c44efccb
WD
254 change_uid ? file->uid : sxp->st.st_uid,
255 change_gid ? file->gid : sxp->st.st_gid) != 0) {
3ae9c1ee 256 /* shouldn't have attempted to change uid or gid
35d8f76e 257@@ -206,7 +207,7 @@ int set_file_attrs(char *fname, struct f
c44efccb
WD
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 */
35d8f76e 266@@ -224,6 +225,24 @@ int set_file_attrs(char *fname, struct f
e66b9b1f
WD
267 if (preserve_xattrs && set_xattr(fname, file, sxp) == 0)
268 updated = 1;
c44efccb 269 #endif
e66b9b1f
WD
270+
271+ if (am_root < 0 && !S_ISLNK(file->mode)) {
3ae9c1ee
WD
272+ switch (set_stat_xattr(fname, file)) {
273+ case 0:
274+ break;
275+ case -1:
c44efccb
WD
276+ rsyserr(FERROR, errno,
277+ "write of stat xattr failed for %s",
278+ full_fname(fname));
3ae9c1ee
WD
279+ break;
280+ case -2:
281+ rsyserr(FERROR, errno,
282+ "delete of stat xattr failed for %s",
283+ full_fname(fname));
284+ break;
c44efccb
WD
285+ }
286+ }
287+
e66b9b1f
WD
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.
35d8f76e 291@@ -237,7 +256,7 @@ int set_file_attrs(char *fname, struct f
c44efccb 292
e66b9b1f
WD
293 #ifdef HAVE_CHMOD
294 if ((sxp->st.st_mode & CHMOD_BITS) != (new_mode & CHMOD_BITS)) {
295- int ret = do_chmod(fname, new_mode);
9b91764c 296+ int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
e66b9b1f
WD
297 if (ret < 0) {
298 rsyserr(FERROR, errno,
299 "failed to set permissions on %s",
7ac2aef2
WD
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
c44efccb
WD
356--- old/syscall.c
357+++ new/syscall.c
e66b9b1f 358@@ -28,6 +28,7 @@
c44efccb
WD
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;
e66b9b1f 366@@ -79,6 +80,15 @@ int do_mknod(char *pathname, mode_t mode
c44efccb
WD
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);
c44efccb
WD
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
e66b9b1f 419@@ -26,11 +26,15 @@
c44efccb
WD
420 #ifdef SUPPORT_XATTRS
421
422 extern int dry_run;
423+extern int am_root;
e66b9b1f 424+extern mode_t orig_umask;
c44efccb
WD
425 extern unsigned int file_struct_len;
426
427 #define RSYNC_XAL_INITIAL 5
e66b9b1f
WD
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
c44efccb
WD
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
d30eff93 444+ && name[10] == '%' && strcmp(name, FAKE_XATTR) == 0)
c44efccb
WD
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)
e66b9b1f 452@@ -285,10 +295,19 @@ void receive_xattr(struct file_struct *f
c44efccb
WD
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
d30eff93 458+ && ptr[10] == '%' && strcmp(ptr, FAKE_XATTR) == 0) {
c44efccb
WD
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
1e883fdf
WD
470 if (strncmp(rxa->name, UNIQUE_PREFIX, UPRE_LEN) == 0) {
471 rxa->name_len -= UPRE_LEN;
35d8f76e 472@@ -365,4 +384,103 @@ int set_xattr(const char *fname, const s
e66b9b1f
WD
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;
35d8f76e 510+ mode_t mode;
e66b9b1f
WD
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+
7ac2aef2
WD
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);
35d8f76e
WD
526+ if (fst.st_mode != mode)
527+ do_chmod(fname, mode);
e66b9b1f
WD
528+ if (!IS_DEVICE(fst.st_mode) && !IS_SPECIAL(fst.st_mode))
529+ fst.st_rdev = 0; /* just in case */
530+
35d8f76e 531+ if (mode == file->mode && fst.st_rdev == rdev
e66b9b1f
WD
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 */