Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 160 additions & 3 deletions lib/sysxattrs.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,186 @@

#if defined HAVE_LINUX_XATTRS

#include <linux/fs.h>
#define FS_FL_ATTR "user.rsync.lsattr"
#define FS_FL_ATTR_BUF_SIZE sizeof("-2147483648")

#ifdef FS_IOC_GETFLAGS

#define FS_FL_SETTABLE (FS_APPEND_FL|FS_COMPR_FL|FS_DIRSYNC_FL|FS_IMMUTABLE_FL|FS_JOURNAL_DATA_FL|FS_NOATIME_FL|\
FS_NOCOW_FL|FS_NODUMP_FL|FS_NOTAIL_FL|FS_PROJINHERIT_FL|FS_SECRM_FL|FS_SYNC_FL|FS_TOPDIR_FL|FS_UNRM_FL|\
FS_CASEFOLD_FL|FS_NOCOMP_FL|FS_PROJINHERIT_FL|FS_DAX_FL)

static int handle_fs_fl_impl(int lsattr_fd, const char *source, const char *dest, int open_flags, int *lsattr_flags) {
int ret;
struct stat st;
if (source) {
int lsattr_fd_owned = 0;
if (lsattr_fd == -1) {
if ((open_flags & O_NOFOLLOW ? lstat : stat)(source, &st)) {
rsyserr(FERROR_XFER, errno,
"handle_fs_fl_impl: stat(%s) for source failed",
full_fname(source));
return -1;
}
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
return 0;
}
lsattr_fd_owned = 1;
lsattr_fd = open(source, open_flags);
if (lsattr_fd == -1) {
rsyserr(FERROR_XFER, errno,
"handle_fs_fl_impl: open(%s) for source failed",
full_fname(source));
return -1;
}
}
ret = ioctl(lsattr_fd, FS_IOC_GETFLAGS, lsattr_flags);
if (ret) {
rsyserr(FERROR_XFER, ret,
"handle_fs_fl_impl: FS_IOC_GETFLAGS(%s) for source failed",
full_fname(source));
if (lsattr_fd_owned)
close(lsattr_fd);
assert(ret < 0);
return ret;
}
*lsattr_flags &= FS_FL_SETTABLE;
if (lsattr_fd_owned)
close(lsattr_fd);
}
if (dest) {
if ((open_flags & O_NOFOLLOW ? lstat : stat)(dest, &st)) {
rsyserr(FERROR_XFER, errno,
"handle_fs_fl_impl: stat(%s) for dest failed",
full_fname(dest));
return -1;
}
if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
return 0;
}
const int chattr_fd = open(dest, open_flags);
if (chattr_fd == -1) {
rsyserr(FERROR_XFER, errno,
"handle_fs_fl_impl: open(%s) for dest failed",
full_fname(dest));
return -1;
}
int chattr_flags = 0;
ret = ioctl(chattr_fd, FS_IOC_GETFLAGS, &chattr_flags);
if (ret) {
rsyserr(FERROR_XFER, ret,
"handle_fs_fl_impl: FS_IOC_GETFLAGS(%s) for dest failed",
full_fname(dest));
close(chattr_fd);
assert(ret < 0);
return ret;
}
chattr_flags &= ~FS_FL_SETTABLE;
chattr_flags |= *lsattr_flags;
ret = ioctl(chattr_fd, FS_IOC_SETFLAGS, &chattr_flags);
if (ret) {
rsyserr(FERROR_XFER, ret,
"handle_fs_fl_impl: FS_IOC_SETFLAGS(%s) for dest failed",
full_fname(dest));
close(chattr_fd);
assert(ret < 0);
return ret;
}
close(chattr_fd);
}
return 0;
}

static int handle_fs_fl(int source_fd, const char *source, const char *dest, int nofollow, char *rsync_lsattr, size_t buf_size) {
if (dest && !rsync_lsattr)
return -1;
int lsattr_flags = 0;
if (dest)
lsattr_flags = atoi(rsync_lsattr); // NOLINT(*-err34-c)
int open_flags = O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK;
if (nofollow)
open_flags |= O_NOFOLLOW;
int ret = handle_fs_fl_impl(source_fd, source, dest, open_flags, &lsattr_flags);
if (ret)
return ret;
if (!source)
return 0;
char rsync_lsattr_tmp[FS_FL_ATTR_BUF_SIZE];
ret = sprintf(rsync_lsattr_tmp, "%d", lsattr_flags) + 1;
if (ret < 0)
return ret;
if (rsync_lsattr) {
if (buf_size < (size_t) ret)
return -ERANGE;
memcpy(rsync_lsattr, rsync_lsattr_tmp, ret);
}
return ret;
}
#endif

ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
{
#ifdef FS_IOC_GETFLAGS
if (!strcmp(name, FS_FL_ATTR))
return handle_fs_fl(-1, path, NULL, 1, value, size);
#endif
return lgetxattr(path, name, value, size);
}

ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
{
#ifdef FS_IOC_GETFLAGS
if (!strcmp(name, FS_FL_ATTR))
return handle_fs_fl(filedes, "(fd)", NULL, 0, value, size);
#endif
return fgetxattr(filedes, name, value, size);
}

int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
{
#ifdef FS_IOC_GETFLAGS
if (!strcmp(name, FS_FL_ATTR))
return handle_fs_fl(-1, NULL, path, 1, (void *) value, size);
#endif
return lsetxattr(path, name, value, size, 0);
}

#ifdef FS_IOC_GETFLAGS
static const char FS_FL_ZERO[FS_FL_ATTR_BUF_SIZE] = "0";
#endif

int sys_lremovexattr(const char *path, const char *name)
{
#ifdef FS_IOC_GETFLAGS
if (!strcmp(name, FS_FL_ATTR))
return handle_fs_fl(-1, NULL, path, 1, (char *) FS_FL_ZERO, strlen(FS_FL_ZERO));
#endif
return lremovexattr(path, name);
}

ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
return llistxattr(path, list, size);
ssize_t sys_llistxattr(const char *path, char *list, size_t size) {
ssize_t ret;
#ifdef FS_IOC_GETFLAGS
char fs_fl_attr_buf[FS_FL_ATTR_BUF_SIZE];
ret = handle_fs_fl(-1, path, NULL, 1, fs_fl_attr_buf, FS_FL_ATTR_BUF_SIZE);
if (ret < 0)
return ret;
#endif
ret = llistxattr(path, list, size);
#ifdef FS_IOC_GETFLAGS
if (ret < 0)
return ret;
if (strcmp(fs_fl_attr_buf, FS_FL_ZERO) != 0) {
if (list) {
if (ret + sizeof(FS_FL_ATTR) > size)
return -ERANGE;
memcpy(&list[ret], FS_FL_ATTR, sizeof(FS_FL_ATTR));
}
ret += sizeof(FS_FL_ATTR);
}
#endif
return ret;
}

#elif HAVE_OSX_XATTRS
Expand Down
2 changes: 1 addition & 1 deletion rsync.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ has its own detailed description later in this manpage.
--executability, -E preserve executability
--chmod=CHMOD affect file and/or directory permissions
--acls, -A preserve ACLs (implies --perms)
--xattrs, -X preserve extended attributes
--xattrs, -X preserve file attributes (including extended attributes)
--owner, -o preserve owner (super-user only)
--group, -g preserve group
--devices preserve device files (super-user only)
Expand Down