Skip to content

Commit 31e1338

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 31e1338

File tree

2 files changed

+161
-4
lines changed

2 files changed

+161
-4
lines changed

lib/sysxattrs.c

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

38160
ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
39161
{
162+
#ifdef FS_IOC_GETFLAGS
163+
if (!strcmp(name, FS_FL_ATTR))
164+
return handle_fs_fl(filedes, "(fd)", NULL, 0, value, size);
165+
#endif
40166
return fgetxattr(filedes, name, value, size);
41167
}
42168

43169
int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
44170
{
171+
#ifdef FS_IOC_GETFLAGS
172+
if (!strcmp(name, FS_FL_ATTR))
173+
return handle_fs_fl(-1, NULL, path, 1, (void *) value, size);
174+
#endif
45175
return lsetxattr(path, name, value, size, 0);
46176
}
47177

178+
#ifdef FS_IOC_GETFLAGS
179+
static const char FS_FL_ZERO[FS_FL_ATTR_BUF_SIZE] = "0";
180+
#endif
181+
48182
int sys_lremovexattr(const char *path, const char *name)
49183
{
184+
#ifdef FS_IOC_GETFLAGS
185+
if (!strcmp(name, FS_FL_ATTR))
186+
return handle_fs_fl(-1, NULL, path, 1, (char *) FS_FL_ZERO, strlen(FS_FL_ZERO));
187+
#endif
50188
return lremovexattr(path, name);
51189
}
52190

53-
ssize_t sys_llistxattr(const char *path, char *list, size_t size)
54-
{
55-
return llistxattr(path, list, size);
191+
ssize_t sys_llistxattr(const char *path, char *list, size_t size) {
192+
ssize_t ret;
193+
#ifdef FS_IOC_GETFLAGS
194+
char fs_fl_attr_buf[FS_FL_ATTR_BUF_SIZE];
195+
ret = handle_fs_fl(-1, path, NULL, 1, fs_fl_attr_buf, FS_FL_ATTR_BUF_SIZE);
196+
if (ret < 0)
197+
return ret;
198+
#endif
199+
ret = llistxattr(path, list, size);
200+
#ifdef FS_IOC_GETFLAGS
201+
if (ret < 0)
202+
return ret;
203+
if (strcmp(fs_fl_attr_buf, FS_FL_ZERO) != 0) {
204+
if (list) {
205+
if (ret + sizeof(FS_FL_ATTR) > size)
206+
return -ERANGE;
207+
memcpy(&list[ret], FS_FL_ATTR, sizeof(FS_FL_ATTR));
208+
}
209+
ret += sizeof(FS_FL_ATTR);
210+
}
211+
#endif
212+
return ret;
56213
}
57214

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