[RFC v7 21/21] um: nommu: add block device support of UML
Anton Ivanov
anton.ivanov at cambridgegreys.com
Wed Oct 7 10:17:31 EDT 2020
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 at 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')
--
Anton R. Ivanov
Cambridgegreys Limited. Registered in England. Company Number 10273661
https://www.cambridgegreys.com/
More information about the linux-um
mailing list