nwconn: implement AFP Get File Information
All checks were successful
Source release / source-package (push) Successful in 48s
All checks were successful
Source release / source-package (push) Successful in 48s
Wire NCP 0x23/0x05 AFP Get File Information to a conservative read-only reply for SYS:-style paths. The WebSDK documents NCP 0x2222/35/05 as taking a Volume Number, AFP Entry ID, request bit map, and AFP path modifier string, and returning an AFP file information record with entry id, parent id, attributes, data and resource fork lengths, offspring count, NetWare dates, Finder Info, long and short names, owner id, access privileges, and ProDOS information. The SDK headers expose the same call as AFPGetFileInformation() and NWAFPGetFileInformation(), with the wire reply matching RECPKT_AFPFILEINFO. Resolve the supplied path through the existing mars_nwe path machinery, require the optional Netatalk/libatalk backend as for the entry-id probe, and fill the fields that can be derived safely from Unix stat data and the existing libatalk helpers. Finder Info and resource fork length are read through nwatalk when present; entry ids fall back to the existing stat-derived AFP id until persistent CNID/AppleDouble ids are implemented. Parent id and ProDOS-specific data remain zero for now. Add a Linux afp_file_info_smoke test using ncpfs/libncp so the new call can be exercised without an AppleTalk client. The test sends raw SYS:-style paths with directory handle 0, matching the verified AFP Entry ID smoke-test path. This implements only the read-only AFP file information query for path-based requests; entry-id-only lookup, persistent CNID mapping, and write-side AFP semantics remain future work.
This commit is contained in:
201
src/nwconn.c
201
src/nwconn.c
@@ -419,6 +419,7 @@ static const char *afp_call_name(int ufunc)
|
||||
switch (ufunc) {
|
||||
case 0x03: return("AFP Delete");
|
||||
case 0x04: return("AFP Get Entry ID From Name");
|
||||
case 0x05: return("AFP Get File Information");
|
||||
case 0x06: return("AFP Get Entry ID From NetWare Handle");
|
||||
case 0x07: return("AFP Rename");
|
||||
case 0x08: return("AFP Open File Fork");
|
||||
@@ -532,6 +533,194 @@ static int afp_get_entry_id_from_path_name(uint8 *afp_req, int afp_len,
|
||||
return(4);
|
||||
}
|
||||
|
||||
|
||||
static void afp_copy_fixed_name(uint8 *dst, int dst_len,
|
||||
const uint8 *src, int src_len)
|
||||
{
|
||||
int i;
|
||||
|
||||
memset(dst, 0, dst_len);
|
||||
if (!src || src_len < 1) return;
|
||||
if (src_len > dst_len) src_len = dst_len;
|
||||
for (i = 0; i < src_len; i++) {
|
||||
uint8 c = src[i];
|
||||
if (!c) break;
|
||||
dst[i] = c;
|
||||
}
|
||||
}
|
||||
|
||||
static void afp_leaf_name_from_path(uint8 *dst, int dst_len,
|
||||
const uint8 *path, int path_len)
|
||||
{
|
||||
int start = 0;
|
||||
int end = path_len;
|
||||
int i;
|
||||
|
||||
if (!path || path_len < 1) {
|
||||
afp_copy_fixed_name(dst, dst_len, (const uint8 *)"", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
while (end > 0 && (path[end - 1] == '/' || path[end - 1] == '\\'))
|
||||
end--;
|
||||
|
||||
for (i = end - 1; i >= 0; i--) {
|
||||
if (path[i] == ':' || path[i] == '/' || path[i] == '\\') {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (start >= end) {
|
||||
int colon = -1;
|
||||
for (i = 0; i < path_len; i++) {
|
||||
if (path[i] == ':') {
|
||||
colon = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (colon > 0) {
|
||||
start = 0;
|
||||
end = colon;
|
||||
}
|
||||
}
|
||||
|
||||
afp_copy_fixed_name(dst, dst_len, path + start, end - start);
|
||||
}
|
||||
|
||||
static uint16 afp_basic_attributes(const struct stat *stb)
|
||||
{
|
||||
if (S_ISDIR(stb->st_mode))
|
||||
return(0x0400); /* AFP_SA_SUBDIR */
|
||||
return(0x0000); /* AFP_SA_NORMAL */
|
||||
}
|
||||
|
||||
static uint16 afp_basic_access_privileges(const struct stat *stb)
|
||||
{
|
||||
if (S_ISDIR(stb->st_mode))
|
||||
return(0x4000 | 0x2000 | 0x8000); /* search, parental, modify attrs */
|
||||
return(0x0100 | 0x0200 | 0x0400 | 0x1000 | 0x8000); /* rw open delete modify */
|
||||
}
|
||||
|
||||
static uint16 afp_count_offspring(const char *unixname, const struct stat *stb)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
uint32 count = 0;
|
||||
|
||||
if (!S_ISDIR(stb->st_mode)) return(0);
|
||||
|
||||
dir = opendir(unixname);
|
||||
if (!dir) return(0);
|
||||
while ((de = readdir(dir)) != NULL) {
|
||||
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
|
||||
continue;
|
||||
if (count < 0xffff) count++;
|
||||
}
|
||||
closedir(dir);
|
||||
return((uint16)count);
|
||||
}
|
||||
|
||||
static int afp_get_file_information(uint8 *afp_req, int afp_len,
|
||||
uint8 *response)
|
||||
{
|
||||
uint8 volume_number;
|
||||
uint32 request_entry_id;
|
||||
uint16 request_mask;
|
||||
int path_len;
|
||||
int volume;
|
||||
char unixname[PATH_MAX];
|
||||
struct stat stbuff;
|
||||
uint32 entry_id = 0;
|
||||
uint32 parent_id = 0;
|
||||
uint32 resource_size = 0;
|
||||
uint8 finder_info[NWATALK_FINDER_INFO_LEN];
|
||||
uint8 empty_path = 0;
|
||||
int result;
|
||||
|
||||
if (afp_len < 9) {
|
||||
XDPRINTF((2,0, "AFP Get File Information rejected: short request len=%d",
|
||||
afp_len));
|
||||
return(-0x7e); /* NCP Boundary Check Failed */
|
||||
}
|
||||
|
||||
volume_number = afp_req[1];
|
||||
request_entry_id = GET_BE32(afp_req + 2);
|
||||
request_mask = GET_BE16(afp_req + 6);
|
||||
path_len = (int)afp_req[8];
|
||||
if (path_len < 0 || afp_len < 9 + path_len) {
|
||||
XDPRINTF((2,0, "AFP Get File Information rejected: boundary check len=%d path_len=%d",
|
||||
afp_len, path_len));
|
||||
return(-0x7e);
|
||||
}
|
||||
|
||||
if (!nwatalk_backend_available()) {
|
||||
XDPRINTF((3,0, "AFP Get File Information rejected: libatalk backend unavailable"));
|
||||
return(-0xbf); /* invalid namespace */
|
||||
}
|
||||
|
||||
if (!path_len) {
|
||||
XDPRINTF((2,0, "AFP Get File Information rejected: entry-id-only lookup unsupported vol=%d entry=0x%08x mask=0x%04x",
|
||||
(int)volume_number, request_entry_id, request_mask));
|
||||
return(-0x9c); /* Invalid Path until persistent entry-id lookup exists */
|
||||
}
|
||||
|
||||
volume = conn_get_kpl_unxname(unixname, sizeof(unixname), 0,
|
||||
path_len ? afp_req + 9 : &empty_path,
|
||||
path_len);
|
||||
if (volume < 0) {
|
||||
XDPRINTF((2,0, "AFP Get File Information path resolve failed: vol=%d entry=0x%08x path='%s' result=-0x%x",
|
||||
(int)volume_number, request_entry_id,
|
||||
visable_data(afp_req + 9, path_len), -volume));
|
||||
return(volume);
|
||||
}
|
||||
|
||||
if (stat(unixname, &stbuff)) {
|
||||
XDPRINTF((2,0, "AFP Get File Information stat failed: vol=%d entry=0x%08x path='%s' unix='%s' errno=%d",
|
||||
(int)volume_number, request_entry_id,
|
||||
visable_data(afp_req + 9, path_len), unixname, errno));
|
||||
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);
|
||||
|
||||
memset(response, 0, 120);
|
||||
U32_TO_BE32(entry_id, response + 0);
|
||||
U32_TO_BE32(parent_id, response + 4);
|
||||
U16_TO_BE16(afp_basic_attributes(&stbuff), response + 8);
|
||||
U32_TO_BE32(S_ISDIR(stbuff.st_mode) ? 0 : (uint32)stbuff.st_size, response + 10);
|
||||
|
||||
if (!S_ISDIR(stbuff.st_mode))
|
||||
(void)nwatalk_get_resource_fork_size(unixname, &resource_size);
|
||||
U32_TO_BE32(resource_size, response + 14);
|
||||
U16_TO_BE16(afp_count_offspring(unixname, &stbuff), response + 18);
|
||||
|
||||
un_date_2_nw(stbuff.st_ctime, response + 20, 1);
|
||||
un_date_2_nw(stbuff.st_atime, response + 22, 1);
|
||||
un_date_2_nw(stbuff.st_mtime, response + 24, 1);
|
||||
un_time_2_nw(stbuff.st_mtime, response + 26, 1);
|
||||
U16_TO_BE16(0, response + 28); /* Backup Date */
|
||||
U16_TO_BE16(0, response + 30); /* Backup Time */
|
||||
|
||||
memset(finder_info, 0, sizeof(finder_info));
|
||||
(void)nwatalk_get_finder_info(unixname, finder_info, sizeof(finder_info));
|
||||
memcpy(response + 32, finder_info, NWATALK_FINDER_INFO_LEN);
|
||||
|
||||
afp_leaf_name_from_path(response + 64, 32, afp_req + 9, path_len);
|
||||
U32_TO_BE32(get_file_owner(&stbuff), response + 96);
|
||||
afp_leaf_name_from_path(response + 100, 12, afp_req + 9, path_len);
|
||||
U16_TO_BE16(afp_basic_access_privileges(&stbuff), response + 112);
|
||||
/* ProDOS info at offset 114 stays zero until a real Mac namespace maps it. */
|
||||
|
||||
XDPRINTF((3,0, "AFP Get File Information: vol=%d entry=0x%08x mask=0x%04x path='%s' reply_entry=0x%08x%s",
|
||||
(int)volume_number, request_entry_id, request_mask,
|
||||
visable_data(afp_req + 9, path_len), entry_id,
|
||||
(result < 0) ? " fallback" : ""));
|
||||
return(120);
|
||||
}
|
||||
|
||||
static int handle_ncp_serv(void)
|
||||
{
|
||||
int function = (int)ncprequest->function;
|
||||
@@ -2472,7 +2661,8 @@ static int handle_ncp_serv(void)
|
||||
*
|
||||
* WebSDK / headers identify the old NCP 0x2222/35 AFP
|
||||
* subfunctions used by nwafp.h, including AFP Delete
|
||||
* (0x03), Get Entry ID From Name (0x04), Get Entry ID From
|
||||
* (0x03), Get Entry ID From Name (0x04), Get File
|
||||
* Information (0x05), Get Entry ID From
|
||||
* NetWare Handle (0x06), Rename (0x07), Open File Fork
|
||||
* (0x08), Alloc Temporary Dir Handle (0x0b), Get Entry ID
|
||||
* From Path Name (0x0c), the AFP 2.0 create calls
|
||||
@@ -2480,7 +2670,9 @@ static int handle_ncp_serv(void)
|
||||
* Scan File Information (0x11).
|
||||
*
|
||||
* Implement the path-name entry-id probe first because the
|
||||
* SDK helpers use it to test AFP support. It still requires
|
||||
* SDK helpers use it to test AFP support, then expose the
|
||||
* read-only AFP Get File Information query for the same
|
||||
* SYS:-style path inputs. Both still require
|
||||
* the optional libatalk backend to be present; without a Mac
|
||||
* namespace backend, keep returning invalid namespace.
|
||||
*/
|
||||
@@ -2489,6 +2681,11 @@ static int handle_ncp_serv(void)
|
||||
afp_len, responsedata);
|
||||
if (result > -1) data_len = result;
|
||||
else completition = (uint8)-result;
|
||||
} else if (ufunc == 0x05) {
|
||||
int result = afp_get_file_information(afp_req,
|
||||
afp_len, responsedata);
|
||||
if (result > -1) data_len = result;
|
||||
else completition = (uint8)-result;
|
||||
} else {
|
||||
XDPRINTF((3,0, "AFP call rejected: ufunc=0x%02x (%s), Mac namespace unavailable, libatalk backend=%s",
|
||||
ufunc, afp_call_name(ufunc),
|
||||
|
||||
Reference in New Issue
Block a user