/*
 * Copyright (c) 2024 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

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

#include <stdlib.h>
#include <fcntl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <TargetConditionals.h>

#include <darwintest.h>
#include <darwintest/utils.h>

#if !TARGET_OS_OSX
#define FSTYPE_LIFS  "lifs"
#endif /* !TARGET_OS_OSX */

#define FSTYPE_MSDOS "msdos"
#define FSTYPE_APFS  "apfs"

/* rdar://137970358: Disable the test for now until the root cause was determined */
#if 0
#define RUN_TEST     ((TARGET_OS_OSX || TARGET_OS_IOS) && !TARGET_OS_XR)
#else
#define RUN_TEST     0
#endif

static char template[MAXPATHLEN];
static char *testdir = NULL;
static char *output_buffer = NULL;
static char image_path[PATH_MAX];
static char mount_path[PATH_MAX];
static char disk[PATH_MAX];

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

static void
get_mount_path(const char *diskid, char *path)
{
	int i, mntsize;
	struct statfs *mntbuf;
	char diskurl[NAME_MAX];

	if ((mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0) {
		T_FAIL("getmntinfo failure");
		return;
	}

	snprintf(diskurl, sizeof(diskurl), "/%s", diskid);

	for (i = 0; i < mntsize; i++) {
#if TARGET_OS_OSX
		/* check if this mount is one we want */
		if (strcmp(mntbuf[i].f_fstypename, FSTYPE_MSDOS)) {
			continue;
		}
#else
		/* check if this mount is one we want */
		if (strcmp(mntbuf[i].f_fstypename, FSTYPE_LIFS)) {
			continue;
		}

		/* validate fstype */
		if (strncmp(mntbuf[i].f_mntfromname, FSTYPE_MSDOS, strlen(FSTYPE_MSDOS))) {
			continue;
		}
#endif /* TARGET_OS_OSX */

		/* validate disk */
		if (strstr(mntbuf[i].f_mntfromname, diskurl) == NULL) {
			continue;
		}

		strlcpy(path, mntbuf[i].f_mntonname, PATH_MAX);
		return;
	}

	T_FAIL("Cannot find mount path");
}

/*
 * run an external program
 */
static int
do_exec(const char *cmd)
{
	FILE *fp;
	char *pos;
	char *buffer = output_buffer;
	int output_len = PATH_MAX;

	/* Open the command for reading. */
	fp = popen(cmd, "r");
	if (fp == NULL) {
		T_FAIL("Failed to run command");
		return -1;
	}

	/* Read the output a line at a time - output it. */
	while (fgets(buffer, output_len, fp) != NULL) {
		size_t bytes = strlen(buffer);

		buffer += bytes;
		output_len -= bytes;
	}

	/* replace last '\n' with '\0' */
	pos = strrchr(output_buffer, '\n');
	*pos = '\0';

	/* close */
	pclose(fp);
	return 0;
}

static void
cleanup(void)
{
	char args[PATH_MAX];

	if (mount_path[0] != '\0') {
		unmount(mount_path, MNT_FORCE);
	}
	if (disk[0] != '\0') {
		snprintf(args, sizeof(args), "diskutil eject %s", disk);
		do_exec(args);
	}
	if (image_path[0] != '\0') {
		unlink(image_path);
	}
	if (testdir) {
		rmdir(testdir);
	}
	if (output_buffer) {
		free(output_buffer);
	}
}

static void
test_getattrlist(const char *path, const char *fstypename, uint32_t mount_extflags)
{
	struct myattrbuf {
		uint32_t length;
		attribute_set_t returned_attrs;
		uint32_t mount_extflags;
		attrreference_t fstypename_ref;
		char fstypename[MFSTYPENAMELEN];
	} attrbuf;

	struct attrlist attrs = {
		.bitmapcount = ATTR_BIT_MAP_COUNT,
		.commonattr = ATTR_CMN_RETURNED_ATTRS,
		.volattr = ATTR_VOL_MOUNTEXTFLAGS | ATTR_VOL_FSTYPENAME,
	};

	T_LOG("Testing %s", path);

	T_ASSERT_POSIX_SUCCESS(getattrlist(path, &attrs, &attrbuf,
	    sizeof(attrbuf), FSOPT_REPORT_FULLSIZE | FSOPT_PACK_INVAL_ATTRS),
	    "Calling getattrlist");

	T_ASSERT_TRUE(attrbuf.length <= sizeof(attrbuf),
	    "Asserting attrbuf.length <= sizeof(attrbuf)");

	/* Verifing ATTR_VOL_FSTYPENAME and ATTR_VOL_MOUNTEXTFLAGS enabled */
	T_ASSERT_BITS_SET(attrbuf.returned_attrs.volattr, ATTR_VOL_FSTYPENAME | ATTR_VOL_MOUNTEXTFLAGS,
	    "Asserting ATTR_VOL_FSTYPENAME and ATTR_VOL_MOUNTEXTFLAGS was returned");

	/* Verifing ATTR_VOL_FSTYPENAME content */
	T_ASSERT_EQ(strncmp(attrbuf.fstypename, fstypename, strlen(fstypename)), 0,
	    "Asserting that fstypename matches");

	/* Verifing ATTR_VOL_MOUNTEXTFLAGS content */
	T_ASSERT_EQ(attrbuf.mount_extflags, mount_extflags,
	    "Asserting that mount_extflags matches");
}

T_DECL(getattrlist_mountextflags,
    "test ATTR_VOL_MOUNTEXTFLAGS")
{
#if (!RUN_TEST)
	T_SKIP("Test disabled for this platform");
#endif

	char *diskp = NULL;
	char args[PATH_MAX];

	image_path[0] = mount_path[0] = disk[0] = '\0';

	T_ATEND(cleanup);
	T_SETUPBEGIN;

	/* Allocate output buffer */
	output_buffer = malloc(PATH_MAX);

	/* Create test directory */
	snprintf(template, sizeof(template), "%s/getattrlist_mountextflags-XXXXXX", dt_tmpdir());
	T_ASSERT_POSIX_NOTNULL((testdir = mkdtemp(template)), "Creating test root dir");

	/* Create image path */
	snprintf(image_path, sizeof(image_path), "%s/msdos.dmg", testdir);

	/* Create disk image */
	snprintf(args, sizeof(args), "diskimagetool create -fs none -s 1m %s", image_path);
	T_ASSERT_POSIX_SUCCESS(do_exec(args), "Creating disk image %s", image_path);

	/* Attach disk image */
	snprintf(args, sizeof(args), "diskimagetool attach --external %s", image_path);
	T_ASSERT_POSIX_SUCCESS(do_exec(args), "Attaching disk image %s", image_path);

	/* Extract device identifier */
	T_ASSERT_POSIX_NOTNULL((diskp = strstr(output_buffer, "disk")), "Extracting device identifier: %s", diskp);
	strlcpy(disk, diskp, PATH_MAX);

	/* Execute newfs_msdos disk image */
	snprintf(args, sizeof(args), "newfs_msdos -v MSDOS %s", disk);
	T_ASSERT_POSIX_SUCCESS(do_exec(args), "Executing newfs_msdos on disk %s", disk);

	/* Mount disk image */
	snprintf(args, sizeof(args), "datest --mount --device %s", disk);
	T_ASSERT_POSIX_SUCCESS(do_exec(args), "Mounting disk image");

	/* Get the mount path */
	get_mount_path(disk, mount_path);
	T_ASSERT_NE_STR(mount_path, "", "Got msdos filesystem mount path %s", mount_path);

	T_SETUPEND;

	/* Testing existing directory */
	test_getattrlist("/", FSTYPE_APFS, 0);

#if TARGET_OS_OSX
	/* Testing Data volume directory */
	test_getattrlist("/private/var/tmp/", FSTYPE_APFS, MNT_EXT_ROOT_DATA_VOL);
#endif /* TARGET_OS_OSX */

	/* Testing msdos volume directory */
#if TARGET_OS_OSX
	test_getattrlist(mount_path, FSTYPE_MSDOS, MNT_EXT_FSKIT);
#else
	test_getattrlist(mount_path, FSTYPE_LIFS, MNT_EXT_FSKIT);
#endif /* TARGET_OS_OSX */
}
