- Really fixed the ability to configure without xattr support.
[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
1fdd9ea6
WD
50TODO:
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
e66b9b1f
WD
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;
c44efccb 69
e66b9b1f
WD
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;
c44efccb
WD
76--- old/clientserver.c
77+++ new/clientserver.c
611f9f3c
WD
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. */
c44efccb 81
611f9f3c 82+ if (lp_fake_super(i))
c44efccb 83+ am_root = -1;
611f9f3c
WD
84+ else if (am_root < 0) /* Treat --fake-super from client as --super. */
85+ am_root = 2;
c44efccb 86+
611f9f3c
WD
87 if (filesfrom_fd == 0)
88 filesfrom_fd = f_in;
89
e66b9b1f
WD
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
1fdd9ea6
WD
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
e66b9b1f
WD
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",
1fdd9ea6 159@@ -963,7 +943,7 @@ struct file_struct *make_file(char *fnam
e66b9b1f
WD
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;
c44efccb
WD
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");
7ac2aef2 246+ rprintf(F," --fake-super store/recover privileged attrs using xattrs\n");
c44efccb
WD
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 },
e66b9b1f
WD
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);
c44efccb
WD
269--- old/rsync.c
270+++ new/rsync.c
35d8f76e
WD
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
c44efccb
WD
280 (long)sxp->st.st_gid, (long)file->gid);
281 }
282 }
3ae9c1ee
WD
283- if (do_lchown(fname,
284+ if (am_root < 0)
285+ ;
286+ else if (do_lchown(fname,
c44efccb
WD
287 change_uid ? file->uid : sxp->st.st_uid,
288 change_gid ? file->gid : sxp->st.st_gid) != 0) {
3ae9c1ee 289 /* shouldn't have attempted to change uid or gid
35d8f76e 290@@ -206,7 +207,7 @@ int set_file_attrs(char *fname, struct f
c44efccb
WD
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 */
1fdd9ea6
WD
299@@ -223,6 +224,8 @@ int set_file_attrs(char *fname, struct f
300 #ifdef SUPPORT_XATTRS
e66b9b1f
WD
301 if (preserve_xattrs && set_xattr(fname, file, sxp) == 0)
302 updated = 1;
1fdd9ea6
WD
303+ if (am_root < 0)
304+ set_stat_xattr(fname, file);
c44efccb 305 #endif
e66b9b1f
WD
306 #ifdef SUPPORT_ACLS
307 /* It's OK to call set_acl() now, even for a dir, as the generator
1fdd9ea6 308@@ -237,7 +240,7 @@ int set_file_attrs(char *fname, struct f
c44efccb 309
e66b9b1f
WD
310 #ifdef HAVE_CHMOD
311 if ((sxp->st.st_mode & CHMOD_BITS) != (new_mode & CHMOD_BITS)) {
312- int ret = do_chmod(fname, new_mode);
9b91764c 313+ int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
e66b9b1f
WD
314 if (ret < 0) {
315 rsyserr(FERROR, errno,
316 "failed to set permissions on %s",
1fdd9ea6
WD
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+}
7ac2aef2
WD
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
c44efccb
WD
412--- old/syscall.c
413+++ new/syscall.c
e66b9b1f 414@@ -28,6 +28,7 @@
c44efccb
WD
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;
e66b9b1f 422@@ -79,6 +80,15 @@ int do_mknod(char *pathname, mode_t mode
c44efccb
WD
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);
c44efccb
WD
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
1fdd9ea6 475@@ -28,11 +28,15 @@
c44efccb 476 extern int dry_run;
1fdd9ea6
WD
477 extern int read_only;
478 extern int list_only;
c44efccb 479+extern int am_root;
e66b9b1f 480+extern mode_t orig_umask;
c44efccb
WD
481 extern unsigned int file_struct_len;
482
483 #define RSYNC_XAL_INITIAL 5
e66b9b1f
WD
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;
1fdd9ea6 491@@ -132,9 +136,15 @@ static int rsync_xal_get(const char *fna
c44efccb
WD
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
d30eff93 500+ && name[10] == '%' && strcmp(name, FAKE_XATTR) == 0)
c44efccb
WD
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)
1fdd9ea6 508@@ -287,10 +297,19 @@ void receive_xattr(struct file_struct *f
c44efccb
WD
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
d30eff93 514+ && ptr[10] == '%' && strcmp(ptr, FAKE_XATTR) == 0) {
c44efccb
WD
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
1e883fdf
WD
526 if (strncmp(rxa->name, UNIQUE_PREFIX, UPRE_LEN) == 0) {
527 rxa->name_len -= UPRE_LEN;
1fdd9ea6 528@@ -372,4 +391,146 @@ int set_xattr(const char *fname, const s
e66b9b1f
WD
529 return rsync_xal_set(fname, lst + ndx); /* TODO: This needs to return 1 if no xattrs changed! */
530 }
531
1fdd9ea6 532+int get_stat_xattr(const char *fname, int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
e66b9b1f
WD
533+{
534+ int mode, rdev_major, rdev_minor, uid, gid, len;
535+ char buf[256];
536+
1fdd9ea6
WD
537+ if (am_root >= 0)
538+ return -1;
539+
540+ if (xst)
541+ *xst = *fst;
e66b9b1f 542+ else
1fdd9ea6
WD
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";
e66b9b1f 549+ len = sys_fgetxattr(fd, FAKE_XATTR, buf, sizeof buf - 1);
1fdd9ea6
WD
550+ }
551+ if (len >= (int)sizeof buf) {
552+ len = -1;
553+ errno = ERANGE;
554+ }
555+ if (len < 0) {
e66b9b1f
WD
556+ if (errno == ENOTSUP || errno == ENOATTR)
557+ return -1;
1fdd9ea6
WD
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));
e66b9b1f
WD
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) {
1fdd9ea6
WD
571+ rprintf(FERROR, "Corrupt %s xattr attached to %s: \"%s\"\n",
572+ FAKE_XATTR, full_fname(fname), buf);
573+ exit_cleanup(RERR_FILEIO);
e66b9b1f
WD
574+ }
575+
1fdd9ea6
WD
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;
e66b9b1f
WD
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;
35d8f76e 588+ mode_t mode;
e66b9b1f
WD
589+
590+ if (dry_run)
591+ return 0;
592+
1fdd9ea6
WD
593+ if (read_only || list_only) {
594+ rsyserr(FERROR, EROFS, "failed to write xattr %s for %s",
595+ FAKE_XATTR, full_fname(fname));
e66b9b1f 596+ return -1;
1fdd9ea6
WD
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+ }
e66b9b1f
WD
604+
605+ if (IS_DEVICE(file->mode) || IS_SPECIAL(file->mode))
606+ rdev = file->u.rdev;
607+ else
608+ rdev = 0;
609+
7ac2aef2
WD
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);
35d8f76e
WD
613+ if (fst.st_mode != mode)
614+ do_chmod(fname, mode);
e66b9b1f
WD
615+ if (!IS_DEVICE(fst.st_mode) && !IS_SPECIAL(fst.st_mode))
616+ fst.st_rdev = 0; /* just in case */
617+
35d8f76e 618+ if (mode == file->mode && fst.st_rdev == rdev
e66b9b1f
WD
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 */
1fdd9ea6
WD
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+ }
e66b9b1f
WD
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",
1fdd9ea6 634+ to_wire_mode(file->mode),
e66b9b1f
WD
635+ (int)major(rdev), (int)minor(rdev),
636+ (int)file->uid, (int)file->gid);
1fdd9ea6
WD
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+ }
e66b9b1f 645+ }
1fdd9ea6 646+
e66b9b1f
WD
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);
1fdd9ea6 653+ if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
e66b9b1f
WD
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);
1fdd9ea6 661+ if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
e66b9b1f
WD
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);
1fdd9ea6 669+ if ((ret < 0 || get_stat_xattr(NULL, fd, fst, xst) < 0) && xst)
e66b9b1f
WD
670+ xst->st_mode = 0;
671+ return ret;
672+}
673+
674 #endif /* SUPPORT_XATTRS */