nwnss: add Netatalk AFP userspace xattr boundary
This commit is contained in:
57
include/nwnss/internal/macNSpaceUserspace.h
Normal file
57
include/nwnss/internal/macNSpaceUserspace.h
Normal 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_ */
|
||||
@@ -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
|
||||
|
||||
233
src/nwnss/comn/namespace/macNSpaceUserspace.c
Normal file
233
src/nwnss/comn/namespace/macNSpaceUserspace.c
Normal 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);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
add_subdirectory(aes)
|
||||
add_subdirectory(alarm)
|
||||
add_subdirectory(afp)
|
||||
add_subdirectory(asyncio)
|
||||
add_subdirectory(bit)
|
||||
add_subdirectory(bitmap)
|
||||
|
||||
4
tests/nwnss/afp/CMakeLists.txt
Normal file
4
tests/nwnss/afp/CMakeLists.txt
Normal 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)
|
||||
118
tests/nwnss/afp/test_nwnss_afp.c
Normal file
118
tests/nwnss/afp/test_nwnss_afp.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user