Skip to content

Commit edf6f57

Browse files
committed
Add lsattr/chattr support to --xattrs
This is like `bsdtar --fflags`. Repurposing `sys_llistxattr` allows cross-platform interoperability, such as for a transfer from Linux to Mac then from Mac to Linux. It's also simpler by not affecting the rest of the codebase. There is no new cmdline option because #165 wants this to work with just `-avX`. Closes: #165 Closes: #508
1 parent 797e17f commit edf6f57

File tree

2 files changed

+173
-4
lines changed

2 files changed

+173
-4
lines changed

lib/sysxattrs.c

Lines changed: 172 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,198 @@
3030

3131
#if defined HAVE_LINUX_XATTRS
3232

33+
#include <linux/fs.h>
34+
#define FS_FL_ATTR "user.rsync.lsattr"
35+
#define FS_FL_ATTR_BUF_SIZE sizeof("-2147483648")
36+
37+
#ifdef FS_IOC_GETFLAGS
38+
39+
#define FS_FL_SETTABLE (FS_APPEND_FL|FS_COMPR_FL|FS_DIRSYNC_FL|FS_IMMUTABLE_FL|FS_JOURNAL_DATA_FL|FS_NOATIME_FL|\
40+
FS_NOCOW_FL|FS_NODUMP_FL|FS_NOTAIL_FL|FS_PROJINHERIT_FL|FS_SECRM_FL|FS_SYNC_FL|FS_TOPDIR_FL|FS_UNRM_FL|\
41+
FS_CASEFOLD_FL|FS_NOCOMP_FL|FS_PROJINHERIT_FL|FS_DAX_FL)
42+
43+
static void close_keep_errno(int fd) {
44+
const int prev = errno;
45+
close(fd);
46+
errno = prev;
47+
}
48+
49+
static int handle_fs_fl_impl(int lsattr_fd, const char *source, const char *dest, int open_flags, int *lsattr_flags) {
50+
int ret;
51+
struct stat st;
52+
if (source) {
53+
int lsattr_fd_owned = 0;
54+
if (lsattr_fd == -1) {
55+
lsattr_fd_owned = 1;
56+
lsattr_fd = open(source, open_flags);
57+
if (lsattr_fd == -1) {
58+
rsyserr(FERROR_XFER, errno,
59+
"handle_fs_fl_impl: open(%s) for source failed",
60+
full_fname(source));
61+
return -1;
62+
}
63+
}
64+
if (fstat(lsattr_fd, &st)) {
65+
rsyserr(FERROR_XFER, errno,
66+
"handle_fs_fl_impl: stat(%s) for source failed",
67+
full_fname(source));
68+
if (lsattr_fd_owned)
69+
close_keep_errno(lsattr_fd);
70+
return -1;
71+
}
72+
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
73+
if (lsattr_fd_owned)
74+
close(lsattr_fd);
75+
return 0;
76+
}
77+
ret = ioctl(lsattr_fd, FS_IOC_GETFLAGS, lsattr_flags);
78+
if (ret) {
79+
rsyserr(FERROR_XFER, ret,
80+
"handle_fs_fl_impl: FS_IOC_GETFLAGS(%s) for source failed",
81+
full_fname(source));
82+
if (lsattr_fd_owned)
83+
close(lsattr_fd);
84+
assert(ret < 0);
85+
return ret;
86+
}
87+
*lsattr_flags &= FS_FL_SETTABLE;
88+
if (lsattr_fd_owned)
89+
close(lsattr_fd);
90+
}
91+
if (dest) {
92+
const int chattr_fd = open(dest, open_flags);
93+
if (chattr_fd == -1) {
94+
rsyserr(FERROR_XFER, errno,
95+
"handle_fs_fl_impl: open(%s) for dest failed",
96+
full_fname(dest));
97+
return -1;
98+
}
99+
if (fstat(chattr_fd, &st)) {
100+
rsyserr(FERROR_XFER, errno,
101+
"handle_fs_fl_impl: stat(%s) for dest failed",
102+
full_fname(dest));
103+
close_keep_errno(chattr_fd);
104+
return -1;
105+
}
106+
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
107+
close(chattr_fd);
108+
return 0;
109+
}
110+
int chattr_flags = 0;
111+
ret = ioctl(chattr_fd, FS_IOC_GETFLAGS, &chattr_flags);
112+
if (ret) {
113+
rsyserr(FERROR_XFER, ret,
114+
"handle_fs_fl_impl: FS_IOC_GETFLAGS(%s) for dest failed",
115+
full_fname(dest));
116+
close(chattr_fd);
117+
assert(ret < 0);
118+
return ret;
119+
}
120+
chattr_flags &= ~FS_FL_SETTABLE;
121+
chattr_flags |= *lsattr_flags;
122+
ret = ioctl(chattr_fd, FS_IOC_SETFLAGS, &chattr_flags);
123+
if (ret) {
124+
rsyserr(FERROR_XFER, ret,
125+
"handle_fs_fl_impl: FS_IOC_SETFLAGS(%s) for dest failed",
126+
full_fname(dest));
127+
close(chattr_fd);
128+
assert(ret < 0);
129+
return ret;
130+
}
131+
close(chattr_fd);
132+
}
133+
return 0;
134+
}
135+
136+
static int handle_fs_fl(int source_fd, const char *source, const char *dest, int nofollow, char *rsync_lsattr, size_t buf_size) {
137+
if (dest && !rsync_lsattr)
138+
return -1;
139+
int lsattr_flags = 0;
140+
if (dest)
141+
lsattr_flags = atoi(rsync_lsattr); // NOLINT(*-err34-c)
142+
int open_flags = O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK;
143+
if (nofollow)
144+
open_flags |= O_NOFOLLOW;
145+
int ret = handle_fs_fl_impl(source_fd, source, dest, open_flags, &lsattr_flags);
146+
if (ret)
147+
return ret;
148+
if (!source)
149+
return 0;
150+
char rsync_lsattr_tmp[FS_FL_ATTR_BUF_SIZE];
151+
ret = sprintf(rsync_lsattr_tmp, "%d", lsattr_flags) + 1;
152+
if (ret < 0)
153+
return ret;
154+
if (rsync_lsattr) {
155+
if (buf_size < (size_t) ret)
156+
return -ERANGE;
157+
memcpy(rsync_lsattr, rsync_lsattr_tmp, ret);
158+
}
159+
return ret;
160+
}
161+
#endif
162+
33163
ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
34164
{
165+
#ifdef FS_IOC_GETFLAGS
166+
if (!strcmp(name, FS_FL_ATTR))
167+
return handle_fs_fl(-1, path, NULL, 1, value, size);
168+
#endif
35169
return lgetxattr(path, name, value, size);
36170
}
37171

38172
ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
39173
{
174+
#ifdef FS_IOC_GETFLAGS
175+
if (!strcmp(name, FS_FL_ATTR))
176+
return handle_fs_fl(filedes, "(fd)", NULL, 0, value, size);
177+
#endif
40178
return fgetxattr(filedes, name, value, size);
41179
}
42180

43181
int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
44182
{
183+
#ifdef FS_IOC_GETFLAGS
184+
if (!strcmp(name, FS_FL_ATTR))
185+
return handle_fs_fl(-1, NULL, path, 1, (void *) value, size);
186+
#endif
45187
return lsetxattr(path, name, value, size, 0);
46188
}
47189

190+
#ifdef FS_IOC_GETFLAGS
191+
static const char FS_FL_ZERO[FS_FL_ATTR_BUF_SIZE] = "0";
192+
#endif
193+
48194
int sys_lremovexattr(const char *path, const char *name)
49195
{
196+
#ifdef FS_IOC_GETFLAGS
197+
if (!strcmp(name, FS_FL_ATTR))
198+
return handle_fs_fl(-1, NULL, path, 1, (char *) FS_FL_ZERO, strlen(FS_FL_ZERO));
199+
#endif
50200
return lremovexattr(path, name);
51201
}
52202

53-
ssize_t sys_llistxattr(const char *path, char *list, size_t size)
54-
{
55-
return llistxattr(path, list, size);
203+
ssize_t sys_llistxattr(const char *path, char *list, size_t size) {
204+
ssize_t ret;
205+
#ifdef FS_IOC_GETFLAGS
206+
char fs_fl_attr_buf[FS_FL_ATTR_BUF_SIZE];
207+
ret = handle_fs_fl(-1, path, NULL, 1, fs_fl_attr_buf, FS_FL_ATTR_BUF_SIZE);
208+
if (ret < 0)
209+
return ret;
210+
#endif
211+
ret = llistxattr(path, list, size);
212+
#ifdef FS_IOC_GETFLAGS
213+
if (ret < 0)
214+
return ret;
215+
if (strcmp(fs_fl_attr_buf, FS_FL_ZERO) != 0) {
216+
if (list) {
217+
if (ret + sizeof(FS_FL_ATTR) > size)
218+
return -ERANGE;
219+
memcpy(&list[ret], FS_FL_ATTR, sizeof(FS_FL_ATTR));
220+
}
221+
ret += sizeof(FS_FL_ATTR);
222+
}
223+
#endif
224+
return ret;
56225
}
57226

58227
#elif HAVE_OSX_XATTRS

rsync.1.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ has its own detailed description later in this manpage.
449449
--executability, -E preserve executability
450450
--chmod=CHMOD affect file and/or directory permissions
451451
--acls, -A preserve ACLs (implies --perms)
452-
--xattrs, -X preserve extended attributes
452+
--xattrs, -X preserve file attributes (including extended attributes)
453453
--owner, -o preserve owner (super-user only)
454454
--group, -g preserve group
455455
--devices preserve device files (super-user only)

0 commit comments

Comments
 (0)