tests: add AFP metadata rights negative smoke
All checks were successful
Source release / source-package (push) Successful in 52s

Extend the AFP Set File Information smoke helper with an explicit expected-completion mode so negative WebSDK/NWAFP probes can assert the server completion byte instead of treating any non-zero completion as a helper failure.

Use that support from afp_smoke_suite.sh to add optional Modify-rights negative coverage for AFP metadata writes.  When a readonly test user such as NOPASSUSER is supplied, the suite now verifies that FinderInfo and AFP-only Invisible/System xattr writes are rejected with completion 0x8c, matching the mars_nwe trustee/effective-rights Modify policy gate added to afp_set_file_information().

The optional --prepare-readonly-rights mode deliberately reuses the standard ncpfs trustee utilities rather than adding ad-hoc test NCPs: nwrevoke removes an explicit assignment for the readonly user on the smoke target, nwgrant grants only read/file-scan rights, and nwrevoke restores the object to inherited rights after the negative probes.  A cleanup trap also revokes the temporary assignment if the suite exits early.

This keeps the AFP smoke harness aligned with the convergence plan: AFP remains an Apple-facing adapter over existing mars_nwe NetWare trustee semantics, while test setup uses existing NetWare administration utilities.

Tests: bash -n tests/linux/afp_smoke_suite.sh; gcc -Iinclude -I/mnt/data/stubs -fsyntax-only tests/linux/afp_set_file_info_smoke.c; git diff --check
This commit is contained in:
OpenAI
2026-05-30 15:15:40 +00:00
committed by Mario Fetka
parent dacfc0f7a1
commit 4240aff077
4 changed files with 167 additions and 3 deletions

View File

@@ -440,6 +440,13 @@ AFP Set File Information metadata-rights convergence:
and Modify timestamp continues to route through `nw_utime_node()`. The new
check only covers metadata that has to remain AFP-specific, keeping AFP as an
Apple-facing adapter over mars_nwe policy rather than a parallel file server.
- The Linux AFP smoke suite now has optional negative coverage for this policy
gate. With `--readonly-user NOPASSUSER --readonly-no-password`, the suite can
run FinderInfo, Invisible, and System writes as a user that is expected to lack
Modify rights and assert completion `0x8c`. With `--prepare-readonly-rights`,
the suite uses the existing ncpfs `nwgrant`/`nwrevoke` trustee utilities to
grant only read/file-scan rights (`[RF]` by default) before the negative probe
and revoke the explicit trustee assignment afterwards.
Endpoint order:

View File

@@ -87,6 +87,28 @@ collected separately. Use `--stop-on-failure` for strict bisect-style runs; by
default the script keeps going so one failing endpoint does not hide later AFP
output from the report.
The suite can optionally exercise the Modify-rights negative path with a second
user. For a no-password test user such as `NOPASSUSER`, run from the build
`tests/linux` directory:
```sh
./afp_smoke_suite.sh \
-S MARS -U SUPERVISOR -P secret \
--path SYS:PUBLIC/pmdflts.ini \
--unix-path /var/mars_nwe/SYS/public/pmdflts.ini \
--readonly-user NOPASSUSER --readonly-no-password \
--prepare-readonly-rights \
--out /tmp/mars-afp-smoke.txt
```
`--prepare-readonly-rights` uses the standard ncpfs trustee utilities instead
of ad-hoc test NCPs: it calls `nwrevoke` to remove any explicit assignment for
the readonly user on the smoke file, then `nwgrant -r '[RF]'` to grant read and
file-scan rights without Modify. After the negative probes it runs `nwrevoke`
again so the file returns to inherited rights. Use this only on smoke files or
paths where removing an explicit trustee assignment for the readonly test user
is acceptable.
AFP metadata writes and NetWare Modify rights:
FinderInfo and AFP-only attribute metadata are stored in `org.mars-nwe.afp.*`

View File

@@ -38,7 +38,7 @@
static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [--afp09|--afp20] [--allow-invalid-namespace] [--allow-invalid-path] "
"Usage: %s [--afp09|--afp20] [--expect-completion CODE] [--allow-invalid-namespace] [--allow-invalid-path] "
"[--volume N] [--entry-id ID] [--type FOUR] [--creator FOUR] "
"[--invisible|--clear-invisible|--system|--clear-system|--archive|--clear-archive] "
"[--mtime-epoch SECONDS] [--finder-info-only|--attributes-only|--timestamp-only] "
@@ -52,8 +52,9 @@ static void usage(const char *prog)
" %s -S MARS -U SUPERVISOR -P secret --invisible --attributes-only SYS:PUBLIC/pmdflts.ini\n"
" %s -S MARS -U SUPERVISOR -P secret --archive --attributes-only SYS:PUBLIC/pmdflts.ini\n"
" %s -S MARS -U SUPERVISOR -P secret --mtime-epoch 1700000000 --timestamp-only SYS:PUBLIC/pmdflts.ini\n"
" %s --allow-invalid-namespace -S MARS SYS:PUBLIC/pmdflts.ini\n",
prog, prog, prog, prog, prog, prog);
" %s --allow-invalid-namespace -S MARS SYS:PUBLIC/pmdflts.ini\n"
" %s --expect-completion 0x8c -S MARS -U NOPASSUSER -n --finder-info-only SYS:PUBLIC/pmdflts.ini\n",
prog, prog, prog, prog, prog, prog, prog);
}
static int parse_u32(const char *text, uint32_t *value)
@@ -160,6 +161,7 @@ int main(int argc, char **argv)
const char *path = NULL;
int allow_invalid_namespace = 0;
int allow_invalid_path = 0;
int expect_completion = -1;
uint32_t volume_number = 0;
uint32_t entry_id = 0;
uint8_t finder_info[32];
@@ -200,6 +202,14 @@ int main(int argc, char **argv)
set_subfunction = AFP_SET_FILE_INFORMATION;
} else if (!strcmp(argv[i], "--afp20")) {
set_subfunction = AFP20_SET_FILE_INFORMATION;
} else if (!strcmp(argv[i], "--expect-completion")) {
uint32_t completion;
if (++i >= argc || parse_u32(argv[i], &completion) || completion > 255) {
fprintf(stderr, "invalid --expect-completion value\n");
ncp_close(conn);
return 2;
}
expect_completion = (int)completion;
} else if (!strcmp(argv[i], "--allow-invalid-namespace")) {
allow_invalid_namespace = 1;
} else if (!strcmp(argv[i], "--allow-invalid-path")) {
@@ -347,6 +357,20 @@ int main(int argc, char **argv)
request,
data_off,
NULL);
if (expect_completion >= 0) {
if ((((unsigned int)err) & 0xff) == (unsigned int)expect_completion) {
printf("AFP Set File Information returned expected completion 0x%02x: subfunction=0x%02x path=%s bitmap=0x%04x\n",
expect_completion, set_subfunction, path, request_mask);
ncp_close(conn);
return 0;
}
fprintf(stderr,
"AFP Set File Information expected completion 0x%02x but got 0x%02x (%u): subfunction=0x%02x path=%s bitmap=0x%04x\n",
expect_completion, (unsigned int)err & 0xff, (unsigned int)err,
set_subfunction, path, request_mask);
ncp_close(conn);
return 1;
}
if (err == NWE_INVALID_NAMESPACE && allow_invalid_namespace) {
printf("AFP Set File Information returned invalid namespace as expected for path=%s\n", path);
ncp_close(conn);

View File

@@ -18,6 +18,11 @@ OUT_FILE=""
FINDER_TYPE="TEXT"
FINDER_CREATOR="MARS"
TIMESTAMP_EPOCH="1700000000"
READONLY_USER=""
READONLY_PASSWORD=""
READONLY_NO_PASSWORD=0
PREPARE_READONLY_RIGHTS=0
READONLY_RIGHTS="[RF]"
KEEP_GOING=1
CAPTURE_LOG=1
@@ -33,6 +38,11 @@ Options:
--type FOURCC FinderInfo type written by Set File Info (default: $FINDER_TYPE)
--creator FOURCC FinderInfo creator written by Set File Info (default: $FINDER_CREATOR)
--mtime-epoch SECONDS AFP modify timestamp to write (default: $TIMESTAMP_EPOCH)
--readonly-user USER Optional user expected to lack Modify rights
--readonly-password PASS Password for --readonly-user
--readonly-no-password Use ncpfs -n for --readonly-user
--prepare-readonly-rights Prepare/revoke explicit rights around the negative test
--readonly-rights RIGHTS Rights granted by --prepare-readonly-rights (default: $READONLY_RIGHTS)
--no-log Do not tail/grep the server log
--stop-on-failure Stop after the first failing smoke command
-h, --help Show this help
@@ -65,6 +75,16 @@ while [ $# -gt 0 ]; do
FINDER_CREATOR=$2; shift 2 ;;
--mtime-epoch)
TIMESTAMP_EPOCH=$2; shift 2 ;;
--readonly-user)
READONLY_USER=$2; shift 2 ;;
--readonly-password)
READONLY_PASSWORD=$2; READONLY_NO_PASSWORD=0; shift 2 ;;
--readonly-no-password)
READONLY_PASSWORD=""; READONLY_NO_PASSWORD=1; shift ;;
--prepare-readonly-rights)
PREPARE_READONLY_RIGHTS=1; shift ;;
--readonly-rights)
READONLY_RIGHTS=$2; shift 2 ;;
--no-log)
CAPTURE_LOG=0; shift ;;
--stop-on-failure)
@@ -102,8 +122,14 @@ REPORT_TMP=$(mktemp "${TMPDIR:-/tmp}/mars-afp-smoke.XXXXXX")
LOG_TMP=$(mktemp "${TMPDIR:-/tmp}/mars-afp-log.XXXXXX")
LOG_PID=""
FAILURES=0
RIGHTS_PREPARED=0
cleanup() {
if [ "$RIGHTS_PREPARED" -eq 1 ] && [ -n "$READONLY_USER" ]; then
nwrevoke -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
-o "$READONLY_USER" -t 1 "$NETWARE_PATH" >/dev/null 2>&1 || true
RIGHTS_PREPARED=0
fi
if [ -n "$LOG_PID" ] && kill -0 "$LOG_PID" 2>/dev/null; then
kill "$LOG_PID" 2>/dev/null || true
wait "$LOG_PID" 2>/dev/null || true
@@ -140,6 +166,27 @@ run_cmd() {
fi
}
run_optional_cmd() {
local label=$1
local printable=$2
shift 2
section "$label"
emit "\$ $printable"
"$@" 2>&1 | tee -a "$REPORT_TMP"
local status=${PIPESTATUS[0]}
emit "[exit=$status]"
return "$status"
}
readonly_print_auth() {
if [ "$READONLY_NO_PASSWORD" -eq 1 ]; then
printf '%s' "-n"
else
printf '%s' "-P ******"
fi
}
finish_report() {
if [ "$CAPTURE_LOG" -eq 1 ]; then
sleep 1
@@ -175,6 +222,16 @@ emit "log=$LOG_FILE"
emit "finder_type=$FINDER_TYPE"
emit "finder_creator=$FINDER_CREATOR"
emit "mtime_epoch=$TIMESTAMP_EPOCH"
if [ -n "$READONLY_USER" ]; then
emit "readonly_user=$READONLY_USER"
if [ "$READONLY_NO_PASSWORD" -eq 1 ]; then
emit "readonly_auth=no-password"
else
emit "readonly_auth=password"
fi
emit "prepare_readonly_rights=$PREPARE_READONLY_RIGHTS"
emit "readonly_rights=$READONLY_RIGHTS"
fi
for helper in \
afp_entry_id_smoke \
@@ -205,6 +262,23 @@ fi
COMMON_PRINT="-S $SERVER -U $USER_NAME -P ******"
if [ -n "$READONLY_USER" ] && [ "$PREPARE_READONLY_RIGHTS" -eq 1 ]; then
# Establish a conservative explicit trustee assignment for the negative
# metadata-write probes. The final nwrevoke below removes only this
# explicit assignment and returns the object to its inherited rights.
run_optional_cmd \
"Prepare readonly trustee cleanup" \
"nwrevoke -S $SERVER -U $USER_NAME -P ****** -o $READONLY_USER -t 1 '$NETWARE_PATH'" \
nwrevoke -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
-o "$READONLY_USER" -t 1 "$NETWARE_PATH" || true
run_cmd \
"Prepare readonly trustee rights" \
"nwgrant -S $SERVER -U $USER_NAME -P ****** -o $READONLY_USER -t 1 -r '$READONLY_RIGHTS' '$NETWARE_PATH'" \
nwgrant -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
-o "$READONLY_USER" -t 1 -r "$READONLY_RIGHTS" "$NETWARE_PATH"
RIGHTS_PREPARED=1
fi
run_cmd \
"AFP Entry ID From Path Name" \
"./afp_entry_id_smoke $COMMON_PRINT '$NETWARE_PATH'" \
@@ -318,6 +392,43 @@ run_cmd \
"$SCRIPT_DIR/afp_set_file_info_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
--attributes-only --clear-archive "$NETWARE_PATH"
if [ -n "$READONLY_USER" ]; then
READONLY_PRINT="-S $SERVER -U $READONLY_USER $(readonly_print_auth)"
if [ "$READONLY_NO_PASSWORD" -eq 1 ]; then
READONLY_AUTH_ARGS=(-n)
else
READONLY_AUTH_ARGS=(-P "$READONLY_PASSWORD")
fi
run_cmd \
"AFP metadata Modify rights rejected: FinderInfo" \
"./afp_set_file_info_smoke --expect-completion 0x8c $READONLY_PRINT --finder-info-only --type '$FINDER_TYPE' --creator '$FINDER_CREATOR' '$NETWARE_PATH'" \
"$SCRIPT_DIR/afp_set_file_info_smoke" --expect-completion 0x8c \
-S "$SERVER" -U "$READONLY_USER" "${READONLY_AUTH_ARGS[@]}" \
--finder-info-only --type "$FINDER_TYPE" --creator "$FINDER_CREATOR" "$NETWARE_PATH"
run_cmd \
"AFP metadata Modify rights rejected: Invisible" \
"./afp_set_file_info_smoke --expect-completion 0x8c $READONLY_PRINT --attributes-only --invisible '$NETWARE_PATH'" \
"$SCRIPT_DIR/afp_set_file_info_smoke" --expect-completion 0x8c \
-S "$SERVER" -U "$READONLY_USER" "${READONLY_AUTH_ARGS[@]}" \
--attributes-only --invisible "$NETWARE_PATH"
run_cmd \
"AFP metadata Modify rights rejected: System" \
"./afp_set_file_info_smoke --expect-completion 0x8c $READONLY_PRINT --attributes-only --system '$NETWARE_PATH'" \
"$SCRIPT_DIR/afp_set_file_info_smoke" --expect-completion 0x8c \
-S "$SERVER" -U "$READONLY_USER" "${READONLY_AUTH_ARGS[@]}" \
--attributes-only --system "$NETWARE_PATH"
fi
if [ -n "$READONLY_USER" ] && [ "$PREPARE_READONLY_RIGHTS" -eq 1 ]; then
run_cmd \
"Restore readonly trustee assignment" \
"nwrevoke -S $SERVER -U $USER_NAME -P ****** -o $READONLY_USER -t 1 '$NETWARE_PATH'" \
nwrevoke -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
-o "$READONLY_USER" -t 1 "$NETWARE_PATH"
RIGHTS_PREPARED=0
fi
if command -v stat >/dev/null 2>&1; then
run_cmd \
"Linux stat: AFP Modify Timestamp" \