nwconn: route AFP set file timestamps via NetWare helper
All checks were successful
Source release / source-package (push) Successful in 51s
All checks were successful
Source release / source-package (push) Successful in 51s
Extend the conservative AFP Set File Information implementation to accept the file modification timestamp bitmap for path-backed file requests. The WebSDK/NWAFP Set File Information payload carries the timestamp in the same bitmap-ordered parameter stream as file attributes and FinderInfo, so the parser now admits the documented modification timestamp field while continuing to reject every other Set File Information bitmap bit. Do not implement a new AFP-specific timestamp backend. After resolving the raw VOL:-style smoke path to the effective mars_nwe volume and Unix path, convert the AFP/NW DOS date+time fields to time_t and route the update through the existing nw_utime_node() helper. That keeps trustee Modify-right checks and the established utime(2) fallback behavior shared with classic NetWare/NCP timestamp updates. Keep the implementation deliberately file-only and path-backed. Directory timestamps, create/access/backup timestamp fields, Entry-ID-only Set File Information, resource-fork semantics, DOS attribute mapping, Delete, Rename, Create, and Remove stay TODO so later patches can wire them to the existing NetWare helpers with focused smoke coverage. Update afp_set_file_info_smoke with --mtime-epoch and --timestamp-only, verify the written AFP date/time via the follow-up Get File Information record, and extend afp_smoke_suite.sh to run the timestamp probe and record the backing Linux stat output. The suite helper is already copied as a build target, so the new test is propagated into the build tree by the normal tests build. Tests: - git diff --check - bash -n tests/linux/afp_smoke_suite.sh - gcc -Iinclude -I/mnt/data/stubs -fsyntax-only tests/linux/afp_set_file_info_smoke.c
This commit is contained in:
@@ -489,13 +489,14 @@ NCP 0x2222/35/16 AFP 2.0 Set File Information
|
||||
```
|
||||
|
||||
The helper exercises two deliberately narrow write-safe AFP metadata subsets:
|
||||
the file FinderInfo bitmap (`0x0020`) and the file Attributes bitmap (`0x0001`)
|
||||
restricted to metadata-only file flags: Finder Invisible, System, and Backup.
|
||||
It sends path-backed raw `VOL:`-style requests, writes the 32-byte FinderInfo
|
||||
block to mars_nwe's private
|
||||
`org.mars-nwe.afp.finder-info` metadata key, writes the narrow AFP attribute word
|
||||
to `org.mars-nwe.afp.attributes`, and immediately verifies the updates through
|
||||
AFP 2.0 Get File Information. On Linux the source-level `org.mars-nwe.afp.*` name is stored via the
|
||||
the file FinderInfo bitmap (`0x0020`), the file Attributes bitmap (`0x0001`)
|
||||
restricted to metadata-only file flags: Finder Invisible, System, and Backup,
|
||||
and the file modification timestamp bitmap (`0x0010`). It sends path-backed
|
||||
raw `VOL:`-style requests, writes the 32-byte FinderInfo block to mars_nwe's
|
||||
private `org.mars-nwe.afp.finder-info` metadata key, writes the narrow AFP
|
||||
attribute word to `org.mars-nwe.afp.attributes`, routes modification timestamp
|
||||
writes through the existing NetWare timestamp helper, and immediately verifies
|
||||
the updates through AFP 2.0 Get File Information. On Linux the source-level `org.mars-nwe.afp.*` name is stored via the
|
||||
portable `user.` xattr namespace by mars_nwe's local xattr wrapper, the same
|
||||
pattern Netatalk uses for its `org.netatalk.*` metadata names.
|
||||
|
||||
@@ -614,15 +615,36 @@ AFP 2.0 Set File Information: vol=0 request_vol=0 entry=0x00000000 mask=0x0001 p
|
||||
AFP 2.0 Set File Information: vol=0 request_vol=0 entry=0x00000000 mask=0x0001 path='SYS:PUBLIC/pmdflts.ini' attributes attrs=0x0004
|
||||
AFP 2.0 Set File Information: vol=0 request_vol=0 entry=0x00000000 mask=0x0001 path='SYS:PUBLIC/pmdflts.ini' attributes attrs=0x8040
|
||||
```
|
||||
|
||||
Modification timestamp writes are deliberately routed through the existing
|
||||
NetWare `nw_utime_node()` path so trustee Modify rights and the established
|
||||
`utime(2)` fallback behavior stay shared with classic NCP timestamp updates.
|
||||
The first timestamp smoke uses a fixed Unix epoch that the helper converts into
|
||||
the AFP/NW DOS date+time fields and verifies through the follow-up Get File
|
||||
Information response:
|
||||
|
||||
```sh
|
||||
./tests/linux/afp_set_file_info_smoke \
|
||||
-S MARS -U SUPERVISOR -P secret \
|
||||
--timestamp-only --mtime-epoch 1700000000 \
|
||||
SYS:PUBLIC/pmdflts.ini
|
||||
|
||||
stat -c 'mtime_epoch=%Y mtime=%y' /var/mars_nwe/SYS/public/pmdflts.ini
|
||||
```
|
||||
|
||||
This currently remains file-only and path-backed, just like the FinderInfo and
|
||||
metadata-attribute Set File Information probes; directory timestamps and
|
||||
Entry-ID-only Set File Information are left for later resolver work.
|
||||
|
||||
The legacy `0x09` endpoint is deliberately routed through the same narrow
|
||||
metadata-only implementation as AFP 2.0 `0x10`; it does not add create, rename,
|
||||
delete, timestamp, or fork-write semantics.
|
||||
implementation as AFP 2.0 `0x10`; it does not add create, rename, delete,
|
||||
directory timestamp, or fork-write semantics.
|
||||
|
||||
All other Set File Information bitmap bits and AFP attribute bits, including
|
||||
NoWrite, NoRename, NoDelete, NoCopy, and the computed data/resource-fork-open
|
||||
flags, are intentionally rejected for now. That keeps timestamp,
|
||||
DOS/NetWare mode-bit mapping, enforcement, resource-fork, and Entry-ID-only
|
||||
write semantics out of this metadata-only smoke path.
|
||||
flags, are intentionally rejected for now. That keeps create/access/backup
|
||||
timestamps, DOS/NetWare mode-bit mapping, enforcement, resource-fork, and
|
||||
Entry-ID-only write semantics out of this conservative smoke path.
|
||||
|
||||
If the server was built without the optional Netatalk/libatalk backend, use
|
||||
`--allow-invalid-namespace` for the expected negative test. Use
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <ncp/nwcalls.h>
|
||||
#include <ncp/ncplib.h>
|
||||
@@ -23,6 +24,7 @@
|
||||
#define AFP_SET_FILE_INFORMATION 0x09
|
||||
#define AFP20_SET_FILE_INFORMATION 0x10
|
||||
#define AFP_ATTRIBUTES_MASK 0x0001
|
||||
#define AFP_MODIFY_DATE_MASK 0x0010
|
||||
#define AFP_FINDER_INFO_MASK 0x0020
|
||||
#define AFP_ATTR_INVISIBLE 0x0001
|
||||
#define AFP_ATTR_SYSTEM 0x0004
|
||||
@@ -39,7 +41,7 @@ static void usage(const char *prog)
|
||||
"Usage: %s [--afp09|--afp20] [--allow-invalid-namespace] [--allow-invalid-path] "
|
||||
"[--volume N] [--entry-id ID] [--type FOUR] [--creator FOUR] "
|
||||
"[--invisible|--clear-invisible|--system|--clear-system|--backup|--clear-backup] "
|
||||
"[--finder-info-only|--attributes-only] "
|
||||
"[--mtime-epoch SECONDS] [--finder-info-only|--attributes-only|--timestamp-only] "
|
||||
"[ncpfs options] PATH\n"
|
||||
"\n"
|
||||
"ncpfs options are parsed by ncp_initialize(), for example:\n"
|
||||
@@ -49,8 +51,9 @@ static void usage(const char *prog)
|
||||
" %s -S MARS -U SUPERVISOR -P secret --type TEXT --creator MARS SYS:PUBLIC/pmdflts.ini\n"
|
||||
" %s -S MARS -U SUPERVISOR -P secret --invisible --attributes-only SYS:PUBLIC/pmdflts.ini\n"
|
||||
" %s -S MARS -U SUPERVISOR -P secret --backup --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, prog, prog, prog, prog, prog);
|
||||
}
|
||||
|
||||
static int parse_u32(const char *text, uint32_t *value)
|
||||
@@ -103,6 +106,27 @@ static int copy_fourcc(const char *text, uint8_t out[4])
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int epoch_to_nw_time(uint32_t epoch, uint8_t out[4])
|
||||
{
|
||||
time_t t = (time_t)epoch;
|
||||
struct tm *tmv = localtime(&t);
|
||||
uint16_t date;
|
||||
uint16_t timev;
|
||||
|
||||
if (!tmv || tmv->tm_year + 1900 < 1980 || tmv->tm_year + 1900 > 2107)
|
||||
return -1;
|
||||
|
||||
date = (uint16_t)(((tmv->tm_year + 1900 - 1980) << 9) |
|
||||
((tmv->tm_mon + 1) << 5) |
|
||||
tmv->tm_mday);
|
||||
timev = (uint16_t)((tmv->tm_hour << 11) |
|
||||
(tmv->tm_min << 5) |
|
||||
(tmv->tm_sec / 2));
|
||||
cpu_to_be16(date, out + 0);
|
||||
cpu_to_be16(timev, out + 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NWCCODE afp_get_file_info(NWCONN_HANDLE conn, const char *path,
|
||||
uint32_t volume_number, uint32_t entry_id,
|
||||
uint8_t reply_buf[AFP_REPLY_LEN])
|
||||
@@ -144,8 +168,10 @@ int main(int argc, char **argv)
|
||||
uint16_t attr_request = 0;
|
||||
uint16_t attr_verify_mask = 0;
|
||||
uint16_t attr_expected = 0;
|
||||
uint8_t modify_time[4];
|
||||
int have_modify_time = 0;
|
||||
uint8_t verify_buf[AFP_REPLY_LEN];
|
||||
uint8_t request[1 + 4 + 2 + 1 + 255 + 1 + 2 + 1 + 32];
|
||||
uint8_t request[1 + 4 + 2 + 1 + 255 + 1 + 2 + 4 + 1 + 32];
|
||||
size_t path_len;
|
||||
size_t afp_data_off;
|
||||
size_t data_off;
|
||||
@@ -153,6 +179,7 @@ int main(int argc, char **argv)
|
||||
NWCCODE err;
|
||||
|
||||
memset(finder_info, 0, sizeof(finder_info));
|
||||
memset(modify_time, 0, sizeof(modify_time));
|
||||
memcpy(finder_info + 0, "TEXT", 4);
|
||||
memcpy(finder_info + 4, "MARS", 4);
|
||||
|
||||
@@ -219,10 +246,22 @@ int main(int argc, char **argv)
|
||||
attr_request = AFP_ATTR_BACKUP;
|
||||
attr_verify_mask = AFP_ATTR_BACKUP;
|
||||
attr_expected = 0;
|
||||
} else if (!strcmp(argv[i], "--mtime-epoch")) {
|
||||
uint32_t epoch;
|
||||
if (++i >= argc || parse_u32(argv[i], &epoch) || epoch_to_nw_time(epoch, modify_time)) {
|
||||
fprintf(stderr, "invalid --mtime-epoch value\n");
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
request_mask |= AFP_MODIFY_DATE_MASK;
|
||||
have_modify_time = 1;
|
||||
} else if (!strcmp(argv[i], "--attributes-only")) {
|
||||
request_mask &= ~AFP_FINDER_INFO_MASK;
|
||||
request_mask &= ~(AFP_FINDER_INFO_MASK | AFP_MODIFY_DATE_MASK);
|
||||
} else if (!strcmp(argv[i], "--finder-info-only")) {
|
||||
request_mask &= ~AFP_ATTRIBUTES_MASK;
|
||||
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_MODIFY_DATE_MASK);
|
||||
} else if (!strcmp(argv[i], "--timestamp-only")) {
|
||||
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_FINDER_INFO_MASK);
|
||||
request_mask |= AFP_MODIFY_DATE_MASK;
|
||||
} else if (!strcmp(argv[i], "--type")) {
|
||||
if (++i >= argc || copy_fourcc(argv[i], finder_info + 0)) {
|
||||
fprintf(stderr, "invalid --type value, expected four characters\n");
|
||||
@@ -285,7 +324,17 @@ int main(int argc, char **argv)
|
||||
cpu_to_be16(attr_request, request + data_off);
|
||||
data_off += 2;
|
||||
}
|
||||
if ((request_mask & AFP_FINDER_INFO_MASK) && (request_mask & AFP_ATTRIBUTES_MASK) &&
|
||||
if (request_mask & AFP_MODIFY_DATE_MASK) {
|
||||
if (!have_modify_time) {
|
||||
fprintf(stderr, "--timestamp-only requires --mtime-epoch\n");
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
memcpy(request + data_off, modify_time, sizeof(modify_time));
|
||||
data_off += sizeof(modify_time);
|
||||
}
|
||||
if ((request_mask & AFP_FINDER_INFO_MASK) &&
|
||||
(request_mask & (AFP_ATTRIBUTES_MASK | AFP_MODIFY_DATE_MASK)) &&
|
||||
((data_off + 1) & 1))
|
||||
data_off++;
|
||||
if (request_mask & AFP_FINDER_INFO_MASK) {
|
||||
@@ -334,6 +383,16 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((request_mask & AFP_MODIFY_DATE_MASK) &&
|
||||
memcmp(verify_buf + 24, modify_time, sizeof(modify_time))) {
|
||||
fprintf(stderr,
|
||||
"AFP Set File Information timestamp verify mismatch: path=%s got=0x%04x%04x expected=0x%04x%04x\n",
|
||||
path, be16_to_cpu(verify_buf + 24), be16_to_cpu(verify_buf + 26),
|
||||
be16_to_cpu(modify_time + 0), be16_to_cpu(modify_time + 2));
|
||||
ncp_close(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((request_mask & AFP_ATTRIBUTES_MASK) &&
|
||||
((be16_to_cpu(verify_buf + 8) & attr_verify_mask) != attr_expected)) {
|
||||
fprintf(stderr,
|
||||
@@ -343,8 +402,9 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("AFP Set File Info subfunction=0x%02x path=%s bitmap=0x%04x attrs=0x%04x finder_type=%.4s finder_creator=%.4s entry_id=0x%08x verified\n",
|
||||
printf("AFP Set File Info subfunction=0x%02x path=%s bitmap=0x%04x attrs=0x%04x modify=0x%04x%04x finder_type=%.4s finder_creator=%.4s entry_id=0x%08x verified\n",
|
||||
set_subfunction, path, request_mask, be16_to_cpu(verify_buf + 8),
|
||||
be16_to_cpu(verify_buf + 24), be16_to_cpu(verify_buf + 26),
|
||||
finder_info + 0, finder_info + 4, be32_to_cpu(verify_buf + 0));
|
||||
|
||||
ncp_close(conn);
|
||||
|
||||
@@ -17,6 +17,7 @@ LOG_FILE="/var/log/mars_nwe/nw.log"
|
||||
OUT_FILE=""
|
||||
FINDER_TYPE="TEXT"
|
||||
FINDER_CREATOR="MARS"
|
||||
TIMESTAMP_EPOCH="1700000000"
|
||||
KEEP_GOING=1
|
||||
CAPTURE_LOG=1
|
||||
|
||||
@@ -31,6 +32,7 @@ Options:
|
||||
--out FILE Write the complete report to FILE as well as stdout
|
||||
--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)
|
||||
--no-log Do not tail/grep the server log
|
||||
--stop-on-failure Stop after the first failing smoke command
|
||||
-h, --help Show this help
|
||||
@@ -61,6 +63,8 @@ while [ $# -gt 0 ]; do
|
||||
FINDER_TYPE=$2; shift 2 ;;
|
||||
--creator)
|
||||
FINDER_CREATOR=$2; shift 2 ;;
|
||||
--mtime-epoch)
|
||||
TIMESTAMP_EPOCH=$2; shift 2 ;;
|
||||
--no-log)
|
||||
CAPTURE_LOG=0; shift ;;
|
||||
--stop-on-failure)
|
||||
@@ -170,6 +174,7 @@ emit "unix_path=$UNIX_PATH"
|
||||
emit "log=$LOG_FILE"
|
||||
emit "finder_type=$FINDER_TYPE"
|
||||
emit "finder_creator=$FINDER_CREATOR"
|
||||
emit "mtime_epoch=$TIMESTAMP_EPOCH"
|
||||
|
||||
for helper in \
|
||||
afp_entry_id_smoke \
|
||||
@@ -265,6 +270,12 @@ run_cmd \
|
||||
"$SCRIPT_DIR/afp_set_file_info_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
|
||||
--afp09 --attributes-only --clear-invisible "$NETWARE_PATH"
|
||||
|
||||
run_cmd \
|
||||
"AFP Set File Information Modify Timestamp" \
|
||||
"./afp_set_file_info_smoke $COMMON_PRINT --timestamp-only --mtime-epoch '$TIMESTAMP_EPOCH' '$NETWARE_PATH'" \
|
||||
"$SCRIPT_DIR/afp_set_file_info_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
|
||||
--timestamp-only --mtime-epoch "$TIMESTAMP_EPOCH" "$NETWARE_PATH"
|
||||
|
||||
run_cmd \
|
||||
"AFP Set File Information System" \
|
||||
"./afp_set_file_info_smoke $COMMON_PRINT --attributes-only --system '$NETWARE_PATH'" \
|
||||
@@ -289,6 +300,13 @@ run_cmd \
|
||||
"$SCRIPT_DIR/afp_set_file_info_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
|
||||
--attributes-only --clear-backup "$NETWARE_PATH"
|
||||
|
||||
if command -v stat >/dev/null 2>&1; then
|
||||
run_cmd \
|
||||
"Linux stat: AFP Modify Timestamp" \
|
||||
"stat -c 'mtime_epoch=%Y mtime=%y' '$UNIX_PATH'" \
|
||||
stat -c 'mtime_epoch=%Y mtime=%y' "$UNIX_PATH"
|
||||
fi
|
||||
|
||||
if command -v getfattr >/dev/null 2>&1; then
|
||||
run_cmd \
|
||||
"Linux xattr: AFP FinderInfo" \
|
||||
|
||||
Reference in New Issue
Block a user