diff --git a/TODO.md b/TODO.md index acbea30..2fd4cc3 100644 --- a/TODO.md +++ b/TODO.md @@ -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. diff --git a/src/nwatalk.c b/src/nwatalk.c index 85aeaa6..d4c5e9d 100644 --- a/src/nwatalk.c +++ b/src/nwatalk.c @@ -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); } diff --git a/src/nwconn.c b/src/nwconn.c index b837835..f4af0e8 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -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 diff --git a/tests/linux/README.md b/tests/linux/README.md index a8cda27..2ce9bcf 100644 --- a/tests/linux/README.md +++ b/tests/linux/README.md @@ -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 diff --git a/tests/linux/afp_set_file_info_smoke.c b/tests/linux/afp_set_file_info_smoke.c index 3fb9603..6af1dde 100644 --- a/tests/linux/afp_set_file_info_smoke.c +++ b/tests/linux/afp_set_file_info_smoke.c @@ -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);