nwconn: persist additional AFP metadata attributes
All checks were successful
Source release / source-package (push) Successful in 49s

Extend the conservative AFP 2.0 Set File Information smoke path to accept the metadata-only System and Backup file attribute bits alongside the already-supported Finder Invisible bit.

The WebSDK/NWAFP Set File Information request uses the file Attributes bitmap to pass a set/clear attribute word. Netatalk stores several AFP file attributes in AppleDouble metadata while computing open-fork state dynamically and leaving enforcement-sensitive bits to the file/fork paths. Mirror only the low-risk mars_nwe subset here: persist Invisible, System, and Backup in the private org.mars-nwe.afp.attributes xattr, and keep NoWrite, NoRename, NoDelete, NoCopy, data-fork-open, resource-fork-open, timestamps, resource forks, and Entry-ID-only write semantics rejected until they have deliberate enforcement and backend design.

The xattr payload remains versioned and unchanged. Reads now expose the three supported metadata bits through Get/Scan File Information, and writes preserve the existing set/clear semantics over the supported mask.

Update the Linux Set File Information smoke helper with --system/--clear-system and --backup/--clear-backup options, while keeping the smoke-suite default unchanged. Document the expected Linux xattr forms 0x01000004 and 0x01000040 for those optional probes.

Tests: git diff --check; gcc -Iinclude -I/mnt/data/stubs -fsyntax-only tests/linux/afp_set_file_info_smoke.c.
This commit is contained in:
ChatGPT
2026-05-30 10:53:34 +00:00
committed by Mario Fetka
parent c57af8c7dc
commit cfd036e54c
5 changed files with 74 additions and 20 deletions

15
TODO.md
View File

@@ -248,7 +248,8 @@ Current status:
CNID/base-ID lookup exists.
- `AFP 2.0 Set File Information` is implemented only for path-backed file
metadata smoke writes: the FinderInfo bitmap (`0x0020`) and the AFP
Attributes bitmap (`0x0001`) restricted to the Finder Invisible bit. Linux
Attributes bitmap (`0x0001`) restricted to metadata-only file bits: Finder
Invisible, System, and Backup. Linux
smoke coverage exists in `tests/linux/afp_set_file_info_smoke`; runtime
FinderInfo coverage is green for `SYS:PUBLIC/pmdflts.ini` with Finder type
`TEXT` and creator `MARS`. The helper writes 32 bytes of FinderInfo to
@@ -265,8 +266,9 @@ Current status:
`fallback` marker on that first verification Get File Information diagnostic
describes the entry-id origin, not the FinderInfo write result. Follow-up
probes should read the cached mars_nwe entry id and omit the fallback marker.
All other Set File Information bits remain rejected until their write
semantics are explicitly designed.
System and Backup are now supported by the same narrow xattr-only attribute
path. All other Set File Information bits and AFP attribute bits remain
rejected until their write/enforcement semantics are explicitly designed.
- The AFP dispatcher now decodes the WebSDK/NWAFP subfunction number in
diagnostics so real client probes can be mapped to the corresponding AFP
call before implementation work starts.
@@ -304,9 +306,10 @@ Follow-up:
`0x7b9c42e1` Entry ID. AFP directory-scan continuation remains directory
iteration based: `last_seen` skips past the previously returned object, but
the next returned Entry ID is not required to be numerically greater than the
continuation token. FinderInfo and the Finder Invisible AFP attribute now have deliberately narrow
write paths through AFP 2.0 Set File Information; CNID allocation and broader
AFP metadata writes still need a deliberate write-safe design.
continuation token. FinderInfo plus the Finder Invisible/System/Backup AFP attributes now have
deliberately narrow write paths through AFP 2.0 Set File Information; CNID
allocation and broader AFP metadata writes still need a deliberate write-safe
design.
- Put additional future mars_nwe-owned AFP metadata under `org.mars-nwe.afp.*`
(or a compact `org.mars-nwe.afp.metadata` record) and keep Netatalk-owned
metadata under Netatalk's own `org.netatalk.*` keys.

View File

@@ -23,7 +23,11 @@
#define MARS_NWE_AFP_ATTRIBUTES_VERSION 1
#define NWATALK_AFP_ATTR_INVISIBLE 0x0001
#define NWATALK_AFP_ATTR_SYSTEM 0x0004
#define NWATALK_AFP_ATTR_BACKUP 0x0040
#define NWATALK_AFP_ATTR_SETCLR 0x8000
#define NWATALK_AFP_ATTR_STORED_MASK \
(NWATALK_AFP_ATTR_INVISIBLE | NWATALK_AFP_ATTR_SYSTEM | NWATALK_AFP_ATTR_BACKUP)
typedef struct {
uint8 version;
@@ -185,7 +189,7 @@ int nwatalk_get_afp_attributes(const char *path, uint16 *attributes)
len = mars_nwe_getxattr(path, MARS_NWE_AFP_ATTRIBUTES_XATTR,
&d, sizeof(d));
if (len == sizeof(d) && d.version == MARS_NWE_AFP_ATTRIBUTES_VERSION) {
*attributes = GET_BE16(d.attributes) & NWATALK_AFP_ATTR_INVISIBLE;
*attributes = GET_BE16(d.attributes) & NWATALK_AFP_ATTR_STORED_MASK;
return(0);
}
#else
@@ -205,7 +209,7 @@ int nwatalk_set_afp_attributes(const char *path, uint16 attributes)
if (!path || !*path) return(-0x9c);
if (requested & ~NWATALK_AFP_ATTR_INVISIBLE) {
if (requested & ~NWATALK_AFP_ATTR_STORED_MASK) {
XDPRINTF((3,0,"AFP attributes xattr write rejected for %s attrs=0x%04x", path, attributes));
return(-0x9c);
}

View File

@@ -1096,7 +1096,11 @@ static int afp_get_file_information(uint8 *afp_req, int afp_len,
#define AFP_FILE_BITMAP_ATTRIBUTES 0x0001
#define AFP_FILE_BITMAP_FINDER_INFO 0x0020
#define AFP_ATTR_INVISIBLE 0x0001
#define AFP_ATTR_SYSTEM 0x0004
#define AFP_ATTR_BACKUP 0x0040
#define AFP_ATTR_SETCLR 0x8000
#define AFP_ATTR_STORED_MASK \
(AFP_ATTR_INVISIBLE | AFP_ATTR_SYSTEM | AFP_ATTR_BACKUP)
static int afp_set_file_information(uint8 *afp_req, int afp_len,
const char *call_name)
@@ -1173,7 +1177,7 @@ static int afp_set_file_information(uint8 *afp_req, int afp_len,
if (request_mask & AFP_FILE_BITMAP_ATTRIBUTES) {
uint16 requested_attrs = GET_BE16(afp_req + data_off);
uint16 requested_bits = requested_attrs & ~AFP_ATTR_SETCLR;
if (requested_bits & ~AFP_ATTR_INVISIBLE) {
if (requested_bits & ~AFP_ATTR_STORED_MASK) {
XDPRINTF((2,0, "%s rejected: unsupported AFP attributes attrs=0x%04x path='%s'",
call_name, requested_attrs, visable_data(afp_req + 9, path_len)));
return(-0x9c);
@@ -3358,8 +3362,9 @@ static int handle_ncp_serv(void)
* effective rights. Then expose
* the read-only AFP Get File Information query for the same
* SYS:-style path inputs. AFP 2.0 Set File Information
* accepts only the FinderInfo bitmap as a metadata-only
* xattr write smoke path. AFP 2.0 Get File Information
* accepts only FinderInfo plus the
* metadata-only Invisible/System/Backup file attributes as
* xattr write smoke paths. AFP 2.0 Get File Information
* uses the same request/reply layout for this read-only
* path-backed subset, so route it through the same helper
* until persistent entry-id lookup and richer AFP 2.0

View File

@@ -449,8 +449,9 @@ 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 the Finder Invisible bit. It sends path-backed raw `VOL:`-style
requests, writes the 32-byte FinderInfo block to mars_nwe's private
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
@@ -522,10 +523,30 @@ user.org.mars-nwe.afp.attributes=0x01000001
```
Use `--clear-invisible --attributes-only` to clear that bit; the same xattr then
stores `0x01000000`. All other Set File Information bitmap bits and all other
AFP attribute bits are intentionally rejected for now. That keeps timestamp,
DOS/NetWare mode-bit mapping, resource-fork, and Entry-ID-only write semantics
out of this metadata-only smoke path.
stores `0x01000000`. The same helper can exercise the two additional
metadata-only file attribute bits that Netatalk treats as stored AFP file
attributes and that do not require immediate Unix mode-bit or DOS/NetWare
attribute mutation:
```sh
./tests/linux/afp_set_file_info_smoke \
-S MARS -U SUPERVISOR -P secret \
--attributes-only --system \
SYS:PUBLIC/pmdflts.ini
./tests/linux/afp_set_file_info_smoke \
-S MARS -U SUPERVISOR -P secret \
--attributes-only --backup \
SYS:PUBLIC/pmdflts.ini
```
For System the xattr value stores `0x01000004`; for Backup it stores
`0x01000040`. Use `--clear-system` and `--clear-backup` to remove those bits.
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.
If the server was built without the optional Netatalk/libatalk backend, use
`--allow-invalid-namespace` for the expected negative test. Use

View File

@@ -24,7 +24,10 @@
#define AFP_ATTRIBUTES_MASK 0x0001
#define AFP_FINDER_INFO_MASK 0x0020
#define AFP_ATTR_INVISIBLE 0x0001
#define AFP_ATTR_SYSTEM 0x0004
#define AFP_ATTR_BACKUP 0x0040
#define AFP_ATTR_SETCLR 0x8000
#define AFP_ATTR_STORED_MASK (AFP_ATTR_INVISIBLE | AFP_ATTR_SYSTEM | AFP_ATTR_BACKUP)
#define AFP_REPLY_LEN 120
#define NWE_INVALID_NAMESPACE 0xbf
#define NWE_INVALID_PATH 0x9c
@@ -34,7 +37,8 @@ static void usage(const char *prog)
fprintf(stderr,
"Usage: %s [--allow-invalid-namespace] [--allow-invalid-path] "
"[--volume N] [--entry-id ID] [--type FOUR] [--creator FOUR] "
"[--invisible|--clear-invisible] [--finder-info-only|--attributes-only] "
"[--invisible|--clear-invisible|--system|--clear-system|--backup|--clear-backup] "
"[--finder-info-only|--attributes-only] "
"[ncpfs options] PATH\n"
"\n"
"ncpfs options are parsed by ncp_initialize(), for example:\n"
@@ -43,8 +47,9 @@ static void usage(const char *prog)
"Examples:\n"
" %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 --allow-invalid-namespace -S MARS SYS:PUBLIC/pmdflts.ini\n",
prog, prog, prog, prog);
prog, prog, prog, prog, prog);
}
static int parse_u32(const char *text, uint32_t *value)
@@ -185,6 +190,22 @@ int main(int argc, char **argv)
request_mask |= AFP_ATTRIBUTES_MASK;
attr_request = AFP_ATTR_INVISIBLE;
attr_expected = 0;
} else if (!strcmp(argv[i], "--system")) {
request_mask |= AFP_ATTRIBUTES_MASK;
attr_request = AFP_ATTR_SETCLR | AFP_ATTR_SYSTEM;
attr_expected = AFP_ATTR_SYSTEM;
} else if (!strcmp(argv[i], "--clear-system")) {
request_mask |= AFP_ATTRIBUTES_MASK;
attr_request = AFP_ATTR_SYSTEM;
attr_expected = 0;
} else if (!strcmp(argv[i], "--backup")) {
request_mask |= AFP_ATTRIBUTES_MASK;
attr_request = AFP_ATTR_SETCLR | AFP_ATTR_BACKUP;
attr_expected = AFP_ATTR_BACKUP;
} else if (!strcmp(argv[i], "--clear-backup")) {
request_mask |= AFP_ATTRIBUTES_MASK;
attr_request = AFP_ATTR_BACKUP;
attr_expected = 0;
} else if (!strcmp(argv[i], "--attributes-only")) {
request_mask &= ~AFP_FINDER_INFO_MASK;
} else if (!strcmp(argv[i], "--finder-info-only")) {
@@ -301,7 +322,7 @@ int main(int argc, char **argv)
}
if ((request_mask & AFP_ATTRIBUTES_MASK) &&
((be16_to_cpu(verify_buf + 8) & AFP_ATTR_INVISIBLE) != attr_expected)) {
((be16_to_cpu(verify_buf + 8) & AFP_ATTR_STORED_MASK) != attr_expected)) {
fprintf(stderr,
"AFP Set File Information attr verify mismatch: path=%s attrs=0x%04x expected_invisible=0x%04x\n",
path, be16_to_cpu(verify_buf + 8), attr_expected);