nwnss: prefer kernel quota capability probes

This commit is contained in:
OpenAI
2026-06-19 13:25:36 +00:00
committed by Mario Fetka
parent 147d8ad474
commit 4f9282d82d
5 changed files with 205 additions and 33 deletions

View File

@@ -5,6 +5,11 @@
#include <stddef.h>
#include <stdint.h>
#define NSS_QUOTA_LINUX_CAP_USER 0x00000001U
#define NSS_QUOTA_LINUX_CAP_GROUP 0x00000002U
#define NSS_QUOTA_LINUX_CAP_PROJECT 0x00000004U
#define NSS_QUOTA_NWQUOTA_CAPABLE 0x00010000U
#include <public/zXattr.h>
#include <public/zOmni.h>
@@ -88,6 +93,8 @@ int NssQuotaProviderIsUserspace(NssQuotaProviderKind_e provider);
int NssQuotaProviderIsLinuxQuota(NssQuotaProviderKind_e provider);
int NssQuotaProviderIsNwQuota(NssQuotaProviderKind_e provider);
int NssQuotaProviderIsOtherfsApproximation(NssQuotaProviderKind_e provider);
NssQuotaProviderKind_e NssQuotaProviderForOtherfsCapabilities(uint32_t caps);
int NssQuotaProbeLinuxQuotaCapabilitiesFd(int fd, uint32_t *caps);
NssQuotaProviderKind_e NssQuotaProviderForOtherfsMount(const char *fsType,
const char *mountOptions,
int nwQuotaAvailable);

View File

@@ -9,8 +9,16 @@
* entering this layer.
*/
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#ifdef __linux__
#include <sys/quota.h>
#include <sys/syscall.h>
#include <unistd.h>
#endif
#include <zParams.h>
#include <internal/nssUserspaceQuota.h>
@@ -449,6 +457,106 @@ int NssQuotaProviderIsOtherfsApproximation(NssQuotaProviderKind_e provider)
}
NssQuotaProviderKind_e NssQuotaProviderForOtherfsCapabilities(uint32_t caps)
{
/*
* Linux quota capabilities are preferred when the host filesystem reports
* active quota accounting/enforcement through the kernel quota interface.
* Project quota is the best fit for directory-tree quota. User quota is
* the best fit for NSS user quota. Plain/group quota is kept as a weaker
* Linux approximation. If Linux quota is not active, OtherFS can still use
* the simulated NetWare/NSS quota provider via netware.* xattrs or sidecar
* metadata, so the default userspace fallback is NWQUOTA.
*/
if ((caps & NSS_QUOTA_LINUX_CAP_PROJECT) != 0) {
return NSS_QUOTA_PROVIDER_OTHERFS_LINUX_PROJECT_QUOTA;
}
if ((caps & NSS_QUOTA_LINUX_CAP_USER) != 0) {
return NSS_QUOTA_PROVIDER_OTHERFS_LINUX_USER_QUOTA;
}
if ((caps & NSS_QUOTA_LINUX_CAP_GROUP) != 0) {
return NSS_QUOTA_PROVIDER_OTHERFS_LINUX_QUOTA;
}
return NSS_QUOTA_PROVIDER_OTHERFS_NWQUOTA;
}
#ifdef __linux__
static int nssQuotaProbeFdType(int fd, int quotaType, uint32_t cap,
uint32_t *caps)
{
#ifdef SYS_quotactl_fd
struct dqinfo info;
long rc;
memset(&info, 0, sizeof(info));
errno = 0;
rc = syscall(SYS_quotactl_fd, (unsigned int)fd,
QCMD(Q_GETINFO, quotaType), 0, &info);
if (rc == 0) {
*caps |= cap;
return 0;
}
switch (errno) {
case ESRCH: /* quota not enabled for this type */
case EINVAL: /* quota type not supported by fs/kernel */
case ENOSYS: /* kernel without quota syscall support */
case ENOTSUP:
#ifdef EOPNOTSUPP
#if EOPNOTSUPP != ENOTSUP
case EOPNOTSUPP:
#endif
#endif
case EPERM: /* unprivileged probe cannot inspect this quota */
return 0;
default:
return -errno;
}
#else
(void)fd;
(void)quotaType;
(void)cap;
(void)caps;
return -ENOSYS;
#endif
}
#endif
int NssQuotaProbeLinuxQuotaCapabilitiesFd(int fd, uint32_t *caps)
{
#ifdef __linux__
int rc;
if (caps == NULL || fd < 0) {
return NSS_QUOTA_ERR_INVAL;
}
*caps = 0;
rc = nssQuotaProbeFdType(fd, USRQUOTA, NSS_QUOTA_LINUX_CAP_USER, caps);
if (rc < 0) {
return rc;
}
rc = nssQuotaProbeFdType(fd, GRPQUOTA, NSS_QUOTA_LINUX_CAP_GROUP, caps);
if (rc < 0) {
return rc;
}
#ifdef PRJQUOTA
rc = nssQuotaProbeFdType(fd, PRJQUOTA, NSS_QUOTA_LINUX_CAP_PROJECT, caps);
if (rc < 0) {
return rc;
}
#endif
return NSS_QUOTA_OK;
#else
(void)fd;
if (caps == NULL) {
return NSS_QUOTA_ERR_INVAL;
}
*caps = 0;
return -ENOSYS;
#endif
}
static int nssQuotaMountOptionMatches(const char *options, const char *needle)
{
size_t needleLen;
@@ -485,39 +593,38 @@ NssQuotaProviderKind_e
NssQuotaProviderForOtherfsMount(const char *fsType, const char *mountOptions,
int nwQuotaAvailable)
{
uint32_t caps = 0;
(void)fsType;
(void)nwQuotaAvailable;
/*
* Live provider selection is capability based, not filesystem-name based.
* XFS is the first loop-volume smoke target because it is easy to create
* and mount with project quotas in CTest, but OtherFS should accept any
* host filesystem that reports the matching quota capability in its mount
* properties. If Linux quota support is not visible for the mount, fall
* back to simulated NetWare/NSS quota metadata when available.
* Compatibility path for callers/tests that only have parsed mount
* properties. Live callers should prefer
* NssQuotaProbeLinuxQuotaCapabilitiesFd() and then pass the resulting
* capability mask to NssQuotaProviderForOtherfsCapabilities(). The
* simulated NWQuota provider is always the OtherFS fallback when no Linux
* quota capability is active.
*/
if (nssQuotaMountOptionMatches(mountOptions, "prjquota") ||
nssQuotaMountOptionMatches(mountOptions, "pquota") ||
nssQuotaMountOptionMatches(mountOptions, "project")) {
return NSS_QUOTA_PROVIDER_OTHERFS_LINUX_PROJECT_QUOTA;
caps |= NSS_QUOTA_LINUX_CAP_PROJECT;
}
if (nssQuotaMountOptionMatches(mountOptions, "usrquota") ||
nssQuotaMountOptionMatches(mountOptions, "uquota") ||
nssQuotaMountOptionMatches(mountOptions, "userquota")) {
return NSS_QUOTA_PROVIDER_OTHERFS_LINUX_USER_QUOTA;
caps |= NSS_QUOTA_LINUX_CAP_USER;
}
if (nssQuotaMountOptionMatches(mountOptions, "grpquota") ||
nssQuotaMountOptionMatches(mountOptions, "gquota") ||
nssQuotaMountOptionMatches(mountOptions, "quota")) {
return NSS_QUOTA_PROVIDER_OTHERFS_LINUX_QUOTA;
caps |= NSS_QUOTA_LINUX_CAP_GROUP;
}
if (nwQuotaAvailable) {
return NSS_QUOTA_PROVIDER_OTHERFS_NWQUOTA;
}
return NSS_QUOTA_PROVIDER_UNSET;
return NssQuotaProviderForOtherfsCapabilities(caps);
}
const char *NssQuotaCheckResultName(NssQuotaCheckResult_e result)

View File

@@ -71,10 +71,13 @@ plain_opts=$(findmnt -n -o OPTIONS --target "$PLAIN_MNT")
quota_fstype=$(findmnt -n -o FSTYPE --target "$QUOTA_MNT")
quota_opts=$(findmnt -n -o OPTIONS --target "$QUOTA_MNT")
"$PROBE" "$plain_fstype" "$plain_opts" 0 unset
"$PROBE" "$plain_fstype" "$plain_opts" 0 otherfs-nwquota
"$PROBE" "$plain_fstype" "$plain_opts" 1 otherfs-nwquota
"$PROBE" "$quota_fstype" "$quota_opts" 0 otherfs-linux-project-quota
"$PROBE" "$quota_fstype" "$quota_opts" 1 otherfs-linux-project-quota
run_root "$PROBE" --fd "$PLAIN_MNT" otherfs-nwquota
run_root "$PROBE" --fd "$QUOTA_MNT" otherfs-linux-project-quota
printf 'plain XFS: fs=%s options=%s\n' "$plain_fstype" "$plain_opts"
printf 'quota XFS: fs=%s options=%s\n' "$quota_fstype" "$quota_opts"

View File

@@ -116,8 +116,16 @@ int main(void)
CHECK(!NssQuotaProviderIsNwQuota(writeView.provider));
CHECK(strcmp(NssQuotaCheckResultName(NSS_QUOTA_CHECK_DIR_EXCEEDED),
"dir-exceeded") == 0);
CHECK(NssQuotaProviderForOtherfsCapabilities(0) ==
NSS_QUOTA_PROVIDER_OTHERFS_NWQUOTA);
CHECK(NssQuotaProviderForOtherfsCapabilities(NSS_QUOTA_LINUX_CAP_PROJECT) ==
NSS_QUOTA_PROVIDER_OTHERFS_LINUX_PROJECT_QUOTA);
CHECK(NssQuotaProviderForOtherfsCapabilities(NSS_QUOTA_LINUX_CAP_USER) ==
NSS_QUOTA_PROVIDER_OTHERFS_LINUX_USER_QUOTA);
CHECK(NssQuotaProviderForOtherfsCapabilities(NSS_QUOTA_LINUX_CAP_GROUP) ==
NSS_QUOTA_PROVIDER_OTHERFS_LINUX_QUOTA);
CHECK(NssQuotaProviderForOtherfsMount("xfs", "rw,relatime", 0) ==
NSS_QUOTA_PROVIDER_UNSET);
NSS_QUOTA_PROVIDER_OTHERFS_NWQUOTA);
CHECK(NssQuotaProviderForOtherfsMount("xfs", "rw,relatime", 1) ==
NSS_QUOTA_PROVIDER_OTHERFS_NWQUOTA);
CHECK(NssQuotaProviderForOtherfsMount("xfs", "rw,prjquota", 0) ==

View File

@@ -1,9 +1,14 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <internal/nssUserspaceQuota.h>
#define SKIP 77
static NssQuotaProviderKind_e parse_provider(const char *name)
{
if (strcmp(name, "unset") == 0) {
@@ -27,31 +32,73 @@ static NssQuotaProviderKind_e parse_provider(const char *name)
return (NssQuotaProviderKind_e)-1;
}
int main(int argc, char **argv)
static int check_expected(const char *context, NssQuotaProviderKind_e actual,
const char *expectedName)
{
NssQuotaProviderKind_e expected;
NssQuotaProviderKind_e actual;
int nwQuotaAvailable;
if (argc != 5) {
fprintf(stderr, "Usage: %s FSTYPE MOUNT_OPTIONS NWQUOTA_AVAILABLE EXPECTED_PROVIDER\n", argv[0]);
return 2;
}
nwQuotaAvailable = atoi(argv[3]) != 0;
expected = parse_provider(argv[4]);
expected = parse_provider(expectedName);
if (expected == (NssQuotaProviderKind_e)-1) {
fprintf(stderr, "unknown expected provider: %s\n", argv[4]);
fprintf(stderr, "unknown expected provider: %s\n", expectedName);
return 2;
}
actual = NssQuotaProviderForOtherfsMount(argv[1], argv[2], nwQuotaAvailable);
if (actual != expected) {
fprintf(stderr, "provider mismatch: fs=%s options=%s nwquota=%d expected=%s actual=%s\n",
argv[1], argv[2], nwQuotaAvailable,
NssQuotaProviderName(expected), NssQuotaProviderName(actual));
fprintf(stderr, "provider mismatch: %s expected=%s actual=%s\n",
context, NssQuotaProviderName(expected),
NssQuotaProviderName(actual));
return 1;
}
return 0;
}
static int probe_fd_provider(const char *path, const char *expectedName)
{
NssQuotaProviderKind_e actual;
uint32_t caps;
int fd;
int rc;
fd = open(path, O_RDONLY | O_DIRECTORY);
if (fd < 0) {
perror(path);
return 1;
}
return 0;
rc = NssQuotaProbeLinuxQuotaCapabilitiesFd(fd, &caps);
close(fd);
if (rc == -ENOSYS) {
fprintf(stderr, "quotactl_fd quota capability probe is not available\n");
return SKIP;
}
if (rc < 0) {
fprintf(stderr, "quota capability probe failed for %s: %d\n", path, rc);
return 1;
}
actual = NssQuotaProviderForOtherfsCapabilities(caps);
return check_expected(path, actual, expectedName);
}
int main(int argc, char **argv)
{
NssQuotaProviderKind_e actual;
int nwQuotaAvailable;
if (argc == 4 && strcmp(argv[1], "--fd") == 0) {
return probe_fd_provider(argv[2], argv[3]);
}
if (argc != 5) {
fprintf(stderr, "Usage: %s FSTYPE MOUNT_OPTIONS NWQUOTA_AVAILABLE EXPECTED_PROVIDER\n", argv[0]);
fprintf(stderr, " %s --fd MOUNTPOINT EXPECTED_PROVIDER\n", argv[0]);
return 2;
}
/* NWQuota is now the unconditional OtherFS fallback; keep the argument
* only for compatibility with existing test invocations. */
nwQuotaAvailable = atoi(argv[3]) != 0;
(void)nwQuotaAvailable;
actual = NssQuotaProviderForOtherfsMount(argv[1], argv[2], nwQuotaAvailable);
return check_expected(argv[2], actual, argv[4]);
}