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