nwatalk: cache AFP fallback entry ids in xattrs
All checks were successful
Source release / source-package (push) Successful in 48s
All checks were successful
Source release / source-package (push) Successful in 48s
The AFP smoke endpoints can now read mars_nwe-owned entry ids from the versioned org.mars-nwe.afp.entry-id xattr, but a newly discovered file still had to fall back to the temporary stat-derived id on every request until a real CNID allocator exists. Preserve the existing WebSDK/NWAFP response semantics while making that fallback sticky: when Get Entry ID, Get File Information, or Scan File Information has no mars_nwe xattr and no Netatalk/libatalk AppleDouble/CNID id, derive the existing compatibility id and cache it through nwatalk_set_entry_id(). The first request still logs fallback so diagnostics remain honest about the id origin; subsequent requests should read the xattr directly and avoid re-entering the stat fallback path. Keep the write narrowly scoped to mars_nwe's private AFP metadata namespace. The payload is versioned, big-endian, and stored through the nwxattr helper, so Linux persists it as user.org.mars-nwe.afp.entry-id while source-level code continues to use the Netatalk-style org.mars-nwe.afp.entry-id name. This does not implement CNID allocation, parent-id lookup, entry-id-only resolution, FinderInfo mutation beyond the existing smoke path, or resource-fork semantics. Tests: - git diff --check - cmake --build build-xattr-off --target nwconn with ENABLE_NETATALK_LIBATALK=OFF - cmake --build build-xattr-on --target nwconn with ENABLE_NETATALK_LIBATALK=ON against Netatalk 4.4.3 headers plus local link stubs
This commit is contained in:
26
TODO.md
26
TODO.md
@@ -251,11 +251,14 @@ Current status:
|
||||
`tests/linux/afp_set_file_info_smoke`; runtime coverage is green for
|
||||
`SYS:PUBLIC/pmdflts.ini` with Finder type `TEXT` and creator `MARS`. The
|
||||
helper writes 32 bytes of FinderInfo to `org.mars-nwe.afp.finder-info` and
|
||||
verifies the result through AFP 2.0 Get File Information. A `fallback` marker
|
||||
on the verification Get File Information diagnostic still describes the
|
||||
stat-derived entry-id source, not the FinderInfo write result. All other Set
|
||||
File Information bits remain rejected until their write semantics are
|
||||
explicitly designed.
|
||||
verifies the result through AFP 2.0 Get File Information. The first
|
||||
stat-derived AFP entry id for a path is now cached in the versioned
|
||||
`org.mars-nwe.afp.entry-id` xattr; a `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.
|
||||
- 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.
|
||||
@@ -282,14 +285,15 @@ Follow-up:
|
||||
- Keep returning invalid namespace for AFP calls that still lack a real per-volume Mac
|
||||
namespace/AFP metadata layer. Do not return success for additional AFP calls without
|
||||
data/resource fork and Finder Info semantics.
|
||||
- Replace the temporary stat-derived AFP entry-id fallback with a persistent
|
||||
CNID/directory-id mapping once the libatalk/CNID backend is integrated.
|
||||
- Replace the compatibility stat-derived AFP entry-id generator with a real
|
||||
CNID/directory-id allocator once the libatalk/CNID backend is integrated.
|
||||
- mars_nwe-owned AFP entry ids are probed first from the versioned
|
||||
`org.mars-nwe.afp.entry-id` xattr before consulting Netatalk/libatalk
|
||||
AppleDouble/CNID metadata and, finally, the stat-derived fallback.
|
||||
FinderInfo now has a deliberately narrow write path through AFP 2.0 Set File
|
||||
Information; CNID allocation and broader AFP metadata writes still need a
|
||||
deliberate write-safe design.
|
||||
AppleDouble/CNID metadata. If neither source has an id, mars_nwe derives the
|
||||
existing stat-compatible id and caches it in that xattr so subsequent probes
|
||||
can use persistent mars_nwe metadata. FinderInfo now has a deliberately narrow
|
||||
write path 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.
|
||||
|
||||
@@ -12,5 +12,6 @@ int nwatalk_set_finder_info(const char *path, const uint8 *finder_info,
|
||||
int finder_info_len);
|
||||
int nwatalk_get_resource_fork_size(const char *path, uint32 *resource_size);
|
||||
int nwatalk_get_entry_id(const char *path, uint32 *entry_id);
|
||||
int nwatalk_set_entry_id(const char *path, uint32 entry_id);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -156,6 +156,36 @@ int nwatalk_set_finder_info(const char *path, const uint8 *finder_info,
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
int nwatalk_set_entry_id(const char *path, uint32 entry_id)
|
||||
{
|
||||
#if XATTR_SUPPORT
|
||||
MARS_NWE_AFP_ENTRY_ID_XATTR_DATA d;
|
||||
|
||||
if (!path || !*path) return(-0x9c);
|
||||
entry_id &= 0x7fffffffU;
|
||||
if (!entry_id) return(-0x9c);
|
||||
|
||||
memset(&d, 0, sizeof(d));
|
||||
d.version = MARS_NWE_AFP_ENTRY_ID_VERSION;
|
||||
U32_TO_BE32(entry_id, d.entry_id);
|
||||
|
||||
if (mars_nwe_setxattr(path, MARS_NWE_AFP_ENTRY_ID_XATTR,
|
||||
&d, sizeof(d), 0)) {
|
||||
int err = errno;
|
||||
XDPRINTF((5,0,"AFP entry-id xattr write ignored for %s entry=0x%08x errno=%d",
|
||||
path, entry_id, err));
|
||||
return(-0x8c);
|
||||
}
|
||||
|
||||
return(0);
|
||||
#else
|
||||
(void)path;
|
||||
(void)entry_id;
|
||||
return(-0xbf);
|
||||
#endif
|
||||
}
|
||||
|
||||
int nwatalk_get_resource_fork_size(const char *path, uint32 *resource_size)
|
||||
{
|
||||
#if NETATALK_SUPPORT
|
||||
|
||||
61
src/nwconn.c
61
src/nwconn.c
@@ -507,6 +507,34 @@ static uint32 afp_fallback_entry_id(int volume, const struct stat *stb)
|
||||
}
|
||||
|
||||
|
||||
|
||||
static uint32 afp_get_or_create_entry_id(const char *unixname, int volume,
|
||||
const struct stat *stb,
|
||||
int *fallback_out)
|
||||
/*
|
||||
* Return a persistent mars_nwe AFP entry id when available. If neither the
|
||||
* mars_nwe xattr nor Netatalk/libatalk metadata contains an id yet, derive the
|
||||
* existing stat-backed compatibility id and cache that id in mars_nwe's private
|
||||
* AFP xattr namespace. The first caller still records fallback diagnostics so
|
||||
* the log remains honest about the id origin; follow-up requests should read the
|
||||
* xattr directly and no longer need the temporary stat derivation path.
|
||||
*/
|
||||
{
|
||||
uint32 entry_id = 0;
|
||||
int result;
|
||||
|
||||
result = nwatalk_get_entry_id(unixname, &entry_id);
|
||||
if (!result && entry_id) {
|
||||
if (fallback_out) *fallback_out = 0;
|
||||
return(entry_id);
|
||||
}
|
||||
|
||||
entry_id = afp_fallback_entry_id(volume, stb);
|
||||
if (fallback_out) *fallback_out = 1;
|
||||
(void)nwatalk_set_entry_id(unixname, entry_id);
|
||||
return(entry_id);
|
||||
}
|
||||
|
||||
static int afp_get_entry_id_from_name(uint8 *afp_req, int afp_len,
|
||||
uint8 *response)
|
||||
{
|
||||
@@ -561,15 +589,13 @@ static int afp_get_entry_id_from_name(uint8 *afp_req, int afp_len,
|
||||
return(-0x9c); /* Invalid Path */
|
||||
}
|
||||
|
||||
result = nwatalk_get_entry_id(unixname, &entry_id);
|
||||
if (result < 0 || !entry_id)
|
||||
entry_id = afp_fallback_entry_id(volume, &stbuff);
|
||||
entry_id = afp_get_or_create_entry_id(unixname, volume, &stbuff, &result);
|
||||
|
||||
U32_TO_BE32(entry_id, response);
|
||||
XDPRINTF((3,0, "AFP Get Entry ID From Name: vol=%d entry=0x%08x path='%s' reply_entry=0x%08x%s",
|
||||
(int)volume_number, request_entry_id,
|
||||
visable_data(afp_req + 7, path_len), entry_id,
|
||||
(result < 0) ? " fallback" : ""));
|
||||
result ? " fallback" : ""));
|
||||
return(4);
|
||||
}
|
||||
|
||||
@@ -612,9 +638,7 @@ static int afp_get_entry_id_from_netware_handle(uint8 *afp_req, int afp_len,
|
||||
volume = afp_volume_from_unixname((char *)unixname);
|
||||
if (volume < 0) volume = 0;
|
||||
|
||||
result = nwatalk_get_entry_id((char *)unixname, &entry_id);
|
||||
if (result < 0 || !entry_id)
|
||||
entry_id = afp_fallback_entry_id(volume, &stbuff);
|
||||
entry_id = afp_get_or_create_entry_id((char *)unixname, volume, &stbuff, &result);
|
||||
|
||||
response[0] = (uint8)volume;
|
||||
U32_TO_BE32(entry_id, response + 1);
|
||||
@@ -622,7 +646,7 @@ static int afp_get_entry_id_from_netware_handle(uint8 *afp_req, int afp_len,
|
||||
|
||||
XDPRINTF((3,0, "AFP Get Entry ID From NetWare Handle: handle=%u volume=%d unix='%s' entry=0x%08x%s",
|
||||
fhandle, volume, unixname, entry_id,
|
||||
(result < 0) ? " fallback" : ""));
|
||||
result ? " fallback" : ""));
|
||||
return(6);
|
||||
}
|
||||
|
||||
@@ -844,14 +868,12 @@ static int afp_get_entry_id_from_path_name(uint8 *afp_req, int afp_len,
|
||||
return(-0x9c); /* Invalid Path */
|
||||
}
|
||||
|
||||
result = nwatalk_get_entry_id(unixname, &entry_id);
|
||||
if (result < 0 || !entry_id)
|
||||
entry_id = afp_fallback_entry_id(volume, &stbuff);
|
||||
entry_id = afp_get_or_create_entry_id(unixname, volume, &stbuff, &result);
|
||||
|
||||
U32_TO_BE32(entry_id, response);
|
||||
XDPRINTF((3,0, "AFP Get Entry ID From Path Name: dh=%d path='%s' entry=0x%08x%s",
|
||||
(int)dir_handle, visable_data(afp_req + 3, path_len), entry_id,
|
||||
(result < 0) ? " fallback" : ""));
|
||||
result ? " fallback" : ""));
|
||||
return(4);
|
||||
}
|
||||
|
||||
@@ -956,16 +978,11 @@ static int afp_fill_file_info_response(const char *unixname,
|
||||
uint32 parent_id = 0;
|
||||
uint32 resource_size = 0;
|
||||
uint8 finder_info[NWATALK_FINDER_INFO_LEN];
|
||||
int result;
|
||||
|
||||
if (stat(unixname, &stbuff))
|
||||
return(-0x9c); /* Invalid Path */
|
||||
|
||||
result = nwatalk_get_entry_id(unixname, &entry_id);
|
||||
if (result < 0 || !entry_id) {
|
||||
entry_id = afp_fallback_entry_id(volume, &stbuff);
|
||||
if (fallback_out) *fallback_out = 1;
|
||||
} else if (fallback_out) *fallback_out = 0;
|
||||
entry_id = afp_get_or_create_entry_id(unixname, volume, &stbuff, fallback_out);
|
||||
|
||||
memset(response, 0, 120);
|
||||
U32_TO_BE32(entry_id, response + 0);
|
||||
@@ -1280,7 +1297,6 @@ static int afp_scan_file_information(uint8 *afp_req, int afp_len,
|
||||
struct stat stbuff;
|
||||
int child_fallback = 0;
|
||||
uint32 child_entry_id = 0;
|
||||
int child_result;
|
||||
|
||||
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
|
||||
continue;
|
||||
@@ -1289,11 +1305,8 @@ static int afp_scan_file_information(uint8 *afp_req, int afp_len,
|
||||
if (stat(childname, &stbuff))
|
||||
continue;
|
||||
|
||||
child_result = nwatalk_get_entry_id(childname, &child_entry_id);
|
||||
if (child_result < 0 || !child_entry_id) {
|
||||
child_entry_id = afp_fallback_entry_id(volume, &stbuff);
|
||||
child_fallback = 1;
|
||||
}
|
||||
child_entry_id = afp_get_or_create_entry_id(childname, volume, &stbuff,
|
||||
&child_fallback);
|
||||
|
||||
if (last_seen_id && !seen) {
|
||||
if (child_entry_id == last_seen_id)
|
||||
|
||||
@@ -7,11 +7,13 @@ The tests use the ncpfs/libncp client library. They are not built by default
|
||||
because they require the host ncpfs development headers/library and a running
|
||||
NetWare-compatible server.
|
||||
|
||||
The AFP endpoints are intentionally conservative. When persistent AFP entry
|
||||
ids become available, mars_nwe-owned ids are read from the versioned
|
||||
`org.mars-nwe.afp.entry-id` xattr before falling back to Netatalk/libatalk
|
||||
AppleDouble/CNID metadata and then the temporary stat-derived fallback. The
|
||||
first AFP write smoke path is deliberately limited to the FinderInfo bitmap of
|
||||
The AFP endpoints are intentionally conservative. mars_nwe-owned ids are read
|
||||
from the versioned `org.mars-nwe.afp.entry-id` xattr before falling back to
|
||||
Netatalk/libatalk AppleDouble/CNID metadata. When neither source has an id yet,
|
||||
the existing stat-derived compatibility id is cached in that xattr so subsequent
|
||||
AFP probes can reuse the same mars_nwe-owned id instead of re-entering the
|
||||
temporary fallback path. The first AFP write smoke path is deliberately limited
|
||||
to the FinderInfo bitmap of
|
||||
AFP 2.0 Set File Information; CNID allocation, DOS attribute mapping, resource
|
||||
fork writes, and data-fork writes remain separate write-safety work. mars_nwe
|
||||
source uses Netatalk-style `org.mars-nwe.<domain>.*` xattr names; AFP
|
||||
@@ -401,13 +403,29 @@ AFP 2.0 Set File Information: vol=0 request_vol=0 entry=0x00000000 mask=0x0020 p
|
||||
AFP 2.0 Get File Information: vol=0 entry=0x00000000 mask=0xffff path='SYS:PUBLIC/pmdflts.ini' reply_entry=0x23c8787d fallback
|
||||
```
|
||||
|
||||
The `fallback` marker on the verification Get File Information diagnostic still
|
||||
refers to the entry-id source: the returned entry id is derived from the current
|
||||
stat-backed fallback path because no persistent CNID or `org.mars-nwe.afp.entry-id`
|
||||
metadata was present. It does not mean the FinderInfo write was ignored; the
|
||||
The `fallback` marker on the first verification Get File Information diagnostic
|
||||
still refers to the entry-id source: the returned entry id was derived from the
|
||||
stat-backed compatibility path because no CNID or mars_nwe entry-id xattr existed
|
||||
yet. The server now caches that derived id in `org.mars-nwe.afp.entry-id`, so a
|
||||
second probe of the same file should reuse the xattr-backed id and normally omit
|
||||
the `fallback` marker. It does not mean the FinderInfo write was ignored; the
|
||||
helper verifies the written FinderInfo through the follow-up Get File Information
|
||||
reply.
|
||||
|
||||
Linux xattr checks for the FinderInfo and cached Entry ID look like this:
|
||||
|
||||
```sh
|
||||
getfattr -n user.org.mars-nwe.afp.finder-info -e hex /var/mars_nwe/SYS/public/pmdflts.ini
|
||||
getfattr -n user.org.mars-nwe.afp.entry-id -e hex /var/mars_nwe/SYS/public/pmdflts.ini
|
||||
```
|
||||
|
||||
For the verified FinderInfo smoke run, the FinderInfo xattr starts with
|
||||
`TEXTMARS`:
|
||||
|
||||
```text
|
||||
user.org.mars-nwe.afp.finder-info=0x544558544d415253000000000000000000000000000000000000000000000000
|
||||
```
|
||||
|
||||
All other Set File Information bitmap bits are intentionally rejected for now.
|
||||
That keeps attribute, timestamp, DOS/NetWare mode-bit mapping, resource-fork,
|
||||
and Entry-ID-only write semantics out of this first metadata-only write smoke
|
||||
|
||||
Reference in New Issue
Block a user