From 77bd01026ccd01d31aadec2402c3d5ce1db37684 Mon Sep 17 00:00:00 2001 From: OpenAI Date: Fri, 12 Jun 2026 08:12:30 +0000 Subject: [PATCH] tests: add DOS quota write-deny smoke --- CMakeLists.txt | 1 + README.md | 2 + nwtests.c | 76 +++++++++++++++++++++++++++++++++++++ test/README.md | 3 +- test/cmnwmeta.sh | 5 ++- test/quota/DQTC.BAT | 61 ++++++++++++++++++++++++++++++ test/quota/DQTSTA.BAT | 87 +++++++++++++++++++++++++++++++++++++++++++ test/quota/DQTZIP.BAT | 19 ++++++++++ test/quota/README.md | 55 +++++++++++++++++++++++++++ 9 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 test/quota/DQTC.BAT create mode 100644 test/quota/DQTSTA.BAT create mode 100644 test/quota/DQTZIP.BAT create mode 100644 test/quota/README.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 64115f0..bddbed3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ set(MARS_DOSUTILS_NEW_ONLY_TOOLS creator whoami ncopy + tests ) if(MARS_DOSUTILS_BUILD_FROM_SOURCE) diff --git a/README.md b/README.md index 2920fc6..fe7969f 100644 --- a/README.md +++ b/README.md @@ -727,3 +727,5 @@ Prints the effective-rights values returned by several Client32/NCP paths for co `TESTS EFFRIGHT path` also prints exploratory old NCP22 effective-rights call variants for comparing with Novell RIGHTS. `TESTS EFFRIGHT path` additionally tests NCP22 subfunction 50 object effective rights using the current login object id. + +`TESTS WRITE4K file [count]` writes one or more exact 4K blocks and returns non-zero if open/write/close fails. It is used by the DOS quota smoke to prove that the 13th 4K write is denied after Linux/MARS-NWE set a 12x4K user quota. diff --git a/nwtests.c b/nwtests.c index 820e4d6..a7a7aa8 100644 --- a/nwtests.c +++ b/nwtests.c @@ -25,6 +25,11 @@ #include "net.h" #include "ncpapi.h" +#include +#include +#include +#include +#include #define TEST_RIGHT_S 0x01 #define TEST_RIGHT_R 0x02 @@ -55,6 +60,7 @@ static void tests_usage(void) fprintf(stdout, " TESTS NCP2225CREATE file [date time id] (create xattr metadata)\n"); fprintf(stdout, " TESTS NCP2225MODID file [id] (modifier-id xattr metadata)\n"); fprintf(stdout, " TESTS NCP2225MAXSPACE dir [blocks] (directory maximum-space/quota)\n"); + fprintf(stdout, " TESTS WRITE4K file [count] (write count 4K blocks; default 1)\n"); fprintf(stdout, " TESTS NCP221EINFO dir (dump NCP22/1E directory info layout)\n"); fprintf(stdout, " TESTS NCP22S4 path RF|42|0x42|ALL|NONE\n"); fprintf(stdout, " TESTS NCP22REN oldpath newpath (NCP22/2E Rename Or Move old)\n"); @@ -1236,6 +1242,68 @@ static int tests_ncp2225adate(char *path, char *date_arg) + +static int tests_parse_int_arg(char *s, int *value_out) +{ + long v; + char *end; + + if (!s || !*s || !value_out) + return(-1); + + v = strtol(s, &end, 0); + if (!end || *end || v < 1 || v > 32767L) + return(-1); + + *value_out = (int)v; + return(0); +} + +static int tests_write4k(char *path, char *count_arg) +{ + char buf[4096]; + int count = 1; + int fd; + int i; + + if (!path || !*path) { + fprintf(stdout, "Usage: TESTS WRITE4K file [count]\n"); + return(1); + } + + if (count_arg && tests_parse_int_arg(count_arg, &count)) { + fprintf(stdout, "Bad WRITE4K count '%s'. Use a positive decimal/hex number.\n", count_arg); + return(1); + } + + memset(buf, 'Q', sizeof(buf)); + fd = open(path, O_BINARY | O_RDWR | O_CREAT | O_TRUNC | O_DENYNONE, + S_IREAD | S_IWRITE); + if (fd < 0) { + fprintf(stdout, "WRITE4K open failed path=%s errno=%d\n", path, errno); + return(2); + } + + for (i = 0; i < count; i++) { + unsigned written = 0; + if (_dos_write(fd, buf, sizeof(buf), &written) || written != sizeof(buf)) { + fprintf(stdout, "WRITE4K write failed path=%s block=%d/%d written=%u errno=%d\n", + path, i + 1, count, written, errno); + close(fd); + return(3); + } + } + + if (close(fd)) { + fprintf(stdout, "WRITE4K close failed path=%s errno=%d\n", path, errno); + return(4); + } + + fprintf(stdout, "WRITE4K ok path=%s blocks=%d bytes=%ld\n", + path, count, (long)count * 4096L); + return(0); +} + static int tests_ncp2225maxspace(char *path, char *space_arg) { uint8 dhandle = 0; @@ -2669,6 +2737,14 @@ int func_tests(int argc, char *argv[], int mode) return tests_ncp2225adate(argv[2], argv[3]); } + if (tool_strsame(argv[1], "WRITE4K")) { + if (argc < 3) + return tests_write4k(NULL, NULL); + if (argc < 4) + return tests_write4k(argv[2], NULL); + return tests_write4k(argv[2], argv[3]); + } + if (tool_strsame(argv[1], "NCP2225MAXSPACE")) { if (argc < 3) return tests_ncp2225maxspace(NULL, NULL); diff --git a/test/README.md b/test/README.md index 2586f96..d25f6cd 100644 --- a/test/README.md +++ b/test/README.md @@ -72,6 +72,7 @@ script or log. | `rightsuser/` | varies | Additional user-rights experiments. | | `renmove/` | varies | Rename/move experiments. | | `ncopy/` | varies | NCOPY experiments. | +| `quota/` | `DQTSTA.BAT` + `DQTC.BAT` | Mixed Linux/DOS userquota write-deny smoke: Linux sets quota, DOS writes until deny. | | `filer/` | varies | FILER-related notes/experiments. | ## Host-side NSS/OES metadata readback @@ -84,7 +85,7 @@ sh dosutils/test/cmnwmeta.sh /path/to/SYS /path/to/nwfs_xattr_dump > meta.log ``` The collector decodes `netware.metadata`, `netware.quota`, and -`netware.userquota` from the files and directories changed by the DOS tests. +`netware.userquota.0` from the files and directories changed by the DOS tests. This is the expected verification layer for the NSS migration: DOS tools or NCPFS perform the mutation, and `nwfs_xattr_dump` proves that the resulting state landed in the OES-compatible `netware.*` metadata rather than only in the diff --git a/test/cmnwmeta.sh b/test/cmnwmeta.sh index e298a32..df3123c 100755 --- a/test/cmnwmeta.sh +++ b/test/cmnwmeta.sh @@ -5,7 +5,7 @@ # sh dosutils/test/cmnwmeta.sh /path/to/SYS /path/to/nwfs_xattr_dump > meta.log # # The dumper reads the mars-nwe userspace xattr mapping, so on Linux it looks -# at user.netware.metadata, user.netware.quota and user.netware.userquota. +# at user.netware.metadata, user.netware.quota and user.netware.userquota.0. set -eu @@ -44,7 +44,8 @@ for rel in \ NDIRTST NDIRCMP \ TFILE \ NCPTST NCPCMP \ - RUTEST RUTCMP + RUTEST RUTCMP \ + DQTTEST DQTCMP do if [ -e "$SYSROOT/$rel" ]; then find "$SYSROOT/$rel" -xdev \( -type f -o -type d \) -print diff --git a/test/quota/DQTC.BAT b/test/quota/DQTC.BAT new file mode 100644 index 0000000..06f7db9 --- /dev/null +++ b/test/quota/DQTC.BAT @@ -0,0 +1,61 @@ +@ECHO OFF +REM DQTC.BAT +REM Run as NOPASSUSER after Linux-side quota setup. + +F: +CD \ +IF NOT EXIST DQTCMP\NUL MD DQTCMP +IF NOT EXIST DQTCMP\DOS\NUL MD DQTCMP\DOS +IF NOT EXIST DQTTEST\NCPQFILL\NUL MD DQTTEST\NCPQFILL +IF EXIST F:\DQTCMP\PASS.TAG DEL F:\DQTCMP\PASS.TAG +IF EXIST F:\DQTCMP\FAIL.TAG DEL F:\DQTCMP\FAIL.TAG +IF EXIST F:\DQTCMP\DOS\WRITE.LOG DEL F:\DQTCMP\DOS\WRITE.LOG + +ECHO DQTC DOS quota write test as current user > F:\DQTCMP\DOS\WRITE.LOG +ECHO Writing 12 allowed 4K files. >> F:\DQTCMP\DOS\WRITE.LOG + +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00001.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00002.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00003.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00004.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00005.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00006.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00007.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00008.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00009.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00010.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00011.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\Q00012.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO FAILGOOD + +ECHO Attempting expected failing 13th 4K file. >> F:\DQTCMP\DOS\WRITE.LOG +PUBLIC\TESTS WRITE4K F:\DQTTEST\NCPQFILL\QFAIL.BIN 1 >> F:\DQTCMP\DOS\WRITE.LOG +IF ERRORLEVEL 1 GOTO PASS + +ECHO FAIL: expected QFAIL.BIN to be denied, but it succeeded. >> F:\DQTCMP\DOS\WRITE.LOG +ECHO FAIL > F:\DQTCMP\FAIL.TAG +GOTO END + +:FAILGOOD +ECHO FAIL: an allowed 4K write failed before the quota boundary. >> F:\DQTCMP\DOS\WRITE.LOG +ECHO FAIL > F:\DQTCMP\FAIL.TAG +GOTO END + +:PASS +ECHO PASS: 13th 4K file was denied. >> F:\DQTCMP\DOS\WRITE.LOG +ECHO PASS > F:\DQTCMP\PASS.TAG +GOTO END + +:END +TYPE F:\DQTCMP\DOS\WRITE.LOG diff --git a/test/quota/DQTSTA.BAT b/test/quota/DQTSTA.BAT new file mode 100644 index 0000000..0b32c3f --- /dev/null +++ b/test/quota/DQTSTA.BAT @@ -0,0 +1,87 @@ +@ECHO OFF +REM DQTSTA.BAT +REM DOS-side quota write-deny smoke for MARS-NWE. +REM +REM This is intentionally a mixed Linux/DOS test: +REM 1. Run DQTSTA PREP as SUPERVISOR from the target volume. +REM 2. On Linux set the volume userquota for NOPASSUSER. +REM 3. Login as NOPASSUSER and run DQTC.BAT from this directory. +REM 4. Login as SUPERVISOR and run DQTSTA PART2, then DQTZIP. + +IF "%1"=="" GOTO PREP +IF "%1"=="PREP" GOTO PREP +IF "%1"=="prep" GOTO PREP +IF "%1"=="PART2" GOTO PART2 +IF "%1"=="part2" GOTO PART2 +ECHO Usage: DQTSTA [PREP^|PART2] +GOTO END + +:PREP +F: +CD \ + +IF EXIST DQTTEST\NUL DELTREE /Y DQTTEST\*.* +IF EXIST DQTCMP\NUL DELTREE /Y DQTCMP\*.* +IF NOT EXIST DQTTEST\NUL MD DQTTEST +IF NOT EXIST DQTTEST\NCPQFILL\NUL MD DQTTEST\NCPQFILL +IF NOT EXIST DQTCMP\NUL MD DQTCMP +IF NOT EXIST DQTCMP\DOS\NUL MD DQTCMP\DOS +IF NOT EXIST DQTCMP\SETUP\NUL MD DQTCMP\SETUP + +IF EXIST F:\DQTCMP\RUN.LOG DEL F:\DQTCMP\RUN.LOG +IF EXIST F:\DQTCMP\SUMMARY.TXT DEL F:\DQTCMP\SUMMARY.TXT +IF EXIST F:\DQTCMP\FAIL.TAG DEL F:\DQTCMP\FAIL.TAG +IF EXIST F:\DQTCMP\PASS.TAG DEL F:\DQTCMP\PASS.TAG +IF EXIST F:\DQTCMP\DQTSTA.ZIP DEL F:\DQTCMP\DQTSTA.ZIP +IF EXIST F:\DQTCMP\ZIP.LOG DEL F:\DQTCMP\ZIP.LOG +IF EXIST F:\DQTCMP\DOS\*.OUT DEL F:\DQTCMP\DOS\*.OUT + +ECHO DQTSTA DOS quota write-deny smoke > F:\DQTCMP\RUN.LOG +ECHO Volume-side setup is done from DOS. Quota limit is set from Linux. >> F:\DQTCMP\RUN.LOG +ECHO Expected Linux setup: userquota for NOPASSUSER allows 12x4K, then DOS write 13th file fails. >> F:\DQTCMP\RUN.LOG +ECHO. >> F:\DQTCMP\RUN.LOG + +REM Give the normal user enough rights to create files and write result logs. +IF EXIST F:\NPUBLIC\GRANT.EXE GOTO HAVEGRANT +ECHO ERROR: F:\NPUBLIC\GRANT.EXE not found. >> F:\DQTCMP\RUN.LOG +ECHO ERROR: F:\NPUBLIC\GRANT.EXE not found. +GOTO END + +:HAVEGRANT +NPUBLIC\GRANT R W C E M F A FOR F:\DQTTEST TO USER NOPASSUSER > F:\DQTCMP\SETUP\GRANT1.OUT +NPUBLIC\GRANT R W C E M F A FOR F:\DQTTEST\NCPQFILL TO USER NOPASSUSER > F:\DQTCMP\SETUP\GRANT2.OUT +NPUBLIC\GRANT R W C E M F A FOR F:\DQTCMP TO USER NOPASSUSER > F:\DQTCMP\SETUP\GRANT3.OUT +NPUBLIC\RIGHTS F:\DQTTEST\NCPQFILL > F:\DQTCMP\SETUP\RIGHTS.OUT + +ECHO === NEXT LINUX STEP === > F:\DQTCMP\LINUX.TXT +ECHO Set volume userquota for NOPASSUSER to 12x4K on this volume. >> F:\DQTCMP\LINUX.TXT +ECHO Then login as NOPASSUSER and run: >> F:\DQTCMP\LINUX.TXT +ECHO F:\DQTC.BAT >> F:\DQTCMP\LINUX.TXT +ECHO Then login as SUPERVISOR and run: >> F:\DQTCMP\LINUX.TXT +ECHO F:\DQTSTA.BAT PART2 >> F:\DQTCMP\LINUX.TXT + +ECHO DQTSTA PREP finished. >> F:\DQTCMP\RUN.LOG +ECHO. +ECHO DQTSTA PREP fertig. +ECHO Jetzt Linux quota fuer NOPASSUSER setzen, dann als NOPASSUSER F:\DQTC.BAT starten. +GOTO END + +:PART2 +F: +CD \ +IF NOT EXIST DQTCMP\NUL MD DQTCMP + +ECHO DQTSTA PART2 summary > F:\DQTCMP\SUMMARY.TXT +IF EXIST F:\DQTCMP\PASS.TAG ECHO PASS: DOS quota deny observed. >> F:\DQTCMP\SUMMARY.TXT +IF EXIST F:\DQTCMP\FAIL.TAG ECHO FAIL: DOS quota deny was not observed. >> F:\DQTCMP\SUMMARY.TXT +IF NOT EXIST F:\DQTCMP\PASS.TAG IF NOT EXIST F:\DQTCMP\FAIL.TAG ECHO UNKNOWN: DQTC.BAT result tags missing. >> F:\DQTCMP\SUMMARY.TXT +ECHO. >> F:\DQTCMP\SUMMARY.TXT +ECHO DOS log files are in F:\DQTCMP\DOS. >> F:\DQTCMP\SUMMARY.TXT +ECHO Linux/nw.log evidence should be collected with the matching mars-nwe smoke wrapper. >> F:\DQTCMP\SUMMARY.TXT + +ECHO. +TYPE F:\DQTCMP\SUMMARY.TXT +ECHO Optional: run DQTZIP. +GOTO END + +:END diff --git a/test/quota/DQTZIP.BAT b/test/quota/DQTZIP.BAT new file mode 100644 index 0000000..35bcae3 --- /dev/null +++ b/test/quota/DQTZIP.BAT @@ -0,0 +1,19 @@ +@ECHO OFF +REM DQTZIP.BAT +REM Optional packer for DOS quota smoke result files. + +F: +CD \DQTCMP + +IF EXIST DQTSTA.ZIP DEL DQTSTA.ZIP +IF EXIST ZIP.LOG DEL ZIP.LOG + +ECHO Creating DQTSTA.ZIP > ZIP.LOG +ZIP -r DQTSTA.ZIP RUN.LOG SUMMARY.TXT LINUX.TXT PASS.TAG FAIL.TAG DOS SETUP >> ZIP.LOG + +ECHO. +ECHO ZIP step finished. +ECHO Check: +ECHO F:\DQTCMP\DQTSTA.ZIP +ECHO F:\DQTCMP\ZIP.LOG +ECHO. diff --git a/test/quota/README.md b/test/quota/README.md new file mode 100644 index 0000000..2fe9809 --- /dev/null +++ b/test/quota/README.md @@ -0,0 +1,55 @@ +# DOS quota write-deny smoke + +This test proves the last step of NetWare-style quota enforcement with DOS +board tools: Linux/NCPFS sets the user quota, then a real DOS client logs in as +`NOPASSUSER` and attempts the file writes. + +It is intentionally split because the authoritative quota setup remains on the +Linux/MARS-NWE side: + +1. In DOS, login as `SUPERVISOR` on the target volume and run: + + ```bat + F: + DQTSTA PREP + ``` + + This creates `F:\DQTTEST\NCPQFILL`, grants `NOPASSUSER` write/create rights, + and creates `F:\DQTCMP` for logs. + +2. On Linux, set the user quota for `NOPASSUSER` to 12 4K blocks on the same + volume. With the mars-nwe ncpfs helper this is typically: + + ```sh + ./nwfs_ncpfs_userquota -S MARS -U SUPERVISOR -P 'password' \ + --volume QUOTA --object NOPASSUSER --type 1 --limit-4k 12 + ``` + + For SYS/NWQUOTA metadata-backend testing, use `--volume SYS` instead. + +3. In DOS, login as `NOPASSUSER` and run: + + ```bat + F:\DQTC.BAT + ``` + + `DQTC` writes `Q00001.BIN` through `Q00012.BIN` as 4K files, then expects + `QFAIL.BIN` to be denied. + +4. In DOS, login as `SUPERVISOR` and run: + + ```bat + F:\DQTSTA PART2 + F:\DQTZIP + ``` + +Expected result: + +```text +PASS: DOS quota deny observed. +``` + +The test does not set quotas itself. That keeps the authority split explicit: +Linuxquota volumes are configured/enforced by the Linux backend, while NWQUOTA +metadata volumes use the MARS-NWE metadata backend. The DOS part proves the +client-visible write behavior.