Skip to content

Instantly share code, notes, and snippets.

@erichelgeson
Created September 24, 2026 19:25
Show Gist options
  • Select an option

  • Save erichelgeson/c22abe1f39da0998f51ccbf7c6b938c6 to your computer and use it in GitHub Desktop.

Select an option

Save erichelgeson/c22abe1f39da0998f51ccbf7c6b938c6 to your computer and use it in GitHub Desktop.
rsrcfork_check.c
// rsrcfork_check — probe whether a volume supports classic Mac resource forks.
//
// Build: clang -O2 -Wall -o rsrcfork_check rsrcfork_check.c
// Usage: ./rsrcfork_check [directory-on-volume-to-test] (default: $TMPDIR)
//
// Exercises every public path to the resource fork:
// - volume capability bits (getattrlist ATTR_VOL_CAPABILITIES)
// - file/..namedfork/rsrc (POSIX path to the fork)
// - com.apple.ResourceFork (xattr API, incl. offset writes for large forks)
// - ATTR_FILE_RSRCLENGTH (fork size as reported by the filesystem)
// - copyfile(COPYFILE_ALL) (fork preservation on copy)
// - truncation / deletion of the fork
#include <copyfile.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/attr.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/xattr.h>
#include <unistd.h>
static int passes, fails;
static void result(int ok, const char *name, const char *detail) {
printf(" [%s] %-48s %s\n", ok ? "PASS" : "FAIL", name, detail ? detail : "");
ok ? passes++ : fails++;
}
static void fill(unsigned char *buf, size_t n, unsigned seed) {
for (size_t i = 0; i < n; i++) buf[i] = (unsigned char)((i * 31 + seed) & 0xff);
}
static int write_all(int fd, const void *buf, size_t n) {
const unsigned char *p = buf;
while (n) {
ssize_t w = write(fd, p, n);
if (w < 0) return -1;
p += w; n -= (size_t)w;
}
return 0;
}
static ssize_t read_all(const char *path, unsigned char *buf, size_t cap) {
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
size_t total = 0;
ssize_t r;
while (total < cap && (r = read(fd, buf + total, cap - total)) > 0) total += (size_t)r;
close(fd);
return (ssize_t)total;
}
static int make_file(const char *path) {
int fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (fd < 0) return -1;
int rc = write_all(fd, "data fork\n", 10);
close(fd);
return rc;
}
static long long rsrc_length(const char *path) {
struct attrlist al = { .bitmapcount = ATTR_BIT_MAP_COUNT, .fileattr = ATTR_FILE_RSRCLENGTH };
struct { uint32_t len; off_t rsrclen; } __attribute__((packed)) buf;
if (getattrlist(path, &al, &buf, sizeof buf, 0) != 0) return -1;
return (long long)buf.rsrclen;
}
static void volume_info(const char *dir) {
struct statfs sfs;
if (statfs(dir, &sfs) == 0)
printf("Volume: %s (fs type: %s, device: %s)\n", sfs.f_mntonname, sfs.f_fstypename, sfs.f_mntfromname);
struct attrlist al = { .bitmapcount = ATTR_BIT_MAP_COUNT, .volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES };
struct { uint32_t len; vol_capabilities_attr_t caps; } __attribute__((packed)) buf;
if (getattrlist(sfs.f_mntonname, &al, &buf, sizeof buf, 0) != 0) {
printf(" (could not read volume capabilities: %s)\n", strerror(errno));
return;
}
uint32_t v = buf.caps.valid[VOL_CAPABILITIES_INTERFACES];
uint32_t c = buf.caps.capabilities[VOL_CAPABILITIES_INTERFACES];
#define CAP(bit) (!(v & (bit)) ? "unknown" : (c & (bit)) ? "yes" : "no")
printf(" Capability EXTENDED_ATTR : %s\n", CAP(VOL_CAP_INT_EXTENDED_ATTR));
printf(" Capability NAMEDSTREAMS : %s (filesystem advertises named-fork support)\n",
CAP(VOL_CAP_INT_NAMEDSTREAMS));
#undef CAP
}
int main(int argc, char **argv) {
const char *dir = argc > 1 ? argv[1] : (getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp");
struct utsname u; uname(&u);
printf("rsrcfork_check — %s %s\n", u.sysname, u.release);
volume_info(dir);
printf("\n");
char file[1024], fork[1100], copy[1100], copyfork[1200];
snprintf(file, sizeof file, "%s/rsrcfork_check.%d", dir, getpid());
snprintf(fork, sizeof fork, "%s/..namedfork/rsrc", file);
snprintf(copy, sizeof copy, "%s.copy", file);
snprintf(copyfork, sizeof copyfork, "%s/..namedfork/rsrc", copy);
if (make_file(file) != 0) { perror("create test file"); return 2; }
enum { SMALL = 4096, LARGE = 4 * 1024 * 1024 };
unsigned char *want = malloc(LARGE), *got = malloc(LARGE + 1);
char msg[256];
ssize_t n;
// 1. Write via ..namedfork/rsrc, read back via same path.
fill(want, SMALL, 1);
int fd = open(fork, O_WRONLY | O_CREAT, 0644);
int ok = fd >= 0 && write_all(fd, want, SMALL) == 0;
if (fd >= 0) close(fd);
snprintf(msg, sizeof msg, "%s", ok ? "" : strerror(errno));
result(ok, "write fork via ..namedfork/rsrc", msg);
n = read_all(fork, got, LARGE);
ok = n == SMALL && memcmp(want, got, SMALL) == 0;
snprintf(msg, sizeof msg, "read %zd bytes", n);
result(ok, "read fork via ..namedfork/rsrc", msg);
// 2. Same bytes visible through the xattr API.
n = getxattr(file, XATTR_RESOURCEFORK_NAME, got, LARGE, 0, 0);
ok = n == SMALL && memcmp(want, got, SMALL) == 0;
snprintf(msg, sizeof msg, "%zd bytes%s%s", n, n < 0 ? ": " : "", n < 0 ? strerror(errno) : "");
result(ok, "read same fork via getxattr(" XATTR_RESOURCEFORK_NAME ")", msg);
// 3. Filesystem-reported fork length.
long long len = rsrc_length(file);
snprintf(msg, sizeof msg, "ATTR_FILE_RSRCLENGTH = %lld", len);
result(len == SMALL, "getattrlist reports fork length", msg);
// 4. Data fork untouched.
struct stat st; stat(file, &st);
snprintf(msg, sizeof msg, "st_size = %lld", (long long)st.st_size);
result(st.st_size == 10, "data fork independent of resource fork", msg);
// 5. Large fork via setxattr with offsets (how Carbon/copyfile write big forks),
// read back via ..namedfork/rsrc.
fill(want, LARGE, 7);
ok = 1;
for (size_t off = 0; off < LARGE && ok; off += 256 * 1024)
ok = setxattr(file, XATTR_RESOURCEFORK_NAME, want + off, 256 * 1024, (uint32_t)off, 0) == 0;
snprintf(msg, sizeof msg, "%s", ok ? "4 MiB in 256 KiB chunks" : strerror(errno));
result(ok, "write 4 MiB fork via setxattr(position)", msg);
n = read_all(fork, got, LARGE + 1);
ok = n == LARGE && memcmp(want, got, LARGE) == 0;
snprintf(msg, sizeof msg, "read %zd bytes", n);
result(ok, "read 4 MiB fork via ..namedfork/rsrc", msg);
// 6. Listed in xattr names.
char names[4096];
n = listxattr(file, names, sizeof names, 0);
ok = 0;
for (ssize_t i = 0; i < n; i += strlen(names + i) + 1)
if (strcmp(names + i, XATTR_RESOURCEFORK_NAME) == 0) ok = 1;
result(ok, "listxattr shows " XATTR_RESOURCEFORK_NAME, "");
// 7. copyfile preserves the fork.
unlink(copy);
ok = copyfile(file, copy, NULL, COPYFILE_ALL) == 0;
snprintf(msg, sizeof msg, "%s", ok ? "" : strerror(errno));
result(ok, "copyfile(COPYFILE_ALL)", msg);
n = read_all(copyfork, got, LARGE + 1);
ok = n == LARGE && memcmp(want, got, LARGE) == 0;
snprintf(msg, sizeof msg, "copy fork = %zd bytes", n);
result(ok, "copy retains identical resource fork", msg);
// 8. Truncate fork via ..namedfork/rsrc.
fd = open(fork, O_WRONLY | O_TRUNC);
if (fd >= 0) close(fd);
len = rsrc_length(file);
snprintf(msg, sizeof msg, "length after O_TRUNC = %lld", len);
result(fd >= 0 && len == 0, "truncate fork via ..namedfork/rsrc", msg);
// 9. Remove fork via removexattr.
fd = open(fork, O_WRONLY); if (fd >= 0) { write_all(fd, "x", 1); close(fd); }
ok = removexattr(file, XATTR_RESOURCEFORK_NAME, 0) == 0;
ok = ok && getxattr(file, XATTR_RESOURCEFORK_NAME, NULL, 0, 0, 0) < 0 && errno == ENOATTR;
result(ok, "removexattr deletes fork", "");
unlink(file);
unlink(copy);
free(want); free(got);
printf("\n%d passed, %d failed — resource forks are %s on this volume.\n",
passes, fails, fails == 0 ? "SUPPORTED" : passes == 0 ? "NOT SUPPORTED" : "PARTIALLY SUPPORTED");
return fails ? 1 : 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment