nwconn: persist additional AFP metadata attributes
All checks were successful
Source release / source-package (push) Successful in 49s
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:
15
TODO.md
15
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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
11
src/nwconn.c
11
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user