Skip to content

Commit 519aa5a

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 519aa5a

File tree

2 files changed

+167
-4
lines changed

2 files changed

+167
-4
lines changed

lib/sysxattrs.c

Lines changed: 166 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,192 @@
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+
if ((open_flags & O_NOFOLLOW ? lstat : stat)(source, &st)) {
56+
rsyserr(FERROR_XFER, errno,
57+
"handle_fs_fl_impl: stat(%s) for source failed",
58+
full_fname(source));
59+
return -1;
60+
}
61+
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
62+
return 0;
63+
}
64+
lsattr_fd_owned = 1;
65+
lsattr_fd = open(source, open_flags);
66+
if (lsattr_fd == -1) {
67+
rsyserr(FERROR_XFER, errno,
68+
"handle_fs_fl_impl: open(%s) for source failed",
69+
full_fname(source));
70+
return -1;
71+
}
72+
}
73+
ret = ioctl(lsattr_fd, FS_IOC_GETFLAGS, lsattr_flags);
74+
if (ret) {
75+
rsyserr(FERROR_XFER, ret,
76+
"handle_fs_fl_impl: FS_IOC_GETFLAGS(%s) for source failed",
77+
full_fname(source));
78+
if (lsattr_fd_owned)
79+
close(lsattr_fd);
80+
assert(ret < 0);
81+
return ret;
82+
}
83+
*lsattr_flags &= FS_FL_SETTABLE;
84+
if (lsattr_fd_owned)
85+
close(lsattr_fd);
86+
}
87+
if (dest) {
88+
if ((open_flags & O_NOFOLLOW ? lstat : stat)(dest, &st)) {
89+
rsyserr(FERROR_XFER, errno,
90+
"handle_fs_fl_impl: stat(%s) for dest failed",
91+
full_fname(dest));
92+
return -1;
93+
}
94+
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
95+
return 0;
96+
}
97+
const int chattr_fd = open(dest, open_flags);
98+
if (chattr_fd == -1) {
99+
rsyserr(FERROR_XFER, errno,
100+
"handle_fs_fl_impl: open(%s) for dest failed",
101+
full_fname(dest));
102+
return -1;
103+
}
104+
int chattr_flags = 0;
105+
ret = ioctl(chattr_fd, FS_IOC_GETFLAGS, &chattr_flags);
106+
if (ret) {
107+
rsyserr(FERROR_XFER, ret,
108+
"handle_fs_fl_impl: FS_IOC_GETFLAGS(%s) for dest failed",
109+
full_fname(dest));
110+
close(chattr_fd);
111+
assert(ret < 0);
112+
return ret;
113+
}
114+
chattr_flags &= ~FS_FL_SETTABLE;
115+
chattr_flags |= *lsattr_flags;
116+
ret = ioctl(chattr_fd, FS_IOC_SETFLAGS, &chattr_flags);
117+
if (ret) {
118+
rsyserr(FERROR_XFER, ret,
119+
"handle_fs_fl_impl: FS_IOC_SETFLAGS(%s) for dest failed",
120+
full_fname(dest));
121+
close(chattr_fd);
122+
assert(ret < 0);
123+
return ret;
124+
}
125+
close(chattr_fd);
126+
}
127+
return 0;
128+
}
129+
130+
static int handle_fs_fl(int source_fd, const char *source, const char *dest, int nofollow, char *rsync_lsattr, size_t buf_size) {
131+
if (dest && !rsync_lsattr)
132+
return -1;
133+
int lsattr_flags = 0;
134+
if (dest)
135+
lsattr_flags = atoi(rsync_lsattr); // NOLINT(*-err34-c)
136+
int open_flags = O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK;
137+
if (nofollow)
138+
open_flags |= O_NOFOLLOW;
139+
int ret = handle_fs_fl_impl(source_fd, source, dest, open_flags, &lsattr_flags);
140+
if (ret)
141+
return ret;
142+
if (!source)
143+
return 0;
144+
char rsync_lsattr_tmp[FS_FL_ATTR_BUF_SIZE];
145+
ret = sprintf(rsync_lsattr_tmp, "%d", lsattr_flags) + 1;
146+
if (ret < 0)
147+
return ret;
148+
if (rsync_lsattr) {
149+
if (buf_size < (size_t) ret)
150+
return -ERANGE;
151+
memcpy(rsync_lsattr, rsync_lsattr_tmp, ret);
152+
}
153+
return ret;
154+
}
155+
#endif
156+
33157
ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
34158
{
159+
#ifdef FS_IOC_GETFLAGS
160+
if (!strcmp(name, FS_FL_ATTR))
161+
return handle_fs_fl(-1, path, NULL, 1, value, size);
162+
#endif
35163
return lgetxattr(path, name, value, size);
36164
}
37165

38166
ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
39167
{
168+
#ifdef FS_IOC_GETFLAGS
169+
if (!strcmp(name, FS_FL_ATTR))
170+
return handle_fs_fl(filedes, "(fd)", NULL, 0, value, size);
171+
#endif
40172
return fgetxattr(filedes, name, value, size);
41173
}
42174

43175
int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
44176
{
177+
#ifdef FS_IOC_GETFLAGS
178+
if (!strcmp(name, FS_FL_ATTR))
179+
return handle_fs_fl(-1, NULL, path, 1, (void *) value, size);
180+
#endif
45181
return lsetxattr(path, name, value, size, 0);
46182
}
47183

184+
#ifdef FS_IOC_GETFLAGS
185+
static const char FS_FL_ZERO[FS_FL_ATTR_BUF_SIZE] = "0";
186+
#endif
187+
48188
int sys_lremovexattr(const char *path, const char *name)
49189
{
190+
#ifdef FS_IOC_GETFLAGS
191+
if (!strcmp(name, FS_FL_ATTR))
192+
return handle_fs_fl(-1, NULL, path, 1, (char *) FS_FL_ZERO, strlen(FS_FL_ZERO));
193+
#endif
50194
return lremovexattr(path, name);
51195
}
52196

53-
ssize_t sys_llistxattr(const char *path, char *list, size_t size)
54-
{
55-
return llistxattr(path, list, size);
197+
ssize_t sys_llistxattr(const char *path, char *list, size_t size) {
198+
ssize_t ret;
199+
#ifdef FS_IOC_GETFLAGS
200+
char fs_fl_attr_buf[FS_FL_ATTR_BUF_SIZE];
201+
ret = handle_fs_fl(-1, path, NULL, 1, fs_fl_attr_buf, FS_FL_ATTR_BUF_SIZE);
202+
if (ret < 0)
203+
return ret;
204+
#endif
205+
ret = llistxattr(path, list, size);
206+
#ifdef FS_IOC_GETFLAGS
207+
if (ret < 0)
208+
return ret;
209+
if (strcmp(fs_fl_attr_buf, FS_FL_ZERO) != 0) {
210+
if (list) {
211+
if (ret + sizeof(FS_FL_ATTR) > size)
212+
return -ERANGE;
213+
memcpy(&list[ret], FS_FL_ATTR, sizeof(FS_FL_ATTR));
214+
}
215+
ret += sizeof(FS_FL_ATTR);
216+
}
217+
#endif
218+
return ret;
56219
}
57220

58221
#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)