Loading...
/* compile: xcrun -sdk macosx.internal clang -ldarwintest -o getattrlist_fullpath getattrlist_fullpath.c -g -Weverything */

#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/attr.h>
#include <System/sys/fsgetpath.h>

#include <darwintest.h>
#include <darwintest_utils.h>

#define MAXLONGPATHLEN 4096

T_GLOBAL_META(
	T_META_NAMESPACE("xnu.vfs"),
	T_META_RADAR_COMPONENT_NAME("xnu"),
	T_META_RADAR_COMPONENT_VERSION("vfs"),
	T_META_ASROOT(false),
	T_META_CHECK_LEAKS(false));

static char *
fast_realpath(const char *path, unsigned int flags)
{
	struct {
		uint32_t        size;
		attrreference_t fullPathAttr;
		char            fullPathBuf[MAXLONGPATHLEN];
	} __attribute__((aligned(4), packed)) buf;

	struct attrlist al = {
		.bitmapcount = ATTR_BIT_MAP_COUNT,
		.commonattr = ATTR_CMN_FULLPATH,
	};

	if (getattrlist(path, &al, &buf, sizeof(buf), FSOPT_ATTR_CMN_EXTENDED | flags) < 0) {
		return NULL;
	}

	return strdup((char *)&buf.fullPathAttr + buf.fullPathAttr.attr_dataoffset);
}

static void
test_realpath(char *input, unsigned int flags, char *output)
{
	T_ASSERT_EQ_STR(fast_realpath(input, flags), output, "Testing input '%s' (0x%x), output '%s'", input, flags, output);
}

T_DECL(getattrlist_fullpath,
    "getattrlist ATTR_CMN_FULLPATH should preserve input path prefix in output")
{
	test_realpath("/private/etc/hosts", FSOPT_NOFOLLOW, "/private/etc/hosts");
	test_realpath("/etc/hosts", FSOPT_NOFOLLOW, "/private/etc/hosts");

	/* Test for .nofollow prefix */
	test_realpath("/.nofollow/etc/hosts", FSOPT_NOFOLLOW, NULL);
	test_realpath("/.nofollow/private/etc/hosts", FSOPT_NOFOLLOW, "/.nofollow/private/etc/hosts");

	/* Test for RESOLVE_NOFOLLOW_ANY resolve prefix */
	test_realpath("/.resolve/1/etc/hosts", FSOPT_NOFOLLOW, NULL);
	test_realpath("/.resolve/1/private/etc/hosts", FSOPT_NOFOLLOW, "/.resolve/1/private/etc/hosts");
}

T_DECL(getattrlist_fullpath_firmlinks,
    "getattrlist ATTR_CMN_FULLPATH with FSOPT_AUTOFIRMLINKPATH",
    T_META_ENABLED(TARGET_OS_OSX)) // test relies on macOS /System/Volumes/Data firmlink path
{
	remove("/tmp/symlinktoroot");
	T_ASSERT_POSIX_SUCCESS(symlink("/", "/tmp/symlinktoroot"), "create symlink to /");

	char *firmlink_path = fast_realpath("/private", 0);
	T_ASSERT_EQ_STR(firmlink_path, "/private", "/private firmlink path should be /private");

	char *nofirmlink_path = fast_realpath("/private", FSOPT_NOFIRMLINKPATH);
	T_ASSERT_EQ_STR(nofirmlink_path, "/System/Volumes/Data/private", "/private nofirmlink path should be /System/Volumes/Data/private");

	test_realpath("/private", FSOPT_AUTOFIRMLINKPATH, firmlink_path);
	test_realpath("/System/Volumes/Data/private", FSOPT_AUTOFIRMLINKPATH, nofirmlink_path);

	test_realpath("/tmp/symlinktoroot/private", FSOPT_AUTOFIRMLINKPATH, firmlink_path);
	test_realpath("/tmp/symlinktoroot/System/Volumes/Data/private", FSOPT_AUTOFIRMLINKPATH, nofirmlink_path);

	test_realpath("/System/Volumes/Data/private/tmp/symlinktoroot/private", FSOPT_AUTOFIRMLINKPATH, firmlink_path);
	test_realpath("/System/Volumes/Data/private/tmp/symlinktoroot/System/Volumes/Data/private", FSOPT_AUTOFIRMLINKPATH, nofirmlink_path);

	remove("/tmp/symlinktoroot");
}