diff mbox series

[RFC,v7,21/21] um: nommu: add block device support of UML

Message ID a8686efcbad3c445d3e5bde054c04a58877915f1.1601960644.git.thehajime@gmail.com
State Not Applicable
Headers show
Series [RFC,v7,01/21] um: split build in kernel and host parts | expand

Commit Message

Hajime Tazaki Oct. 6, 2020, 9:44 a.m. UTC
This commit adds block device support for nommu mode, and also added
several host utilities to run a simple test program
(tools/um/test/disk.c) successfully.

Signed-off-by: Hajime Tazaki <thehajime@gmail.com>
---
 arch/um/include/asm/xor.h                 |   3 +-
 arch/um/include/shared/as-layout.h        |   1 +
 arch/um/kernel/um_arch.c                  |   5 +
 arch/um/nommu/include/uapi/asm/host_ops.h |   7 +
 arch/um/nommu/include/uapi/asm/unistd.h   |   2 +
 arch/um/nommu/um/Kconfig                  |   8 +
 arch/um/nommu/um/setup.c                  |  12 +
 tools/um/Targets                          |   2 +
 tools/um/include/lkl.h                    | 206 ++++++++++
 tools/um/include/lkl_host.h               |   1 +
 tools/um/lib/Build                        |   1 +
 tools/um/lib/fs.c                         | 461 ++++++++++++++++++++++
 tools/um/lib/posix-host.c                 |   1 +
 tools/um/lib/utils.c                      |   3 +
 tools/um/tests/Build                      |   1 +
 tools/um/tests/cla.c                      | 159 ++++++++
 tools/um/tests/cla.h                      |  33 ++
 tools/um/tests/disk.c                     | 168 ++++++++
 tools/um/tests/disk.sh                    |  70 ++++
 tools/um/tests/run.py                     |   2 +
 20 files changed, 1145 insertions(+), 1 deletion(-)
 create mode 100644 tools/um/lib/fs.c
 create mode 100644 tools/um/tests/cla.c
 create mode 100644 tools/um/tests/cla.h
 create mode 100644 tools/um/tests/disk.c
 create mode 100755 tools/um/tests/disk.sh

Comments

Anton Ivanov Oct. 7, 2020, 2:17 p.m. UTC | #1
On 06/10/2020 10:44, Hajime Tazaki wrote:
> This commit adds block device support for nommu mode, and also added
> several host utilities to run a simple test program
> (tools/um/test/disk.c) successfully.
>
> Signed-off-by: Hajime Tazaki <thehajime@gmail.com>
> ---
>   arch/um/include/asm/xor.h                 |   3 +-
>   arch/um/include/shared/as-layout.h        |   1 +
>   arch/um/kernel/um_arch.c                  |   5 +
>   arch/um/nommu/include/uapi/asm/host_ops.h |   7 +
>   arch/um/nommu/include/uapi/asm/unistd.h   |   2 +
>   arch/um/nommu/um/Kconfig                  |   8 +
>   arch/um/nommu/um/setup.c                  |  12 +
>   tools/um/Targets                          |   2 +
>   tools/um/include/lkl.h                    | 206 ++++++++++
>   tools/um/include/lkl_host.h               |   1 +
>   tools/um/lib/Build                        |   1 +
>   tools/um/lib/fs.c                         | 461 ++++++++++++++++++++++
>   tools/um/lib/posix-host.c                 |   1 +
>   tools/um/lib/utils.c                      |   3 +
>   tools/um/tests/Build                      |   1 +
>   tools/um/tests/cla.c                      | 159 ++++++++
>   tools/um/tests/cla.h                      |  33 ++
>   tools/um/tests/disk.c                     | 168 ++++++++
>   tools/um/tests/disk.sh                    |  70 ++++
>   tools/um/tests/run.py                     |   2 +
>   20 files changed, 1145 insertions(+), 1 deletion(-)
>   create mode 100644 tools/um/lib/fs.c
>   create mode 100644 tools/um/tests/cla.c
>   create mode 100644 tools/um/tests/cla.h
>   create mode 100644 tools/um/tests/disk.c
>   create mode 100755 tools/um/tests/disk.sh
>
> diff --git a/arch/um/include/asm/xor.h b/arch/um/include/asm/xor.h
> index 36b33d62a35d..a5ea458a1ae9 100644
> --- a/arch/um/include/asm/xor.h
> +++ b/arch/um/include/asm/xor.h
> @@ -4,4 +4,5 @@
>   
>   /* pick an arbitrary one - measuring isn't possible with inf-cpu */
>   #define XOR_SELECT_TEMPLATE(x)	\
> -	(time_travel_mode == TT_MODE_INFCPU ? &xor_block_8regs : NULL)
> +	(time_travel_mode == TT_MODE_INFCPU || (!IS_ENABLED(CONFIG_MMU)) ? \
> +	 &xor_block_8regs : NULL)
> diff --git a/arch/um/include/shared/as-layout.h b/arch/um/include/shared/as-layout.h
> index 5f286ef2721b..4423437a5ace 100644
> --- a/arch/um/include/shared/as-layout.h
> +++ b/arch/um/include/shared/as-layout.h
> @@ -57,6 +57,7 @@ extern unsigned long host_task_size;
>   
>   extern int linux_main(int argc, char **argv);
>   extern void uml_finishsetup(void);
> +extern void uml_set_args(char *args);
>   
>   struct siginfo;
>   extern void (*sig_info[])(int, struct siginfo *si, struct uml_pt_regs *);
> diff --git a/arch/um/kernel/um_arch.c b/arch/um/kernel/um_arch.c
> index e2cb76c03b25..da6e06cf2808 100644
> --- a/arch/um/kernel/um_arch.c
> +++ b/arch/um/kernel/um_arch.c
> @@ -41,6 +41,11 @@ static void __init add_arg(char *arg)
>   	strcat(command_line, arg);
>   }
>   
> +void __init uml_set_args(char *args)
> +{
> +	strcat(command_line, args);
> +}
> +
>   /*
>    * These fields are initialized at boot time and not changed.
>    * XXX This structure is used only in the non-SMP case.  Maybe this
> diff --git a/arch/um/nommu/include/uapi/asm/host_ops.h b/arch/um/nommu/include/uapi/asm/host_ops.h
> index 0811494c080c..1b2507502969 100644
> --- a/arch/um/nommu/include/uapi/asm/host_ops.h
> +++ b/arch/um/nommu/include/uapi/asm/host_ops.h
> @@ -15,6 +15,11 @@ struct lkl_jmp_buf {
>    *
>    * These operations must be provided by a host library or by the application
>    * itself.
> + *
> + * @um_devices - string containg the list of UML devices in command line
> + * format. This string is appended to the kernel command line and
> + * is provided here for convenience to be implemented by the host library.
> + *
>    * @print - optional operation that receives console messages
>    * @panic - called during a kernel panic
>    *
> @@ -65,6 +70,8 @@ struct lkl_jmp_buf {
>    *
>    */
>   struct lkl_host_operations {
> +	const char *um_devices;
> +
>   	void (*print)(const char *str, int len);
>   	void (*panic)(void);
>   
> diff --git a/arch/um/nommu/include/uapi/asm/unistd.h b/arch/um/nommu/include/uapi/asm/unistd.h
> index 1762ebb829f5..320762099a62 100644
> --- a/arch/um/nommu/include/uapi/asm/unistd.h
> +++ b/arch/um/nommu/include/uapi/asm/unistd.h
> @@ -3,6 +3,8 @@
>   #define __UM_NOMMU_UAPI_UNISTD_H
>   
>   #define __ARCH_WANT_NEW_STAT
> +#define __ARCH_WANT_SET_GET_RLIMIT
> +
>   #include <asm/bitsperlong.h>
>   
>   #if __BITS_PER_LONG == 64
> diff --git a/arch/um/nommu/um/Kconfig b/arch/um/nommu/um/Kconfig
> index 20b3eaccb6f0..c6a3f472fe75 100644
> --- a/arch/um/nommu/um/Kconfig
> +++ b/arch/um/nommu/um/Kconfig
> @@ -4,6 +4,10 @@ config UML_NOMMU
>   	select UACCESS_MEMCPY
>   	select ARCH_THREAD_STACK_ALLOCATOR
>   	select ARCH_HAS_SYSCALL_WRAPPER
> +	select VFAT_FS
> +	select NLS_CODEPAGE_437
> +	select NLS_ISO8859_1
> +	select BTRFS_FS
>   
>   config 64BIT
>   	bool
> @@ -35,3 +39,7 @@ config STACKTRACE_SUPPORT
>   config PRINTK_TIME
>   	bool
>   	default y
> +
> +config RAID6_PQ_BENCHMARK
> +	bool
> +	default n

Why are we touching this? I thought this is already defined in lib/Kconfig?

Brgds,

A.

> diff --git a/arch/um/nommu/um/setup.c b/arch/um/nommu/um/setup.c
> index 922188690139..d71d61979ad6 100644
> --- a/arch/um/nommu/um/setup.c
> +++ b/arch/um/nommu/um/setup.c
> @@ -45,13 +45,25 @@ static void __init *lkl_run_kernel(void *arg)
>   	return NULL;
>   }
>   
> +static char _cmd_line[COMMAND_LINE_SIZE];
>   int __init lkl_start_kernel(struct lkl_host_operations *ops,
>   			    const char *fmt, ...)
>   {
> +	va_list ap;
>   	int ret;
>   
>   	lkl_ops = ops;
>   
> +	va_start(ap, fmt);
> +	ret = vsnprintf(_cmd_line, COMMAND_LINE_SIZE, fmt, ap);
> +	va_end(ap);
> +
> +	if (ops->um_devices)
> +		strscpy(_cmd_line + ret, ops->um_devices,
> +			COMMAND_LINE_SIZE - ret);
> +
> +	uml_set_args(_cmd_line);
> +
>   	init_sem = lkl_ops->sem_alloc(0);
>   	if (!init_sem)
>   		return -ENOMEM;
> diff --git a/tools/um/Targets b/tools/um/Targets
> index 2bb90381843c..f5f8ec4b9dbb 100644
> --- a/tools/um/Targets
> +++ b/tools/um/Targets
> @@ -1,10 +1,12 @@
>   ifeq ($(UMMODE),library)
>   progs-y += tests/boot
> +progs-y += tests/disk
>   else
>   progs-y += uml/linux
>   endif
>   
>   LDFLAGS_boot-y := -pie
> +LDFLAGS_disk-y := -pie
>   
>   LDLIBS := -lrt -lpthread -lutil
>   LDFLAGS_linux-y := -no-pie -Wl,--wrap,malloc -Wl,--wrap,free -Wl,--wrap,calloc
> diff --git a/tools/um/include/lkl.h b/tools/um/include/lkl.h
> index 707e01b64a70..4a9d874bfbf0 100644
> --- a/tools/um/include/lkl.h
> +++ b/tools/um/include/lkl.h
> @@ -113,6 +113,16 @@ static inline long lkl_sys_creat(const char *file, int mode)
>   }
>   #endif
>   
> +#ifdef __lkl__NR_faccessat
> +/**
> + * lkl_sys_access - wrapper for lkl_sys_faccessat
> + */
> +static inline long lkl_sys_access(const char *file, int mode)
> +{
> +	return lkl_sys_faccessat(LKL_AT_FDCWD, file, mode);
> +}
> +#endif
> +
>   #ifdef __lkl__NR_mkdirat
>   /**
>    * lkl_sys_mkdir - wrapper for lkl_sys_mkdirat
> @@ -123,6 +133,36 @@ static inline long lkl_sys_mkdir(const char *path, mode_t mode)
>   }
>   #endif
>   
> +#ifdef __lkl__NR_unlinkat
> +/**
> + * lkl_sys_rmdir - wrapper for lkl_sys_unlinkrat
> + */
> +static inline long lkl_sys_rmdir(const char *path)
> +{
> +	return lkl_sys_unlinkat(LKL_AT_FDCWD, path, LKL_AT_REMOVEDIR);
> +}
> +#endif
> +
> +#ifdef __lkl__NR_unlinkat
> +/**
> + * lkl_sys_unlink - wrapper for lkl_sys_unlinkat
> + */
> +static inline long lkl_sys_unlink(const char *path)
> +{
> +	return lkl_sys_unlinkat(LKL_AT_FDCWD, path, 0);
> +}
> +#endif
> +
> +#ifdef __lkl__NR_mknodat
> +/**
> + * lkl_sys_mknod - wrapper for lkl_sys_mknodat
> + */
> +static inline long lkl_sys_mknod(const char *path, mode_t mode, dev_t dev)
> +{
> +	return lkl_sys_mknodat(LKL_AT_FDCWD, path, mode, dev);
> +}
> +#endif
> +
>   #ifdef __lkl__NR_epoll_create1
>   /**
>    * lkl_sys_epoll_create - wrapper for lkl_sys_epoll_create1
> @@ -144,6 +184,172 @@ static inline long lkl_sys_epoll_wait(int fd, struct lkl_epoll_event *ev,
>   }
>   #endif
>   
> +/**
> + * struct lkl_dev_blk_ops - block device host operations, defined in lkl_host.h.
> + */
> +struct lkl_dev_blk_ops;
> +
> +/**
> + * lkl_disk - host disk handle
> + *
> + * @dev - a pointer to private information for this disk backend
> + * @fd - a POSIX file descriptor that can be used by preadv/pwritev
> + * @handle - an NT file handle that can be used by ReadFile/WriteFile
> + */
> +struct lkl_disk {
> +	void *dev;
> +	union {
> +		int fd;
> +		void *handle;
> +	};
> +	struct lkl_dev_blk_ops *ops;
> +};
> +
> +/**
> + * lkl_disk_add - add a new disk
> + *
> + * @disk - the host disk handle
> + * @returns a disk id (0 is valid) or a strictly negative value in case of error
> + */
> +int lkl_disk_add(struct lkl_disk *disk);
> +
> +/**
> + * lkl_disk_remove - remove a disk
> + *
> + * This function makes a cleanup of the @disk's private information
> + * that was initialized by lkl_disk_add before.
> + *
> + * @disk - the host disk handle
> + */
> +int lkl_disk_remove(struct lkl_disk disk);
> +
> +/**
> + * lkl_encode_dev_from_sysfs_blkdev - extract device id from sysfs
> + *
> + * This function returns the device id for the given sysfs dev node.
> + * The content of the node has to be in the form 'MAJOR:MINOR'.
> + * Also, this function expects an absolute path which means that sysfs
> + * already has to be mounted at the given path
> + *
> + * @sysfs_path - absolute path to the sysfs dev node
> + * @pdevid - pointer to memory where dev id will be returned
> + * @returns - 0 on success, a negative value on error
> + */
> +int lkl_encode_dev_from_sysfs(const char *sysfs_path, uint32_t *pdevid);
> +
> +/**
> + * lkl_mount_dev - mount a disk
> + *
> + * This functions creates a device file for the given disk, creates a mount
> + * point and mounts the device over the mount point.
> + *
> + * @disk_id - the disk id identifying the disk to be mounted
> + * @part - disk partition or zero for full disk
> + * @fs_type - filesystem type
> + * @flags - mount flags
> + * @opts - additional filesystem specific mount options
> + * @mnt_str - a string that will be filled by this function with the path where
> + * the filesystem has been mounted
> + * @mnt_str_len - size of mnt_str
> + * @returns - 0 on success, a negative value on error
> + */
> +long lkl_mount_dev(unsigned int disk_id, unsigned int part, const char *fs_type,
> +		   int flags, const char *opts,
> +		   char *mnt_str, unsigned int mnt_str_len);
> +
> +/**
> + * lkl_umount_dev - umount a disk
> + *
> + * This functions umounts the given disks and removes the device file and the
> + * mount point.
> + *
> + * @disk_id - the disk id identifying the disk to be mounted
> + * @part - disk partition or zero for full disk
> + * @flags - umount flags
> + * @timeout_ms - timeout to wait for the kernel to flush closed files so that
> + * umount can succeed
> + * @returns - 0 on success, a negative value on error
> + */
> +long lkl_umount_dev(unsigned int disk_id, unsigned int part, int flags,
> +		    long timeout_ms);
> +
> +/**
> + * lkl_umount_timeout - umount filesystem with timeout
> + *
> + * @path - the path to unmount
> + * @flags - umount flags
> + * @timeout_ms - timeout to wait for the kernel to flush closed files so that
> + * umount can succeed
> + * @returns - 0 on success, a negative value on error
> + */
> +long lkl_umount_timeout(char *path, int flags, long timeout_ms);
> +
> +/**
> + * lkl_opendir - open a directory
> + *
> + * @path - directory path
> + * @err - pointer to store the error in case of failure
> + * @returns - a handle to be used when calling lkl_readdir
> + */
> +struct lkl_dir *lkl_opendir(const char *path, int *err);
> +
> +/**
> + * lkl_fdopendir - open a directory
> + *
> + * @fd - file descriptor
> + * @err - pointer to store the error in case of failure
> + * @returns - a handle to be used when calling lkl_readdir
> + */
> +struct lkl_dir *lkl_fdopendir(int fd, int *err);
> +
> +/**
> + * lkl_rewinddir - reset directory stream
> + *
> + * @dir - the directory handler as returned by lkl_opendir
> + */
> +void lkl_rewinddir(struct lkl_dir *dir);
> +
> +/**
> + * lkl_closedir - close the directory
> + *
> + * @dir - the directory handler as returned by lkl_opendir
> + */
> +int lkl_closedir(struct lkl_dir *dir);
> +
> +/**
> + * lkl_readdir - get the next available entry of the directory
> + *
> + * @dir - the directory handler as returned by lkl_opendir
> + * @returns - a lkl_dirent64 entry or NULL if the end of the directory stream is
> + * reached or if an error occurred; check lkl_errdir() to distinguish between
> + * errors or end of the directory stream
> + */
> +struct lkl_linux_dirent64 *lkl_readdir(struct lkl_dir *dir);
> +
> +/**
> + * lkl_errdir - checks if an error occurred during the last lkl_readdir call
> + *
> + * @dir - the directory handler as returned by lkl_opendir
> + * @returns - 0 if no error occurred, or a negative value otherwise
> + */
> +int lkl_errdir(struct lkl_dir *dir);
> +
> +/**
> + * lkl_dirfd - gets the file descriptor associated with the directory handle
> + *
> + * @dir - the directory handle as returned by lkl_opendir
> + * @returns - a positive value,which is the LKL file descriptor associated with
> + * the directory handle, or a negative value otherwise
> + */
> +int lkl_dirfd(struct lkl_dir *dir);
> +
> +/**
> + * lkl_mount_fs - mount a file system type like proc, sys
> + * @fstype - file system type. e.g. proc, sys
> + * @returns - 0 on success. 1 if it's already mounted. negative on failure.
> + */
> +int lkl_mount_fs(char *fstype);
> +
>   #ifdef __cplusplus
>   }
>   #endif
> diff --git a/tools/um/include/lkl_host.h b/tools/um/include/lkl_host.h
> index 85e80eb4ad0d..12dd95616e43 100644
> --- a/tools/um/include/lkl_host.h
> +++ b/tools/um/include/lkl_host.h
> @@ -10,6 +10,7 @@ extern "C" {
>   #include <lkl.h>
>   
>   extern struct lkl_host_operations lkl_host_ops;
> +extern char lkl_um_devs[4096];
>   
>   /**
>    * lkl_printf - print a message via the host print operation
> diff --git a/tools/um/lib/Build b/tools/um/lib/Build
> index dddff26a3b4e..29491b40746c 100644
> --- a/tools/um/lib/Build
> +++ b/tools/um/lib/Build
> @@ -4,3 +4,4 @@ CFLAGS_posix-host.o += -D_FILE_OFFSET_BITS=64
>   liblinux-$(CONFIG_UMMODE_LIB) += utils.o
>   liblinux-$(CONFIG_UMMODE_LIB) += posix-host.o
>   liblinux-$(CONFIG_UMMODE_LIB) += jmp_buf.o
> +liblinux-$(CONFIG_UMMODE_LIB) += fs.o
> diff --git a/tools/um/lib/fs.c b/tools/um/lib/fs.c
> new file mode 100644
> index 000000000000..948aac9730c2
> --- /dev/null
> +++ b/tools/um/lib/fs.c
> @@ -0,0 +1,461 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <stdarg.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <stdlib.h>
> +#include <lkl_host.h>
> +
> +#define MAX_FSTYPE_LEN 50
> +
> +static struct lkl_disk *lkl_disks[16];
> +static int registered_blk_dev_idx;
> +
> +static int lkl_disk_um_add(struct lkl_disk *disk, const char *blkparams)
> +{
> +	/* concat strings */
> +	snprintf(lkl_um_devs + strlen(lkl_um_devs), sizeof(lkl_um_devs),
> +		 " ubd%d=%s", registered_blk_dev_idx, blkparams);
> +
> +	return registered_blk_dev_idx++;
> +}
> +
> +int lkl_disk_add(struct lkl_disk *disk)
> +{
> +	int ret = -1;
> +
> +	ret = lkl_disk_um_add(disk, disk->dev);
> +
> +	lkl_disks[ret] = disk;
> +
> +	return ret;
> +}
> +
> +int lkl_disk_remove(struct lkl_disk disk)
> +{
> +	/* FIXME */
> +	return 0;
> +}
> +
> +int lkl_mount_fs(char *fstype)
> +{
> +	char dir[MAX_FSTYPE_LEN+2] = "/";
> +	int flags = 0, ret = 0;
> +
> +	strncat(dir, fstype, MAX_FSTYPE_LEN);
> +
> +	/* Create with regular umask */
> +	ret = lkl_sys_mkdir(dir, 0xff);
> +	if (ret && ret != -LKL_EEXIST) {
> +		lkl_perror("mount_fs mkdir", ret);
> +		return ret;
> +	}
> +
> +	/* We have no use for nonzero flags right now */
> +	ret = lkl_sys_mount("none", dir, fstype, flags, NULL);
> +	if (ret && ret != -LKL_EBUSY) {
> +		lkl_sys_rmdir(dir);
> +		return ret;
> +	}
> +
> +	if (ret == -LKL_EBUSY)
> +		return 1;
> +	return 0;
> +}
> +
> +static uint32_t new_encode_dev(unsigned int major, unsigned int minor)
> +{
> +	return (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12);
> +}
> +
> +static int startswith(const char *str, const char *pre)
> +{
> +	return strncmp(pre, str, strlen(pre)) == 0;
> +}
> +
> +static int get_node_with_prefix(const char *path, const char *prefix,
> +				char *result, unsigned int result_len)
> +{
> +	struct lkl_dir *dir = NULL;
> +	struct lkl_linux_dirent64 *dirent;
> +	int ret;
> +
> +	dir = lkl_opendir(path, &ret);
> +	if (!dir)
> +		return ret;
> +
> +	ret = -LKL_ENOENT;
> +
> +	while ((dirent = lkl_readdir(dir))) {
> +		if (startswith(dirent->d_name, prefix)) {
> +			if (strlen(dirent->d_name) + 1 > result_len) {
> +				ret = -LKL_ENOMEM;
> +				break;
> +			}
> +			memcpy(result, dirent->d_name, strlen(dirent->d_name));
> +			result[strlen(dirent->d_name)] = '\0';
> +			ret = 0;
> +			break;
> +		}
> +	}
> +
> +	lkl_closedir(dir);
> +
> +	return ret;
> +}
> +
> +int lkl_encode_dev_from_sysfs(const char *sysfs_path, uint32_t *pdevid)
> +{
> +	int ret;
> +	long fd;
> +	int major, minor;
> +	char buf[16] = { 0, };
> +	char *bufptr;
> +
> +	fd = lkl_sys_open(sysfs_path, LKL_O_RDONLY, 0);
> +	if (fd < 0)
> +		return fd;
> +
> +	ret = lkl_sys_read(fd, buf, sizeof(buf));
> +	if (ret < 0)
> +		goto out_close;
> +
> +	if (ret == sizeof(buf)) {
> +		ret = -LKL_ENOBUFS;
> +		goto out_close;
> +	}
> +
> +	bufptr = strchr(buf, ':');
> +	if (bufptr == NULL) {
> +		ret = -LKL_EINVAL;
> +		goto out_close;
> +	}
> +	bufptr[0] = '\0';
> +	bufptr++;
> +
> +	major = atoi(buf);
> +	minor = atoi(bufptr);
> +
> +	*pdevid = new_encode_dev(major, minor);
> +	ret = 0;
> +
> +out_close:
> +	lkl_sys_close(fd);
> +
> +	return ret;
> +}
> +
> +#define SYSFS_DEV_UMBLK_CMDLINE_PATH \
> +	"/sysfs/devices/platform/uml-blkdev.%d"
> +
> +struct abuf {
> +	char *mem, *ptr;
> +	unsigned int len;
> +};
> +
> +static int snprintf_append(struct abuf *buf, const char *fmt, ...)
> +{
> +	int ret;
> +	va_list args;
> +
> +	if (!buf->ptr)
> +		buf->ptr = buf->mem;
> +
> +	va_start(args, fmt);
> +	ret = vsnprintf(buf->ptr, buf->len - (buf->ptr - buf->mem), fmt, args);
> +	va_end(args);
> +
> +	if (ret < 0 || (ret >= (int)(buf->len - (buf->ptr - buf->mem))))
> +		return -LKL_ENOMEM;
> +
> +	buf->ptr += ret;
> +
> +	return 0;
> +}
> +
> +static int __lkl_get_blkdev(int disk_id, unsigned int part, uint32_t *pdevid,
> +			    const char *sysfs_path_fmt, const char *drv_prefix,
> +			    const char *disk_prefix)
> +{
> +	char sysfs_path[LKL_PATH_MAX];
> +	char drv_name[LKL_PATH_MAX];
> +	char disk_name[LKL_PATH_MAX];
> +	struct abuf sysfs_path_buf = {
> +		.mem = sysfs_path,
> +		.len = sizeof(sysfs_path),
> +	};
> +	int ret;
> +
> +	if (disk_id < 0)
> +		return -LKL_EINVAL;
> +
> +	ret = lkl_mount_fs("sysfs");
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = snprintf_append(&sysfs_path_buf, sysfs_path_fmt, disk_id);
> +	if (ret)
> +		return ret;
> +
> +	ret = get_node_with_prefix(sysfs_path, drv_prefix, drv_name,
> +				   sizeof(drv_name));
> +	if (ret)
> +		return ret;
> +
> +	ret = snprintf_append(&sysfs_path_buf, "/%s/block", drv_name);
> +	if (ret)
> +		return ret;
> +
> +	ret = get_node_with_prefix(sysfs_path, disk_prefix, disk_name,
> +				   sizeof(disk_name));
> +	if (ret)
> +		return ret;
> +
> +	if (!part)
> +		ret = snprintf_append(&sysfs_path_buf, "/%s/dev", disk_name);
> +	else
> +		ret = snprintf_append(&sysfs_path_buf, "/%s/%s%d/dev",
> +				      disk_name, disk_name, part);
> +	if (ret)
> +		return ret;
> +
> +	return lkl_encode_dev_from_sysfs(sysfs_path, pdevid);
> +}
> +
> +int lkl_get_blkdev(int disk_id, unsigned int part, uint32_t *pdevid)
> +{
> +	char *fmt;
> +
> +	fmt = SYSFS_DEV_UMBLK_CMDLINE_PATH;
> +	return __lkl_get_blkdev(disk_id, part, pdevid, fmt, "", "ubd");
> +}
> +
> +long lkl_mount_dev(unsigned int disk_id, unsigned int part,
> +		   const char *fs_type, int flags,
> +		   const char *data, char *mnt_str, unsigned int mnt_str_len)
> +{
> +	char dev_str[] = { "/dev/xxxxxxxx" };
> +	unsigned int dev;
> +	int err;
> +	char _data[4096]; /* FIXME: PAGE_SIZE is not exported by LKL */
> +
> +	if (mnt_str_len < sizeof(dev_str))
> +		return -LKL_ENOMEM;
> +
> +	err = lkl_get_blkdev(disk_id, part, &dev);
> +	if (err < 0)
> +		return err;
> +
> +	snprintf(dev_str, sizeof(dev_str), "/dev/%08x", dev);
> +	snprintf(mnt_str, mnt_str_len, "/mnt/%08x", dev);
> +
> +	err = lkl_sys_access("/dev", LKL_S_IRWXO);
> +	if (err < 0) {
> +		if (err == -LKL_ENOENT)
> +			err = lkl_sys_mkdir("/dev", 0700);
> +		if (err < 0)
> +			return err;
> +	}
> +
> +	err = lkl_sys_mknod(dev_str, LKL_S_IFBLK | 0600, dev);
> +	if (err < 0)
> +		return err;
> +
> +	err = lkl_sys_access("/mnt", LKL_S_IRWXO);
> +	if (err < 0) {
> +		if (err == -LKL_ENOENT)
> +			err = lkl_sys_mkdir("/mnt", 0700);
> +		if (err < 0)
> +			return err;
> +	}
> +
> +	err = lkl_sys_mkdir(mnt_str, 0700);
> +	if (err < 0) {
> +		lkl_sys_unlink(dev_str);
> +		return err;
> +	}
> +
> +	/* kernel always copies a full page */
> +	if (data) {
> +		strncpy(_data, data, sizeof(_data));
> +		_data[sizeof(_data) - 1] = 0;
> +	} else {
> +		_data[0] = 0;
> +	}
> +
> +	err = lkl_sys_mount(dev_str, mnt_str, (char *)fs_type, flags, _data);
> +	if (err < 0) {
> +		lkl_sys_unlink(dev_str);
> +		lkl_sys_rmdir(mnt_str);
> +		return err;
> +	}
> +
> +	return 0;
> +}
> +
> +long lkl_umount_timeout(char *path, int flags, long timeout_ms)
> +{
> +	long incr = 10000000; /* 10 ms */
> +	struct lkl_timespec ts = {
> +		.tv_sec = 0,
> +		.tv_nsec = incr,
> +	};
> +	long err;
> +
> +	do {
> +		err = lkl_sys_umount(path, flags);
> +		if (err == -LKL_EBUSY) {
> +			lkl_sys_nanosleep((struct __lkl__kernel_timespec *)&ts,
> +					  NULL);
> +			timeout_ms -= incr / 1000000;
> +		}
> +	} while (err == -LKL_EBUSY && timeout_ms > 0);
> +
> +	return err;
> +}
> +
> +long lkl_umount_dev(unsigned int disk_id, unsigned int part, int flags,
> +		    long timeout_ms)
> +{
> +	char dev_str[] = { "/dev/xxxxxxxx" };
> +	char mnt_str[] = { "/mnt/xxxxxxxx" };
> +	unsigned int dev;
> +	int err;
> +
> +	err = lkl_get_blkdev(disk_id, part, &dev);
> +	if (err < 0)
> +		return err;
> +
> +	snprintf(dev_str, sizeof(dev_str), "/dev/%08x", dev);
> +	snprintf(mnt_str, sizeof(mnt_str), "/mnt/%08x", dev);
> +
> +	err = lkl_umount_timeout(mnt_str, flags, timeout_ms);
> +	if (err)
> +		return err;
> +
> +	err = lkl_sys_unlink(dev_str);
> +	if (err)
> +		return err;
> +
> +	return lkl_sys_rmdir(mnt_str);
> +}
> +
> +struct lkl_dir {
> +	int fd;
> +	char buf[1024];
> +	char *pos;
> +	int len;
> +};
> +
> +static struct lkl_dir *lkl_dir_alloc(int *err)
> +{
> +	struct lkl_dir *dir = lkl_host_ops.mem_alloc(sizeof(struct lkl_dir));
> +
> +	if (!dir) {
> +		*err = -LKL_ENOMEM;
> +		return NULL;
> +	}
> +
> +	dir->len = 0;
> +	dir->pos = NULL;
> +
> +	return dir;
> +}
> +
> +struct lkl_dir *lkl_opendir(const char *path, int *err)
> +{
> +	struct lkl_dir *dir = lkl_dir_alloc(err);
> +
> +	if (!dir) {
> +		*err = -LKL_ENOMEM;
> +		return NULL;
> +	}
> +
> +	dir->fd = lkl_sys_open(path, LKL_O_RDONLY | LKL_O_DIRECTORY, 0);
> +	if (dir->fd < 0) {
> +		*err = dir->fd;
> +		lkl_host_ops.mem_free(dir);
> +		return NULL;
> +	}
> +
> +	*err = 0;
> +
> +	return dir;
> +}
> +
> +struct lkl_dir *lkl_fdopendir(int fd, int *err)
> +{
> +	struct lkl_dir *dir = lkl_dir_alloc(err);
> +
> +	if (!dir)
> +		return NULL;
> +
> +	dir->fd = fd;
> +
> +	return dir;
> +}
> +
> +void lkl_rewinddir(struct lkl_dir *dir)
> +{
> +	lkl_sys_lseek(dir->fd, 0, LKL_SEEK_SET);
> +	dir->len = 0;
> +	dir->pos = NULL;
> +}
> +
> +int lkl_closedir(struct lkl_dir *dir)
> +{
> +	int ret;
> +
> +	ret = lkl_sys_close(dir->fd);
> +	lkl_host_ops.mem_free(dir);
> +
> +	return ret;
> +}
> +
> +struct lkl_linux_dirent64 *lkl_readdir(struct lkl_dir *dir)
> +{
> +	struct lkl_linux_dirent64 *de;
> +
> +	if (dir->len < 0)
> +		return NULL;
> +
> +	if (!dir->pos || dir->pos - dir->buf >= dir->len)
> +		goto read_buf;
> +
> +return_de:
> +	de = (struct lkl_linux_dirent64 *)dir->pos;
> +	dir->pos += de->d_reclen;
> +
> +	return de;
> +
> +read_buf:
> +	dir->pos = NULL;
> +	de = (struct lkl_linux_dirent64 *)dir->buf;
> +	dir->len = lkl_sys_getdents64(dir->fd, de, sizeof(dir->buf));
> +	if (dir->len <= 0)
> +		return NULL;
> +
> +	dir->pos = dir->buf;
> +	goto return_de;
> +}
> +
> +int lkl_errdir(struct lkl_dir *dir)
> +{
> +	if (dir->len >= 0)
> +		return 0;
> +
> +	return dir->len;
> +}
> +
> +int lkl_dirfd(struct lkl_dir *dir)
> +{
> +	return dir->fd;
> +}
> +
> +int lkl_set_fd_limit(unsigned int fd_limit)
> +{
> +	struct lkl_rlimit rlim = {
> +		.rlim_cur = fd_limit,
> +		.rlim_max = fd_limit,
> +	};
> +	return lkl_sys_setrlimit(LKL_RLIMIT_NOFILE, &rlim);
> +}
> diff --git a/tools/um/lib/posix-host.c b/tools/um/lib/posix-host.c
> index b6b5b2902254..8fd88031bf2b 100644
> --- a/tools/um/lib/posix-host.c
> +++ b/tools/um/lib/posix-host.c
> @@ -263,6 +263,7 @@ static void *tls_get(struct lkl_tls_key *key)
>   }
>   
>   struct lkl_host_operations lkl_host_ops = {
> +	.um_devices = lkl_um_devs,
>   	.panic = panic,
>   	.print = print,
>   	.mem_alloc = (void *)malloc,
> diff --git a/tools/um/lib/utils.c b/tools/um/lib/utils.c
> index 4930479a8a35..ac65cd744a14 100644
> --- a/tools/um/lib/utils.c
> +++ b/tools/um/lib/utils.c
> @@ -4,6 +4,9 @@
>   #include <string.h>
>   #include <lkl_host.h>
>   
> +/* XXX: find a better place */
> +char lkl_um_devs[4096];
> +
>   static const char * const lkl_err_strings[] = {
>   	"Success",
>   	"Operation not permitted",
> diff --git a/tools/um/tests/Build b/tools/um/tests/Build
> index 564560486f98..1aa78f9ed7ef 100644
> --- a/tools/um/tests/Build
> +++ b/tools/um/tests/Build
> @@ -1,3 +1,4 @@
>   boot-y += boot.o test.o
> +disk-y += disk.o test.o cla.o
>   
>   CFLAGS_test.o += -Wno-implicit-fallthrough
> diff --git a/tools/um/tests/cla.c b/tools/um/tests/cla.c
> new file mode 100644
> index 000000000000..694e3a3822be
> --- /dev/null
> +++ b/tools/um/tests/cla.c
> @@ -0,0 +1,159 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <stdio.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <stdlib.h>
> +#ifdef __MINGW32__
> +#include <winsock2.h>
> +#else
> +#include <sys/socket.h>
> +#include <netinet/in.h>
> +#include <arpa/inet.h>
> +#endif
> +
> +#include "cla.h"
> +
> +static int cl_arg_parse_bool(struct cl_arg *arg, const char *value)
> +{
> +	*((int *)arg->store) = 1;
> +	return 0;
> +}
> +
> +static int cl_arg_parse_str(struct cl_arg *arg, const char *value)
> +{
> +	*((const char **)arg->store) = value;
> +	return 0;
> +}
> +
> +static int cl_arg_parse_int(struct cl_arg *arg, const char *value)
> +{
> +	errno = 0;
> +	*((int *)arg->store) = strtol(value, NULL, 0);
> +	return errno == 0;
> +}
> +
> +static int cl_arg_parse_str_set(struct cl_arg *arg, const char *value)
> +{
> +	const char **set = arg->set;
> +	int i;
> +
> +	for (i = 0; set[i] != NULL; i++) {
> +		if (strcmp(set[i], value) == 0) {
> +			*((int *)arg->store) = i;
> +			return 0;
> +		}
> +	}
> +
> +	return (-1);
> +}
> +
> +static int cl_arg_parse_ipv4(struct cl_arg *arg, const char *value)
> +{
> +	unsigned int addr;
> +
> +	if (!value)
> +		return (-1);
> +
> +	addr = inet_addr(value);
> +	if (addr == INADDR_NONE)
> +		return (-1);
> +	*((unsigned int *)arg->store) = addr;
> +	return 0;
> +}
> +
> +static cl_arg_parser_t parsers[] = {
> +	[CL_ARG_BOOL] = cl_arg_parse_bool,
> +	[CL_ARG_INT] = cl_arg_parse_int,
> +	[CL_ARG_STR] = cl_arg_parse_str,
> +	[CL_ARG_STR_SET] = cl_arg_parse_str_set,
> +	[CL_ARG_IPV4] = cl_arg_parse_ipv4,
> +};
> +
> +static struct cl_arg *find_short_arg(char name, struct cl_arg *args)
> +{
> +	struct cl_arg *arg;
> +
> +	for (arg = args; arg->short_name != 0; arg++) {
> +		if (arg->short_name == name)
> +			return arg;
> +	}
> +
> +	return NULL;
> +}
> +
> +static struct cl_arg *find_long_arg(const char *name, struct cl_arg *args)
> +{
> +	struct cl_arg *arg;
> +
> +	for (arg = args; arg->long_name; arg++) {
> +		if (strcmp(arg->long_name, name) == 0)
> +			return arg;
> +	}
> +
> +	return NULL;
> +}
> +
> +static void print_help(struct cl_arg *args)
> +{
> +	struct cl_arg *arg;
> +
> +	fprintf(stderr, "usage:\n");
> +	for (arg = args; arg->long_name; arg++) {
> +		fprintf(stderr, "-%c, --%-20s %s", arg->short_name,
> +			arg->long_name, arg->help);
> +		if (arg->type == CL_ARG_STR_SET) {
> +			const char **set = arg->set;
> +
> +			fprintf(stderr, " [ ");
> +			while (*set != NULL)
> +				fprintf(stderr, "%s ", *(set++));
> +			fprintf(stderr, "]");
> +		}
> +		fprintf(stderr, "\n");
> +	}
> +}
> +
> +int cla_parse_args(int argc, const char **argv, struct cl_arg *args)
> +{
> +	int i;
> +
> +	for (i = 1; i < argc; i++) {
> +		struct cl_arg *arg = NULL;
> +		cl_arg_parser_t parser;
> +
> +		if (argv[i][0] == '-') {
> +			if (argv[i][1] != '-')
> +				arg = find_short_arg(argv[i][1], args);
> +			else
> +				arg = find_long_arg(&argv[i][2], args);
> +		}
> +
> +		if (!arg) {
> +			fprintf(stderr, "unknown option '%s'\n", argv[i]);
> +			print_help(args);
> +			return (-1);
> +		}
> +
> +		if (arg->type == CL_ARG_USER || arg->type >= CL_ARG_END)
> +			parser = arg->parser;
> +		else
> +			parser = parsers[arg->type];
> +
> +		if (!parser) {
> +			fprintf(stderr, "can't parse --'%s'/-'%c'\n",
> +				arg->long_name, args->short_name);
> +			return (-1);
> +		}
> +
> +		if (parser(arg, argv[i + 1]) < 0) {
> +			fprintf(stderr, "can't parse '%s'\n", argv[i]);
> +			print_help(args);
> +			return (-1);
> +		}
> +
> +		if (arg->has_arg)
> +			i++;
> +	}
> +
> +	return 0;
> +}
> diff --git a/tools/um/tests/cla.h b/tools/um/tests/cla.h
> new file mode 100644
> index 000000000000..3d879233681f
> --- /dev/null
> +++ b/tools/um/tests/cla.h
> @@ -0,0 +1,33 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef _LKL_TEST_CLA_H
> +#define _LKL_TEST_CLA_H
> +
> +enum cl_arg_type {
> +	CL_ARG_USER = 0,
> +	CL_ARG_BOOL,
> +	CL_ARG_INT,
> +	CL_ARG_STR,
> +	CL_ARG_STR_SET,
> +	CL_ARG_IPV4,
> +	CL_ARG_END,
> +};
> +
> +struct cl_arg;
> +
> +typedef int (*cl_arg_parser_t)(struct cl_arg *arg, const char *value);
> +
> +struct cl_arg {
> +	const char *long_name;
> +	char short_name;
> +	const char *help;
> +	int has_arg;
> +	enum cl_arg_type type;
> +	void *store;
> +	void *set;
> +	cl_arg_parser_t parser;
> +};
> +
> +int cla_parse_args(int argc, const char **argv, struct cl_arg *args);
> +
> +
> +#endif /* _LKL_TEST_CLA_H */
> diff --git a/tools/um/tests/disk.c b/tools/um/tests/disk.c
> new file mode 100644
> index 000000000000..325934935e6a
> --- /dev/null
> +++ b/tools/um/tests/disk.c
> @@ -0,0 +1,168 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <string.h>
> +#include <time.h>
> +#include <stdlib.h>
> +#include <stdint.h>
> +#include <lkl.h>
> +#include <lkl_host.h>
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +#include <sys/ioctl.h>
> +
> +#include "test.h"
> +#include "cla.h"
> +
> +static struct {
> +	int printk;
> +	const char *disk;
> +	const char *fstype;
> +	int partition;
> +} cla;
> +
> +struct cl_arg args[] = {
> +	{"disk", 'd', "disk file to use", 1, CL_ARG_STR, &cla.disk},
> +	{"partition", 'P', "partition to mount", 1, CL_ARG_INT, &cla.partition},
> +	{"type", 't', "filesystem type", 1, CL_ARG_STR, &cla.fstype},
> +	{0},
> +};
> +
> +
> +static struct lkl_disk disk;
> +static int disk_id = -1;
> +
> +int lkl_test_disk_add(void)
> +{
> +	disk.fd = open(cla.disk, O_RDWR);
> +	if (disk.fd < 0)
> +		goto out_unlink;
> +
> +	disk.ops = NULL;
> +	disk.dev = (char *)cla.disk;
> +
> +	disk_id = lkl_disk_add(&disk);
> +	if (disk_id < 0)
> +		goto out_close;
> +
> +	goto out;
> +
> +out_close:
> +	close(disk.fd);
> +
> +out_unlink:
> +	unlink(cla.disk);
> +
> +out:
> +	lkl_test_logf("disk fd/handle %x disk_id %d", disk.fd, disk_id);
> +
> +	if (disk_id >= 0)
> +		return TEST_SUCCESS;
> +
> +	return TEST_FAILURE;
> +}
> +
> +int lkl_test_disk_remove(void)
> +{
> +	int ret;
> +
> +	ret = lkl_disk_remove(disk);
> +
> +	close(disk.fd);
> +
> +	if (ret == 0)
> +		return TEST_SUCCESS;
> +
> +	return TEST_FAILURE;
> +}
> +
> +
> +static char mnt_point[32];
> +
> +LKL_TEST_CALL(mount_dev, lkl_mount_dev, 0, disk_id, cla.partition, cla.fstype,
> +	      0, NULL, mnt_point, sizeof(mnt_point))
> +
> +static int lkl_test_umount_dev(void)
> +{
> +	long ret, ret2;
> +
> +	ret = lkl_sys_chdir("/");
> +
> +	ret2 = lkl_umount_dev(disk_id, cla.partition, 0, 1000);
> +
> +	lkl_test_logf("%ld %ld", ret, ret2);
> +
> +	if (!ret && !ret2)
> +		return TEST_SUCCESS;
> +
> +	return TEST_FAILURE;
> +}
> +
> +struct lkl_dir *dir;
> +
> +static int lkl_test_opendir(void)
> +{
> +	int err;
> +
> +	dir = lkl_opendir(mnt_point, &err);
> +
> +	lkl_test_logf("lkl_opedir(%s) = %d %s\n", mnt_point, err,
> +		      lkl_strerror(err));
> +
> +	if (err == 0)
> +		return TEST_SUCCESS;
> +
> +	return TEST_FAILURE;
> +}
> +
> +static int lkl_test_readdir(void)
> +{
> +	struct lkl_linux_dirent64 *de = lkl_readdir(dir);
> +	int wr = 0;
> +
> +	while (de) {
> +		wr += lkl_test_logf("%s ", de->d_name);
> +		if (wr >= 70) {
> +			lkl_test_logf("\n");
> +			wr = 0;
> +			break;
> +		}
> +		de = lkl_readdir(dir);
> +	}
> +
> +	if (lkl_errdir(dir) == 0)
> +		return TEST_SUCCESS;
> +
> +	return TEST_FAILURE;
> +}
> +
> +LKL_TEST_CALL(closedir, lkl_closedir, 0, dir);
> +LKL_TEST_CALL(chdir_mnt_point, lkl_sys_chdir, 0, mnt_point);
> +LKL_TEST_CALL(start_kernel, lkl_start_kernel, 0, &lkl_host_ops,
> +	     "mem=16M loglevel=8");
> +LKL_TEST_CALL(stop_kernel, lkl_sys_halt, 0);
> +
> +struct lkl_test tests[] = {
> +	LKL_TEST(disk_add),
> +	LKL_TEST(start_kernel),
> +	LKL_TEST(mount_dev),
> +	LKL_TEST(chdir_mnt_point),
> +	LKL_TEST(opendir),
> +	LKL_TEST(readdir),
> +	LKL_TEST(closedir),
> +	LKL_TEST(umount_dev),
> +	LKL_TEST(stop_kernel),
> +	LKL_TEST(disk_remove),
> +
> +};
> +
> +int main(int argc, const char **argv)
> +{
> +	if (cla_parse_args(argc, argv, args) < 0)
> +		return (-1);
> +
> +	lkl_host_ops.print = lkl_test_log;
> +
> +	return lkl_test_run(tests, sizeof(tests)/sizeof(struct lkl_test),
> +			    "disk %s", cla.fstype);
> +}
> diff --git a/tools/um/tests/disk.sh b/tools/um/tests/disk.sh
> new file mode 100755
> index 000000000000..e2ec6cf69d4b
> --- /dev/null
> +++ b/tools/um/tests/disk.sh
> @@ -0,0 +1,70 @@
> +#!/usr/bin/env bash
> +# SPDX-License-Identifier: GPL-2.0
> +
> +script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd)
> +
> +source $script_dir/test.sh
> +
> +function prepfs()
> +{
> +    set -e
> +
> +    file=`mktemp`
> +
> +    dd if=/dev/zero of=$file bs=1024 count=204800
> +
> +    yes | mkfs.$1 $file
> +
> +    if ! [ -z $ANDROID_WDIR ]; then
> +        adb shell mkdir -p $ANDROID_WDIR
> +        adb push $file $ANDROID_WDIR
> +        rm $file
> +        file=$ANDROID_WDIR/$(basename $file)
> +    fi
> +    if ! [ -z $BSD_WDIR ]; then
> +        $MYSSH mkdir -p $BSD_WDIR
> +        ssh_copy $file $BSD_WDIR
> +        rm $file
> +        file=$BSD_WDIR/$(basename $file)
> +    fi
> +
> +    export_vars file
> +}
> +
> +function cleanfs()
> +{
> +    set -e
> +
> +    if ! [ -z $ANDROID_WDIR ]; then
> +        adb shell rm $1
> +        adb shell rm $ANDROID_WDIR/disk
> +    elif ! [ -z $BSD_WDIR ]; then
> +        $MYSSH rm $1
> +        $MYSSH rm $BSD_WDIR/disk
> +    else
> +        rm $1
> +    fi
> +}
> +
> +if [ "$1" = "-t" ]; then
> +    shift
> +    fstype=$1
> +    shift
> +fi
> +
> +if [ -z "$fstype" ]; then
> +    fstype="ext4"
> +fi
> +
> +if [ -z $(which mkfs.$fstype) ]; then
> +    lkl_test_plan 0 "disk $fstype"
> +    echo "no mkfs.$fstype command"
> +    exit 0
> +fi
> +
> +lkl_test_plan 1 "disk $fstype"
> +lkl_test_run 1 prepfs $fstype
> +lkl_test_exec $script_dir/disk -d $file -t $fstype $@
> +lkl_test_plan 1 "disk $fstype"
> +lkl_test_run 1 cleanfs $file
> +
> diff --git a/tools/um/tests/run.py b/tools/um/tests/run.py
> index c96ede90b6ad..97d6dedc217c 100755
> --- a/tools/um/tests/run.py
> +++ b/tools/um/tests/run.py
> @@ -50,6 +50,8 @@ mydir=os.path.dirname(os.path.realpath(__file__))
>   
>   tests = [
>       'boot.sh',
> +    'disk.sh -t ext4',
> +    'disk.sh -t vfat',
>   ]
>   
>   parser = argparse.ArgumentParser(description='LKL test runner')
Hajime Tazaki Oct. 8, 2020, 12:13 p.m. UTC | #2
On Wed, 07 Oct 2020 23:17:31 +0900,
Anton Ivanov wrote:

> > diff --git a/arch/um/nommu/um/Kconfig b/arch/um/nommu/um/Kconfig
> > index 20b3eaccb6f0..c6a3f472fe75 100644
> > --- a/arch/um/nommu/um/Kconfig
> > +++ b/arch/um/nommu/um/Kconfig
> > @@ -4,6 +4,10 @@ config UML_NOMMU
> >   	select UACCESS_MEMCPY
> >   	select ARCH_THREAD_STACK_ALLOCATOR
> >   	select ARCH_HAS_SYSCALL_WRAPPER
> > +	select VFAT_FS
> > +	select NLS_CODEPAGE_437
> > +	select NLS_ISO8859_1
> > +	select BTRFS_FS
> >     config 64BIT
> >   	bool
> > @@ -35,3 +39,7 @@ config STACKTRACE_SUPPORT
> >   config PRINTK_TIME
> >   	bool
> >   	default y
> > +
> > +config RAID6_PQ_BENCHMARK
> > +	bool
> > +	default n
> 
> Why are we touching this? I thought this is already defined in lib/Kconfig?

With the scheduler which LKL implements, it has the same issue with
what time-travel=inf-cpu has when CONFIG_BTRFS_FS is enable.  I tried
to follow the way of the commit d65197a (below), but if I added
"depends on !RAID6_PQ_BENCHMARK" to config UMMODE_LIB, I got "error:
recursive dependency detected!".

https://github.com/thehajime/linux/commit/d65197ad52494bed3b5e64708281b8295f76c391#diff-c170aa964ad412630a2b5addf306ff14

Thus, to avoid this situation, I did the above additional config
RAID6_PQ_BENCHMARK just for UMMODE_LIB.

I plan to rework on Kconfig (UMMODE_LIB and !MMU) and will figure out
more appropriate way.

-- Hajime
diff mbox series

Patch

diff --git a/arch/um/include/asm/xor.h b/arch/um/include/asm/xor.h
index 36b33d62a35d..a5ea458a1ae9 100644
--- a/arch/um/include/asm/xor.h
+++ b/arch/um/include/asm/xor.h
@@ -4,4 +4,5 @@ 
 
 /* pick an arbitrary one - measuring isn't possible with inf-cpu */
 #define XOR_SELECT_TEMPLATE(x)	\
-	(time_travel_mode == TT_MODE_INFCPU ? &xor_block_8regs : NULL)
+	(time_travel_mode == TT_MODE_INFCPU || (!IS_ENABLED(CONFIG_MMU)) ? \
+	 &xor_block_8regs : NULL)
diff --git a/arch/um/include/shared/as-layout.h b/arch/um/include/shared/as-layout.h
index 5f286ef2721b..4423437a5ace 100644
--- a/arch/um/include/shared/as-layout.h
+++ b/arch/um/include/shared/as-layout.h
@@ -57,6 +57,7 @@  extern unsigned long host_task_size;
 
 extern int linux_main(int argc, char **argv);
 extern void uml_finishsetup(void);
+extern void uml_set_args(char *args);
 
 struct siginfo;
 extern void (*sig_info[])(int, struct siginfo *si, struct uml_pt_regs *);
diff --git a/arch/um/kernel/um_arch.c b/arch/um/kernel/um_arch.c
index e2cb76c03b25..da6e06cf2808 100644
--- a/arch/um/kernel/um_arch.c
+++ b/arch/um/kernel/um_arch.c
@@ -41,6 +41,11 @@  static void __init add_arg(char *arg)
 	strcat(command_line, arg);
 }
 
+void __init uml_set_args(char *args)
+{
+	strcat(command_line, args);
+}
+
 /*
  * These fields are initialized at boot time and not changed.
  * XXX This structure is used only in the non-SMP case.  Maybe this
diff --git a/arch/um/nommu/include/uapi/asm/host_ops.h b/arch/um/nommu/include/uapi/asm/host_ops.h
index 0811494c080c..1b2507502969 100644
--- a/arch/um/nommu/include/uapi/asm/host_ops.h
+++ b/arch/um/nommu/include/uapi/asm/host_ops.h
@@ -15,6 +15,11 @@  struct lkl_jmp_buf {
  *
  * These operations must be provided by a host library or by the application
  * itself.
+ *
+ * @um_devices - string containg the list of UML devices in command line
+ * format. This string is appended to the kernel command line and
+ * is provided here for convenience to be implemented by the host library.
+ *
  * @print - optional operation that receives console messages
  * @panic - called during a kernel panic
  *
@@ -65,6 +70,8 @@  struct lkl_jmp_buf {
  *
  */
 struct lkl_host_operations {
+	const char *um_devices;
+
 	void (*print)(const char *str, int len);
 	void (*panic)(void);
 
diff --git a/arch/um/nommu/include/uapi/asm/unistd.h b/arch/um/nommu/include/uapi/asm/unistd.h
index 1762ebb829f5..320762099a62 100644
--- a/arch/um/nommu/include/uapi/asm/unistd.h
+++ b/arch/um/nommu/include/uapi/asm/unistd.h
@@ -3,6 +3,8 @@ 
 #define __UM_NOMMU_UAPI_UNISTD_H
 
 #define __ARCH_WANT_NEW_STAT
+#define __ARCH_WANT_SET_GET_RLIMIT
+
 #include <asm/bitsperlong.h>
 
 #if __BITS_PER_LONG == 64
diff --git a/arch/um/nommu/um/Kconfig b/arch/um/nommu/um/Kconfig
index 20b3eaccb6f0..c6a3f472fe75 100644
--- a/arch/um/nommu/um/Kconfig
+++ b/arch/um/nommu/um/Kconfig
@@ -4,6 +4,10 @@  config UML_NOMMU
 	select UACCESS_MEMCPY
 	select ARCH_THREAD_STACK_ALLOCATOR
 	select ARCH_HAS_SYSCALL_WRAPPER
+	select VFAT_FS
+	select NLS_CODEPAGE_437
+	select NLS_ISO8859_1
+	select BTRFS_FS
 
 config 64BIT
 	bool
@@ -35,3 +39,7 @@  config STACKTRACE_SUPPORT
 config PRINTK_TIME
 	bool
 	default y
+
+config RAID6_PQ_BENCHMARK
+	bool
+	default n
diff --git a/arch/um/nommu/um/setup.c b/arch/um/nommu/um/setup.c
index 922188690139..d71d61979ad6 100644
--- a/arch/um/nommu/um/setup.c
+++ b/arch/um/nommu/um/setup.c
@@ -45,13 +45,25 @@  static void __init *lkl_run_kernel(void *arg)
 	return NULL;
 }
 
+static char _cmd_line[COMMAND_LINE_SIZE];
 int __init lkl_start_kernel(struct lkl_host_operations *ops,
 			    const char *fmt, ...)
 {
+	va_list ap;
 	int ret;
 
 	lkl_ops = ops;
 
+	va_start(ap, fmt);
+	ret = vsnprintf(_cmd_line, COMMAND_LINE_SIZE, fmt, ap);
+	va_end(ap);
+
+	if (ops->um_devices)
+		strscpy(_cmd_line + ret, ops->um_devices,
+			COMMAND_LINE_SIZE - ret);
+
+	uml_set_args(_cmd_line);
+
 	init_sem = lkl_ops->sem_alloc(0);
 	if (!init_sem)
 		return -ENOMEM;
diff --git a/tools/um/Targets b/tools/um/Targets
index 2bb90381843c..f5f8ec4b9dbb 100644
--- a/tools/um/Targets
+++ b/tools/um/Targets
@@ -1,10 +1,12 @@ 
 ifeq ($(UMMODE),library)
 progs-y += tests/boot
+progs-y += tests/disk
 else
 progs-y += uml/linux
 endif
 
 LDFLAGS_boot-y := -pie
+LDFLAGS_disk-y := -pie
 
 LDLIBS := -lrt -lpthread -lutil
 LDFLAGS_linux-y := -no-pie -Wl,--wrap,malloc -Wl,--wrap,free -Wl,--wrap,calloc
diff --git a/tools/um/include/lkl.h b/tools/um/include/lkl.h
index 707e01b64a70..4a9d874bfbf0 100644
--- a/tools/um/include/lkl.h
+++ b/tools/um/include/lkl.h
@@ -113,6 +113,16 @@  static inline long lkl_sys_creat(const char *file, int mode)
 }
 #endif
 
+#ifdef __lkl__NR_faccessat
+/**
+ * lkl_sys_access - wrapper for lkl_sys_faccessat
+ */
+static inline long lkl_sys_access(const char *file, int mode)
+{
+	return lkl_sys_faccessat(LKL_AT_FDCWD, file, mode);
+}
+#endif
+
 #ifdef __lkl__NR_mkdirat
 /**
  * lkl_sys_mkdir - wrapper for lkl_sys_mkdirat
@@ -123,6 +133,36 @@  static inline long lkl_sys_mkdir(const char *path, mode_t mode)
 }
 #endif
 
+#ifdef __lkl__NR_unlinkat
+/**
+ * lkl_sys_rmdir - wrapper for lkl_sys_unlinkrat
+ */
+static inline long lkl_sys_rmdir(const char *path)
+{
+	return lkl_sys_unlinkat(LKL_AT_FDCWD, path, LKL_AT_REMOVEDIR);
+}
+#endif
+
+#ifdef __lkl__NR_unlinkat
+/**
+ * lkl_sys_unlink - wrapper for lkl_sys_unlinkat
+ */
+static inline long lkl_sys_unlink(const char *path)
+{
+	return lkl_sys_unlinkat(LKL_AT_FDCWD, path, 0);
+}
+#endif
+
+#ifdef __lkl__NR_mknodat
+/**
+ * lkl_sys_mknod - wrapper for lkl_sys_mknodat
+ */
+static inline long lkl_sys_mknod(const char *path, mode_t mode, dev_t dev)
+{
+	return lkl_sys_mknodat(LKL_AT_FDCWD, path, mode, dev);
+}
+#endif
+
 #ifdef __lkl__NR_epoll_create1
 /**
  * lkl_sys_epoll_create - wrapper for lkl_sys_epoll_create1
@@ -144,6 +184,172 @@  static inline long lkl_sys_epoll_wait(int fd, struct lkl_epoll_event *ev,
 }
 #endif
 
+/**
+ * struct lkl_dev_blk_ops - block device host operations, defined in lkl_host.h.
+ */
+struct lkl_dev_blk_ops;
+
+/**
+ * lkl_disk - host disk handle
+ *
+ * @dev - a pointer to private information for this disk backend
+ * @fd - a POSIX file descriptor that can be used by preadv/pwritev
+ * @handle - an NT file handle that can be used by ReadFile/WriteFile
+ */
+struct lkl_disk {
+	void *dev;
+	union {
+		int fd;
+		void *handle;
+	};
+	struct lkl_dev_blk_ops *ops;
+};
+
+/**
+ * lkl_disk_add - add a new disk
+ *
+ * @disk - the host disk handle
+ * @returns a disk id (0 is valid) or a strictly negative value in case of error
+ */
+int lkl_disk_add(struct lkl_disk *disk);
+
+/**
+ * lkl_disk_remove - remove a disk
+ *
+ * This function makes a cleanup of the @disk's private information
+ * that was initialized by lkl_disk_add before.
+ *
+ * @disk - the host disk handle
+ */
+int lkl_disk_remove(struct lkl_disk disk);
+
+/**
+ * lkl_encode_dev_from_sysfs_blkdev - extract device id from sysfs
+ *
+ * This function returns the device id for the given sysfs dev node.
+ * The content of the node has to be in the form 'MAJOR:MINOR'.
+ * Also, this function expects an absolute path which means that sysfs
+ * already has to be mounted at the given path
+ *
+ * @sysfs_path - absolute path to the sysfs dev node
+ * @pdevid - pointer to memory where dev id will be returned
+ * @returns - 0 on success, a negative value on error
+ */
+int lkl_encode_dev_from_sysfs(const char *sysfs_path, uint32_t *pdevid);
+
+/**
+ * lkl_mount_dev - mount a disk
+ *
+ * This functions creates a device file for the given disk, creates a mount
+ * point and mounts the device over the mount point.
+ *
+ * @disk_id - the disk id identifying the disk to be mounted
+ * @part - disk partition or zero for full disk
+ * @fs_type - filesystem type
+ * @flags - mount flags
+ * @opts - additional filesystem specific mount options
+ * @mnt_str - a string that will be filled by this function with the path where
+ * the filesystem has been mounted
+ * @mnt_str_len - size of mnt_str
+ * @returns - 0 on success, a negative value on error
+ */
+long lkl_mount_dev(unsigned int disk_id, unsigned int part, const char *fs_type,
+		   int flags, const char *opts,
+		   char *mnt_str, unsigned int mnt_str_len);
+
+/**
+ * lkl_umount_dev - umount a disk
+ *
+ * This functions umounts the given disks and removes the device file and the
+ * mount point.
+ *
+ * @disk_id - the disk id identifying the disk to be mounted
+ * @part - disk partition or zero for full disk
+ * @flags - umount flags
+ * @timeout_ms - timeout to wait for the kernel to flush closed files so that
+ * umount can succeed
+ * @returns - 0 on success, a negative value on error
+ */
+long lkl_umount_dev(unsigned int disk_id, unsigned int part, int flags,
+		    long timeout_ms);
+
+/**
+ * lkl_umount_timeout - umount filesystem with timeout
+ *
+ * @path - the path to unmount
+ * @flags - umount flags
+ * @timeout_ms - timeout to wait for the kernel to flush closed files so that
+ * umount can succeed
+ * @returns - 0 on success, a negative value on error
+ */
+long lkl_umount_timeout(char *path, int flags, long timeout_ms);
+
+/**
+ * lkl_opendir - open a directory
+ *
+ * @path - directory path
+ * @err - pointer to store the error in case of failure
+ * @returns - a handle to be used when calling lkl_readdir
+ */
+struct lkl_dir *lkl_opendir(const char *path, int *err);
+
+/**
+ * lkl_fdopendir - open a directory
+ *
+ * @fd - file descriptor
+ * @err - pointer to store the error in case of failure
+ * @returns - a handle to be used when calling lkl_readdir
+ */
+struct lkl_dir *lkl_fdopendir(int fd, int *err);
+
+/**
+ * lkl_rewinddir - reset directory stream
+ *
+ * @dir - the directory handler as returned by lkl_opendir
+ */
+void lkl_rewinddir(struct lkl_dir *dir);
+
+/**
+ * lkl_closedir - close the directory
+ *
+ * @dir - the directory handler as returned by lkl_opendir
+ */
+int lkl_closedir(struct lkl_dir *dir);
+
+/**
+ * lkl_readdir - get the next available entry of the directory
+ *
+ * @dir - the directory handler as returned by lkl_opendir
+ * @returns - a lkl_dirent64 entry or NULL if the end of the directory stream is
+ * reached or if an error occurred; check lkl_errdir() to distinguish between
+ * errors or end of the directory stream
+ */
+struct lkl_linux_dirent64 *lkl_readdir(struct lkl_dir *dir);
+
+/**
+ * lkl_errdir - checks if an error occurred during the last lkl_readdir call
+ *
+ * @dir - the directory handler as returned by lkl_opendir
+ * @returns - 0 if no error occurred, or a negative value otherwise
+ */
+int lkl_errdir(struct lkl_dir *dir);
+
+/**
+ * lkl_dirfd - gets the file descriptor associated with the directory handle
+ *
+ * @dir - the directory handle as returned by lkl_opendir
+ * @returns - a positive value,which is the LKL file descriptor associated with
+ * the directory handle, or a negative value otherwise
+ */
+int lkl_dirfd(struct lkl_dir *dir);
+
+/**
+ * lkl_mount_fs - mount a file system type like proc, sys
+ * @fstype - file system type. e.g. proc, sys
+ * @returns - 0 on success. 1 if it's already mounted. negative on failure.
+ */
+int lkl_mount_fs(char *fstype);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/tools/um/include/lkl_host.h b/tools/um/include/lkl_host.h
index 85e80eb4ad0d..12dd95616e43 100644
--- a/tools/um/include/lkl_host.h
+++ b/tools/um/include/lkl_host.h
@@ -10,6 +10,7 @@  extern "C" {
 #include <lkl.h>
 
 extern struct lkl_host_operations lkl_host_ops;
+extern char lkl_um_devs[4096];
 
 /**
  * lkl_printf - print a message via the host print operation
diff --git a/tools/um/lib/Build b/tools/um/lib/Build
index dddff26a3b4e..29491b40746c 100644
--- a/tools/um/lib/Build
+++ b/tools/um/lib/Build
@@ -4,3 +4,4 @@  CFLAGS_posix-host.o += -D_FILE_OFFSET_BITS=64
 liblinux-$(CONFIG_UMMODE_LIB) += utils.o
 liblinux-$(CONFIG_UMMODE_LIB) += posix-host.o
 liblinux-$(CONFIG_UMMODE_LIB) += jmp_buf.o
+liblinux-$(CONFIG_UMMODE_LIB) += fs.o
diff --git a/tools/um/lib/fs.c b/tools/um/lib/fs.c
new file mode 100644
index 000000000000..948aac9730c2
--- /dev/null
+++ b/tools/um/lib/fs.c
@@ -0,0 +1,461 @@ 
+// SPDX-License-Identifier: GPL-2.0
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <lkl_host.h>
+
+#define MAX_FSTYPE_LEN 50
+
+static struct lkl_disk *lkl_disks[16];
+static int registered_blk_dev_idx;
+
+static int lkl_disk_um_add(struct lkl_disk *disk, const char *blkparams)
+{
+	/* concat strings */
+	snprintf(lkl_um_devs + strlen(lkl_um_devs), sizeof(lkl_um_devs),
+		 " ubd%d=%s", registered_blk_dev_idx, blkparams);
+
+	return registered_blk_dev_idx++;
+}
+
+int lkl_disk_add(struct lkl_disk *disk)
+{
+	int ret = -1;
+
+	ret = lkl_disk_um_add(disk, disk->dev);
+
+	lkl_disks[ret] = disk;
+
+	return ret;
+}
+
+int lkl_disk_remove(struct lkl_disk disk)
+{
+	/* FIXME */
+	return 0;
+}
+
+int lkl_mount_fs(char *fstype)
+{
+	char dir[MAX_FSTYPE_LEN+2] = "/";
+	int flags = 0, ret = 0;
+
+	strncat(dir, fstype, MAX_FSTYPE_LEN);
+
+	/* Create with regular umask */
+	ret = lkl_sys_mkdir(dir, 0xff);
+	if (ret && ret != -LKL_EEXIST) {
+		lkl_perror("mount_fs mkdir", ret);
+		return ret;
+	}
+
+	/* We have no use for nonzero flags right now */
+	ret = lkl_sys_mount("none", dir, fstype, flags, NULL);
+	if (ret && ret != -LKL_EBUSY) {
+		lkl_sys_rmdir(dir);
+		return ret;
+	}
+
+	if (ret == -LKL_EBUSY)
+		return 1;
+	return 0;
+}
+
+static uint32_t new_encode_dev(unsigned int major, unsigned int minor)
+{
+	return (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12);
+}
+
+static int startswith(const char *str, const char *pre)
+{
+	return strncmp(pre, str, strlen(pre)) == 0;
+}
+
+static int get_node_with_prefix(const char *path, const char *prefix,
+				char *result, unsigned int result_len)
+{
+	struct lkl_dir *dir = NULL;
+	struct lkl_linux_dirent64 *dirent;
+	int ret;
+
+	dir = lkl_opendir(path, &ret);
+	if (!dir)
+		return ret;
+
+	ret = -LKL_ENOENT;
+
+	while ((dirent = lkl_readdir(dir))) {
+		if (startswith(dirent->d_name, prefix)) {
+			if (strlen(dirent->d_name) + 1 > result_len) {
+				ret = -LKL_ENOMEM;
+				break;
+			}
+			memcpy(result, dirent->d_name, strlen(dirent->d_name));
+			result[strlen(dirent->d_name)] = '\0';
+			ret = 0;
+			break;
+		}
+	}
+
+	lkl_closedir(dir);
+
+	return ret;
+}
+
+int lkl_encode_dev_from_sysfs(const char *sysfs_path, uint32_t *pdevid)
+{
+	int ret;
+	long fd;
+	int major, minor;
+	char buf[16] = { 0, };
+	char *bufptr;
+
+	fd = lkl_sys_open(sysfs_path, LKL_O_RDONLY, 0);
+	if (fd < 0)
+		return fd;
+
+	ret = lkl_sys_read(fd, buf, sizeof(buf));
+	if (ret < 0)
+		goto out_close;
+
+	if (ret == sizeof(buf)) {
+		ret = -LKL_ENOBUFS;
+		goto out_close;
+	}
+
+	bufptr = strchr(buf, ':');
+	if (bufptr == NULL) {
+		ret = -LKL_EINVAL;
+		goto out_close;
+	}
+	bufptr[0] = '\0';
+	bufptr++;
+
+	major = atoi(buf);
+	minor = atoi(bufptr);
+
+	*pdevid = new_encode_dev(major, minor);
+	ret = 0;
+
+out_close:
+	lkl_sys_close(fd);
+
+	return ret;
+}
+
+#define SYSFS_DEV_UMBLK_CMDLINE_PATH \
+	"/sysfs/devices/platform/uml-blkdev.%d"
+
+struct abuf {
+	char *mem, *ptr;
+	unsigned int len;
+};
+
+static int snprintf_append(struct abuf *buf, const char *fmt, ...)
+{
+	int ret;
+	va_list args;
+
+	if (!buf->ptr)
+		buf->ptr = buf->mem;
+
+	va_start(args, fmt);
+	ret = vsnprintf(buf->ptr, buf->len - (buf->ptr - buf->mem), fmt, args);
+	va_end(args);
+
+	if (ret < 0 || (ret >= (int)(buf->len - (buf->ptr - buf->mem))))
+		return -LKL_ENOMEM;
+
+	buf->ptr += ret;
+
+	return 0;
+}
+
+static int __lkl_get_blkdev(int disk_id, unsigned int part, uint32_t *pdevid,
+			    const char *sysfs_path_fmt, const char *drv_prefix,
+			    const char *disk_prefix)
+{
+	char sysfs_path[LKL_PATH_MAX];
+	char drv_name[LKL_PATH_MAX];
+	char disk_name[LKL_PATH_MAX];
+	struct abuf sysfs_path_buf = {
+		.mem = sysfs_path,
+		.len = sizeof(sysfs_path),
+	};
+	int ret;
+
+	if (disk_id < 0)
+		return -LKL_EINVAL;
+
+	ret = lkl_mount_fs("sysfs");
+	if (ret < 0)
+		return ret;
+
+	ret = snprintf_append(&sysfs_path_buf, sysfs_path_fmt, disk_id);
+	if (ret)
+		return ret;
+
+	ret = get_node_with_prefix(sysfs_path, drv_prefix, drv_name,
+				   sizeof(drv_name));
+	if (ret)
+		return ret;
+
+	ret = snprintf_append(&sysfs_path_buf, "/%s/block", drv_name);
+	if (ret)
+		return ret;
+
+	ret = get_node_with_prefix(sysfs_path, disk_prefix, disk_name,
+				   sizeof(disk_name));
+	if (ret)
+		return ret;
+
+	if (!part)
+		ret = snprintf_append(&sysfs_path_buf, "/%s/dev", disk_name);
+	else
+		ret = snprintf_append(&sysfs_path_buf, "/%s/%s%d/dev",
+				      disk_name, disk_name, part);
+	if (ret)
+		return ret;
+
+	return lkl_encode_dev_from_sysfs(sysfs_path, pdevid);
+}
+
+int lkl_get_blkdev(int disk_id, unsigned int part, uint32_t *pdevid)
+{
+	char *fmt;
+
+	fmt = SYSFS_DEV_UMBLK_CMDLINE_PATH;
+	return __lkl_get_blkdev(disk_id, part, pdevid, fmt, "", "ubd");
+}
+
+long lkl_mount_dev(unsigned int disk_id, unsigned int part,
+		   const char *fs_type, int flags,
+		   const char *data, char *mnt_str, unsigned int mnt_str_len)
+{
+	char dev_str[] = { "/dev/xxxxxxxx" };
+	unsigned int dev;
+	int err;
+	char _data[4096]; /* FIXME: PAGE_SIZE is not exported by LKL */
+
+	if (mnt_str_len < sizeof(dev_str))
+		return -LKL_ENOMEM;
+
+	err = lkl_get_blkdev(disk_id, part, &dev);
+	if (err < 0)
+		return err;
+
+	snprintf(dev_str, sizeof(dev_str), "/dev/%08x", dev);
+	snprintf(mnt_str, mnt_str_len, "/mnt/%08x", dev);
+
+	err = lkl_sys_access("/dev", LKL_S_IRWXO);
+	if (err < 0) {
+		if (err == -LKL_ENOENT)
+			err = lkl_sys_mkdir("/dev", 0700);
+		if (err < 0)
+			return err;
+	}
+
+	err = lkl_sys_mknod(dev_str, LKL_S_IFBLK | 0600, dev);
+	if (err < 0)
+		return err;
+
+	err = lkl_sys_access("/mnt", LKL_S_IRWXO);
+	if (err < 0) {
+		if (err == -LKL_ENOENT)
+			err = lkl_sys_mkdir("/mnt", 0700);
+		if (err < 0)
+			return err;
+	}
+
+	err = lkl_sys_mkdir(mnt_str, 0700);
+	if (err < 0) {
+		lkl_sys_unlink(dev_str);
+		return err;
+	}
+
+	/* kernel always copies a full page */
+	if (data) {
+		strncpy(_data, data, sizeof(_data));
+		_data[sizeof(_data) - 1] = 0;
+	} else {
+		_data[0] = 0;
+	}
+
+	err = lkl_sys_mount(dev_str, mnt_str, (char *)fs_type, flags, _data);
+	if (err < 0) {
+		lkl_sys_unlink(dev_str);
+		lkl_sys_rmdir(mnt_str);
+		return err;
+	}
+
+	return 0;
+}
+
+long lkl_umount_timeout(char *path, int flags, long timeout_ms)
+{
+	long incr = 10000000; /* 10 ms */
+	struct lkl_timespec ts = {
+		.tv_sec = 0,
+		.tv_nsec = incr,
+	};
+	long err;
+
+	do {
+		err = lkl_sys_umount(path, flags);
+		if (err == -LKL_EBUSY) {
+			lkl_sys_nanosleep((struct __lkl__kernel_timespec *)&ts,
+					  NULL);
+			timeout_ms -= incr / 1000000;
+		}
+	} while (err == -LKL_EBUSY && timeout_ms > 0);
+
+	return err;
+}
+
+long lkl_umount_dev(unsigned int disk_id, unsigned int part, int flags,
+		    long timeout_ms)
+{
+	char dev_str[] = { "/dev/xxxxxxxx" };
+	char mnt_str[] = { "/mnt/xxxxxxxx" };
+	unsigned int dev;
+	int err;
+
+	err = lkl_get_blkdev(disk_id, part, &dev);
+	if (err < 0)
+		return err;
+
+	snprintf(dev_str, sizeof(dev_str), "/dev/%08x", dev);
+	snprintf(mnt_str, sizeof(mnt_str), "/mnt/%08x", dev);
+
+	err = lkl_umount_timeout(mnt_str, flags, timeout_ms);
+	if (err)
+		return err;
+
+	err = lkl_sys_unlink(dev_str);
+	if (err)
+		return err;
+
+	return lkl_sys_rmdir(mnt_str);
+}
+
+struct lkl_dir {
+	int fd;
+	char buf[1024];
+	char *pos;
+	int len;
+};
+
+static struct lkl_dir *lkl_dir_alloc(int *err)
+{
+	struct lkl_dir *dir = lkl_host_ops.mem_alloc(sizeof(struct lkl_dir));
+
+	if (!dir) {
+		*err = -LKL_ENOMEM;
+		return NULL;
+	}
+
+	dir->len = 0;
+	dir->pos = NULL;
+
+	return dir;
+}
+
+struct lkl_dir *lkl_opendir(const char *path, int *err)
+{
+	struct lkl_dir *dir = lkl_dir_alloc(err);
+
+	if (!dir) {
+		*err = -LKL_ENOMEM;
+		return NULL;
+	}
+
+	dir->fd = lkl_sys_open(path, LKL_O_RDONLY | LKL_O_DIRECTORY, 0);
+	if (dir->fd < 0) {
+		*err = dir->fd;
+		lkl_host_ops.mem_free(dir);
+		return NULL;
+	}
+
+	*err = 0;
+
+	return dir;
+}
+
+struct lkl_dir *lkl_fdopendir(int fd, int *err)
+{
+	struct lkl_dir *dir = lkl_dir_alloc(err);
+
+	if (!dir)
+		return NULL;
+
+	dir->fd = fd;
+
+	return dir;
+}
+
+void lkl_rewinddir(struct lkl_dir *dir)
+{
+	lkl_sys_lseek(dir->fd, 0, LKL_SEEK_SET);
+	dir->len = 0;
+	dir->pos = NULL;
+}
+
+int lkl_closedir(struct lkl_dir *dir)
+{
+	int ret;
+
+	ret = lkl_sys_close(dir->fd);
+	lkl_host_ops.mem_free(dir);
+
+	return ret;
+}
+
+struct lkl_linux_dirent64 *lkl_readdir(struct lkl_dir *dir)
+{
+	struct lkl_linux_dirent64 *de;
+
+	if (dir->len < 0)
+		return NULL;
+
+	if (!dir->pos || dir->pos - dir->buf >= dir->len)
+		goto read_buf;
+
+return_de:
+	de = (struct lkl_linux_dirent64 *)dir->pos;
+	dir->pos += de->d_reclen;
+
+	return de;
+
+read_buf:
+	dir->pos = NULL;
+	de = (struct lkl_linux_dirent64 *)dir->buf;
+	dir->len = lkl_sys_getdents64(dir->fd, de, sizeof(dir->buf));
+	if (dir->len <= 0)
+		return NULL;
+
+	dir->pos = dir->buf;
+	goto return_de;
+}
+
+int lkl_errdir(struct lkl_dir *dir)
+{
+	if (dir->len >= 0)
+		return 0;
+
+	return dir->len;
+}
+
+int lkl_dirfd(struct lkl_dir *dir)
+{
+	return dir->fd;
+}
+
+int lkl_set_fd_limit(unsigned int fd_limit)
+{
+	struct lkl_rlimit rlim = {
+		.rlim_cur = fd_limit,
+		.rlim_max = fd_limit,
+	};
+	return lkl_sys_setrlimit(LKL_RLIMIT_NOFILE, &rlim);
+}
diff --git a/tools/um/lib/posix-host.c b/tools/um/lib/posix-host.c
index b6b5b2902254..8fd88031bf2b 100644
--- a/tools/um/lib/posix-host.c
+++ b/tools/um/lib/posix-host.c
@@ -263,6 +263,7 @@  static void *tls_get(struct lkl_tls_key *key)
 }
 
 struct lkl_host_operations lkl_host_ops = {
+	.um_devices = lkl_um_devs,
 	.panic = panic,
 	.print = print,
 	.mem_alloc = (void *)malloc,
diff --git a/tools/um/lib/utils.c b/tools/um/lib/utils.c
index 4930479a8a35..ac65cd744a14 100644
--- a/tools/um/lib/utils.c
+++ b/tools/um/lib/utils.c
@@ -4,6 +4,9 @@ 
 #include <string.h>
 #include <lkl_host.h>
 
+/* XXX: find a better place */
+char lkl_um_devs[4096];
+
 static const char * const lkl_err_strings[] = {
 	"Success",
 	"Operation not permitted",
diff --git a/tools/um/tests/Build b/tools/um/tests/Build
index 564560486f98..1aa78f9ed7ef 100644
--- a/tools/um/tests/Build
+++ b/tools/um/tests/Build
@@ -1,3 +1,4 @@ 
 boot-y += boot.o test.o
+disk-y += disk.o test.o cla.o
 
 CFLAGS_test.o += -Wno-implicit-fallthrough
diff --git a/tools/um/tests/cla.c b/tools/um/tests/cla.c
new file mode 100644
index 000000000000..694e3a3822be
--- /dev/null
+++ b/tools/um/tests/cla.c
@@ -0,0 +1,159 @@ 
+// SPDX-License-Identifier: GPL-2.0
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#ifdef __MINGW32__
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+#include "cla.h"
+
+static int cl_arg_parse_bool(struct cl_arg *arg, const char *value)
+{
+	*((int *)arg->store) = 1;
+	return 0;
+}
+
+static int cl_arg_parse_str(struct cl_arg *arg, const char *value)
+{
+	*((const char **)arg->store) = value;
+	return 0;
+}
+
+static int cl_arg_parse_int(struct cl_arg *arg, const char *value)
+{
+	errno = 0;
+	*((int *)arg->store) = strtol(value, NULL, 0);
+	return errno == 0;
+}
+
+static int cl_arg_parse_str_set(struct cl_arg *arg, const char *value)
+{
+	const char **set = arg->set;
+	int i;
+
+	for (i = 0; set[i] != NULL; i++) {
+		if (strcmp(set[i], value) == 0) {
+			*((int *)arg->store) = i;
+			return 0;
+		}
+	}
+
+	return (-1);
+}
+
+static int cl_arg_parse_ipv4(struct cl_arg *arg, const char *value)
+{
+	unsigned int addr;
+
+	if (!value)
+		return (-1);
+
+	addr = inet_addr(value);
+	if (addr == INADDR_NONE)
+		return (-1);
+	*((unsigned int *)arg->store) = addr;
+	return 0;
+}
+
+static cl_arg_parser_t parsers[] = {
+	[CL_ARG_BOOL] = cl_arg_parse_bool,
+	[CL_ARG_INT] = cl_arg_parse_int,
+	[CL_ARG_STR] = cl_arg_parse_str,
+	[CL_ARG_STR_SET] = cl_arg_parse_str_set,
+	[CL_ARG_IPV4] = cl_arg_parse_ipv4,
+};
+
+static struct cl_arg *find_short_arg(char name, struct cl_arg *args)
+{
+	struct cl_arg *arg;
+
+	for (arg = args; arg->short_name != 0; arg++) {
+		if (arg->short_name == name)
+			return arg;
+	}
+
+	return NULL;
+}
+
+static struct cl_arg *find_long_arg(const char *name, struct cl_arg *args)
+{
+	struct cl_arg *arg;
+
+	for (arg = args; arg->long_name; arg++) {
+		if (strcmp(arg->long_name, name) == 0)
+			return arg;
+	}
+
+	return NULL;
+}
+
+static void print_help(struct cl_arg *args)
+{
+	struct cl_arg *arg;
+
+	fprintf(stderr, "usage:\n");
+	for (arg = args; arg->long_name; arg++) {
+		fprintf(stderr, "-%c, --%-20s %s", arg->short_name,
+			arg->long_name, arg->help);
+		if (arg->type == CL_ARG_STR_SET) {
+			const char **set = arg->set;
+
+			fprintf(stderr, " [ ");
+			while (*set != NULL)
+				fprintf(stderr, "%s ", *(set++));
+			fprintf(stderr, "]");
+		}
+		fprintf(stderr, "\n");
+	}
+}
+
+int cla_parse_args(int argc, const char **argv, struct cl_arg *args)
+{
+	int i;
+
+	for (i = 1; i < argc; i++) {
+		struct cl_arg *arg = NULL;
+		cl_arg_parser_t parser;
+
+		if (argv[i][0] == '-') {
+			if (argv[i][1] != '-')
+				arg = find_short_arg(argv[i][1], args);
+			else
+				arg = find_long_arg(&argv[i][2], args);
+		}
+
+		if (!arg) {
+			fprintf(stderr, "unknown option '%s'\n", argv[i]);
+			print_help(args);
+			return (-1);
+		}
+
+		if (arg->type == CL_ARG_USER || arg->type >= CL_ARG_END)
+			parser = arg->parser;
+		else
+			parser = parsers[arg->type];
+
+		if (!parser) {
+			fprintf(stderr, "can't parse --'%s'/-'%c'\n",
+				arg->long_name, args->short_name);
+			return (-1);
+		}
+
+		if (parser(arg, argv[i + 1]) < 0) {
+			fprintf(stderr, "can't parse '%s'\n", argv[i]);
+			print_help(args);
+			return (-1);
+		}
+
+		if (arg->has_arg)
+			i++;
+	}
+
+	return 0;
+}
diff --git a/tools/um/tests/cla.h b/tools/um/tests/cla.h
new file mode 100644
index 000000000000..3d879233681f
--- /dev/null
+++ b/tools/um/tests/cla.h
@@ -0,0 +1,33 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _LKL_TEST_CLA_H
+#define _LKL_TEST_CLA_H
+
+enum cl_arg_type {
+	CL_ARG_USER = 0,
+	CL_ARG_BOOL,
+	CL_ARG_INT,
+	CL_ARG_STR,
+	CL_ARG_STR_SET,
+	CL_ARG_IPV4,
+	CL_ARG_END,
+};
+
+struct cl_arg;
+
+typedef int (*cl_arg_parser_t)(struct cl_arg *arg, const char *value);
+
+struct cl_arg {
+	const char *long_name;
+	char short_name;
+	const char *help;
+	int has_arg;
+	enum cl_arg_type type;
+	void *store;
+	void *set;
+	cl_arg_parser_t parser;
+};
+
+int cla_parse_args(int argc, const char **argv, struct cl_arg *args);
+
+
+#endif /* _LKL_TEST_CLA_H */
diff --git a/tools/um/tests/disk.c b/tools/um/tests/disk.c
new file mode 100644
index 000000000000..325934935e6a
--- /dev/null
+++ b/tools/um/tests/disk.c
@@ -0,0 +1,168 @@ 
+// SPDX-License-Identifier: GPL-2.0
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <lkl.h>
+#include <lkl_host.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#include "test.h"
+#include "cla.h"
+
+static struct {
+	int printk;
+	const char *disk;
+	const char *fstype;
+	int partition;
+} cla;
+
+struct cl_arg args[] = {
+	{"disk", 'd', "disk file to use", 1, CL_ARG_STR, &cla.disk},
+	{"partition", 'P', "partition to mount", 1, CL_ARG_INT, &cla.partition},
+	{"type", 't', "filesystem type", 1, CL_ARG_STR, &cla.fstype},
+	{0},
+};
+
+
+static struct lkl_disk disk;
+static int disk_id = -1;
+
+int lkl_test_disk_add(void)
+{
+	disk.fd = open(cla.disk, O_RDWR);
+	if (disk.fd < 0)
+		goto out_unlink;
+
+	disk.ops = NULL;
+	disk.dev = (char *)cla.disk;
+
+	disk_id = lkl_disk_add(&disk);
+	if (disk_id < 0)
+		goto out_close;
+
+	goto out;
+
+out_close:
+	close(disk.fd);
+
+out_unlink:
+	unlink(cla.disk);
+
+out:
+	lkl_test_logf("disk fd/handle %x disk_id %d", disk.fd, disk_id);
+
+	if (disk_id >= 0)
+		return TEST_SUCCESS;
+
+	return TEST_FAILURE;
+}
+
+int lkl_test_disk_remove(void)
+{
+	int ret;
+
+	ret = lkl_disk_remove(disk);
+
+	close(disk.fd);
+
+	if (ret == 0)
+		return TEST_SUCCESS;
+
+	return TEST_FAILURE;
+}
+
+
+static char mnt_point[32];
+
+LKL_TEST_CALL(mount_dev, lkl_mount_dev, 0, disk_id, cla.partition, cla.fstype,
+	      0, NULL, mnt_point, sizeof(mnt_point))
+
+static int lkl_test_umount_dev(void)
+{
+	long ret, ret2;
+
+	ret = lkl_sys_chdir("/");
+
+	ret2 = lkl_umount_dev(disk_id, cla.partition, 0, 1000);
+
+	lkl_test_logf("%ld %ld", ret, ret2);
+
+	if (!ret && !ret2)
+		return TEST_SUCCESS;
+
+	return TEST_FAILURE;
+}
+
+struct lkl_dir *dir;
+
+static int lkl_test_opendir(void)
+{
+	int err;
+
+	dir = lkl_opendir(mnt_point, &err);
+
+	lkl_test_logf("lkl_opedir(%s) = %d %s\n", mnt_point, err,
+		      lkl_strerror(err));
+
+	if (err == 0)
+		return TEST_SUCCESS;
+
+	return TEST_FAILURE;
+}
+
+static int lkl_test_readdir(void)
+{
+	struct lkl_linux_dirent64 *de = lkl_readdir(dir);
+	int wr = 0;
+
+	while (de) {
+		wr += lkl_test_logf("%s ", de->d_name);
+		if (wr >= 70) {
+			lkl_test_logf("\n");
+			wr = 0;
+			break;
+		}
+		de = lkl_readdir(dir);
+	}
+
+	if (lkl_errdir(dir) == 0)
+		return TEST_SUCCESS;
+
+	return TEST_FAILURE;
+}
+
+LKL_TEST_CALL(closedir, lkl_closedir, 0, dir);
+LKL_TEST_CALL(chdir_mnt_point, lkl_sys_chdir, 0, mnt_point);
+LKL_TEST_CALL(start_kernel, lkl_start_kernel, 0, &lkl_host_ops,
+	     "mem=16M loglevel=8");
+LKL_TEST_CALL(stop_kernel, lkl_sys_halt, 0);
+
+struct lkl_test tests[] = {
+	LKL_TEST(disk_add),
+	LKL_TEST(start_kernel),
+	LKL_TEST(mount_dev),
+	LKL_TEST(chdir_mnt_point),
+	LKL_TEST(opendir),
+	LKL_TEST(readdir),
+	LKL_TEST(closedir),
+	LKL_TEST(umount_dev),
+	LKL_TEST(stop_kernel),
+	LKL_TEST(disk_remove),
+
+};
+
+int main(int argc, const char **argv)
+{
+	if (cla_parse_args(argc, argv, args) < 0)
+		return (-1);
+
+	lkl_host_ops.print = lkl_test_log;
+
+	return lkl_test_run(tests, sizeof(tests)/sizeof(struct lkl_test),
+			    "disk %s", cla.fstype);
+}
diff --git a/tools/um/tests/disk.sh b/tools/um/tests/disk.sh
new file mode 100755
index 000000000000..e2ec6cf69d4b
--- /dev/null
+++ b/tools/um/tests/disk.sh
@@ -0,0 +1,70 @@ 
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0
+
+script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd)
+
+source $script_dir/test.sh
+
+function prepfs()
+{
+    set -e
+
+    file=`mktemp`
+
+    dd if=/dev/zero of=$file bs=1024 count=204800
+
+    yes | mkfs.$1 $file
+
+    if ! [ -z $ANDROID_WDIR ]; then
+        adb shell mkdir -p $ANDROID_WDIR
+        adb push $file $ANDROID_WDIR
+        rm $file
+        file=$ANDROID_WDIR/$(basename $file)
+    fi
+    if ! [ -z $BSD_WDIR ]; then
+        $MYSSH mkdir -p $BSD_WDIR
+        ssh_copy $file $BSD_WDIR
+        rm $file
+        file=$BSD_WDIR/$(basename $file)
+    fi
+
+    export_vars file
+}
+
+function cleanfs()
+{
+    set -e
+
+    if ! [ -z $ANDROID_WDIR ]; then
+        adb shell rm $1
+        adb shell rm $ANDROID_WDIR/disk
+    elif ! [ -z $BSD_WDIR ]; then
+        $MYSSH rm $1
+        $MYSSH rm $BSD_WDIR/disk
+    else
+        rm $1
+    fi
+}
+
+if [ "$1" = "-t" ]; then
+    shift
+    fstype=$1
+    shift
+fi
+
+if [ -z "$fstype" ]; then
+    fstype="ext4"
+fi
+
+if [ -z $(which mkfs.$fstype) ]; then
+    lkl_test_plan 0 "disk $fstype"
+    echo "no mkfs.$fstype command"
+    exit 0
+fi
+
+lkl_test_plan 1 "disk $fstype"
+lkl_test_run 1 prepfs $fstype
+lkl_test_exec $script_dir/disk -d $file -t $fstype $@
+lkl_test_plan 1 "disk $fstype"
+lkl_test_run 1 cleanfs $file
+
diff --git a/tools/um/tests/run.py b/tools/um/tests/run.py
index c96ede90b6ad..97d6dedc217c 100755
--- a/tools/um/tests/run.py
+++ b/tools/um/tests/run.py
@@ -50,6 +50,8 @@  mydir=os.path.dirname(os.path.realpath(__file__))
 
 tests = [
     'boot.sh',
+    'disk.sh -t ext4',
+    'disk.sh -t vfat',
 ]
 
 parser = argparse.ArgumentParser(description='LKL test runner')