From db9c5419608dea1dffdb5ab042b63bb2edae9c6a Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Thu, 11 Jun 2026 15:36:09 +0000 Subject: [PATCH] tests: add ncpfs directory quota smoke --- AI.md | 2 + REDESIGN.md | 12 + TODO.md | 13 ++ doc/quota/README.md | 11 + tests/README.md | 20 ++ tests/nwfs/CMakeLists.txt | 5 +- tests/nwfs/nwfs_ncpfs_dirquota.c | 256 +++++++++++++++++---- tests/nwfs/nwfs_ncpfs_dirquota_smoke.sh.in | 162 +++++++++++++ 8 files changed, 429 insertions(+), 52 deletions(-) create mode 100644 tests/nwfs/nwfs_ncpfs_dirquota_smoke.sh.in diff --git a/AI.md b/AI.md index 440196b..33eff9e 100644 --- a/AI.md +++ b/AI.md @@ -1,4 +1,6 @@ +Patch 0353 status: added live NCPFS directory-quota smoke for 3.x endpoints. `nwfs_ncpfs_dirquota` now drives decimal `22/36`/wire `0x24` and decimal `22/35`/wire `0x23` directly through libncp `NCPC_SFN`, with readback/expect modes. `nwfs_ncpfs_dirquota_smoke.sh` sets a limit, reads it back over NCP, verifies `netware.metadata`, clears it, and verifies that `22/35` reports no entries. + Patch 0351 status: started closing the MARS-NWE 3.x directory-quota block before namespace work. Added libnwfs `dirquota.c/h`, CTest `nwfs_dirquota_test`, active NCP `22/35`/wire `0x23` get and `22/36`/wire `0x24` set backed by `netware.metadata.nwm_quota_limit`, and fixed `22/40`/wire `0x28` Sequence parsing to Lo-Hi. Code comments name both decimal NCP numbers and wire hex bytes. Remaining directory-quota work is enforcement/adjustment on file growth/create/delete/rename and later `87/39` behind the 4.x line. # AI working notes for mars-nwe diff --git a/REDESIGN.md b/REDESIGN.md index fcfc246..86de53a 100644 --- a/REDESIGN.md +++ b/REDESIGN.md @@ -3410,3 +3410,15 @@ The first NetWare-3.x directory-quota pass is active as of patch 0351. Document `src/nwfs/quota/dirquota.c` is the libnwfs home for portable directory-quota math and NSS/OES-compatible `netware.metadata.nwm_quota_limit` conversion. The live NCP parser stays in `src/nwconn.c`. `22/35` returns no entries for unrestricted directories and one current-directory entry for a finite restriction. `22/36` stores or clears the directory restriction in `netware.metadata`. `22/40` now reads its sequence field as documented Lo-Hi and continues to use the existing MARS DOS scan reply shape until AFP/MAC_RF resource fork data exists. This closes the first 3.x directory-quota endpoint gap without pulling in the full NSS `dirQuotas.c` runtime/cache. Deeper NSS-style parent-min restriction selection, used-space adjustment hooks on create/delete/rename/write, and namespace-aware `87/39` remain follow-up work. + + +### NCPFS directory-quota smoke + +The directory-quota implementation now has two test layers. The CTest helper +`nwfs_dirquota_test` validates the libnwfs arithmetic and metadata conversion. +The live smoke `nwfs_ncpfs_dirquota_smoke.sh` validates the NetWare 3.x NCP +path: it uses libncp `NCPC_SFN(22, 36)` to send decimal `22/36` (wire/code +`0x24`) and `NCPC_SFN(22, 35)` for decimal `22/35` (wire/code `0x23`). The +smoke intentionally verifies both the NCP readback and the host-side +`netware.metadata` result, so parser/wire mistakes and xattr-storage mistakes +are caught independently. diff --git a/TODO.md b/TODO.md index f55cbf2..2bb7643 100644 --- a/TODO.md +++ b/TODO.md @@ -2507,3 +2507,16 @@ Do not merge these back together; a later BSD backend should use its own - Keep Linuxquota, NWQUOTA, and future BSD quota backends in separate files with separate public prefixes: `nwfs_lnxquota_*`, `nwfs_nwquota_*`, and future `nwfs_bsdquota_*`. + + +### Directory quota live smoke + +- Added/keep `tests/nwfs/nwfs_ncpfs_dirquota_smoke.sh` for the 3.x directory + quota endpoints. The helper must name the documented decimal calls and the + code/wire hex values together: + - decimal `22/36`, wire/code `0x24`: set directory disk-space restriction + - decimal `22/35`, wire/code `0x23`: get directory disk-space restriction +- The smoke should stay a live NCPFS test, not just a host xattr test: set over + NCP, read over NCP, then verify `netware.metadata` on the host side. +- `22/40` remains the next directory-quota smoke target once the scan reply + is verified against the documented structure. diff --git a/doc/quota/README.md b/doc/quota/README.md index 9c92f5f..409c72a 100644 --- a/doc/quota/README.md +++ b/doc/quota/README.md @@ -99,3 +99,14 @@ The first active implementation stores finite directory restrictions in `netware.metadata.nwm_quota_limit`. Screenshots from FILER, SYSCON, NWADMIN or reference NetWare servers that demonstrate directory quota behaviour belong under this `doc/quota/` tree. + + +## Directory quota smoke + +`tests/nwfs/nwfs_ncpfs_dirquota_smoke.sh` is the live smoke for the NetWare +3.x directory disk-space restriction calls. It sets a limit with documented +decimal `22/36` (wire/code `0x24`), reads it back with documented decimal +`22/35` (wire/code `0x23`), verifies `netware.metadata`, clears the limit, and +checks that `22/35` returns no entries again. Future screenshots from FILER, +SYSCON, or NetWare reference systems for this behavior belong under +`doc/quota/screenshots/`. diff --git a/tests/README.md b/tests/README.md index b15130a..bfa1b96 100644 --- a/tests/README.md +++ b/tests/README.md @@ -85,3 +85,23 @@ Tests must therefore not validate salvage payloads by opening `SYS:.recycle/...` or `SYS:.salvage/...` through normal NCP file calls. Use the salvage scan/recover/purge endpoints for repository state and verify payload content by reading the restored live file through NCP. + + +## NCPFS directory-quota smoke + +`tests/nwfs/nwfs_ncpfs_dirquota_smoke.sh` is the live smoke for the +MARS-NWE 3.x directory quota endpoints. It uses the `nwfs_ncpfs_dirquota` +helper to call the documented decimal NCPs `22/36` and `22/35` directly +through libncp (`NCPC_SFN(22, 36)` and `NCPC_SFN(22, 35)`; wire/code bytes +`0x24` and `0x23`). The smoke sets a finite directory limit, reads it back, +checks `netware.metadata` with `nwfs_xattr_dump`, clears the limit, and +verifies that `22/35` reports no entries again. + +Example: + +```sh +./nwfs_ncpfs_dirquota_smoke.sh MARS SUPERVISOR secret \ + /var/mars_nwe/SYS /mnt/nw-sys NWFSTEST SYS +``` + +Use `NWFS_NCPFS_DIR_QUOTA_4K=VALUE` to choose the tested limit. diff --git a/tests/nwfs/CMakeLists.txt b/tests/nwfs/CMakeLists.txt index a790cd7..37e65e5 100644 --- a/tests/nwfs/CMakeLists.txt +++ b/tests/nwfs/CMakeLists.txt @@ -63,12 +63,15 @@ configure_file(nwfs_ncpfs_novell_quota_reference.sh.in nwfs_ncpfs_novell_quota_r configure_file(nwfs_ncpfs_userquota_fill_smoke.sh.in nwfs_ncpfs_userquota_fill_smoke.sh @ONLY) configure_file(nwfs_ncpfs_userquota_dual_smoke.sh.in nwfs_ncpfs_userquota_dual_smoke.sh @ONLY) +configure_file(nwfs_ncpfs_dirquota_smoke.sh.in nwfs_ncpfs_dirquota_smoke.sh @ONLY) +set(NWFS_NCPFS_DIRQUOTA_SMOKE "${CMAKE_CURRENT_BINARY_DIR}/nwfs_ncpfs_dirquota_smoke.sh") foreach(NWFS_NCPFS_SMOKE_SCRIPT nwfs_ncpfs_metadata_smoke.sh nwfs_ncpfs_novell_quota_reference.sh nwfs_ncpfs_userquota_fill_smoke.sh - nwfs_ncpfs_userquota_dual_smoke.sh) + nwfs_ncpfs_userquota_dual_smoke.sh + nwfs_ncpfs_dirquota_smoke.sh) file(CHMOD "${CMAKE_CURRENT_BINARY_DIR}/${NWFS_NCPFS_SMOKE_SCRIPT}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE diff --git a/tests/nwfs/nwfs_ncpfs_dirquota.c b/tests/nwfs/nwfs_ncpfs_dirquota.c index daca664..85294e6 100644 --- a/tests/nwfs/nwfs_ncpfs_dirquota.c +++ b/tests/nwfs/nwfs_ncpfs_dirquota.c @@ -1,14 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Manual NCPFS smoke helper for NetWare directory space limits. + * Manual NCPFS smoke helper for NetWare 3.x directory disk-space limits. * - * This is intentionally small and is based on the ncpfs contrib/testing - * dirlimit.c example by Petr Vandrovec. It mutates a directory through - * libncp/NCPFS so the host-side test can verify the mars-nwe - * netware.metadata directory-quota xattr afterwards. + * The NCP documentation names these as decimal 22/35 and 22/36. libncp's + * NCPC_SFN(22, 35/36) builds the same group/subfunction request that mars-nwe + * dispatches as wire/code bytes 0x23 and 0x24 in src/nwconn.c. */ #include +#include #include #include #include @@ -17,14 +17,108 @@ #include #include +#ifndef NCPC_SFN +#define NCPC_SUBFUNCTION 0x10000U +#define NCPC_SFN(fn, subfn) (NCPC_SUBFUNCTION | (((fn) & 0xffU) << 8) | ((subfn) & 0xffU)) +#endif + +struct dirquota_entry { + uint8_t level; + uint32_t max4k; + uint32_t current4k; +}; + static void usage(const char *prog) { fprintf(stderr, - "Usage: %s [-l LIMIT_4K] [-p VOLUME:PATH] NCPFS_MOUNTED_PATH\n" + "Usage: %s [--set-limit-4k LIMIT] [--get] [--expect-limit-4k LIMIT|--expect-empty] [-p VOLUME:PATH] NCPFS_MOUNTED_PATH\n" + " %s -l LIMIT NCPFS_MOUNTED_PATH\n" "\n" - "Sets the NetWare DOS-info MaximumSpace field through libncp.\n" - "LIMIT_4K is in 4 KiB NetWare blocks, matching ncpfs dirlimit.c.\n", - prog); + "Exercises NetWare directory disk-space NCPs through libncp:\n" + " decimal 22/35 / wire 0x23: Get Directory Disk Space Restriction\n" + " decimal 22/36 / wire 0x24: Set Directory Disk Space Restriction\n" + "LIMIT is in 4 KiB NetWare blocks. LIMIT 0 clears the restriction.\n", + prog, prog); +} + +static int parse_u32(const char *text, uint32_t *value) +{ + char *end = NULL; + unsigned long v; + + errno = 0; + v = strtoul(text, &end, 0); + if (errno || !end || *end || v > 0xffffffffUL) + return -1; + *value = (uint32_t)v; + return 0; +} + +static void put_u32_le(uint8_t *p, uint32_t value) +{ + p[0] = (uint8_t)(value & 0xffU); + p[1] = (uint8_t)((value >> 8) & 0xffU); + p[2] = (uint8_t)((value >> 16) & 0xffU); + p[3] = (uint8_t)((value >> 24) & 0xffU); +} + +static uint32_t get_u32_le(const uint8_t *p) +{ + return ((uint32_t)p[0]) | + ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | + ((uint32_t)p[3] << 24); +} + +static NWCCODE ncp22_36_set_dirquota(NWCONN_HANDLE conn, + NWDIR_HANDLE dirhandle, + uint32_t limit4k) +{ + uint8_t rq[5]; + + rq[0] = (uint8_t)dirhandle; + put_u32_le(rq + 1, limit4k); + return NWRequestSimple(conn, NCPC_SFN(22, 36), rq, sizeof(rq), NULL); +} + +static NWCCODE ncp22_35_get_dirquota(NWCONN_HANDLE conn, + NWDIR_HANDLE dirhandle, + struct dirquota_entry *entries, + size_t entry_cap, + size_t *entry_count) +{ + uint8_t rq[1]; + uint8_t rpbuf[1024]; + NW_FRAGMENT rp; + NWCCODE err; + size_t count; + size_t i; + + rq[0] = (uint8_t)dirhandle; + memset(rpbuf, 0, sizeof(rpbuf)); + rp.fragAddr.rw = rpbuf; + rp.fragSize = sizeof(rpbuf); + + err = NWRequestSimple(conn, NCPC_SFN(22, 35), rq, sizeof(rq), &rp); + if (err) + return err; + if (rp.fragSize < 1) + return 0xff; + + count = rpbuf[0]; + if (count > entry_cap) + count = entry_cap; + if (rp.fragSize < 1 + count * 9) + return 0xff; + + for (i = 0; i < count; i++) { + const uint8_t *p = rpbuf + 1 + i * 9; + entries[i].level = p[0]; + entries[i].max4k = get_u32_le(p + 1); + entries[i].current4k = get_u32_le(p + 5); + } + *entry_count = count; + return 0; } int main(int argc, char **argv) @@ -38,46 +132,67 @@ int main(int argc, char **argv) NWDIR_HANDLE dirhandle = 0; NWVOL_NUM volnum = 0; const char *override_path = NULL; - const char *mounted_path; - unsigned long limit = 0; - int set_limit = 0; - int opt; + const char *mounted_path = NULL; + uint32_t set_limit = 0; + uint32_t expect_limit = 0; + int do_set = 0; + int do_get = 0; + int do_expect_limit = 0; + int do_expect_empty = 0; + int i; int len; + struct dirquota_entry entries[16]; + size_t entry_count = 0; - if (NWCallsInit(NULL, NULL)) { - fprintf(stderr, "NWCallsInit failed\n"); - return 2; - } - - while ((opt = getopt(argc, argv, "h?p:l:")) != -1) { - switch (opt) { - case 'l': - errno = 0; - limit = strtoul(optarg, NULL, 0); - if (errno || limit > 0xffffffffUL) { - fprintf(stderr, "invalid limit: %s\n", optarg); + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + usage(argv[0]); + return 0; + } else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--set-limit-4k")) { + if (++i >= argc || parse_u32(argv[i], &set_limit)) { + fprintf(stderr, "invalid limit\n"); return 2; } - set_limit = 1; - break; - case 'p': - override_path = optarg; - break; - case 'h': - case '?': + do_set = 1; + } else if (!strcmp(argv[i], "--get")) { + do_get = 1; + } else if (!strcmp(argv[i], "--expect-limit-4k")) { + if (++i >= argc || parse_u32(argv[i], &expect_limit)) { + fprintf(stderr, "invalid expected limit\n"); + return 2; + } + do_get = 1; + do_expect_limit = 1; + } else if (!strcmp(argv[i], "--expect-empty")) { + do_get = 1; + do_expect_empty = 1; + } else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--path")) { + if (++i >= argc) { + usage(argv[0]); + return 2; + } + override_path = argv[i]; + } else if (argv[i][0] == '-') { + fprintf(stderr, "unknown argument: %s\n", argv[i]); usage(argv[0]); - return opt == 'h' ? 0 : 2; - default: + return 2; + } else if (!mounted_path) { + mounted_path = argv[i]; + } else { usage(argv[0]); return 2; } } - if (!set_limit || optind + 1 != argc) { + if (!mounted_path || (!do_set && !do_get) || (do_expect_limit && do_expect_empty)) { usage(argv[0]); return 2; } - mounted_path = argv[optind]; + + if (NWCallsInit(NULL, NULL)) { + fprintf(stderr, "NWCallsInit failed\n"); + return 2; + } err = NWParsePath(mounted_path, NULL, &conn, volume, volpath); if (err) { @@ -107,7 +222,7 @@ int main(int argc, char **argv) err = ncp_ns_alloc_short_dir_handle(conn, NW_NS_DOS, NCP_DIRSTYLE_NOHANDLE, 0, 0, nwpath, len, - NCP_ALLOC_TEMPORARY, &dirhandle, &volnum); + NCP_ALLOC_PERMANENT, &dirhandle, &volnum); if (err) { fprintf(stderr, "cannot obtain directory handle for %s: %s\n", mounted_path, strnwerror(err)); @@ -115,24 +230,63 @@ int main(int argc, char **argv) return 1; } - { - struct ncp_dos_info info; - memset(&info, 0, sizeof(info)); - info.MaximumSpace = limit; - err = ncp_ns_modify_entry_dos_info(conn, NW_NS_DOS, SA_ALL, - NCP_DIRSTYLE_HANDLE, - volnum, dirhandle, NULL, 0, - DM_MAXIMUM_SPACE, &info); + if (do_set) { + err = ncp22_36_set_dirquota(conn, dirhandle, set_limit); + if (err) { + fprintf(stderr, "NCP 22/36 set directory quota failed on %s (vol=%u handle=0x%02x): %s\n", + mounted_path, (unsigned)volnum, (unsigned)dirhandle, strnwerror(err)); + ncp_dealloc_dir_handle(conn, dirhandle); + ncp_close(conn); + return 1; + } + printf("dirquota set path=%s vol=%u handle=0x%02x limit4k=%u\n", + mounted_path, (unsigned)volnum, (unsigned)dirhandle, + (unsigned)set_limit); } - if (err) { - fprintf(stderr, "cannot set directory quota on %s (vol=%u handle=0x%02x): %s\n", - mounted_path, (unsigned)volnum, (unsigned)dirhandle, strnwerror(err)); - ncp_close(conn); - return 1; + if (do_get) { + err = ncp22_35_get_dirquota(conn, dirhandle, entries, + sizeof(entries) / sizeof(entries[0]), + &entry_count); + if (err) { + fprintf(stderr, "NCP 22/35 get directory quota failed on %s (vol=%u handle=0x%02x): %s\n", + mounted_path, (unsigned)volnum, (unsigned)dirhandle, strnwerror(err)); + ncp_dealloc_dir_handle(conn, dirhandle); + ncp_close(conn); + return 1; + } + printf("dirquota get path=%s vol=%u handle=0x%02x entries=%zu\n", + mounted_path, (unsigned)volnum, (unsigned)dirhandle, entry_count); + for (i = 0; i < (int)entry_count; i++) { + uint32_t used = entries[i].max4k >= entries[i].current4k ? + entries[i].max4k - entries[i].current4k : 0; + printf("dirquota entry%u level=%u max4k=%u current4k=%u used4k=%u\n", + (unsigned)i, (unsigned)entries[i].level, + (unsigned)entries[i].max4k, + (unsigned)entries[i].current4k, + (unsigned)used); + } + + if (do_expect_empty && entry_count != 0) { + fprintf(stderr, "expected no directory quota entries, got %zu\n", entry_count); + ncp_dealloc_dir_handle(conn, dirhandle); + ncp_close(conn); + return 1; + } + if (do_expect_limit) { + if (entry_count < 1 || entries[0].max4k != expect_limit) { + fprintf(stderr, "expected first directory quota max4k=%u, got %s%u\n", + (unsigned)expect_limit, + entry_count < 1 ? "no entry, fallback=" : "", + entry_count < 1 ? 0U : (unsigned)entries[0].max4k); + ncp_dealloc_dir_handle(conn, dirhandle); + ncp_close(conn); + return 1; + } + } } - printf("directory quota set path=%s limit4k=%lu\n", mounted_path, limit); + ncp_dealloc_dir_handle(conn, dirhandle); ncp_close(conn); return 0; } diff --git a/tests/nwfs/nwfs_ncpfs_dirquota_smoke.sh.in b/tests/nwfs/nwfs_ncpfs_dirquota_smoke.sh.in new file mode 100644 index 0000000..fdd6412 --- /dev/null +++ b/tests/nwfs/nwfs_ncpfs_dirquota_smoke.sh.in @@ -0,0 +1,162 @@ +#!/bin/sh +# Manual NCPFS directory-quota smoke for mars-nwe 3.x quota endpoints. +# +# Exercises decimal NCP 22/36 (wire/code 0x24) to set a directory disk-space +# restriction and decimal NCP 22/35 (wire/code 0x23) to read it back. Host-side +# xattr verification checks that the mutation reached netware.metadata. +set -eu + +if [ "$#" -lt 4 ]; then + echo "usage: $0 SERVER ADMIN PASSWORD HOST_SYSROOT [MOUNTPOINT] [TESTDIR] [VOLUME]" >&2 + echo "example: $0 MARS SUPERVISOR secret /var/mars_nwe/SYS /mnt/nw-sys NWFSTEST SYS" >&2 + echo "Set NWFS_NCPFS_DIR_QUOTA_4K to choose the tested limit; default: 12." >&2 + exit 2 +fi + +SERVER=$1 +ADMIN_USER=$2 +ADMIN_PASSWORD=$3 +SYSROOT=$4 +MOUNTPOINT=${5:-} +TESTDIR=${6:-${NWFS_NCPFS_TESTDIR:-NWFSTEST}} +VOLUME=${7:-${NWFS_NCPFS_VOLUME:-SYS}} +DIR_QUOTA_4K=${NWFS_NCPFS_DIR_QUOTA_4K:-12} +DIRQUOTA_HELPER="@NWFS_NCPFS_DIRQUOTA_HELPER@" +DUMP="@CMAKE_CURRENT_BINARY_DIR@/nwfs_xattr_dump" + +case "$TESTDIR" in + /*|*..*|"") echo "TESTDIR must be relative and must not contain '..': $TESTDIR" >&2; exit 2 ;; +esac +case "$VOLUME" in + *:*|*/*|*..*|"") echo "VOLUME must be a bare NetWare volume name: $VOLUME" >&2; exit 2 ;; +esac +case "$DIR_QUOTA_4K" in + ''|*[!0-9]*) echo "NWFS_NCPFS_DIR_QUOTA_4K must be an integer 4K block count" >&2; exit 2 ;; +esac + +if [ -z "$DIRQUOTA_HELPER" ] || [ ! -x "$DIRQUOTA_HELPER" ]; then + echo "nwfs_ncpfs_dirquota helper not built" >&2 + exit 1 +fi +if [ ! -x "$DUMP" ]; then + echo "nwfs_xattr_dump not built: $DUMP" >&2 + exit 1 +fi +if ! command -v ncpmount >/dev/null 2>&1; then + echo "ncpmount missing" >&2 + exit 1 +fi + +if [ -z "$MOUNTPOINT" ]; then + MOUNTPOINT=$(mktemp -d "${TMPDIR:-/tmp}/mars-ncpfs-dirquota.XXXXXX") + CLEAN_MOUNT=1 +else + mkdir -p "$MOUNTPOINT" + CLEAN_MOUNT=0 +fi + +MOUNTED_BY_SCRIPT=0 +OUT=$(mktemp "${TMPDIR:-/tmp}/nwfs_dirquota_out.XXXXXX") +DUMP_OUT=$(mktemp "${TMPDIR:-/tmp}/nwfs_dirquota_dump.XXXXXX") + +ncpfs_mount_is_active() { + mp=$1 + awk -v mp="$mp" '$2 == mp && $3 == "ncpfs" { found = 1 } END { exit found ? 0 : 1 }' /proc/self/mounts +} + +ncpfs_umount_path() { + mp=$1 + if ! ncpfs_mount_is_active "$mp"; then + return 0 + fi + if command -v ncpumount >/dev/null 2>&1; then + ncpumount "$mp" || umount "$mp" + else + umount "$mp" + fi +} + +cleanup() { + if [ "$MOUNTED_BY_SCRIPT" = 1 ]; then + ncpfs_umount_path "$MOUNTPOINT" || true + fi + if [ "$CLEAN_MOUNT" = 1 ]; then + rmdir "$MOUNTPOINT" 2>/dev/null || true + fi + rm -f "$OUT" "$DUMP_OUT" +} +trap cleanup EXIT HUP INT TERM + +purge_testdir_recycle() { + for path in "$SYSROOT"/.recycle/*/"$TESTDIR" "$SYSROOT"/.salvage/*/"$TESTDIR"; do + [ -e "$path" ] || continue + echo "purging old recycle/salvage test remnant $path" >&2 + rm -rf -- "$path" + done +} + +mount_admin() { + err=$(mktemp "${TMPDIR:-/tmp}/nwfs_dirquota_mount.XXXXXX") + ncpfs_umount_path "$MOUNTPOINT" + if ! ncpmount -S "$SERVER" -U "$ADMIN_USER" -P "$ADMIN_PASSWORD" -V "$VOLUME" "$MOUNTPOINT" 2>"$err"; then + echo "ncpmount failed for //$SERVER/$VOLUME at $MOUNTPOINT" >&2 + cat "$err" >&2 || true + rm -f "$err" + exit 1 + fi + rm -f "$err" + MOUNTED_BY_SCRIPT=1 + echo "mounted //$SERVER/$VOLUME at $MOUNTPOINT as $ADMIN_USER" >&2 +} + +verify_dump_limit() { + expected=$1 + "$DUMP" "$SYSROOT/$TESTDIR" | tee "$DUMP_OUT" + if [ "$expected" = 0 ]; then + if grep -q 'dirQuotaLimit=.*active' "$DUMP_OUT"; then + echo "expected directory quota to be cleared on $SYSROOT/$TESTDIR" >&2 + exit 1 + fi + else + if ! grep -q "dirQuotaLimit=$expected active" "$DUMP_OUT"; then + echo "directory quota metadata did not contain dirQuotaLimit=$expected active on $SYSROOT/$TESTDIR" >&2 + exit 1 + fi + fi +} + +purge_testdir_recycle +mount_admin +rm -rf -- "$MOUNTPOINT/$TESTDIR" +mkdir -p -- "$MOUNTPOINT/$TESTDIR" +sync + +printf '# initial directory quota read should be unrestricted\n' +"$DIRQUOTA_HELPER" --expect-empty "$MOUNTPOINT/$TESTDIR" | tee "$OUT" + +printf '# setting directory quota using NCP 22/36 decimal / wire 0x24\n' +"$DIRQUOTA_HELPER" --set-limit-4k "$DIR_QUOTA_4K" "$MOUNTPOINT/$TESTDIR" | tee "$OUT" + +printf '# reading directory quota using NCP 22/35 decimal / wire 0x23\n' +"$DIRQUOTA_HELPER" --expect-limit-4k "$DIR_QUOTA_4K" "$MOUNTPOINT/$TESTDIR" | tee "$OUT" +if ! grep -q "max4k=$DIR_QUOTA_4K" "$OUT"; then + echo "NCP 22/35 readback did not report max4k=$DIR_QUOTA_4K" >&2 + exit 1 +fi + +printf '# verifying host netware.metadata directory quota\n' +verify_dump_limit "$DIR_QUOTA_4K" + +printf '# clearing directory quota using NCP 22/36 decimal / wire 0x24\n' +"$DIRQUOTA_HELPER" --set-limit-4k 0 "$MOUNTPOINT/$TESTDIR" | tee "$OUT" + +printf '# reading cleared directory quota using NCP 22/35 decimal / wire 0x23\n' +"$DIRQUOTA_HELPER" --expect-empty "$MOUNTPOINT/$TESTDIR" | tee "$OUT" + +printf '# verifying host netware.metadata directory quota clear\n' +verify_dump_limit 0 + +rm -rf -- "$MOUNTPOINT/$TESTDIR" +sync + +echo "directory quota smoke completed for $VOLUME:$TESTDIR limit=${DIR_QUOTA_4K}x4K"