Files
rsync/lib/acl.h
T
Andrew Tridgell d7f929a30e lib/acl.c: POSIX ACL get/set via fd/at xattr syscalls + unit test
POSIX ACLs are stored by the kernel as the system.posix_acl_{access,default}
xattrs.  lib/acl.c serializes that wire format and operates on it via
fgetxattr/fsetxattr on a held O_NOFOLLOW fd -- or getxattrat/setxattrat
(AT_SYMLINK_NOFOLLOW) on a dirfd+leaf -- giving a symlink-race-safe ACL
primitive that, unlike libacl's access-only acl_get_fd/acl_set_fd, also covers
the default ACL.

It is self-contained (no libacl, no rsync globals): it speaks a neutral
(tag, perm, id) entry array, so t_acl can compare it directly against the system
libacl as an oracle.  configure gains SUPPORT_ACL_FD (POSIX ACLs + the xattr
header, independent of the -X feature) and an optional HAVE_XATTRAT_SYSCALLS
probe (the *xattrat syscalls, Linux 6.13+); the fd path needs neither.

t_acl exercises every op rsync needs in both directions (set via lib -> read via
libacl and vice versa), round-trips, the default-ACL delete, errno
discrimination, and the NOFOLLOW leaf refusal.  It self-skips (77) without
SUPPORT_ACL_FD or on a filesystem lacking ACL support.
2026-06-15 15:24:42 +10:00

75 lines
3.4 KiB
C

/*
* POSIX ACL get/set/delete via the generic xattr syscalls, addressing the
* kernel "system.posix_acl_{access,default}" attributes directly so that the
* operation can be confined to a held O_NOFOLLOW fd (fsetxattr) or a
* dirfd+leaf with AT_SYMLINK_NOFOLLOW (setxattrat). This replaces the path-
* based libacl acl_*_file() calls on Linux, where those would re-resolve the
* path and could be redirected by a parent-component symlink race.
*
* Copyright (C) 2026 Wayne Davison & the rsync project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, visit the http://fsf.org website.
*/
#ifdef SUPPORT_ACL_FD
#include <stdint.h>
/* A single logical POSIX ACL entry in host-native form. The tag values are
* the stable kernel ABI numbers (== the libacl ACL_* constants), so they map
* straight onto the on-disk e_tag without translation. */
typedef struct {
uint16_t tag; /* RACL_USER_OBJ / USER / GROUP_OBJ / GROUP / MASK / OTHER */
uint16_t perm; /* permission bits: read=4, write=2, execute=1 */
uint32_t id; /* uid/gid for USER/GROUP entries; RACL_UNDEFINED_ID otherwise */
} rsync_acl_ent;
#define RACL_USER_OBJ 0x01
#define RACL_USER 0x02
#define RACL_GROUP_OBJ 0x04
#define RACL_GROUP 0x08
#define RACL_MASK 0x10
#define RACL_OTHER 0x20
#define RACL_UNDEFINED_ID ((uint32_t)-1)
/* Read the access (want_default==0) or default (want_default!=0) ACL.
*
* On success returns 0 and sets *entries to a malloc()ed array of *count
* entries (the caller frees it with free(); *entries may be NULL when
* *count==0, which means "no explicit ACL present" -- e.g. ENODATA).
*
* On failure returns -1 with errno set. Callers distinguish:
* ENOTSUP/EOPNOTSUPP - this filesystem has no ACL support (may differ per fs)
* ENOSYS - the at-variant syscalls are unavailable on this kernel
* The fd-variant operates on a held, already-NOFOLLOW-opened descriptor. The
* at-variant resolves leaf relative to dirfd and never follows a leaf symlink. */
int xacl_get_fd(int fd, int want_default, rsync_acl_ent **entries, int *count);
int xacl_get_at(int dirfd, const char *leaf, int want_default, rsync_acl_ent **entries, int *count);
/* Write the given entries as the access/default ACL. The entries are emitted
* in canonical order; the kernel validates them (a malformed set -> EINVAL). */
int xacl_set_fd(int fd, int want_default, const rsync_acl_ent *entries, int count);
int xacl_set_at(int dirfd, const char *leaf, int want_default, const rsync_acl_ent *entries, int count);
/* Delete a directory's default ACL. A missing default ACL is success. */
int xacl_del_default_fd(int fd);
int xacl_del_default_at(int dirfd, const char *leaf);
/* Cached runtime probe: are the *xattrat syscalls usable on this kernel?
* Returns 0 when they are absent (so callers can fall back) or unbuilt. */
int xacl_at_available(void);
#endif /* SUPPORT_ACL_FD */