nwnss: add Netatalk AFP userspace xattr boundary

This commit is contained in:
a
2026-06-19 19:03:25 +00:00
committed by Mario Fetka
parent 1c69f8dbe8
commit 374b7fb8e4
6 changed files with 414 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
/****************************************************************************
|
| Userspace boundary for NSS Macintosh namespace metadata.
|
| The NSS MAC namespace stores Finder metadata as RVD_MAC_META_DATA and
| resource forks as the data stream named MAC_RF. This boundary maps those
| semantics to Netatalk's adouble:ea host representation for OtherFS userspace
| providers, per object, without extending netware.metadata.
+-------------------------------------------------------------------------*/
#ifndef _MACNSPACE_USERSPACE_H_
#define _MACNSPACE_USERSPACE_H_
#include <stddef.h>
#include <public/zParams.h>
#ifdef __cplusplus
extern "C" {
#endif
#define NSS_MAC_NETATALK_METADATA_XATTR "org.netatalk.Metadata"
#define NSS_MAC_NETATALK_RESOURCE_XATTR "org.netatalk.ResourceFork"
#define NSS_MAC_RESOURCE_FORK_STREAM_NAME "MAC_RF"
#define NSS_MAC_NETATALK_AD_MAGIC 0x00051607u
#define NSS_MAC_NETATALK_AD_VERSION_EA 0x00020002u
#define NSS_MAC_NETATALK_METADATA_SIZE 402u
#define NSS_MAC_NETATALK_FINDER_INFO_SIZE 32u
const char *NssMacUserspaceMetadataXattrName(void);
const char *NssMacUserspaceResourceForkXattrName(void);
const char *NssMacUserspaceResourceForkStreamName(void);
void NssMacUserspaceInitDefaultMacInfo(zMacInfo_s *macInfo, int isDirectory);
int NssMacUserspaceBuildNetatalkMetadata(const zMacInfo_s *macInfo,
unsigned char *buffer,
size_t bufferSize);
int NssMacUserspaceParseNetatalkMetadata(const unsigned char *buffer,
size_t bufferSize,
zMacInfo_s *macInfo);
int NssMacUserspaceWriteNetatalkMetadata(const char *path,
const zMacInfo_s *macInfo);
int NssMacUserspaceReadNetatalkMetadata(const char *path,
zMacInfo_s *macInfo);
int NssMacUserspaceWriteResourceFork(const char *path,
const void *data,
size_t dataSize);
ssize_t NssMacUserspaceReadResourceFork(const char *path,
void *data,
size_t dataSize);
#ifdef __cplusplus
}
#endif
#endif /* _MACNSPACE_USERSPACE_H_ */

View File

@@ -244,6 +244,7 @@ add_library(nwnss SHARED
comn/namespace/dataStreamNSpace.c
comn/namespace/extAttrNSpace.c
comn/namespace/macNSpace.c
comn/namespace/macNSpaceUserspace.c
nss/msg/msg.c
nssStartupNameGlobals.c
library/misc/lbVolume.c

View File

@@ -0,0 +1,233 @@
/****************************************************************************
|
| Userspace boundary for NSS Macintosh namespace metadata.
|
| Original NSS MAC namespace semantics remain in macNSpace.c:
| - Finder/Mac metadata is RVD_MAC_META_DATA (zMacInfo_s/PackedMacInfo_s)
| - Resource forks are the MAC_RF data stream
|
| OtherFS userspace providers do not have an NSS media variable-data store.
| For that case, map the same per-object semantics to Netatalk's adouble:ea
| host representation:
| - org.netatalk.Metadata
| - org.netatalk.ResourceFork
|
| Do not extend netware.metadata and do not create a separate netware AFP
| xattr. This representation is a provider boundary, not NSS media format.
+-------------------------------------------------------------------------*/
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <arpa/inet.h>
#include <sys/../string.h>
#include <include/comnBeasts.h>
#include <internal/macNSpace.h>
#include <internal/macNSpaceUserspace.h>
#define AD_ENTRY_LEN 12u
#define AD_HEADER_LEN 26u
#define ADEID_NUM_EA 8u
#define ADEID_FINDERI 9u
#define ADEID_COMMENT 4u
#define ADEID_FILEDATESI 8u
#define ADEID_AFPFILEI 14u
#define AD_DEV 0x80444556u
#define AD_INO 0x80494E4Fu
#define AD_SYN 0x8053594Eu
#define AD_ID 0x8053567Eu
#define ADEDLEN_FINDERI 32u
#define ADEDLEN_COMMENT 200u
#define ADEDLEN_FILEDATESI 16u
#define ADEDLEN_AFPFILEI 4u
#define ADEDLEN_PRIVDEV 8u
#define ADEDLEN_PRIVINO 8u
#define ADEDLEN_PRIVSYN 8u
#define ADEDLEN_PRIVID 4u
#define ADEDOFF_FINDERI_EA (AD_HEADER_LEN + (ADEID_NUM_EA * AD_ENTRY_LEN))
#define ADEDOFF_COMMENT_EA (ADEDOFF_FINDERI_EA + ADEDLEN_FINDERI)
#define ADEDOFF_FILEDATESI_EA (ADEDOFF_COMMENT_EA + ADEDLEN_COMMENT)
#define ADEDOFF_AFPFILEI_EA (ADEDOFF_FILEDATESI_EA + ADEDLEN_FILEDATESI)
#define ADEDOFF_PRIVDEV_EA (ADEDOFF_AFPFILEI_EA + ADEDLEN_AFPFILEI)
#define ADEDOFF_PRIVINO_EA (ADEDOFF_PRIVDEV_EA + ADEDLEN_PRIVDEV)
#define ADEDOFF_PRIVSYN_EA (ADEDOFF_PRIVINO_EA + ADEDLEN_PRIVINO)
#define ADEDOFF_PRIVID_EA (ADEDOFF_PRIVSYN_EA + ADEDLEN_PRIVSYN)
static void put_be16(unsigned char *buf, uint16_t value)
{
uint16_t disk = htons(value);
memcpy(buf, &disk, sizeof(disk));
}
static void put_be32(unsigned char *buf, uint32_t value)
{
uint32_t disk = htonl(value);
memcpy(buf, &disk, sizeof(disk));
}
static uint32_t get_be32(const unsigned char *buf)
{
uint32_t disk;
memcpy(&disk, buf, sizeof(disk));
return ntohl(disk);
}
static void put_entry(unsigned char *buf, uint32_t eid, uint32_t offset,
uint32_t length)
{
put_be32(buf, eid);
put_be32(buf + 4, offset);
put_be32(buf + 8, length);
}
const char *NssMacUserspaceMetadataXattrName(void)
{
return NSS_MAC_NETATALK_METADATA_XATTR;
}
const char *NssMacUserspaceResourceForkXattrName(void)
{
return NSS_MAC_NETATALK_RESOURCE_XATTR;
}
const char *NssMacUserspaceResourceForkStreamName(void)
{
return NSS_MAC_RESOURCE_FORK_STREAM_NAME;
}
void NssMacUserspaceInitDefaultMacInfo(zMacInfo_s *macInfo, int isDirectory)
{
if (!macInfo)
return;
memset(macInfo, 0, sizeof(*macInfo));
if (!isDirectory) {
macInfo->finderInfo[10] = 0xff;
macInfo->finderInfo[11] = 0xff;
macInfo->finderInfo[12] = 0xff;
macInfo->finderInfo[13] = 0xff;
}
}
int NssMacUserspaceBuildNetatalkMetadata(const zMacInfo_s *macInfo,
unsigned char *buffer,
size_t bufferSize)
{
unsigned char *entry;
if (!macInfo || !buffer)
return -EINVAL;
if (bufferSize < NSS_MAC_NETATALK_METADATA_SIZE)
return -ENOSPC;
memset(buffer, 0, bufferSize);
put_be32(buffer, NSS_MAC_NETATALK_AD_MAGIC);
put_be32(buffer + 4, NSS_MAC_NETATALK_AD_VERSION_EA);
put_be16(buffer + 24, ADEID_NUM_EA);
entry = buffer + AD_HEADER_LEN;
put_entry(entry, ADEID_FINDERI, ADEDOFF_FINDERI_EA, ADEDLEN_FINDERI);
put_entry(entry + AD_ENTRY_LEN, ADEID_COMMENT, ADEDOFF_COMMENT_EA,
ADEDLEN_COMMENT);
put_entry(entry + (2 * AD_ENTRY_LEN), ADEID_FILEDATESI,
ADEDOFF_FILEDATESI_EA, ADEDLEN_FILEDATESI);
put_entry(entry + (3 * AD_ENTRY_LEN), ADEID_AFPFILEI,
ADEDOFF_AFPFILEI_EA, ADEDLEN_AFPFILEI);
put_entry(entry + (4 * AD_ENTRY_LEN), AD_DEV, ADEDOFF_PRIVDEV_EA,
ADEDLEN_PRIVDEV);
put_entry(entry + (5 * AD_ENTRY_LEN), AD_INO, ADEDOFF_PRIVINO_EA,
ADEDLEN_PRIVINO);
put_entry(entry + (6 * AD_ENTRY_LEN), AD_SYN, ADEDOFF_PRIVSYN_EA,
ADEDLEN_PRIVSYN);
put_entry(entry + (7 * AD_ENTRY_LEN), AD_ID, ADEDOFF_PRIVID_EA,
ADEDLEN_PRIVID);
memcpy(buffer + ADEDOFF_FINDERI_EA, macInfo->finderInfo,
ADEDLEN_FINDERI);
/*
* Netatalk's adouble:ea metadata has no ProDOS field. zMacInfo_s
* proDOSInfo and dirRightsMask remain NSS MAC namespace data and must not
* be forced into an invented host xattr layout.
*/
return 0;
}
int NssMacUserspaceParseNetatalkMetadata(const unsigned char *buffer,
size_t bufferSize,
zMacInfo_s *macInfo)
{
if (!buffer || !macInfo)
return -EINVAL;
if (bufferSize < NSS_MAC_NETATALK_METADATA_SIZE)
return -EINVAL;
if (get_be32(buffer) != NSS_MAC_NETATALK_AD_MAGIC ||
get_be32(buffer + 4) != NSS_MAC_NETATALK_AD_VERSION_EA)
return -EINVAL;
memset(macInfo, 0, sizeof(*macInfo));
memcpy(macInfo->finderInfo, buffer + ADEDOFF_FINDERI_EA,
ADEDLEN_FINDERI);
return 0;
}
int NssMacUserspaceWriteNetatalkMetadata(const char *path,
const zMacInfo_s *macInfo)
{
unsigned char buffer[NSS_MAC_NETATALK_METADATA_SIZE];
int rc;
if (!path)
return -EINVAL;
rc = NssMacUserspaceBuildNetatalkMetadata(macInfo, buffer,
sizeof(buffer));
if (rc)
return rc;
if (setxattr(path, NSS_MAC_NETATALK_METADATA_XATTR, buffer,
sizeof(buffer), 0) != 0)
return -errno;
return 0;
}
int NssMacUserspaceReadNetatalkMetadata(const char *path,
zMacInfo_s *macInfo)
{
unsigned char buffer[NSS_MAC_NETATALK_METADATA_SIZE];
ssize_t got;
if (!path)
return -EINVAL;
got = getxattr(path, NSS_MAC_NETATALK_METADATA_XATTR, buffer,
sizeof(buffer));
if (got < 0)
return -errno;
return NssMacUserspaceParseNetatalkMetadata(buffer, (size_t)got,
macInfo);
}
int NssMacUserspaceWriteResourceFork(const char *path,
const void *data,
size_t dataSize)
{
if (!path || (!data && dataSize))
return -EINVAL;
if (setxattr(path, NSS_MAC_NETATALK_RESOURCE_XATTR, data, dataSize, 0) != 0)
return -errno;
return 0;
}
ssize_t NssMacUserspaceReadResourceFork(const char *path,
void *data,
size_t dataSize)
{
if (!path)
return -EINVAL;
return getxattr(path, NSS_MAC_NETATALK_RESOURCE_XATTR, data, dataSize);
}

View File

@@ -2,6 +2,7 @@
add_subdirectory(aes)
add_subdirectory(alarm)
add_subdirectory(afp)
add_subdirectory(asyncio)
add_subdirectory(bit)
add_subdirectory(bitmap)

View File

@@ -0,0 +1,4 @@
add_executable(test_nwnss_afp test_nwnss_afp.c)
target_link_libraries(test_nwnss_afp PRIVATE mars_nwe::nwnss)
add_test(NAME nwnss.afp COMMAND test_nwnss_afp)
set_tests_properties(nwnss.afp PROPERTIES SKIP_RETURN_CODE 77)

View File

@@ -0,0 +1,118 @@
#include <internal/macNSpaceUserspace.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/xattr.h>
#include <unistd.h>
#define SKIP_CODE 77
#define CHECK(expr) \
do { \
if (!(expr)) { \
fprintf(stderr, "check failed: %s at %s:%d\n", #expr, __FILE__, __LINE__); \
return 1; \
} \
} while (0)
static int is_xattr_unsupported(int err)
{
return err == ENOTSUP || err == EOPNOTSUPP || err == ENOSYS ||
err == EPERM || err == EACCES;
}
static int make_temp_file(char *path, size_t pathSize)
{
const char *tmpdir = getenv("TMPDIR");
int fd;
if (!tmpdir || tmpdir[0] == '\0')
tmpdir = ".";
if (snprintf(path, pathSize, "%s/nwnss-afp-XXXXXX", tmpdir) < 0 ||
strlen(path) >= pathSize)
return -1;
fd = mkstemp(path);
if (fd < 0)
return -1;
close(fd);
return 0;
}
int main(void)
{
unsigned char metadata[NSS_MAC_NETATALK_METADATA_SIZE];
unsigned char hostMetadata[NSS_MAC_NETATALK_METADATA_SIZE];
zMacInfo_s macInfo;
zMacInfo_s roundtrip;
char tempPath[4096];
const char resource[] = "resource fork payload";
char resourceOut[sizeof(resource)];
ssize_t got;
int rc;
CHECK(strcmp(NssMacUserspaceMetadataXattrName(),
"org.netatalk.Metadata") == 0);
CHECK(strcmp(NssMacUserspaceResourceForkXattrName(),
"org.netatalk.ResourceFork") == 0);
CHECK(strcmp(NssMacUserspaceResourceForkStreamName(), "MAC_RF") == 0);
NssMacUserspaceInitDefaultMacInfo(&macInfo, 0);
CHECK(macInfo.finderInfo[10] == 0xff);
CHECK(macInfo.finderInfo[11] == 0xff);
CHECK(macInfo.finderInfo[12] == 0xff);
CHECK(macInfo.finderInfo[13] == 0xff);
NssMacUserspaceInitDefaultMacInfo(&macInfo, 1);
CHECK(macInfo.finderInfo[10] == 0x00);
CHECK(macInfo.finderInfo[11] == 0x00);
CHECK(macInfo.finderInfo[12] == 0x00);
CHECK(macInfo.finderInfo[13] == 0x00);
memset(&macInfo, 0, sizeof(macInfo));
memcpy(macInfo.finderInfo, "NSSAFP-FINDER-INFO-ROUNDTRIP!", 30);
CHECK(NssMacUserspaceBuildNetatalkMetadata(&macInfo, metadata,
sizeof(metadata)) == 0);
CHECK(NssMacUserspaceParseNetatalkMetadata(metadata, sizeof(metadata),
&roundtrip) == 0);
CHECK(memcmp(roundtrip.finderInfo, macInfo.finderInfo,
sizeof(macInfo.finderInfo)) == 0);
CHECK(make_temp_file(tempPath, sizeof(tempPath)) == 0);
rc = NssMacUserspaceWriteNetatalkMetadata(tempPath, &macInfo);
if (rc == -ENOTSUP || rc == -EOPNOTSUPP || rc == -ENOSYS ||
rc == -EPERM || rc == -EACCES) {
fprintf(stderr,
"SKIP/WARN: host filesystem does not allow Netatalk AFP xattrs on test file\n");
unlink(tempPath);
return SKIP_CODE;
}
CHECK(rc == 0);
got = getxattr(tempPath, "org.netatalk.Metadata", hostMetadata,
sizeof(hostMetadata));
if (got < 0 && is_xattr_unsupported(errno)) {
fprintf(stderr,
"SKIP/WARN: host filesystem does not allow Netatalk AFP xattrs on test file\n");
unlink(tempPath);
return SKIP_CODE;
}
CHECK(got == (ssize_t)sizeof(hostMetadata));
CHECK(memcmp(hostMetadata, metadata, sizeof(metadata)) == 0);
memset(&roundtrip, 0, sizeof(roundtrip));
CHECK(NssMacUserspaceReadNetatalkMetadata(tempPath, &roundtrip) == 0);
CHECK(memcmp(roundtrip.finderInfo, macInfo.finderInfo,
sizeof(macInfo.finderInfo)) == 0);
CHECK(NssMacUserspaceWriteResourceFork(tempPath, resource,
sizeof(resource)) == 0);
memset(resourceOut, 0, sizeof(resourceOut));
got = NssMacUserspaceReadResourceFork(tempPath, resourceOut,
sizeof(resourceOut));
CHECK(got == (ssize_t)sizeof(resource));
CHECK(memcmp(resourceOut, resource, sizeof(resource)) == 0);
unlink(tempPath);
return 0;
}