This commit adds block device support for library 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@xxxxxxxxx> --- arch/um/Kconfig | 4 + arch/um/include/asm/xor.h | 3 +- arch/um/include/shared/as-layout.h | 1 + arch/um/kernel/um_arch.c | 5 + arch/um/lkl/include/uapi/asm/host_ops.h | 5 + arch/um/lkl/include/uapi/asm/unistd.h | 2 + arch/um/lkl/um/setup.c | 12 + tools/testing/selftests/um/Makefile | 5 +- tools/testing/selftests/um/cla.c | 159 ++++++++ tools/testing/selftests/um/cla.h | 33 ++ tools/testing/selftests/um/disk-ext4.sh | 6 + tools/testing/selftests/um/disk-vfat.sh | 6 + tools/testing/selftests/um/disk.c | 166 +++++++++ tools/testing/selftests/um/disk.sh | 67 ++++ 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 | 2 + 20 files changed, 1144 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/um/cla.c create mode 100644 tools/testing/selftests/um/cla.h create mode 100755 tools/testing/selftests/um/disk-ext4.sh create mode 100755 tools/testing/selftests/um/disk-vfat.sh create mode 100644 tools/testing/selftests/um/disk.c create mode 100755 tools/testing/selftests/um/disk.sh create mode 100644 tools/um/lib/fs.c diff --git a/arch/um/Kconfig b/arch/um/Kconfig index 24c6596260de..5fb6a852d058 100644 --- a/arch/um/Kconfig +++ b/arch/um/Kconfig @@ -29,6 +29,10 @@ config UMMODE_LIB 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 help This mode switches a mode to build a library of UML (Linux Kernel Library/LKL). If this is Y, the build only generates, diff --git a/arch/um/include/asm/xor.h b/arch/um/include/asm/xor.h index 36b33d62a35d..934a576ec4b2 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_UMMODE_LIB)) ? &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 dfc6194b5ac7..3968842346a2 100644 --- a/arch/um/kernel/um_arch.c +++ b/arch/um/kernel/um_arch.c @@ -42,6 +42,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/lkl/include/uapi/asm/host_ops.h b/arch/um/lkl/include/uapi/asm/host_ops.h index 85d7d4790602..c7239df7f807 100644 --- a/arch/um/lkl/include/uapi/asm/host_ops.h +++ b/arch/um/lkl/include/uapi/asm/host_ops.h @@ -12,11 +12,16 @@ struct lkl_jmp_buf { /** * struct lkl_host_operations - host operations used by the Linux kernel * + * @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. + * * These operations must be provided by a host library or by the application * itself. * */ struct lkl_host_operations { + const char *um_devices; }; /** diff --git a/arch/um/lkl/include/uapi/asm/unistd.h b/arch/um/lkl/include/uapi/asm/unistd.h index 6f5d67b45fbd..d777e4b597d2 100644 --- a/arch/um/lkl/include/uapi/asm/unistd.h +++ b/arch/um/lkl/include/uapi/asm/unistd.h @@ -3,6 +3,8 @@ #define __UM_LIBMODE_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/lkl/um/setup.c b/arch/um/lkl/um/setup.c index 12b235826573..f33e9f477b7a 100644 --- a/arch/um/lkl/um/setup.c +++ b/arch/um/lkl/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_sem_alloc(0); if (!init_sem) return -ENOMEM; diff --git a/tools/testing/selftests/um/Makefile b/tools/testing/selftests/um/Makefile index 1b915f4bb751..df9561a0b3b4 100644 --- a/tools/testing/selftests/um/Makefile +++ b/tools/testing/selftests/um/Makefile @@ -4,10 +4,13 @@ top_srcdir = ../../../../ top_objdir = $(OUTPUT)/../../ CFLAGS := -fPIC -I$(top_objdir)/tools/um/include/ -I$(top_srcdir)/tools/um/include/ -g -CFLAGS += test.c +CFLAGS += test.c cla.c LDFLAGS := -pie LDLIBS := -L$(top_objdir)/tools/um -L$(top_srcdir)/tools/um -llinux -lpthread -lrt -lutil TEST_GEN_PROGS := boot +TEST_PROGS := disk-ext4.sh disk-vfat.sh +TEST_PROGS_EXTENDED := test.sh disk.sh +TEST_GEN_FILES := disk include ../lib.mk diff --git a/tools/testing/selftests/um/cla.c b/tools/testing/selftests/um/cla.c new file mode 100644 index 000000000000..694e3a3822be --- /dev/null +++ b/tools/testing/selftests/um/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/testing/selftests/um/cla.h b/tools/testing/selftests/um/cla.h new file mode 100644 index 000000000000..3d879233681f --- /dev/null +++ b/tools/testing/selftests/um/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/testing/selftests/um/disk-ext4.sh b/tools/testing/selftests/um/disk-ext4.sh new file mode 100755 index 000000000000..a021f0efd057 --- /dev/null +++ b/tools/testing/selftests/um/disk-ext4.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 + +script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd) + +${script_dir}/disk.sh -t ext4 diff --git a/tools/testing/selftests/um/disk-vfat.sh b/tools/testing/selftests/um/disk-vfat.sh new file mode 100755 index 000000000000..955ccec40193 --- /dev/null +++ b/tools/testing/selftests/um/disk-vfat.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 + +script_dir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd) + +${script_dir}/disk.sh -t vfat diff --git a/tools/testing/selftests/um/disk.c b/tools/testing/selftests/um/disk.c new file mode 100644 index 000000000000..d70468e66170 --- /dev/null +++ b/tools/testing/selftests/um/disk.c @@ -0,0 +1,166 @@ +// 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_opendir(%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); + + return lkl_test_run(tests, sizeof(tests)/sizeof(struct lkl_test), + "disk %s", cla.fstype); +} diff --git a/tools/testing/selftests/um/disk.sh b/tools/testing/selftests/um/disk.sh new file mode 100755 index 000000000000..9cf67a76bbe3 --- /dev/null +++ b/tools/testing/selftests/um/disk.sh @@ -0,0 +1,67 @@ +#!/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 + echo "no mkfs.$fstype command" + exit 0 +fi + +prepfs $fstype +${OUTPUT}/disk -d $file -t $fstype $@ +cleanfs $file + diff --git a/tools/um/include/lkl.h b/tools/um/include/lkl.h index 97da2d11502f..1cfa228880d4 100644 --- a/tools/um/include/lkl.h +++ b/tools/um/include/lkl.h @@ -120,6 +120,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 @@ -130,6 +140,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 @@ -151,6 +191,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 5fe7a64dc4dd..1a2794ef47a0 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]; #ifdef __cplusplus } 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..352b25f0c56f --- /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_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_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_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 f7c170fd3fd9..5fc7d6822a7f 100644 --- a/tools/um/lib/posix-host.c +++ b/tools/um/lib/posix-host.c @@ -268,5 +268,6 @@ void *lkl_tls_get(struct lkl_tls_key *key) } struct lkl_host_operations lkl_host_ops = { + .um_devices = lkl_um_devs, }; diff --git a/tools/um/lib/utils.c b/tools/um/lib/utils.c index 3b38e1a95124..03841f70f73e 100644 --- a/tools/um/lib/utils.c +++ b/tools/um/lib/utils.c @@ -4,6 +4,8 @@ #include <string.h> #include <lkl_host.h> +char lkl_um_devs[4096]; + static const char * const lkl_err_strings[] = { [0] = "Success", [LKL_EPERM] = "Operation not permitted", -- 2.21.0 (Apple Git-122.2)