From 4f9282d82df80ec2d5900945ed380427e0200ecc Mon Sep 17 00:00:00 2001 From: OpenAI Date: Fri, 19 Jun 2026 13:25:36 +0000 Subject: [PATCH] nwnss: prefer kernel quota capability probes --- include/nwnss/internal/nssUserspaceQuota.h | 7 + src/nwnss/nssUserspaceQuota.c | 135 ++++++++++++++++-- .../quota/nwnss_quota_xfs_volume_test.sh.in | 5 +- tests/nwnss/quota/test_nwnss_quota.c | 10 +- .../quota/test_nwnss_quota_mount_probe.c | 81 ++++++++--- 5 files changed, 205 insertions(+), 33 deletions(-) diff --git a/include/nwnss/internal/nssUserspaceQuota.h b/include/nwnss/internal/nssUserspaceQuota.h index ceb2760..8318201 100644 --- a/include/nwnss/internal/nssUserspaceQuota.h +++ b/include/nwnss/internal/nssUserspaceQuota.h @@ -5,6 +5,11 @@ #include #include +#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 #include @@ -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); diff --git a/src/nwnss/nssUserspaceQuota.c b/src/nwnss/nssUserspaceQuota.c index 5499a1c..55ab7ac 100644 --- a/src/nwnss/nssUserspaceQuota.c +++ b/src/nwnss/nssUserspaceQuota.c @@ -9,8 +9,16 @@ * entering this layer. */ +#include +#include #include +#ifdef __linux__ +#include +#include +#include +#endif + #include #include @@ -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) diff --git a/tests/nwnss/quota/nwnss_quota_xfs_volume_test.sh.in b/tests/nwnss/quota/nwnss_quota_xfs_volume_test.sh.in index b2cb14a..fed6397 100644 --- a/tests/nwnss/quota/nwnss_quota_xfs_volume_test.sh.in +++ b/tests/nwnss/quota/nwnss_quota_xfs_volume_test.sh.in @@ -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" diff --git a/tests/nwnss/quota/test_nwnss_quota.c b/tests/nwnss/quota/test_nwnss_quota.c index a6e83f1..5d32c61 100644 --- a/tests/nwnss/quota/test_nwnss_quota.c +++ b/tests/nwnss/quota/test_nwnss_quota.c @@ -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) == diff --git a/tests/nwnss/quota/test_nwnss_quota_mount_probe.c b/tests/nwnss/quota/test_nwnss_quota_mount_probe.c index 376f29c..6650293 100644 --- a/tests/nwnss/quota/test_nwnss_quota_mount_probe.c +++ b/tests/nwnss/quota/test_nwnss_quota_mount_probe.c @@ -1,9 +1,14 @@ +#include +#include #include #include #include +#include #include +#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]); }