/*
 * No copyright is claimed.  This code is in the public domain; do with
 * it what you wish.
 */
#ifndef UTIL_LINUX_MOUNTUTILS_H
#define UTIL_LINUX_MOUNTUTILS_H

#ifdef HAVE_LINUX_MOUNT_H
#include <linux/mount.h>
#include <linux/unistd.h>
#include <sys/syscall.h>
#include <inttypes.h>

/*
 * File descriptors based mount API
 */
#ifdef HAVE_MOUNTFD_API

/* Accepted by both open_tree() and mount_setattr(). */
#ifndef AT_RECURSIVE
# define AT_RECURSIVE 0x8000
#endif

#ifndef OPEN_TREE_CLONE
# define OPEN_TREE_CLONE 1
#endif

#ifndef OPEN_TREE_CLOEXEC
# define OPEN_TREE_CLOEXEC O_CLOEXEC
#endif

#if !defined(HAVE_OPEN_TREE) && defined(SYS_open_tree)
static inline int open_tree(int dfd, const char *filename, unsigned int flags)
{
	return syscall(SYS_open_tree, dfd, filename, flags);
}
#endif

#ifndef MOVE_MOUNT_F_SYMLINKS
# define MOVE_MOUNT_F_SYMLINKS   0x00000001 /* Follow symlinks on from path */
#endif

#ifndef MOVE_MOUNT_F_AUTOMOUNTS
# define MOVE_MOUNT_F_AUTOMOUNTS 0x00000002 /* Follow automounts on from path */
#endif

#ifndef MOVE_MOUNT_F_EMPTY_PATH
# define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
#endif

#ifndef MOVE_MOUNT_T_SYMLINKS
# define MOVE_MOUNT_T_SYMLINKS   0x00000010 /* Follow symlinks on to path */
#endif

#ifndef MOVE_MOUNT_T_AUTOMOUNTS
# define MOVE_MOUNT_T_AUTOMOUNTS 0x00000020 /* Follow automounts on to path */
#endif

#ifndef MOVE_MOUNT_T_EMPTY_PATH
# define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 /* Empty to path permitted */
#endif

#ifndef MOVE_MOUNT_SET_GROUP
# define MOVE_MOUNT_SET_GROUP    0x00000100 /* Set sharing group instead */
#endif

#ifndef MOVE_MOUNT_BENEATH
# define MOVE_MOUNT_BENEATH      0x00000200 /* Mount beneath top mount */
#endif

#ifndef MOVE_MOUNT__MASK
# define MOVE_MOUNT__MASK        0x00000377
#endif

#if !defined(HAVE_MOVE_MOUNT) && defined(SYS_move_mount)
static inline int move_mount(int from_dfd, const char *from_pathname, int to_dfd,
			     const char *to_pathname, unsigned int flags)
{
	return syscall(SYS_move_mount, from_dfd, from_pathname, to_dfd,
		       to_pathname, flags);
}
#endif

#ifndef MOUNT_ATTR_RDONLY
# define MOUNT_ATTR_RDONLY 0x00000001
#endif

#ifndef MOUNT_ATTR_NOSUID
# define MOUNT_ATTR_NOSUID 0x00000002
#endif

#ifndef MOUNT_ATTR_NODEV
# define MOUNT_ATTR_NODEV 0x00000004
#endif

#ifndef MOUNT_ATTR_NOEXEC
# define MOUNT_ATTR_NOEXEC 0x00000008
#endif

#ifndef MOUNT_ATTR__ATIME
# define MOUNT_ATTR__ATIME 0x00000070
#endif

#ifndef MOUNT_ATTR_RELATIME
# define MOUNT_ATTR_RELATIME 0x00000000
#endif

#ifndef MOUNT_ATTR_NOATIME
# define MOUNT_ATTR_NOATIME 0x00000010
#endif

#ifndef MOUNT_ATTR_STRICTATIME
# define MOUNT_ATTR_STRICTATIME 0x00000020
#endif

#ifndef MOUNT_ATTR_NODIRATIME
# define MOUNT_ATTR_NODIRATIME 0x00000080
#endif

#ifndef MOUNT_ATTR_IDMAP
# define MOUNT_ATTR_IDMAP 0x00100000
#endif

#ifndef MOUNT_ATTR_NOSYMFOLLOW
# define MOUNT_ATTR_NOSYMFOLLOW 0x00200000
#endif

#ifndef HAVE_STRUCT_MOUNT_ATTR
# ifndef MOUNT_ATTR_SIZE_VER0 /* For case mount.h comes from a place invisible for autotools/meson */
struct mount_attr {
	uint64_t attr_set;
	uint64_t attr_clr;
	uint64_t propagation;
	uint64_t userns_fd;
};
# endif
#endif

#if !defined(HAVE_MOUNT_SETATTR) && defined(SYS_mount_setattr)
static inline int mount_setattr(int dfd, const char *path, unsigned int flags,
				struct mount_attr *attr, size_t size)
{
	return syscall(SYS_mount_setattr, dfd, path, flags, attr, size);
}
#endif

#ifndef HAVE_ENUM_FSCONFIG_COMMAND
# ifndef FSOPEN_CLOEXEC /* For case mount.h comes from a place invisible for autotools/meson */
enum fsconfig_command {
	FSCONFIG_SET_FLAG       = 0,    /* Set parameter, supplying no value */
#  define FSCONFIG_SET_FLAG FSCONFIG_SET_FLAG
	FSCONFIG_SET_STRING     = 1,    /* Set parameter, supplying a string value */
#  define FSCONFIG_SET_STRING FSCONFIG_SET_STRING
	FSCONFIG_SET_BINARY     = 2,    /* Set parameter, supplying a binary blob value */
#  define FSCONFIG_SET_BINARY FSCONFIG_SET_BINARY
	FSCONFIG_SET_PATH       = 3,    /* Set parameter, supplying an object by path */
#  define FSCONFIG_SET_PATH FSCONFIG_SET_PATH
	FSCONFIG_SET_PATH_EMPTY = 4,    /* Set parameter, supplying an object by (empty) path */
#  define FSCONFIG_SET_PATH_EMPTY FSCONFIG_SET_PATH_EMPTY
	FSCONFIG_SET_FD         = 5,    /* Set parameter, supplying an object by fd */
#  define FSCONFIG_SET_FD FSCONFIG_SET_FD
	FSCONFIG_CMD_CREATE     = 6,    /* Invoke superblock creation */
#  define FSCONFIG_CMD_CREATE FSCONFIG_CMD_CREATE
	FSCONFIG_CMD_RECONFIGURE = 7,   /* Invoke superblock reconfiguration */
#  define FSCONFIG_CMD_RECONFIGURE FSCONFIG_CMD_RECONFIGURE
	FSCONFIG_CMD_CREATE_EXCL = 8,    /* Create new superblock, fail if reusing existing superblock */
# define FSCONFIG_CMD_CREATE_EXCL FSCONFIG_CMD_CREATE_EXCL
};
# endif /* !FSOPEN_CLOEXEC */
#endif /*!HAVE_ENUM_FSCONFIG_COMMAND */

/*
 * Fallbacks for incomplete enum fsconfig_command in system headrs
 */
#ifndef FSCONFIG_CMD_CREATE_EXCL
# define FSCONFIG_CMD_CREATE_EXCL 8		/* since kernel 6.6 */
#endif

#if !defined(HAVE_FSCONFIG) && defined(SYS_fsconfig)
static inline int fsconfig(int fd, unsigned int cmd, const char *key,
                    const void *value, int aux)
{
        return syscall(SYS_fsconfig, fd, cmd, key, value, aux);
}
#endif

#ifndef FSOPEN_CLOEXEC
# define FSOPEN_CLOEXEC          0x00000001
#endif

#if !defined(HAVE_FSOPEN) && defined(SYS_fsopen)
static inline int fsopen(const char *fsname, unsigned int flags)
{
        return syscall(SYS_fsopen, fsname, flags);
}
#endif

#ifndef FSMOUNT_CLOEXEC
# define FSMOUNT_CLOEXEC         0x00000001
#endif

#if !defined(HAVE_FSMOUNT) && defined(SYS_fsmount)
static inline int fsmount(int fd, unsigned int flags, unsigned int mount_attrs)
{
        return syscall(SYS_fsmount, fd, flags, mount_attrs);
}
#endif

#ifndef FSPICK_CLOEXEC
# define FSPICK_CLOEXEC          0x00000001
#endif

#ifndef FSPICK_SYMLINK_NOFOLLOW
# define FSPICK_SYMLINK_NOFOLLOW 0x00000002
#endif

#ifndef FSPICK_NO_AUTOMOUNT
# define FSPICK_NO_AUTOMOUNT     0x00000004
#endif

#ifdef FSPICK_EMPTY_PATH
# define FSPICK_EMPTY_PATH       0x00000008
#endif

#if !defined(HAVE_FSPICK) && defined(SYS_fspick)
static inline int fspick(int dfd, const char *pathname, unsigned int flags)
{
        return syscall(SYS_fspick, dfd, pathname, flags);
}
#endif

#endif /* HAVE_MOUNTFD_API */

/*
 * statmount() and listmount()
 */
#ifdef HAVE_STATMOUNT_API

#ifndef MNT_ID_REQ_SIZE_VER0
# define MNT_ID_REQ_SIZE_VER0    24 /* sizeof first published struct */
#endif
#ifndef MNT_ID_REQ_SIZE_VER1
# define MNT_ID_REQ_SIZE_VER1    32 /* sizeof second published struct */
#endif

/*
 * The structs mnt_id_req and statmount may differ between kernel versions, so
 * we must ensure that the structs contain everything we need. For now (during
 * development), it seems best to define local copies of the structs to avoid
 * relying on installed kernel headers and to avoid a storm of #ifdefs.
 */

/*
 * listmount() and statmount() request
 */
struct ul_mnt_id_req {
	uint32_t size;
	uint32_t spare;
	uint64_t mnt_id;
	uint64_t param;
	uint64_t mnt_ns_id;
};

/*
 * Please note that due to the variable length of the statmount buffer, the
 * struct cannot be versioned by size (like struct mnt_id_req).
 */
struct ul_statmount {
	uint32_t size;            /* Total size, including strings */
	uint32_t mnt_opts;        /* [str] Mount options of the mount */
	uint64_t mask;            /* What results were written */
	uint32_t sb_dev_major;    /* Device ID */
	uint32_t sb_dev_minor;
	uint64_t sb_magic;        /* ..._SUPER_MAGIC */
	uint32_t sb_flags;        /* SB_{RDONLY,SYNCHRONOUS,DIRSYNC,LAZYTIME} */
	uint32_t fs_type;         /* [str] Filesystem type */
	uint64_t mnt_id;          /* Unique ID of mount */
	uint64_t mnt_parent_id;   /* Unique ID of parent (for root == mnt_id) */
	uint32_t mnt_id_old;      /* Reused IDs used in proc/.../mountinfo */
	uint32_t mnt_parent_id_old;
	uint64_t mnt_attr;        /* MOUNT_ATTR_... */
	uint64_t mnt_propagation; /* MS_{SHARED,SLAVE,PRIVATE,UNBINDABLE} */
	uint64_t mnt_peer_group;  /* ID of shared peer group */
	uint64_t mnt_master;      /* Mount receives propagation from this ID */
	uint64_t propagate_from;  /* Propagation from in current namespace */
	uint32_t mnt_root;        /* [str] Root of mount relative to root of fs */
	uint32_t mnt_point;       /* [str] Mountpoint relative to current root */
	uint64_t mnt_ns_id;       /* ID of the mount namespace */
	uint32_t fs_subtype;      /* [str] Subtype of fs_type (if any) */
	uint32_t sb_source;       /* [str] Source string of the mount */
	uint32_t opt_num;         /* Number of fs options */
	uint32_t opt_array;       /* [str] Array of nul terminated fs options */
	uint32_t opt_sec_num;     /* Number of security options */
	uint32_t opt_sec_array;   /* [str] Array of nul terminated security options */
	uint64_t supported_mask;  /* Mask flags that this kernel supports */
	uint32_t mnt_uidmap_num;  /* Number of uid mappings */
	uint32_t mnt_uidmap;      /* [str] Array of uid mappings (as seen from callers namespace) */
	uint32_t mnt_gidmap_num;  /* Number of gid mappings */
	uint32_t mnt_gidmap;      /* [str] Array of gid mappings (as seen from callers namespace) */
	uint64_t __spare2[43];
	char str[];               /* Variable size part containing strings */
};

/* sb_flags (defined in kernel include/linux/fs.h) */
#ifndef SB_RDONLY
# define SB_RDONLY       BIT(0)   /* Mount read-only */
# define SB_NOSUID       BIT(1)   /* Ignore suid and sgid bits */
# define SB_NODEV        BIT(2)   /* Disallow access to device special files */
# define SB_NOEXEC       BIT(3)   /* Disallow program execution */
# define SB_SYNCHRONOUS  BIT(4)   /* Writes are synced at once */
# define SB_MANDLOCK     BIT(6)   /* Allow mandatory locks on an FS */
# define SB_DIRSYNC      BIT(7)   /* Directory modifications are synchronous */
# define SB_NOATIME      BIT(10)  /* Do not update access times. */
# define SB_NODIRATIME   BIT(11)  /* Do not update directory access times */
# define SB_SILENT       BIT(15)
# define SB_POSIXACL     BIT(16)  /* Supports POSIX ACLs */
# define SB_INLINECRYPT  BIT(17)  /* Use blk-crypto for encrypted files */
# define SB_KERNMOUNT    BIT(22)  /* this is a kern_mount call */
# define SB_I_VERSION    BIT(23)  /* Update inode I_version field */
# define SB_LAZYTIME     BIT(25)  /* Update the on-disk [acm]times lazily */
#endif

/*
 * @mask bits for statmount(2)
 */
#ifndef STATMOUNT_SB_BASIC
# define STATMOUNT_SB_BASIC            0x00000001U   /* Want/got sb_... */
#endif
#ifndef STATMOUNT_MNT_BASIC
# define STATMOUNT_MNT_BASIC           0x00000002U   /* Want/got mnt_... */
#endif
#ifndef STATMOUNT_PROPAGATE_FROM
# define STATMOUNT_PROPAGATE_FROM      0x00000004U   /* Want/got propagate_from */
#endif
#ifndef STATMOUNT_MNT_ROOT
# define STATMOUNT_MNT_ROOT            0x00000008U   /* Want/got mnt_root  */
#endif
#ifndef STATMOUNT_MNT_POINT
# define STATMOUNT_MNT_POINT           0x00000010U   /* Want/got mnt_point */
#endif
#ifndef STATMOUNT_FS_TYPE
# define STATMOUNT_FS_TYPE             0x00000020U   /* Want/got fs_type */
#endif
#ifndef STATMOUNT_MNT_NS_ID
# define STATMOUNT_MNT_NS_ID           0x00000040U   /* Want/got mnt_ns_id */
#endif
#ifndef STATMOUNT_MNT_OPTS
# define STATMOUNT_MNT_OPTS            0x00000080U   /* Want/got mnt_opts */
#endif
#ifndef STATMOUNT_FS_SUBTYPE
# define STATMOUNT_FS_SUBTYPE          0x00000100U   /* Want/got fs_subtype */
#endif
#ifndef STATMOUNT_SB_SOURCE
# define STATMOUNT_SB_SOURCE           0x00000200U   /* Want/got sb_source */
#endif
#ifndef STATMOUNT_OPT_ARRAY
# define STATMOUNT_OPT_ARRAY           0x00000400U   /* Want/got opt_... */
#endif
#ifndef STATMOUNT_OPT_SEC_ARRAY
# define STATMOUNT_OPT_SEC_ARRAY       0x00000800U   /* Want/got opt_sec... */
#endif
#ifndef STATMOUNT_SUPPORTED_MASK
# define STATMOUNT_SUPPORTED_MASK      0x00001000U   /* Want/got supported mask flags */
#endif
#ifndef STATMOUNT_MNT_UIDMAP
# define STATMOUNT_MNT_UIDMAP          0x00002000U   /* Want/got uidmap... */
#endif
#ifndef STATMOUNT_MNT_GIDMAP
# define STATMOUNT_MNT_GIDMAP          0x00004000U   /* Want/got gidmap... */
#endif
/*
 * Special @mnt_id values that can be passed to listmount
 */
#ifndef LSMT_ROOT
# define LSMT_ROOT              0xffffffffffffffff    /* root mount */
#endif

#ifndef LISTMOUNT_REVERSE
# define LISTMOUNT_REVERSE      BIT(0)               /* List later mounts first */
#endif

/* Don't use this "raw" version. See ul_statmount() below. */
#if defined(SYS_statmount)
static inline int ul_statmount_syscall(uint64_t mnt_id,
			uint64_t ns_id,
			uint64_t mask,
			struct ul_statmount *buf,
			size_t bufsize, unsigned int flags)
{
       struct ul_mnt_id_req req = {
		.size = MNT_ID_REQ_SIZE_VER1,
		.mnt_id = mnt_id,
		.param = mask,
		.mnt_ns_id = ns_id
       };

       return syscall(SYS_statmount, &req, buf, bufsize, flags);
}

static inline int has_statmount(void)
{
	errno = 0;

	if (ul_statmount_syscall(0, 0, 0, NULL, 0, 0) < 0 && errno == ENOSYS)
		return 0;
	return 1;
}

/* This is a version of statmount() that reallocates @buf to be large enough to
 * store data for the requested @id. This function never deallocates; it is the
 * caller's responsibility.
 */
static inline int ul_statmount(uint64_t id,
			uint64_t ns_id,
			uint64_t mask,
			struct ul_statmount **buf,
			size_t *bufsiz,
			unsigned int flags)
{
	size_t sz;
	int rc = 0;

	if (!buf || !bufsiz)
		return -EINVAL;

	sz = *bufsiz;
	if (!sz)
		sz = 32 * 1024;

	do {
		if (sz > *bufsiz) {
			struct ul_statmount *tmp = realloc(*buf, sz);
			if (!tmp)
				return -ENOMEM;
			*buf = tmp;
			*bufsiz = sz;
		}

		errno = 0;
		rc = ul_statmount_syscall(id, ns_id, mask, *buf, *bufsiz, flags);
		if (!rc)
			break;
		if (errno != EOVERFLOW)
			break;
		if (sz >= SIZE_MAX / 2)
			break;
		sz <<= 1;
	} while (rc);

	return rc;
}
#endif /* SYS_statmount */


#if defined(SYS_listmount)
static inline ssize_t ul_listmount(uint64_t mnt_id,
			uint64_t ns_id,
			uint64_t last_mnt_id,
			uint64_t list[], size_t num,
			unsigned int flags)
{
       struct ul_mnt_id_req req = {
		.size = MNT_ID_REQ_SIZE_VER1,
		.mnt_id = mnt_id,
		.param = last_mnt_id,
		.mnt_ns_id = ns_id
       };

       return syscall(SYS_listmount, &req, list, num, flags);
}

static inline int has_listmount(void)
{
	uint64_t dummy;

	errno = 0;

	if (ul_listmount(LSMT_ROOT, 0, 0, &dummy, 1, LISTMOUNT_REVERSE) != 1)
		return 0;
	return 1;
}
#endif

#endif /* HAVE_STATMOUNT_API */
#endif /* HAVE_LINUX_MOUNT_H */

/*
 * Default NTFS mount type (used by libmount and libblkid)
 */
#ifndef CONFIG_UL_NTFS_MOUNTTYPE
# define CONFIG_UL_NTFS_MOUNTTYPE "ntfs3"
#endif

/*
 * Convert FS-type (as provided by libblkid or udev) to the preferred
 * kernel FS driver (type used to mount the FS).
 *
 * This is a temporary solution; the final solution should be based on config
 * files like /etc/mount/fs.d/<name> (from lib/configs.c) and managed by
 * libmount.
 */
static inline const char *ul_fstype_to_mounttype(const char *fstype)
{
	if (!fstype)
		return NULL;

	if (strcmp(fstype, "ntfs") == 0)
		return CONFIG_UL_NTFS_MOUNTTYPE;

	return NULL;
}
#endif /* UTIL_LINUX_MOUNTUTILS_H */
