diff options
Diffstat (limited to 'src/kernel/tests/lib')
67 files changed, 15556 insertions, 0 deletions
diff --git a/src/kernel/tests/lib/CMakeLists.txt b/src/kernel/tests/lib/CMakeLists.txt new file mode 100644 index 0000000..0ce8982 --- /dev/null +++ b/src/kernel/tests/lib/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.10) + +project(xloop-kernel-test-lib) + +add_library(libltp STATIC ${CMAKE_CURRENT_SOURCE_DIR}/cloner.c + ${CMAKE_CURRENT_SOURCE_DIR}/get_path.c + ${CMAKE_CURRENT_SOURCE_DIR}/parse_opts.c + ${CMAKE_CURRENT_SOURCE_DIR}/random_range.c + ${CMAKE_CURRENT_SOURCE_DIR}/safe_file_ops.c + ${CMAKE_CURRENT_SOURCE_DIR}/safe_macros.c + ${CMAKE_CURRENT_SOURCE_DIR}/safe_net.c + ${CMAKE_CURRENT_SOURCE_DIR}/safe_pthread.c + ${CMAKE_CURRENT_SOURCE_DIR}/safe_stdio.c + ${CMAKE_CURRENT_SOURCE_DIR}/self_exec.c + ${CMAKE_CURRENT_SOURCE_DIR}/tlibio.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_af_alg.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_ansi_color.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_assert.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_buffers.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_capability.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_cgroup.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_checkpoint.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_checksum.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_clocks.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_cmd.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_coredump.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_cpu.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_crypto.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_device.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_dir_is_empty.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_fill_file.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_fill_fs.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_fs_has_free.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_fs_link_count.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_fs_setup.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_fs_type.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_get_bad_addr.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_hugepage.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_ioctl.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_kconfig.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_kernel.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_kvercmp.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_lockdown.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_memutils.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_mkfs.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_module.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_net.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_parse_opts.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_path_has_mnt_flags.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_pid.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_process_state.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_res.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_resource.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_safe_macros.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_safe_sysv_ipc.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_safe_timerfd.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_sig.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_sig_proc.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_status.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_supported_fs_types.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_sys_conf.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_taint.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_test.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_timer.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_timer_test.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_tmpdir.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_virt.c + ${CMAKE_CURRENT_SOURCE_DIR}/tst_wallclock.c) +target_include_directories(libltp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_options(libltp PUBLIC "-Wno-deprecated-declarations") diff --git a/src/kernel/tests/lib/cloner.c b/src/kernel/tests/lib/cloner.c new file mode 100644 index 0000000..11401f2 --- /dev/null +++ b/src/kernel/tests/lib/cloner.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) International Business Machines Corp., 2009 + * Some wrappers for clone functionality. Thrown together by Serge Hallyn + * <serue@us.ibm.com> based on existing clone usage in ltp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <sched.h> +#include <stdarg.h> +#include "config.h" +#include "tst_clone.h" + +#undef clone /* we want to use clone() */ + +/* + * The ia64 port has never included a prototype for __clone2(). It was updated + * to take eight parameters in glibc commit: + * + * commit 625f22fc7f8e0d61e3e6cff2c65468b91dbad426 + * Author: Ulrich Drepper <drepper@redhat.com> + * Date: Mon Mar 3 19:53:27 2003 +0000 + * + * The first release that contained this commit was glibc-2.3.3 which is old + * enough to assume that __clone2() takes eight parameters. + */ +#if defined(__ia64__) +extern int __clone2(int (*fn) (void *arg), void *child_stack_base, + size_t child_stack_size, int flags, void *arg, + pid_t *parent_tid, void *tls, pid_t *child_tid); +#endif + +#ifndef CLONE_SUPPORTS_7_ARGS +# define clone(fn, stack, flags, arg, ptid, tls, ctid) \ + clone(fn, stack, flags, arg) +#endif + +/* + * ltp_clone: wrapper for clone to hide the architecture dependencies. + * 1. hppa takes bottom of stack and no stacksize (stack grows up) + * 2. __ia64__ takes bottom of stack and uses clone2 + * 3. all others take top of stack (stack grows down) + */ +static int +ltp_clone_(unsigned long flags, int (*fn)(void *arg), void *arg, + size_t stack_size, void *stack, pid_t *ptid, void *tls, pid_t *ctid) +{ + int ret; + +#if defined(__ia64__) + ret = __clone2(fn, stack, stack_size, flags, arg, ptid, tls, ctid); +#else +# if defined(__hppa__) || defined(__metag__) + /* + * These arches grow their stack up, so don't need to adjust the base. + * XXX: This should be made into a runtime test. + */ +# else + /* + * For archs where stack grows downwards, stack points to the topmost + * address of the memory space set up for the child stack. + */ + if (stack) + stack += stack_size; +# endif + + ret = clone(fn, stack, flags, arg, ptid, tls, ctid); +#endif + + return ret; +} + +int ltp_clone(unsigned long flags, int (*fn)(void *arg), void *arg, + size_t stack_size, void *stack) +{ + return ltp_clone_(flags, fn, arg, stack_size, stack, NULL, NULL, NULL); +} + +int ltp_clone7(unsigned long flags, int (*fn)(void *arg), void *arg, + size_t stack_size, void *stack, ...) +{ + pid_t *ptid, *ctid; + void *tls; + va_list arg_clone; + + va_start(arg_clone, stack); + ptid = va_arg(arg_clone, pid_t *); + tls = va_arg(arg_clone, void *); + ctid = va_arg(arg_clone, pid_t *); + va_end(arg_clone); + +#ifdef CLONE_SUPPORTS_7_ARGS + return ltp_clone_(flags, fn, arg, stack_size, stack, ptid, tls, ctid); +#else + errno = ENOSYS; + return -1; +#endif +} + +/* + * ltp_alloc_stack: allocate stack of size 'size', that is sufficiently + * aligned for all arches. User is responsible for freeing allocated + * memory. + * Returns pointer to new stack. On error, returns NULL with errno set. + */ +void *ltp_alloc_stack(size_t size) +{ + void *ret = NULL; + int err; + + err = posix_memalign(&ret, 64, size); + if (err) + errno = err; + + return ret; +} + +/* + * ltp_clone_alloc: also does the memory allocation for clone with a + * caller-specified size. + */ +int +ltp_clone_alloc(unsigned long clone_flags, int (*fn) (void *arg), void *arg, + size_t stack_size) +{ + void *stack; + int ret; + int saved_errno; + + stack = ltp_alloc_stack(stack_size); + if (stack == NULL) + return -1; + + ret = ltp_clone(clone_flags, fn, arg, stack_size, stack); + + if (ret == -1) { + saved_errno = errno; + free(stack); + errno = saved_errno; + } + + return ret; +} + +/* + * ltp_clone_quick: calls ltp_clone_alloc with predetermined stack size. + * Experience thus far suggests that one page is often insufficient, + * while 6*getpagesize() seems adequate. + */ +int ltp_clone_quick(unsigned long clone_flags, int (*fn) (void *arg), void *arg) +{ + size_t stack_size = getpagesize() * 6; + + return ltp_clone_alloc(clone_flags, fn, arg, stack_size); +} diff --git a/src/kernel/tests/lib/errnos.h b/src/kernel/tests/lib/errnos.h new file mode 100644 index 0000000..df8fea8 --- /dev/null +++ b/src/kernel/tests/lib/errnos.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2009-2013 Cyril Hrubis <chrubis@suse.cz> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + */ + +const char *tst_strerrno(int err) +{ + static const struct pair errno_pairs[] = { + STRPAIR(0, "SUCCESS") + /* asm-generic/errno-base.h */ + PAIR(EPERM) + PAIR(ENOENT) + PAIR(ESRCH) + PAIR(EINTR) + PAIR(EIO) + PAIR(ENXIO) + PAIR(E2BIG) + PAIR(ENOEXEC) + PAIR(EBADF) + PAIR(ECHILD) + STRPAIR(EAGAIN, "EAGAIN/EWOULDBLOCK") + PAIR(ENOMEM) + PAIR(EACCES) + PAIR(EFAULT) + PAIR(ENOTBLK) + PAIR(EBUSY) + PAIR(EEXIST) + PAIR(EXDEV) + PAIR(ENODEV) + PAIR(ENOTDIR) + PAIR(EISDIR) + PAIR(EINVAL) + PAIR(ENFILE) + PAIR(EMFILE) + PAIR(ENOTTY) + PAIR(ETXTBSY) + PAIR(EFBIG) + PAIR(ENOSPC) + PAIR(ESPIPE) + PAIR(EROFS) + PAIR(EMLINK) + PAIR(EPIPE) + PAIR(EDOM) + PAIR(ERANGE) + /* asm-generic/errno.h */ + PAIR(EDEADLK) + PAIR(ENAMETOOLONG) + PAIR(ENOLCK) + PAIR(ENOSYS) + PAIR(ENOTEMPTY) + PAIR(ELOOP) + /* EWOULDBLOCK == EAGAIN skipped */ + PAIR(ENOMSG) + PAIR(EIDRM) + PAIR(ECHRNG) + PAIR(EL2NSYNC) + PAIR(EL3HLT) + PAIR(EL3RST) + PAIR(ELNRNG) + PAIR(EUNATCH) + PAIR(ENOCSI) + PAIR(EL2HLT) + PAIR(EBADE) + PAIR(EBADR) + PAIR(EXFULL) + PAIR(ENOANO) + PAIR(EBADRQC) + PAIR(EBADSLT) + /* EDEADLOCK == EDEADLK skipped */ + PAIR(EBFONT) + PAIR(ENOSTR) + PAIR(ENODATA) + PAIR(ETIME) + PAIR(ENOSR) + PAIR(ENONET) + PAIR(ENOPKG) + PAIR(EREMOTE) + PAIR(ENOLINK) + PAIR(EADV) + PAIR(ESRMNT) + PAIR(ECOMM) + PAIR(EPROTO) + PAIR(EMULTIHOP) + PAIR(EDOTDOT) + PAIR(EBADMSG) + PAIR(EOVERFLOW) + PAIR(ENOTUNIQ) + PAIR(EBADFD) + PAIR(EREMCHG) + PAIR(ELIBACC) + PAIR(ELIBBAD) + PAIR(ELIBSCN) + PAIR(ELIBMAX) + PAIR(ELIBEXEC) + PAIR(EILSEQ) + PAIR(ERESTART) + PAIR(ESTRPIPE) + PAIR(EUSERS) + PAIR(ENOTSOCK) + PAIR(EDESTADDRREQ) + PAIR(EMSGSIZE) + PAIR(EPROTOTYPE) + PAIR(ENOPROTOOPT) + PAIR(EPROTONOSUPPORT) + PAIR(ESOCKTNOSUPPORT) + PAIR(EOPNOTSUPP) + PAIR(EPFNOSUPPORT) + PAIR(EAFNOSUPPORT) + PAIR(EADDRINUSE) + PAIR(EADDRNOTAVAIL) + PAIR(ENETDOWN) + PAIR(ENETUNREACH) + PAIR(ENETRESET) + PAIR(ECONNABORTED) + PAIR(ECONNRESET) + PAIR(ENOBUFS) + PAIR(EISCONN) + PAIR(ENOTCONN) + PAIR(ESHUTDOWN) + PAIR(ETOOMANYREFS) + PAIR(ETIMEDOUT) + PAIR(ECONNREFUSED) + PAIR(EHOSTDOWN) + PAIR(EHOSTUNREACH) + PAIR(EALREADY) + PAIR(EINPROGRESS) + PAIR(ESTALE) + PAIR(EUCLEAN) + PAIR(ENOTNAM) + PAIR(ENAVAIL) + PAIR(EISNAM) + PAIR(EREMOTEIO) + PAIR(EDQUOT) + PAIR(ENOMEDIUM) + PAIR(EMEDIUMTYPE) + PAIR(ECANCELED) +#ifdef ENOKEY + PAIR(ENOKEY) +#endif +#ifdef EKEYEXPIRED + PAIR(EKEYEXPIRED) +#endif +#ifdef EKEYREVOKED + PAIR(EKEYREVOKED) +#endif +#ifdef EKEYREJECTED + PAIR(EKEYREJECTED) +#endif +#ifdef EOWNERDEAD + PAIR(EOWNERDEAD) +#endif +#ifdef ENOTRECOVERABLE + PAIR(ENOTRECOVERABLE) +#endif +#ifdef ERFKILL + PAIR(ERFKILL) +#endif +#ifdef EHWPOISON + PAIR(EHWPOISON) +#endif + }; + + PAIR_LOOKUP(errno_pairs, err); +} diff --git a/src/kernel/tests/lib/get_path.c b/src/kernel/tests/lib/get_path.c new file mode 100644 index 0000000..aafbc2c --- /dev/null +++ b/src/kernel/tests/lib/get_path.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2010 Cyril Hrubis chrubis@suse.cz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + /* + * Looks for binary prog_name in $PATH. + * + * If such file exists and if you are able at least to read it, zero is + * returned and absolute path to the file is filled into buf. In case buf is + * too short to hold the absolute path + prog_name for the file we are looking + * for -1 is returned as well as when there is no such file in all paths in + * $PATH. + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "test.h" + +static int file_exist(const char *path) +{ + struct stat st; + + if (!access(path, R_OK) && !stat(path, &st) && S_ISREG(st.st_mode)) + return 1; + + return 0; +} + +int tst_get_path(const char *prog_name, char *buf, size_t buf_len) +{ + const char *path = (const char *)getenv("PATH"); + const char *start = path; + const char *end; + size_t size, ret; + + if (path == NULL) + return -1; + + do { + end = strchr(start, ':'); + + if (end != NULL) + snprintf(buf, MIN(buf_len, (size_t) (end - start + 1)), + "%s", start); + else + snprintf(buf, buf_len, "%s", start); + + size = strlen(buf); + + /* + * "::" inside $PATH, $PATH ending with ':' or $PATH starting + * with ':' should be expanded into current working directory. + */ + if (size == 0) { + snprintf(buf, buf_len, "."); + size = strlen(buf); + } + + /* + * If there is no '/' ad the end of path from $PATH add it. + */ + if (buf[size - 1] != '/') + ret = + snprintf(buf + size, buf_len - size, "/%s", + prog_name); + else + ret = + snprintf(buf + size, buf_len - size, "%s", + prog_name); + + if (buf_len - size > ret && file_exist(buf)) + return 0; + + start = end + 1; + + } while (end != NULL); + + return -1; +} diff --git a/src/kernel/tests/lib/parse_opts.c b/src/kernel/tests/lib/parse_opts.c new file mode 100644 index 0000000..a9d5058 --- /dev/null +++ b/src/kernel/tests/lib/parse_opts.c @@ -0,0 +1,621 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * AUTHOR : William Roske/Richard Logan + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ + */ + +#include "config.h" +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/param.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <unistd.h> +#include <time.h> +#include <stdint.h> + +#include "test.h" +#include "ltp_priv.h" +#include "usctest.h" +#include "tst_clocks.h" + +#ifndef UNIT_TEST +#define UNIT_TEST 0 +#endif + +/* Define flags and args for standard options */ +static int STD_INFINITE = 0; /* flag indciating to loop forever */ +int STD_LOOP_COUNT = 1; /* number of iterations */ + +static float STD_LOOP_DURATION = 0.0; /* duration value in fractional seconds */ + +static char **STD_opt_arr = NULL; /* array of option strings */ +static int STD_argind = 1; /* argv index to next argv element */ + /* (first argument) */ + /* To getopt users, it is like optind */ + +/* + * The following variables are to support system testing additions. + */ +static int STD_TP_barrier = 0; /* flag to do barrier in TEST_PAUSE */ + /* 2 - wait_barrier(), 3 - set_barrier(), * - barrier() */ +static int STD_LP_barrier = 0; /* flag to do barrier in TEST_LOOPING */ + /* 2 - wait_barrier(), 3 - set_barrier(), * - barrier() */ +static int STD_TP_shmem_sz = 0; /* shmalloc this many words per pe in TEST_PAUSE */ +static int STD_LD_shmem = 0; /* flag to do shmem_puts and shmem_gets during delay */ +static int STD_LP_shmem = 0; /* flag to do shmem_puts and gets during TEST_LOOPING */ +static int STD_LD_recfun = 0; /* do recressive function calls in loop delay */ +static int STD_LP_recfun = 0; /* do recressive function calls in TEST_LOOPING */ +static int STD_TP_sbrk = 0; /* do sbrk in TEST_PAUSE */ +static int STD_LP_sbrk = 0; /* do sbrk in TEST_LOOPING */ +static char *STD_start_break = 0; /* original sbrk size */ +static int Debug = 0; + +static struct std_option_t { + char *optstr; + char *help; + char *flag; + char **arg; +} std_options[] = { + {"h", " -h Show this help screen\n", NULL, NULL}, + {"i:", " -i n Execute test n times\n", NULL, NULL}, + {"I:", " -I x Execute test for x seconds\n", NULL, NULL}, +#ifdef UCLINUX + {"C:", + " -C ARG Run the child process with arguments ARG (for internal use)\n", + NULL, NULL}, +#endif + {NULL, NULL, NULL, NULL} +}; + +/* + * Structure for usc_recressive_func argument + */ +struct usc_bigstack_t { + char space[4096]; +}; + +static struct usc_bigstack_t *STD_bigstack = NULL; + +/* define the string length for Mesg and Mesg2 strings */ +#define STRLEN 2048 + +static char Mesg2[STRLEN]; /* holds possible return string */ +static void usc_recressive_func(); + +/* + * Define bits for options that might have env variable default + */ +#define OPT_iteration 01 +#define OPT_duration 04 +#define OPT_delay 010 + +#ifdef UCLINUX +/* Allocated and used in self_exec.c: */ +extern char *child_args; /* Arguments to child when -C is used */ +#endif + +static void print_help(void (*user_help)(void)) +{ + int i; + + for (i = 0; std_options[i].optstr; ++i) { + if (std_options[i].help) + printf("%s", std_options[i].help); + } + + if (user_help) + user_help(); +} + +/********************************************************************** + * parse_opts: + **********************************************************************/ +const char *parse_opts(int ac, char **av, const option_t * user_optarr, + void (*uhf)(void)) +{ + int found; /* flag to indicate that an option specified was */ + /* found in the user's list */ + int k; /* scratch integer for returns and short time usage */ + float ftmp; /* tmp float for parsing env variables */ + char *ptr; /* used in getting env variables */ + int options = 0; /* no options specified */ + int optstrlen, i; + char *optionstr; + int opt; + + /* + * If not the first time this function is called, release the old STD_opt_arr + * vector. + */ + if (STD_opt_arr != NULL) { + free(STD_opt_arr); + STD_opt_arr = NULL; + } + /* Calculate how much space we need for the option string */ + optstrlen = 0; + for (i = 0; std_options[i].optstr; ++i) + optstrlen += strlen(std_options[i].optstr); + if (user_optarr) + for (i = 0; user_optarr[i].option; ++i) { + if (strlen(user_optarr[i].option) > 2) + return + "parse_opts: ERROR - Only short options are allowed"; + optstrlen += strlen(user_optarr[i].option); + } + optstrlen += 1; + + /* Create the option string for getopt */ + optionstr = malloc(optstrlen); + if (!optionstr) + return + "parse_opts: ERROR - Could not allocate memory for optionstr"; + + optionstr[0] = '\0'; + + for (i = 0; std_options[i].optstr; ++i) + strcat(optionstr, std_options[i].optstr); + if (user_optarr) + for (i = 0; user_optarr[i].option; ++i) + /* only add the option if it wasn't there already */ + if (strchr(optionstr, user_optarr[i].option[0]) == NULL) + strcat(optionstr, user_optarr[i].option); + + /* + * Loop through av parsing options. + */ + while ((opt = getopt(ac, av, optionstr)) > 0) { + + STD_argind = optind; + + switch (opt) { + case '?': /* Unknown option */ + return "Unknown option"; + break; + case ':': /* Missing Arg */ + return "Missing argument"; + break; + case 'i': /* Iterations */ + options |= OPT_iteration; + STD_LOOP_COUNT = atoi(optarg); + if (STD_LOOP_COUNT == 0) + STD_INFINITE = 1; + break; + case 'I': /* Time duration */ + options |= OPT_duration; + STD_LOOP_DURATION = atof(optarg); + if (STD_LOOP_DURATION == 0.0) + STD_INFINITE = 1; + break; + case 'h': /* Help */ + print_help(uhf); + exit(0); + break; +#ifdef UCLINUX + case 'C': /* Run child */ + child_args = optarg; + break; +#endif + default: + + /* Check all the user specified options */ + found = 0; + for (i = 0; user_optarr[i].option; ++i) { + + if (opt == user_optarr[i].option[0]) { + /* Yup, This is a user option, set the flag and look for argument */ + if (user_optarr[i].flag) { + *user_optarr[i].flag = 1; + } + found++; + + /* save the argument at the user's location */ + if (user_optarr[i]. + option[strlen(user_optarr[i].option) + - 1] == ':') { + *user_optarr[i].arg = optarg; + } + break; /* option found - break out of the for loop */ + } + } + /* This condition "should never happen". SO CHECK FOR IT!!!! */ + if (!found) { + sprintf(Mesg2, + "parse_opts: ERROR - option:\"%c\" NOT FOUND... INTERNAL " + "ERROR", opt); + return (Mesg2); + } + } + + } + free(optionstr); + + STD_argind = optind; + + /* + * Turn on debug + */ + if (getenv("USC_DEBUG") != NULL) { + Debug = 1; + printf("env USC_DEBUG is defined, turning on debug\n"); + } + if (getenv("USC_VERBOSE") != NULL) { + Debug = 1; + printf("env USC_VERBOSE is defined, turning on debug\n"); + } + + /* + * If the USC_ITERATION_ENV environmental variable is set to + * a number, use that number as iteration count (same as -c option). + * The -c option with arg will be used even if this env var is set. + */ + if (!(options & OPT_iteration) + && (ptr = getenv(USC_ITERATION_ENV)) != NULL) { + if (sscanf(ptr, "%i", &k) == 1) { + if (k == 0) { /* if arg is 0, set infinite loop flag */ + STD_INFINITE = 1; + if (Debug) + printf + ("Using env %s, set STD_INFINITE to 1\n", + USC_ITERATION_ENV); + } else { /* else, set the loop count to the arguement */ + STD_LOOP_COUNT = k; + if (Debug) + printf + ("Using env %s, set STD_LOOP_COUNT to %d\n", + USC_ITERATION_ENV, k); + } + } + } + + /* + * If the USC_LOOP_WALLTIME environmental variable is set, + * use that number as duration (same as -I option). + * The -I option with arg will be used even if this env var is set. + */ + + if (!(options & OPT_duration) && + (ptr = getenv(USC_LOOP_WALLTIME)) != NULL) { + if (sscanf(ptr, "%f", &ftmp) == 1 && ftmp >= 0.0) { + STD_LOOP_DURATION = ftmp; + if (Debug) + printf + ("Using env %s, set STD_LOOP_DURATION to %f\n", + USC_LOOP_WALLTIME, ftmp); + if (STD_LOOP_DURATION == 0.0) { /* if arg is 0, set infinite loop flag */ + STD_INFINITE = 1; + if (Debug) + printf + ("Using env %s, set STD_INFINITE to 1\n", + USC_LOOP_WALLTIME); + } + } + } + if (!(options & OPT_duration) && (ptr = getenv("USC_DURATION")) != NULL) { + if (sscanf(ptr, "%f", &ftmp) == 1 && ftmp >= 0.0) { + STD_LOOP_DURATION = ftmp; + if (Debug) + printf + ("Using env USC_DURATION, set STD_LOOP_DURATION to %f\n", + ftmp); + if (STD_LOOP_DURATION == 0.0) { /* if arg is 0, set infinite loop flag */ + STD_INFINITE = 1; + if (Debug) + printf + ("Using env USC_DURATION, set STD_INFINITE to 1\n"); + } + } + } + + /* + * The following are special system testing envs to turn on special + * hooks in the code. + */ + if ((ptr = getenv("USC_TP_BARRIER")) != NULL) { + if (sscanf(ptr, "%i", &k) == 1 && k >= 0) + STD_TP_barrier = k; + else + STD_TP_barrier = 1; + if (Debug) + printf + ("using env USC_TP_BARRIER, Set STD_TP_barrier to %d\n", + STD_TP_barrier); + } + + if ((ptr = getenv("USC_LP_BARRIER")) != NULL) { + if (sscanf(ptr, "%i", &k) == 1 && k >= 0) + STD_LP_barrier = k; + else + STD_LP_barrier = 1; + if (Debug) + printf + ("using env USC_LP_BARRIER, Set STD_LP_barrier to %d\n", + STD_LP_barrier); + } + + if ((ptr = getenv("USC_TP_SHMEM")) != NULL) { + if (sscanf(ptr, "%i", &k) == 1 && k >= 0) { + STD_TP_shmem_sz = k; + if (Debug) + printf + ("Using env USC_TP_SHMEM, Set STD_TP_shmem_sz to %d\n", + STD_TP_shmem_sz); + } + } + + if ((ptr = getenv("USC_LP_SHMEM")) != NULL) { + if (sscanf(ptr, "%i", &k) == 1 && k >= 0) { + STD_LP_shmem = k; + if (Debug) + printf + ("Using env USC_LP_SHMEM, Set STD_LP_shmem to %d\n", + STD_LP_shmem); + } + } + + if ((ptr = getenv("USC_LD_SHMEM")) != NULL) { + if (sscanf(ptr, "%i", &k) == 1 && k >= 0) { + STD_LD_shmem = k; + if (Debug) + printf + ("Using env USC_LD_SHMEM, Set STD_LD_shmem to %d\n", + STD_LD_shmem); + } + } + + if ((ptr = getenv("USC_TP_SBRK")) != NULL) { + if (sscanf(ptr, "%i", &k) == 1 && k >= 0) { + STD_TP_sbrk = k; + if (Debug) + printf + ("Using env USC_TP_SBRK, Set STD_TP_sbrk to %d\n", + STD_TP_sbrk); + } + } +#if !defined(UCLINUX) + if ((ptr = getenv("USC_LP_SBRK")) != NULL) { + if (sscanf(ptr, "%i", &k) == 1 && k >= 0) { + STD_LP_sbrk = k; + if (Debug) + printf + ("Using env USC_LP_SBRK, Set STD_LP_sbrk to %d\n", + STD_LP_sbrk); + } + } +#endif /* if !defined(UCLINUX) */ + + if ((ptr = getenv("USC_LP_RECFUN")) != NULL) { + if (sscanf(ptr, "%i", &k) == 1 && k >= 0) { + STD_LP_recfun = k; + if (STD_bigstack != NULL) + STD_bigstack = + malloc(sizeof(struct usc_bigstack_t)); + if (Debug) + printf + ("Using env USC_LP_RECFUN, Set STD_LP_recfun to %d\n", + STD_LP_recfun); + } + } + + if ((ptr = getenv("USC_LD_RECFUN")) != NULL) { + if (sscanf(ptr, "%i", &k) == 1 && k >= 0) { + STD_LD_recfun = k; + if (STD_bigstack != NULL) + STD_bigstack = + malloc(sizeof(struct usc_bigstack_t)); + if (Debug) + printf + ("Using env USC_LD_RECFUN, Set STD_LD_recfun to %d\n", + STD_LD_recfun); + } + } +#if UNIT_TEST + printf("The following variables after option and env parsing:\n"); + printf("STD_LOOP_DURATION = %f\n", STD_LOOP_DURATION); + printf("STD_LOOP_COUNT = %d\n", STD_LOOP_COUNT); + printf("STD_INFINITE = %d\n", STD_INFINITE); +#endif + + return NULL; +} + +/*********************************************************************** + * This function will do desired end of global setup test + * hooks. + ***********************************************************************/ +int usc_global_setup_hook(void) +{ +#ifndef UCLINUX + if (STD_TP_sbrk || STD_LP_sbrk) + STD_start_break = sbrk(0); /* get original sbreak size */ + + if (STD_TP_sbrk) { + sbrk(STD_TP_sbrk); + if (Debug) + printf("after sbrk(%d)\n", STD_TP_sbrk); + } +#endif + return 0; +} + +#define USECS_PER_SEC 1000000 /* microseconds per second */ + +static uint64_t get_current_time(void) +{ + struct timespec ts; + + tst_clock_gettime(CLOCK_MONOTONIC, &ts); + + return (((uint64_t) ts.tv_sec) * USECS_PER_SEC) + ts.tv_nsec / 1000; +} + +/*********************************************************************** + * + * This function will determine if test should continue iterating + * If the STD_INFINITE flag is set, return 1. + * If the STD_LOOP_COUNT variable is set, compare it against + * the counter. + * If the STD_LOOP_DURATION variable is set, compare current time against + * calculated stop_time. + * This function will return 1 until all desired looping methods + * have been met. + * + * counter integer is supplied by the user program. + ***********************************************************************/ +int usc_test_looping(int counter) +{ + static int first_time = 1; + static uint64_t stop_time = 0; + int keepgoing = 0; + + /* + * If this is the first iteration and we are looping for + * duration of STD_LOOP_DURATION seconds (fractional) or + * doing loop delays, get the clocks per second. + */ + if (first_time) { + first_time = 0; + + /* + * If looping for duration, calculate stop time in + * clocks. + */ + if (STD_LOOP_DURATION) { + stop_time = + (uint64_t) (USECS_PER_SEC * STD_LOOP_DURATION) + + get_current_time(); + } + } + + if (STD_INFINITE) + keepgoing++; + + if (STD_LOOP_COUNT && counter < STD_LOOP_COUNT) + keepgoing++; + + if (STD_LOOP_DURATION != 0.0 && get_current_time() < stop_time) + keepgoing++; + + if (keepgoing == 0) + return 0; + + /* + * The following code allows special system testing hooks. + */ + + if (STD_LP_recfun) { + if (Debug) + printf + ("calling usc_recressive_func(0, %d, *STD_bigstack)\n", + STD_LP_recfun); + usc_recressive_func(0, STD_LP_recfun, *STD_bigstack); + } +#if !defined(UCLINUX) + if (STD_LP_sbrk) { + if (Debug) + printf("about to do sbrk(%d)\n", STD_LP_sbrk); + sbrk(STD_LP_sbrk); + } +#endif + + if (keepgoing) + return 1; + else + return 0; +} + +/* + * This function recressively calls itself max times. + */ +static void usc_recressive_func(int cnt, int max, struct usc_bigstack_t bstack) +{ + if (cnt < max) + usc_recressive_func(cnt + 1, max, bstack); + +} + +#if UNIT_TEST + +/****************************************************************************** + * UNIT TEST CODE + * UNIT TEST CODE + * + * this following code is provide so that unit testing can + * be done fairly easily. + ******************************************************************************/ + +int Help = 0; +int Help2 = 0; +char *ptr; + +long TEST_RETURN; +int TEST_ERRNO; + +/* for test specific parse_opts options */ +option_t Options[] = { + {"help", &Help2, NULL}, /* -help option */ + {"h", &Help, NULL}, /* -h option */ + +#if INVALID_TEST_CASES + {"missingflag", NULL, &ptr}, /* error */ + {"missingarg:", &Help, NULL}, /* error */ +#endif /* INVALID_TEST_CASES */ + + {NULL, NULL, NULL} +}; + +int main(int argc, char **argv) +{ + int lc; + char *msg; + struct timeval t; + int cnt; + + if ((msg = parse_opts(argc, argv, Options, NULL)) != NULL) { + printf("ERROR: %s\n", msg); + exit(1); + } + + TEST_PAUSE; + + for (lc = 0; TEST_LOOPING(lc); lc++) { + + TEST(gettimeofday(&t, NULL)); + printf("iter=%d: sec:%d, usec:%6.6d %s", lc + 1, t.tv_sec, + t.tv_usec, ctime(&t.tv_sec)); + } + + TEST_CLEANUP; + + exit(0); +} + +#endif /* UNIT_TEST */ diff --git a/src/kernel/tests/lib/random_range.c b/src/kernel/tests/lib/random_range.c new file mode 100644 index 0000000..510a4a1 --- /dev/null +++ b/src/kernel/tests/lib/random_range.c @@ -0,0 +1,892 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <malloc.h> +#include "random_range.h" + +/* + * Internal format of the range array set up by parse_range() + */ + +struct range { + int min; + int max; + int mult; +}; + +/* + * parse_ranges() is a function to parse a comma-separated list of range + * tokens each having the following form: + * + * num + * or + * min:max[:mult] + * + * any of the values may be blank (ie. min::mult, :max, etc.) and default + * values for missing arguments may be supplied by the caller. + * + * The special first form is short hand for 'num:num'. + * + * After parsing the string, the ranges are put into an array of integers, + * which is malloc'd by the routine. The min, max, and mult entries of each + * range can be extracted from the array using the range_min(), range_max(), + * and range_mult() functions. + * + * It is the responsibility of the caller to free the space allocated by + * parse_ranges() - a single call to free() will free the space. + * + * str The string to parse - assumed to be a comma-separated + * list of tokens having the above format. + * defmin default value to plug in for min, if it is missing + * defmax default value to plug in for max, if it is missing + * defmult default value to plug in for mult, if missing + * parse_func A user-supplied function pointer, which parse_ranges() + * can call to parse the min, max, and mult strings. This + * allows for customized number formats. The function + * MUST have the following prototype: + * parse_func(char *str, int *val) + * The function should return -1 if str cannot be parsed + * into an integer, or >= 0 if it was successfully + * parsed. The resulting integer will be stored in + * *val. If parse_func is NULL, parse_ranges will parse + * the tokens in a manner consistent with the the sscanf + * %i format. + * range_ptr A user-supplied char **, which will be set to point + * at malloc'd space which holds the parsed range + * values. If range_ptr is NULL, parse_ranges() just + * parses the string. The data returned in range_ptr + * should not be processed directly - use the functions + * range_min(), range_max(), and range_mult() to access + * data for a given range. + * errptr user-supplied char ** which can be set to point to a + * static error string. If errptr is NULL, it is ignored. + * + * parse_range() returns -1 on error, or the number of ranges parsed. + */ + +static int str_to_int(); +static long long divider(long long, long long, long long, long long); + +int parse_ranges(char *str, int defmin, int defmax, int defmult, + int (*parse_func)(), char **rangeptr, char **errptr) +{ + int ncommas; + char *tmpstr, *cp, *tok, *n1str, *n2str, *multstr; + struct range *rp, *ranges; + static char errmsg[256]; + + if (errptr != NULL) { + *errptr = errmsg; + } + + for (ncommas = 0, cp = str; *cp != '\0'; cp++) { + if (*cp == ',') { + ncommas++; + } + } + + if (parse_func == NULL) { + parse_func = str_to_int; + } + + tmpstr = strdup(str); + ranges = malloc((ncommas + 1) * sizeof(struct range)); + rp = ranges; + + tok = strtok(tmpstr, ","); + while (tok != NULL) { + n1str = tok; + n2str = NULL; + multstr = NULL; + + rp->min = defmin; + rp->max = defmax; + rp->mult = defmult; + + if ((cp = strchr(n1str, ':')) != NULL) { + *cp = '\0'; + n2str = cp + 1; + + if ((cp = strchr(n2str, ':')) != NULL) { + *cp = '\0'; + multstr = cp + 1; + } + } + + /* + * Parse the 'min' field - if it is zero length (:n2[:mult] + * format), retain the default value, otherwise, pass the + * string to the parse function. + */ + + if ((int)strlen(n1str) > 0) { + if ((*parse_func) (n1str, &rp->min) < 0) { + sprintf(errmsg, + "error parsing string %s into an integer", + n1str); + free(tmpstr); + free(ranges); + return -1; + } + } + + /* + * Process the 'max' field - if one was not present (n1 format) + * set max equal to min. If the field was present, but + * zero length (n1: format), retain the default. Otherwise + * pass the string to the parse function. + */ + + if (n2str == NULL) { + rp->max = rp->min; + } else if ((int)strlen(n2str) > 0) { + if ((*parse_func) (n2str, &rp->max) < 0) { + sprintf(errmsg, + "error parsing string %s into an integer", + n2str); + free(tmpstr); + free(ranges); + return -1; + } + } + + /* + * Process the 'mult' field - if one was not present + * (n1:n2 format), or the field was zero length (n1:n2: format) + * then set the mult field to defmult - otherwise pass then + * mult field to the parse function. + */ + + if (multstr != NULL && (int)strlen(multstr) > 0) { + if ((*parse_func) (multstr, &rp->mult) < 0) { + sprintf(errmsg, + "error parsing string %s into an integer", + multstr); + free(tmpstr); + free(ranges); + return -1; + } + } + + rp++; + tok = strtok(NULL, ","); + } + + free(tmpstr); + + if (rangeptr != NULL) { + *rangeptr = (char *)ranges; + } else { + free(ranges); /* just running in parse mode */ + } + + return (rp - ranges); +} + +/* + * The default integer-parsing function + */ + +static int str_to_int(char *str, int *ip) +{ + char c; + + if (sscanf(str, "%i%c", ip, &c) != 1) { + return -1; + } else { + return 0; + } +} + +/* + * Three simple functions to return the min, max, and mult values for a given + * range. It is assumed that rbuf is a range buffer set up by parse_ranges(), + * and that r is a valid range within that buffer. + */ + +int range_min(char *rbuf, int r) +{ + return ((struct range *)rbuf)[r].min; +} + +int range_max(char *rbuf, int r) +{ + return ((struct range *)rbuf)[r].max; +} + +int range_mult(char *rbuf, int r) +{ + return ((struct range *)rbuf)[r].mult; +} + +/***************************************************************************** + * random_range(int start, int end, int mult, char **errp) + * + * Returns a psuedo-random number which is >= 'start', <= 'end', and a multiple + * of 'mult'. Start and end may be any valid integer, but mult must be an + * integer > 0. errp is a char ** which will be set to point to a static + * error message buffer if it is not NULL, and an error occurs. + * + * The errp is the only way to check if the routine fails - currently the only + * failure conditions are: + * + * mult < 1 + * no numbers in the start-end range that are a multiple of 'mult' + * + * If random_range_fails, and errp is a valid pointer, it will point to an + * internal error buffer. If errp is a vaild pointer, and random_range + * is successful, errp will be set to NULL. + * + * Note - if mult is 1 (the most common case), there are error conditions + * possible, and errp need not be used. + * + * Note: Uses lrand48(), assuming that set_random_seed() uses srand48() when + * setting the seed. + *****************************************************************************/ + +long random_range(int min, int max, int mult, char **errp) +{ + int r, nmults, orig_min, orig_max, orig_mult, tmp; + extern long lrand48(); + static char errbuf[128]; + + /* + * Sanity check + */ + + if (mult < 1) { + if (errp != NULL) { + sprintf(errbuf, "mult arg must be greater than 0"); + *errp = errbuf; + } + return -1; + } + + /* + * Save original parameter values for use in error message + */ + + orig_min = min; + orig_max = max; + orig_mult = mult; + + /* + * switch min/max if max < min + */ + + if (max < min) { + tmp = max; + max = min; + min = tmp; + } + + /* + * select the random number + */ + + if ((r = min % mult)) /* bump to the next higher 'mult' multiple */ + min += mult - r; + + if ((r = max % mult)) /* reduce to the next lower 'mult' multiple */ + max -= r; + + if (min > max) { /* no 'mult' multiples between min & max */ + if (errp != NULL) { + sprintf(errbuf, + "no numbers in the range %d:%d that are a multiple of %d", + orig_min, orig_max, orig_mult); + *errp = errbuf; + } + return -1; + } + + if (errp != NULL) { + *errp = NULL; + } + + nmults = ((max - min) / mult) + 1; +#if CRAY + /* + * If max is less than 2gb, then the value can fit in 32 bits + * and the standard lrand48() routine can be used. + */ + if (max <= (long)2147483647) { + return (long)(min + (((long)lrand48() % nmults) * mult)); + } else { + /* + * max is greater than 2gb - meeds more than 32 bits. + * Since lrand48 only will get a number up to 32bits. + */ + long randnum; + randnum = divider(min, max, 0, -1); + return (long)(min + ((randnum % nmults) * mult)); + } + +#else + return (min + ((lrand48() % nmults) * mult)); +#endif + +} + +/* + * Just like random_range, but all values are longs. + */ +long random_rangel(long min, long max, long mult, char **errp) +{ + long r, nmults, orig_min, orig_max, orig_mult, tmp; + extern long lrand48(); + static char errbuf[128]; + + /* + * Sanity check + */ + + if (mult < 1) { + if (errp != NULL) { + sprintf(errbuf, "mult arg must be greater than 0"); + *errp = errbuf; + } + return -1; + } + + /* + * Save original parameter values for use in error message + */ + + orig_min = min; + orig_max = max; + orig_mult = mult; + + /* + * switch min/max if max < min + */ + + if (max < min) { + tmp = max; + max = min; + min = tmp; + } + + /* + * select the random number + */ + + if ((r = min % mult)) /* bump to the next higher 'mult' multiple */ + min += mult - r; + + if ((r = max % mult)) /* reduce to the next lower 'mult' multiple */ + max -= r; + + if (min > max) { /* no 'mult' multiples between min & max */ + if (errp != NULL) { + sprintf(errbuf, + "no numbers in the range %ld:%ld that are a multiple of %ld", + orig_min, orig_max, orig_mult); + *errp = errbuf; + } + return -1; + } + + if (errp != NULL) { + *errp = NULL; + } + + nmults = ((max - min) / mult) + 1; +#if CRAY || (_MIPS_SZLONG == 64) + /* + * If max is less than 2gb, then the value can fit in 32 bits + * and the standard lrand48() routine can be used. + */ + if (max <= (long)2147483647) { + return (long)(min + (((long)lrand48() % nmults) * mult)); + } else { + /* + * max is greater than 2gb - meeds more than 32 bits. + * Since lrand48 only will get a number up to 32bits. + */ + long randnum; + randnum = divider(min, max, 0, -1); + return (long)(min + ((randnum % nmults) * mult)); + } + +#else + return (min + ((lrand48() % nmults) * mult)); +#endif +} + +/* + * Attempts to be just like random_range, but everything is long long (64 bit) + */ +long long random_rangell(long long min, long long max, + long long mult, char **errp) +{ + long long r, nmults, orig_min, orig_max, orig_mult, tmp; + long long randnum; + extern long lrand48(); + static char errbuf[128]; + + /* + * Sanity check + */ + + if (mult < 1) { + if (errp != NULL) { + sprintf(errbuf, "mult arg must be greater than 0"); + *errp = errbuf; + } + return -1; + } + + /* + * Save original parameter values for use in error message + */ + + orig_min = min; + orig_max = max; + orig_mult = mult; + + /* + * switch min/max if max < min + */ + + if (max < min) { + tmp = max; + max = min; + min = tmp; + } + + /* + * select the random number + */ + + if ((r = min % mult)) /* bump to the next higher 'mult' multiple */ + min += mult - r; + + if ((r = max % mult)) /* reduce to the next lower 'mult' multiple */ + max -= r; + + if (min > max) { /* no 'mult' multiples between min & max */ + if (errp != NULL) { + sprintf(errbuf, + "no numbers in the range %lld:%lld that are a multiple of %lld", + orig_min, orig_max, orig_mult); + *errp = errbuf; + } + return -1; + } + + if (errp != NULL) { + *errp = NULL; + } + + nmults = ((max - min) / mult) + 1; + /* + * If max is less than 2gb, then the value can fit in 32 bits + * and the standard lrand48() routine can be used. + */ + if (max <= (long)2147483647) { + return (long long)(min + + (((long long)lrand48() % nmults) * mult)); + } else { + /* + * max is greater than 2gb - meeds more than 32 bits. + * Since lrand48 only will get a number up to 32bits. + */ + randnum = divider(min, max, 0, -1); + return (long long)(min + ((randnum % nmults) * mult)); + } + +} + +/* + * This functional will recusively call itself to return a random + * number min and max. It was designed to work the 64bit numbers + * even when compiled as 32 bit process. + * algorithm: to use the official lrand48() routine - limited to 32 bits. + * find the difference between min and max (max-min). + * if the difference is 2g or less, use the random number gotton from lrand48(). + * Determine the midway point between min and max. + * if the midway point is less than 2g from min or max, + * randomly add the random number gotton from lrand48() to + * either min or the midpoint. + * Otherwise, call outself with min and max being min and midway value or + * midway value and max. This will reduce the range in half. + */ +static long long +divider(long long min, long long max, long long cnt, long long rand) +{ + long long med, half, diff; + + /* + * prevent run away code. We are dividing by two each count. + * if we get to a count of more than 32, we should have gotten + * to 2gb. + */ + if (cnt > 32) + return -1; + + /* + * Only get a random number the first time. + */ + if (cnt == 0 || rand < -1) { + rand = (long long)lrand48(); /* 32 bit random number */ + } + + diff = max - min; + + if (diff <= 2147483647) + return min + rand; + + half = diff / (long long)2; /* half the distance between min and max */ + med = min + half; /* med way point between min and max */ + +#if DEBUG + printf("divider: min=%lld, max=%lld, cnt=%lld, rand=%lld\n", min, max, + cnt, rand); + printf(" diff = %lld, half = %lld, med = %lld\n", diff, half, med); +#endif + + if (half <= 2147483647) { + /* + * If half is smaller than 2gb, we can use the random number + * to pick the number within the min to med or med to max + * if the cnt bit of rand is zero or one, respectively. + */ + if (rand & (1 << cnt)) + return med + rand; + else + return min + rand; + } else { + /* + * recursively call ourself to reduce the value to the bottom half + * or top half (bit cnt is set). + */ + if (rand & (1 << cnt)) { + return divider(med, max, cnt + 1, rand); + } else { + return divider(min, med, cnt + 1, rand); + } + + } + +} + +/***************************************************************************** + * random_range_seed(s) + * + * Sets the random seed to s. Uses srand48(), assuming that lrand48() will + * be used in random_range(). + *****************************************************************************/ + +void random_range_seed(long s) +{ + extern void srand48(); + + srand48(s); +} + +/**************************************************************************** + * random_bit(mask) + * + * This function randomly returns a single bit from the bits + * set in mask. If mask is zero, zero is returned. + * + ****************************************************************************/ +long random_bit(long mask) +{ + int nbits = 0; /* number of set bits in mask */ + long bit; /* used to count bits and num of set bits choosen */ + int nshift; /* used to count bit shifts */ + + if (mask == 0) + return 0; + + /* + * get the number of bits set in mask + */ +#ifndef CRAY + + bit = 1L; + for (nshift = 0; (unsigned int)nshift < sizeof(long) * 8; nshift++) { + if (mask & bit) + nbits++; + bit = bit << 1; + } + +#else + nbits = _popcnt(mask); +#endif /* if CRAY */ + + /* + * randomly choose a bit. + */ + bit = random_range(1, nbits, 1, NULL); + + /* + * shift bits until you determine which bit was randomly choosen. + * nshift will hold the number of shifts to make. + */ + + nshift = 0; + while (bit) { + /* check if the current one's bit is set */ + if (mask & 1L) { + bit--; + } + mask = mask >> 1; + nshift++; + } + + return 01L << (nshift - 1); + +} + +#if RANDOM_BIT_UNITTEST +/* + * The following is a unit test main function for random_bit(). + */ +main(argc, argv) +int argc; +char **argv; +{ + int ind; + int cnt, iter; + long mask, ret; + + printf("test for first and last bit set\n"); + mask = 1L; + ret = random_bit(mask); + printf("random_bit(%#o) returned %#o\n", mask, ret); + + mask = 1L << (sizeof(long) * 8 - 1); + ret = random_bit(mask); + printf("random_bit(%#o) returned %#o\n", mask, ret); + + if (argc >= 3) { + iter = atoi(argv[1]); + for (ind = 2; ind < argc; ind++) { + printf("Calling random_bit %d times for mask %#o\n", + iter, mask); + sscanf(argv[ind], "%i", &mask); + for (cnt = 0; cnt < iter; cnt++) { + ret = random_bit(mask); + printf("random_bit(%#o) returned %#o\n", mask, + ret); + } + } + } + exit(0); +} + +#endif /* end if RANDOM_BIT_UNITTEST */ + +#if UNIT_TEST +/* + * The following is a unit test main function for random_range*(). + */ + +#define PARTNUM 10 /* used to determine even distribution of random numbers */ +#define MEG 1024*1024*1024 +#define GIG 1073741824 +int main(argc, argv) +int argc; +char **argv; +{ + int ind; + int cnt, iter = 10; + int imin = 0, imult = 1, itmin, itmax = 0; +#if CRAY + int imax = 6 * GIG; /* higher than 32 bits */ +#else + int imax = 1048576; +#endif + + long lret, lmin = 0, lmult = 1, ltmin, ltmax = 0; +#if CRAY || (_MIPS_SZLONG == 64) + long lmax = 6 * (long)GIG; /* higher than 32 bits */ +#else + long lmax = 1048576; +#endif + long long llret, llmin = 0, llmult = 1, lltmin, lltmax = 0; + long long llmax = (long long)80 * (long long)GIG; + + long part; + long long lpart; + long cntarr[PARTNUM]; + long valbound[PARTNUM]; + long long lvalbound[PARTNUM]; + + for (ind = 0; ind < PARTNUM; ind++) + cntarr[ind] = 0; + + if (argc < 2) { + printf("Usage: %s func [iterations] \n", argv[0]); + printf + ("func can be random_range, random_rangel, random_rangell\n"); + exit(1); + } + + if (argc >= 3) { + if (sscanf(argv[2], "%i", &iter) != 1) { + printf("Usage: %s [func iterations] \n", argv[0]); + printf("argv[2] is not a number\n"); + exit(1); + } + } + + /* + * random_rangel () + */ + if (strcmp(argv[1], "random_rangel") == 0) { + ltmin = lmax; + part = lmax / PARTNUM; + for (ind = 0; ind < PARTNUM; ind++) { + valbound[ind] = part * ind; + } + + for (cnt = 0; cnt < iter; cnt++) { + lret = random_rangel(lmin, lmax, lmult, NULL); + if (iter < 100) + printf("%ld\n", lret); + if (lret < ltmin) + ltmin = lret; + if (lret > ltmax) + ltmax = lret; + for (ind = 0; ind < PARTNUM - 1; ind++) { + if (valbound[ind] < lret + && lret <= valbound[ind + 1]) { + cntarr[ind]++; + break; + } + } + if (lret > valbound[PARTNUM - 1]) { + cntarr[PARTNUM - 1]++; + } + } + for (ind = 0; ind < PARTNUM - 1; ind++) { + printf("%2d %-13ld to %-13ld %5ld %4.4f\n", ind + 1, + valbound[ind], valbound[ind + 1], cntarr[ind], + (float)(cntarr[ind] / (float)iter)); + } + printf("%2d %-13ld to %-13ld %5ld %4.4f\n", PARTNUM, + valbound[PARTNUM - 1], lmax, cntarr[PARTNUM - 1], + (float)(cntarr[PARTNUM - 1] / (float)iter)); + printf(" min=%ld, max=%ld\n", ltmin, ltmax); + + } else if (strcmp(argv[1], "random_rangell") == 0) { + /* + * random_rangell() unit test + */ + lltmin = llmax; + lpart = llmax / PARTNUM; + for (ind = 0; ind < PARTNUM; ind++) { + lvalbound[ind] = (long long)(lpart * ind); + } + + for (cnt = 0; cnt < iter; cnt++) { + llret = random_rangell(llmin, llmax, llmult, NULL); + if (iter < 100) + printf("random_rangell returned %lld\n", llret); + if (llret < lltmin) + lltmin = llret; + if (llret > lltmax) + lltmax = llret; + + for (ind = 0; ind < PARTNUM - 1; ind++) { + if (lvalbound[ind] < llret + && llret <= lvalbound[ind + 1]) { + cntarr[ind]++; + break; + } + } + if (llret > lvalbound[PARTNUM - 1]) { + cntarr[PARTNUM - 1]++; + } + } + for (ind = 0; ind < PARTNUM - 1; ind++) { + printf("%2d %-13lld to %-13lld %5ld %4.4f\n", + ind + 1, lvalbound[ind], lvalbound[ind + 1], + cntarr[ind], (float)(cntarr[ind] / (float)iter)); + } + printf("%2d %-13lld to %-13lld %5ld %4.4f\n", PARTNUM, + lvalbound[PARTNUM - 1], llmax, cntarr[PARTNUM - 1], + (float)(cntarr[PARTNUM - 1] / (float)iter)); + printf(" min=%lld, max=%lld\n", lltmin, lltmax); + + } else { + /* + * random_range() unit test + */ + itmin = imax; + part = imax / PARTNUM; + for (ind = 0; ind < PARTNUM; ind++) { + valbound[ind] = part * ind; + } + + for (cnt = 0; cnt < iter; cnt++) { + lret = random_range(imin, imax, imult, NULL); + if (iter < 100) + printf("%ld\n", lret); + if (lret < itmin) + itmin = lret; + if (lret > itmax) + itmax = lret; + + for (ind = 0; ind < PARTNUM - 1; ind++) { + if (valbound[ind] < lret + && lret <= valbound[ind + 1]) { + cntarr[ind]++; + break; + } + } + if (lret > valbound[PARTNUM - 1]) { + cntarr[PARTNUM - 1]++; + } + } + for (ind = 0; ind < PARTNUM - 1; ind++) { + printf("%2d %-13ld to %-13ld %5ld %4.4f\n", ind + 1, + valbound[ind], valbound[ind + 1], cntarr[ind], + (float)(cntarr[ind] / (float)iter)); + } + printf("%2d %-13ld to %-13ld %5ld %4.4f\n", PARTNUM, + valbound[PARTNUM - 1], (long)imax, cntarr[PARTNUM - 1], + (float)(cntarr[PARTNUM - 1] / (float)iter)); + printf(" min=%d, max=%d\n", itmin, itmax); + + } + + exit(0); +} + +#endif diff --git a/src/kernel/tests/lib/safe_file_ops.c b/src/kernel/tests/lib/safe_file_ops.c new file mode 100644 index 0000000..e06d399 --- /dev/null +++ b/src/kernel/tests/lib/safe_file_ops.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2012 Cyril Hrubis chrubis@suse.cz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include <stdarg.h> +#include <stdio.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <utime.h> + +#include "test.h" +#include "safe_file_ops_fn.h" + +/* + * Count number of expected assigned conversions. Any conversion starts with '%'. + * The '%%' matches % and no assignment is done. The %*x matches as x would do but + * the assignment is suppressed. + * + * NOTE: This is not 100% correct for complex scanf strings, but will do for + * all of our intended usage. + */ +static int count_scanf_conversions(const char *fmt) +{ + unsigned int cnt = 0; + int flag = 0; + + while (*fmt) { + switch (*fmt) { + case '%': + if (flag) { + cnt--; + flag = 0; + } else { + flag = 1; + cnt++; + } + break; + case '*': + if (flag) { + cnt--; + flag = 0; + } + break; + default: + flag = 0; + } + + fmt++; + } + + return cnt; +} + +int file_scanf(const char *file, const int lineno, + const char *path, const char *fmt, ...) +{ + va_list va; + FILE *f; + int exp_convs, ret; + + f = fopen(path, "r"); + + if (f == NULL) { + tst_resm(TWARN, + "Failed to open FILE '%s' at %s:%d", + path, file, lineno); + return 1; + } + + exp_convs = count_scanf_conversions(fmt); + + va_start(va, fmt); + ret = vfscanf(f, fmt, va); + va_end(va); + + if (ret == EOF) { + tst_resm(TWARN, + "The FILE '%s' ended prematurely at %s:%d", + path, file, lineno); + goto err; + } + + if (ret != exp_convs) { + tst_resm(TWARN, + "Expected %i conversions got %i FILE '%s' at %s:%d", + exp_convs, ret, path, file, lineno); + goto err; + } + + if (fclose(f)) { + tst_resm(TWARN, + "Failed to close FILE '%s' at %s:%d", + path, file, lineno); + return 1; + } + + return 0; + +err: + if (fclose(f)) { + tst_resm(TWARN, + "Failed to close FILE '%s' at %s:%d", + path, file, lineno); + } + return 1; +} + +void safe_file_scanf(const char *file, const int lineno, + void (*cleanup_fn) (void), + const char *path, const char *fmt, ...) +{ + va_list va; + FILE *f; + int exp_convs, ret; + + f = fopen(path, "r"); + + if (f == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to open FILE '%s' for reading at %s:%d", + path, file, lineno); + return; + } + + exp_convs = count_scanf_conversions(fmt); + + va_start(va, fmt); + ret = vfscanf(f, fmt, va); + va_end(va); + + if (ret == EOF) { + tst_brkm(TBROK, cleanup_fn, + "The FILE '%s' ended prematurely at %s:%d", + path, file, lineno); + return; + } + + if (ret != exp_convs) { + tst_brkm(TBROK, cleanup_fn, + "Expected %i conversions got %i FILE '%s' at %s:%d", + exp_convs, ret, path, file, lineno); + return; + } + + if (fclose(f)) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to close FILE '%s' at %s:%d", + path, file, lineno); + return; + } +} + + +/* + * Try to parse each line from file specified by 'path' according + * to scanf format 'fmt'. If all fields could be parsed, stop and + * return 0, otherwise continue or return 1 if EOF is reached. + */ +int file_lines_scanf(const char *file, const int lineno, + void (*cleanup_fn)(void), int strict, + const char *path, const char *fmt, ...) +{ + FILE *fp; + int ret = 0; + int arg_count = 0; + char line[BUFSIZ]; + va_list ap; + + if (!fmt) { + tst_brkm(TBROK, cleanup_fn, "pattern is NULL, %s:%d", + file, lineno); + return 1; + } + + fp = fopen(path, "r"); + if (fp == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to open FILE '%s' for reading at %s:%d", + path, file, lineno); + return 1; + } + + arg_count = count_scanf_conversions(fmt); + + while (fgets(line, BUFSIZ, fp) != NULL) { + va_start(ap, fmt); + ret = vsscanf(line, fmt, ap); + va_end(ap); + + if (ret == arg_count) + break; + } + fclose(fp); + + if (strict && ret != arg_count) { + tst_brkm(TBROK, cleanup_fn, "Expected %i conversions got %i" + " FILE '%s' at %s:%d", arg_count, ret, path, file, lineno); + return 1; + } + + return !(ret == arg_count); +} + +int file_printf(const char *file, const int lineno, + const char *path, const char *fmt, ...) +{ + va_list va; + FILE *f; + + f = fopen(path, "w"); + + if (f == NULL) { + tst_resm(TWARN, + "Failed to open FILE '%s' at %s:%d", + path, file, lineno); + return 1; + } + + va_start(va, fmt); + + if (vfprintf(f, fmt, va) < 0) { + tst_resm(TWARN, + "Failed to print to FILE '%s' at %s:%d", + path, file, lineno); + goto err; + } + + va_end(va); + + if (fclose(f)) { + tst_resm(TWARN, + "Failed to close FILE '%s' at %s:%d", + path, file, lineno); + return 1; + } + + return 0; + +err: + if (fclose(f)) { + tst_resm(TWARN, + "Failed to close FILE '%s' at %s:%d", + path, file, lineno); + } + return 1; +} + +void safe_file_printf(const char *file, const int lineno, + void (*cleanup_fn) (void), + const char *path, const char *fmt, ...) +{ + va_list va; + FILE *f; + + f = fopen(path, "w"); + + if (f == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to open FILE '%s' for writing at %s:%d", + path, file, lineno); + return; + } + + va_start(va, fmt); + + if (vfprintf(f, fmt, va) < 0) { + tst_brkm(TBROK, cleanup_fn, + "Failed to print to FILE '%s' at %s:%d", + path, file, lineno); + return; + } + + va_end(va); + + if (fclose(f)) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to close FILE '%s' at %s:%d", + path, file, lineno); + return; + } +} + +//TODO: C implementation? better error condition reporting? +void safe_cp(const char *file, const int lineno, + void (*cleanup_fn) (void), const char *src, const char *dst) +{ + size_t len = strlen(src) + strlen(dst) + 16; + char buf[len]; + int ret; + + snprintf(buf, sizeof(buf), "cp \"%s\" \"%s\"", src, dst); + + ret = system(buf); + + if (ret) { + tst_brkm(TBROK, cleanup_fn, + "Failed to copy '%s' to '%s' at %s:%d", + src, dst, file, lineno); + } +} + +#ifndef HAVE_UTIMENSAT + +static void set_time(struct timeval *res, const struct timespec *src, + long cur_tv_sec, long cur_tv_usec) +{ + switch (src->tv_nsec) { + case UTIME_NOW: + break; + case UTIME_OMIT: + res->tv_sec = cur_tv_sec; + res->tv_usec = cur_tv_usec; + break; + default: + res->tv_sec = src->tv_sec; + res->tv_usec = src->tv_nsec / 1000; + } +} + +#endif + +void safe_touch(const char *file, const int lineno, + void (*cleanup_fn)(void), + const char *pathname, + mode_t mode, const struct timespec times[2]) +{ + int ret; + mode_t defmode; + + defmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + ret = open(pathname, O_CREAT | O_WRONLY, defmode); + if (ret == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to open file '%s' at %s:%d", + pathname, file, lineno); + return; + } + + ret = close(ret); + if (ret == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to close file '%s' at %s:%d", + pathname, file, lineno); + return; + } + + if (mode != 0) { + ret = chmod(pathname, mode); + if (ret == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to chmod file '%s' at %s:%d", + pathname, file, lineno); + return; + } + } + + +#ifdef HAVE_UTIMENSAT + ret = utimensat(AT_FDCWD, pathname, times, 0); +#else + if (times == NULL) { + ret = utimes(pathname, NULL); + } else { + struct stat sb; + struct timeval cotimes[2]; + + ret = stat(pathname, &sb); + if (ret == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to stat file '%s' at %s:%d", + pathname, file, lineno); + return; + } + + ret = gettimeofday(cotimes, NULL); + if (ret == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to gettimeofday() at %s:%d", + file, lineno); + return; + } + + cotimes[1] = cotimes[0]; + + set_time(cotimes, times, + sb.st_atime, sb.st_atim.tv_nsec / 1000); + set_time(cotimes + 1, times + 1, + sb.st_mtime, sb.st_mtim.tv_nsec / 1000); + + ret = utimes(pathname, cotimes); + } +#endif + if (ret == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "Failed to update the access/modification time on file" + " '%s' at %s:%d", pathname, file, lineno); + } +} diff --git a/src/kernel/tests/lib/safe_macros.c b/src/kernel/tests/lib/safe_macros.c new file mode 100644 index 0000000..4f48d75 --- /dev/null +++ b/src/kernel/tests/lib/safe_macros.c @@ -0,0 +1,1109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) Linux Test Project, 2010-2020 + */ + +#define _GNU_SOURCE +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/mount.h> +#include <sys/xattr.h> +#include <sys/sysinfo.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <malloc.h> +#include "test.h" +#include "safe_macros.h" + +char *safe_basename(const char *file, const int lineno, + void (*cleanup_fn) (void), char *path) +{ + char *rval; + + rval = basename(path); + if (rval == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: basename(%s) failed", + file, lineno, path); + } + + return rval; +} + +int +safe_chdir(const char *file, const int lineno, void (*cleanup_fn) (void), + const char *path) +{ + int rval; + + rval = chdir(path); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: chdir(%s) failed", + file, lineno, path); + } + + return rval; +} + +int +safe_close(const char *file, const int lineno, void (*cleanup_fn) (void), + int fildes) +{ + int rval; + + rval = close(fildes); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: close(%d) failed", + file, lineno, fildes); + } + + return rval; +} + +int +safe_creat(const char *file, const int lineno, void (*cleanup_fn) (void), + const char *pathname, mode_t mode) +{ + int rval; + + rval = creat(pathname, mode); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: creat(%s,0%o) failed", + file, lineno, pathname, mode); + } + + return rval; +} + +char *safe_dirname(const char *file, const int lineno, + void (*cleanup_fn) (void), char *path) +{ + char *rval; + + rval = dirname(path); + if (rval == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: dirname(%s) failed", + file, lineno, path); + } + + return rval; +} + +char *safe_getcwd(const char *file, const int lineno, void (*cleanup_fn) (void), + char *buf, size_t size) +{ + char *rval; + + rval = getcwd(buf, size); + if (rval == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: getcwd(%p,%zu) failed", + file, lineno, buf, size); + } + + return rval; +} + +struct passwd *safe_getpwnam(const char *file, const int lineno, + void (*cleanup_fn) (void), const char *name) +{ + struct passwd *rval; + + rval = getpwnam(name); + if (rval == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: getpwnam(%s) failed", + file, lineno, name); + } + + return rval; +} + +int +safe_getrusage(const char *file, const int lineno, void (*cleanup_fn) (void), + int who, struct rusage *usage) +{ + int rval; + + rval = getrusage(who, usage); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: getrusage(%d,%p) failed", + file, lineno, who, usage); + } + + return rval; +} + +void *safe_malloc(const char *file, const int lineno, void (*cleanup_fn) (void), + size_t size) +{ + void *rval; + + rval = malloc(size); + if (rval == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: malloc(%zu) failed", + file, lineno, size); + } + + return rval; +} + +int safe_mkdir(const char *file, const int lineno, void (*cleanup_fn) (void), + const char *pathname, mode_t mode) +{ + int rval; + + rval = mkdir(pathname, mode); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: mkdir(%s,0%o) failed", + file, lineno, pathname, mode); + } + + return (rval); +} + +int safe_rmdir(const char *file, const int lineno, void (*cleanup_fn) (void), + const char *pathname) +{ + int rval; + + rval = rmdir(pathname); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: rmdir(%s) failed", + file, lineno, pathname); + } + + return (rval); +} + +int safe_munmap(const char *file, const int lineno, void (*cleanup_fn) (void), + void *addr, size_t length) +{ + int rval; + + rval = munmap(addr, length); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: munmap(%p,%zu) failed", + file, lineno, addr, length); + } + + return rval; +} + +int safe_open(const char *file, const int lineno, void (*cleanup_fn) (void), + const char *pathname, int oflags, ...) +{ + va_list ap; + int rval; + mode_t mode; + + va_start(ap, oflags); + + /* Android's NDK's mode_t is smaller than an int, which results in + * SIGILL here when passing the mode_t type. + */ + mode = va_arg(ap, int); + + va_end(ap); + + rval = open(pathname, oflags, mode); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: open(%s,%d,0%o) failed", + file, lineno, pathname, oflags, mode); + } + + return rval; +} + +int safe_pipe(const char *file, const int lineno, void (*cleanup_fn) (void), + int fildes[2]) +{ + int rval; + + rval = pipe(fildes); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: pipe({%d,%d}) failed", + file, lineno, fildes[0], fildes[1]); + } + + return rval; +} + +ssize_t safe_read(const char *file, const int lineno, void (*cleanup_fn) (void), + char len_strict, int fildes, void *buf, size_t nbyte) +{ + ssize_t rval; + + rval = read(fildes, buf, nbyte); + if (rval == -1 || (len_strict && (size_t)rval != nbyte)) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: read(%d,%p,%zu) failed, returned %zd", + file, lineno, fildes, buf, nbyte, rval); + } + + return rval; +} + +int safe_setegid(const char *file, const int lineno, void (*cleanup_fn) (void), + gid_t egid) +{ + int rval; + + rval = setegid(egid); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: setegid(%u) failed", + file, lineno, (unsigned) egid); + } + + return rval; +} + +int safe_seteuid(const char *file, const int lineno, void (*cleanup_fn) (void), + uid_t euid) +{ + int rval; + + rval = seteuid(euid); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: seteuid(%u) failed", + file, lineno, (unsigned) euid); + } + + return rval; +} + +int safe_setgid(const char *file, const int lineno, void (*cleanup_fn) (void), + gid_t gid) +{ + int rval; + + rval = setgid(gid); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: setgid(%u) failed", + file, lineno, (unsigned) gid); + } + + return rval; +} + +int safe_setuid(const char *file, const int lineno, void (*cleanup_fn) (void), + uid_t uid) +{ + int rval; + + rval = setuid(uid); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: setuid(%u) failed", + file, lineno, (unsigned) uid); + } + + return rval; +} + +int safe_getresuid(const char *file, const int lineno, void (*cleanup_fn)(void), + uid_t *ruid, uid_t *euid, uid_t *suid) +{ + int rval; + + rval = getresuid(ruid, euid, suid); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: getresuid(%p, %p, %p) failed", + file, lineno, ruid, euid, suid); + } + + return rval; +} + +int safe_getresgid(const char *file, const int lineno, void (*cleanup_fn)(void), + gid_t *rgid, gid_t *egid, gid_t *sgid) +{ + int rval; + + rval = getresgid(rgid, egid, sgid); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: getresgid(%p, %p, %p) failed", + file, lineno, rgid, egid, sgid); + } + + return rval; +} + +int safe_unlink(const char *file, const int lineno, void (*cleanup_fn) (void), + const char *pathname) +{ + int rval; + + rval = unlink(pathname); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: unlink(%s) failed", + file, lineno, pathname); + } + + return rval; +} + + +int safe_link(const char *file, const int lineno, + void (cleanup_fn)(void), const char *oldpath, + const char *newpath) +{ + int rval; + + rval = link(oldpath, newpath); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: link(%s,%s) failed", + file, lineno, oldpath, newpath); + } + + return rval; +} + +int safe_linkat(const char *file, const int lineno, + void (cleanup_fn)(void), int olddirfd, const char *oldpath, + int newdirfd, const char *newpath, int flags) +{ + int rval; + + rval = linkat(olddirfd, oldpath, newdirfd, newpath, flags); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: linkat(%d,%s,%d,%s,%d) failed", + file, lineno, olddirfd, oldpath, newdirfd, + newpath, flags); + } + + return rval; +} + +ssize_t safe_readlink(const char *file, const int lineno, + void (cleanup_fn)(void), const char *path, + char *buf, size_t bufsize) +{ + ssize_t rval; + + rval = readlink(path, buf, bufsize); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: readlink(%s,%p,%zu) failed", + file, lineno, path, buf, bufsize); + } else { + /* readlink does not append a NUL byte to the buffer. + * Add it now. */ + if ((size_t) rval < bufsize) + buf[rval] = '\0'; + else + buf[bufsize-1] = '\0'; + } + + return rval; +} + +int safe_symlink(const char *file, const int lineno, + void (cleanup_fn)(void), const char *oldpath, + const char *newpath) +{ + int rval; + + rval = symlink(oldpath, newpath); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: symlink(%s,%s) failed", + file, lineno, oldpath, newpath); + } + + return rval; +} + +ssize_t safe_write(const char *file, const int lineno, void (cleanup_fn) (void), + char len_strict, int fildes, const void *buf, size_t nbyte) +{ + ssize_t rval; + + rval = write(fildes, buf, nbyte); + if (rval == -1 || (len_strict && (size_t)rval != nbyte)) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: write(%d,%p,%zu) failed", + file, lineno, fildes, buf, rval); + } + + return rval; +} + +long safe_strtol(const char *file, const int lineno, + void (cleanup_fn) (void), char *str, long min, long max) +{ + long rval; + char *endptr; + + errno = 0; + rval = strtol(str, &endptr, 10); + + if ((errno == ERANGE && (rval == LONG_MAX || rval == LONG_MIN)) + || (errno != 0 && rval == 0)) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: strtol(%s) failed", file, lineno, str); + return rval; + } + + if (endptr == str || (*endptr != '\0' && *endptr != '\n')) { + tst_brkm(TBROK, cleanup_fn, + "%s:%d: strtol(%s): Invalid value", file, lineno, str); + return 0; + } + + if (rval > max || rval < min) { + tst_brkm(TBROK, cleanup_fn, + "%s:%d: strtol(%s): %ld is out of range %ld - %ld", + file, lineno, str, rval, min, max); + return 0; + } + + return rval; +} + +unsigned long safe_strtoul(const char *file, const int lineno, + void (cleanup_fn) (void), char *str, + unsigned long min, unsigned long max) +{ + unsigned long rval; + char *endptr; + + errno = 0; + rval = strtoul(str, &endptr, 10); + + if ((errno == ERANGE && rval == ULONG_MAX) + || (errno != 0 && rval == 0)) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: strtoul(%s) failed", file, lineno, str); + return rval; + } + + if (rval > max || rval < min) { + tst_brkm(TBROK, cleanup_fn, + "%s:%d: strtoul(%s): %lu is out of range %lu - %lu", + file, lineno, str, rval, min, max); + return 0; + } + + if (endptr == str || (*endptr != '\0' && *endptr != '\n')) { + tst_brkm(TBROK, cleanup_fn, + "Invalid value: '%s' at %s:%d", str, file, lineno); + return 0; + } + + return rval; +} + +long safe_sysconf(const char *file, const int lineno, + void (cleanup_fn) (void), int name) +{ + long rval; + errno = 0; + + rval = sysconf(name); + + if (rval == -1) { + if (errno) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: sysconf(%d) failed", + file, lineno, name); + } else { + tst_resm(TINFO, "%s:%d: sysconf(%d): " + "queried option is not available" + " or there is no definite limit", + file, lineno, name); + } + } + + return rval; +} + +int safe_chmod(const char *file, const int lineno, + void (cleanup_fn)(void), const char *path, mode_t mode) +{ + int rval; + + rval = chmod(path, mode); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: chmod(%s,0%o) failed", + file, lineno, path, mode); + } + + return rval; +} + +int safe_fchmod(const char *file, const int lineno, + void (cleanup_fn)(void), int fd, mode_t mode) +{ + int rval; + + rval = fchmod(fd, mode); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: fchmod(%d,0%o) failed", + file, lineno, fd, mode); + } + + return rval; +} + +int safe_chown(const char *file, const int lineno, void (cleanup_fn)(void), + const char *path, uid_t owner, gid_t group) +{ + int rval; + + rval = chown(path, owner, group); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: chown(%s,%d,%d) failed", + file, lineno, path, owner, group); + } + + return rval; +} + +int safe_fchown(const char *file, const int lineno, void (cleanup_fn)(void), + int fd, uid_t owner, gid_t group) +{ + int rval; + + rval = fchown(fd, owner, group); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: fchown(%d,%d,%d) failed", + file, lineno, fd, owner, group); + } + + return rval; +} + +pid_t safe_wait(const char *file, const int lineno, void (cleanup_fn)(void), + int *status) +{ + pid_t rval; + + rval = wait(status); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: wait(%p) failed", + file, lineno, status); + } + + return rval; +} + +pid_t safe_waitpid(const char *file, const int lineno, void (cleanup_fn)(void), + pid_t pid, int *status, int opts) +{ + pid_t rval; + + rval = waitpid(pid, status, opts); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: waitpid(%d,%p,%d) failed", + file, lineno, pid, status, opts); + } + + return rval; +} + +void *safe_memalign(const char *file, const int lineno, + void (*cleanup_fn) (void), size_t alignment, size_t size) +{ + void *rval; + + rval = memalign(alignment, size); + if (rval == NULL) + tst_brkm(TBROK | TERRNO, cleanup_fn, "memalign failed at %s:%d", + file, lineno); + + return rval; +} + +int safe_kill(const char *file, const int lineno, void (cleanup_fn)(void), + pid_t pid, int sig) +{ + int rval; + + rval = kill(pid, sig); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: kill(%d,%s) failed", + file, lineno, pid, tst_strsig(sig)); + } + + return rval; +} + +int safe_mkfifo(const char *file, const int lineno, + void (*cleanup_fn)(void), const char *pathname, mode_t mode) +{ + int rval; + + rval = mkfifo(pathname, mode); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: mkfifo(%s, 0%o) failed", + file, lineno, pathname, mode); + } + + return rval; +} + +int safe_rename(const char *file, const int lineno, void (*cleanup_fn)(void), + const char *oldpath, const char *newpath) +{ + int rval; + + rval = rename(oldpath, newpath); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: rename(%s, %s) failed", + file, lineno, oldpath, newpath); + } + + return rval; +} + +static const char *const fuse_fs_types[] = { + "exfat", + "ntfs", +}; + +static int possibly_fuse(const char *fs_type) +{ + unsigned int i; + + if (!fs_type) + return 0; + + for (i = 0; i < ARRAY_SIZE(fuse_fs_types); i++) { + if (!strcmp(fuse_fs_types[i], fs_type)) + return 1; + } + + return 0; +} + +int safe_mount(const char *file, const int lineno, void (*cleanup_fn)(void), + const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const void *data) +{ + int rval; + + /* + * Don't try using the kernel's NTFS driver when mounting NTFS, since + * the kernel's NTFS driver doesn't have proper write support. + */ + if (!filesystemtype || strcmp(filesystemtype, "ntfs")) { + rval = mount(source, target, filesystemtype, mountflags, data); + if (!rval) + return 0; + } + + /* + * The FUSE filesystem executes mount.fuse helper, which tries to + * execute corresponding binary name which is encoded at the start of + * the source string and separated by # from the device name. + * + * The mount helpers are called mount.$fs_type. + */ + if (possibly_fuse(filesystemtype)) { + char buf[1024]; + + tst_resm(TINFO, "Trying FUSE..."); + snprintf(buf, sizeof(buf), "mount.%s '%s' '%s'", + filesystemtype, source, target); + + rval = tst_system(buf); + if (WIFEXITED(rval) && WEXITSTATUS(rval) == 0) + return 0; + + tst_brkm(TBROK, cleanup_fn, "mount.%s failed with %i", + filesystemtype, rval); + return -1; + } else { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: mount(%s, %s, %s, %lu, %p) failed", + file, lineno, source, target, filesystemtype, + mountflags, data); + } + + return -1; +} + +int safe_umount(const char *file, const int lineno, void (*cleanup_fn)(void), + const char *target) +{ + int rval; + + rval = tst_umount(target); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: umount(%s) failed", + file, lineno, target); + } + + return rval; +} + +DIR* safe_opendir(const char *file, const int lineno, void (cleanup_fn)(void), + const char *name) +{ + DIR *rval; + + rval = opendir(name); + + if (!rval) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: opendir(%s) failed", file, lineno, name); + } + + return rval; +} + +int safe_closedir(const char *file, const int lineno, void (cleanup_fn)(void), + DIR *dirp) +{ + int rval; + + rval = closedir(dirp); + + if (rval) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: closedir(%p) failed", file, lineno, dirp); + } + + return rval; +} + +struct dirent *safe_readdir(const char *file, const int lineno, void (cleanup_fn)(void), + DIR *dirp) +{ + struct dirent *rval; + int err = errno; + + errno = 0; + rval = readdir(dirp); + + if (!rval && errno) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: readdir(%p) failed", file, lineno, dirp); + } + + errno = err; + return rval; +} + +int safe_getpriority(const char *file, const int lineno, int which, id_t who) +{ + int rval, err = errno; + + errno = 0; + rval = getpriority(which, who); + if (errno) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d getpriority(%i, %i) failed", + file, lineno, which, who); + } + + errno = err; + return rval; +} + +ssize_t safe_getxattr(const char *file, const int lineno, const char *path, + const char *name, void *value, size_t size) +{ + ssize_t rval; + + rval = getxattr(path, name, value, size); + + if (rval == -1) { + if (errno == ENOTSUP) { + tst_brkm(TCONF, NULL, + "%s:%d: no xattr support in fs or mounted " + "without user_xattr option", file, lineno); + } + + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: getxattr(%s, %s, %p, %zu) failed", + file, lineno, path, name, value, size); + } + + return rval; +} + +int safe_setxattr(const char *file, const int lineno, const char *path, + const char *name, const void *value, size_t size, int flags) +{ + int rval; + + rval = setxattr(path, name, value, size, flags); + + if (rval) { + if (errno == ENOTSUP) { + tst_brkm(TCONF, NULL, + "%s:%d: no xattr support in fs or mounted " + "without user_xattr option", file, lineno); + } + + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: setxattr(%s, %s, %p, %zu) failed", + file, lineno, path, name, value, size); + } + + return rval; +} + +int safe_lsetxattr(const char *file, const int lineno, const char *path, + const char *name, const void *value, size_t size, int flags) +{ + int rval; + + rval = lsetxattr(path, name, value, size, flags); + + if (rval) { + if (errno == ENOTSUP) { + tst_brkm(TCONF, NULL, + "%s:%d: no xattr support in fs or mounted " + "without user_xattr option", file, lineno); + } + + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: lsetxattr(%s, %s, %p, %zu, %i) failed", + file, lineno, path, name, value, size, flags); + } + + return rval; +} + +int safe_fsetxattr(const char *file, const int lineno, int fd, const char *name, + const void *value, size_t size, int flags) +{ + int rval; + + rval = fsetxattr(fd, name, value, size, flags); + + if (rval) { + if (errno == ENOTSUP) { + tst_brkm(TCONF, NULL, + "%s:%d: no xattr support in fs or mounted " + "without user_xattr option", file, lineno); + } + + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: fsetxattr(%i, %s, %p, %zu, %i) failed", + file, lineno, fd, name, value, size, flags); + } + + return rval; +} + +int safe_removexattr(const char *file, const int lineno, const char *path, + const char *name) +{ + int rval; + + rval = removexattr(path, name); + + if (rval) { + if (errno == ENOTSUP) { + tst_brkm(TCONF, NULL, + "%s:%d: no xattr support in fs or mounted " + "without user_xattr option", file, lineno); + } + + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: removexattr(%s, %s) failed", + file, lineno, path, name); + } + + return rval; +} + +int safe_lremovexattr(const char *file, const int lineno, const char *path, + const char *name) +{ + int rval; + + rval = lremovexattr(path, name); + + if (rval) { + if (errno == ENOTSUP) { + tst_brkm(TCONF, NULL, + "%s:%d: no xattr support in fs or mounted " + "without user_xattr option", file, lineno); + } + + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: lremovexattr(%s, %s) failed", + file, lineno, path, name); + } + + return rval; +} + +int safe_fremovexattr(const char *file, const int lineno, int fd, + const char *name) +{ + int rval; + + rval = fremovexattr(fd, name); + + if (rval) { + if (errno == ENOTSUP) { + tst_brkm(TCONF, NULL, + "%s:%d: no xattr support in fs or mounted " + "without user_xattr option", file, lineno); + } + + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: fremovexattr(%i, %s) failed", + file, lineno, fd, name); + } + + return rval; +} + +int safe_fsync(const char *file, const int lineno, int fd) +{ + int rval; + + rval = fsync(fd); + + if (rval) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: fsync(%i) failed", file, lineno, fd); + } + + return rval; +} + +pid_t safe_setsid(const char *file, const int lineno) +{ + pid_t rval; + + rval = setsid(); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: setsid() failed", file, lineno); + } + + return rval; +} + +int safe_mknod(const char *file, const int lineno, const char *pathname, + mode_t mode, dev_t dev) +{ + int rval; + + rval = mknod(pathname, mode, dev); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: mknod() failed", file, lineno); + } + + return rval; +} + +int safe_mlock(const char *file, const int lineno, const void *addr, + size_t len) +{ + int rval; + + rval = mlock(addr, len); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: mlock() failed", file, lineno); + } + + return rval; +} + +int safe_munlock(const char *file, const int lineno, const void *addr, + size_t len) +{ + int rval; + + rval = munlock(addr, len); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: munlock() failed", file, lineno); + } + + return rval; +} + +int safe_mincore(const char *file, const int lineno, void *start, + size_t length, unsigned char *vec) +{ + int rval; + + rval = mincore(start, length, vec); + if (rval == -1) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: mincore() failed", file, lineno); + } + + return rval; +} + +int safe_sysinfo(const char *file, const int lineno, struct sysinfo *info) +{ + int ret; + + errno = 0; + ret = sysinfo(info); + + if (ret == -1) { + tst_brkm_(file, lineno, TBROK | TERRNO, NULL, + "sysinfo() failed"); + } else if (ret) { + tst_brkm_(file, lineno, TBROK | TERRNO, NULL, + "Invalid sysinfo() return value %d", ret); + } + + return ret; +} diff --git a/src/kernel/tests/lib/safe_net.c b/src/kernel/tests/lib/safe_net.c new file mode 100644 index 0000000..4993680 --- /dev/null +++ b/src/kernel/tests/lib/safe_net.c @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2015 Fujitsu Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include "test.h" +#include "safe_net_fn.h" + +char *tst_sock_addr(const struct sockaddr *sa, socklen_t salen, char *res, + size_t len) +{ + char portstr[8]; + + switch (sa->sa_family) { + + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + + if (!inet_ntop(AF_INET, &sin->sin_addr, res, len)) + return NULL; + + if (ntohs(sin->sin_port) != 0) { + snprintf(portstr, sizeof(portstr), ":%d", + ntohs(sin->sin_port)); + strcat(res, portstr); + } + + return res; + } + + case AF_INET6: { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + + res[0] = '['; + if (!inet_ntop(AF_INET6, &sin6->sin6_addr, res + 1, len - 1)) + return NULL; + + if (ntohs(sin6->sin6_port) != 0) { + snprintf(portstr, sizeof(portstr), "]:%d", + ntohs(sin6->sin6_port)); + strcat(res, portstr); + return res; + } + + return res + 1; + } + + case AF_UNIX: { + struct sockaddr_un *unp = (struct sockaddr_un *)sa; + + if (unp->sun_path[0] == '\0') + strcpy(res, "(no pathname bound)"); + else + snprintf(res, len, "%s", unp->sun_path); + + return res; + } + + default: { + snprintf(res, len, + "sock_ntop: unknown AF_xxx: %d, len: %d", + sa->sa_family, salen); + + return res; + } + + } +} + +int tst_getsockport(const char *file, const int lineno, int sockfd) +{ + struct sockaddr_storage ss; + socklen_t addrlen = sizeof(ss); + struct sockaddr *sa = (struct sockaddr *)&ss; + + safe_getsockname(file, lineno, NULL, sockfd, sa, &addrlen); + + switch (sa->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + + return ntohs(sin->sin_port); + } + case AF_INET6: { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + + return ntohs(sin6->sin6_port); + } } + + return -1; +} + +int safe_socket(const char *file, const int lineno, void (cleanup_fn)(void), + int domain, int type, int protocol) +{ + int rval, ttype; + + rval = socket(domain, type, protocol); + + if (rval < 0) { + switch (errno) { + case EPROTONOSUPPORT: + case ESOCKTNOSUPPORT: + case EOPNOTSUPP: + case EPFNOSUPPORT: + case EAFNOSUPPORT: + ttype = TCONF; + break; + default: + ttype = TBROK; + } + + tst_brkm(ttype | TERRNO, cleanup_fn, + "%s:%d: socket(%d, %d, %d) failed", file, lineno, + domain, type, protocol); + } + + return rval; +} + +int safe_socketpair(const char *file, const int lineno, int domain, int type, + int protocol, int sv[]) +{ + int rval, ttype; + + rval = socketpair(domain, type, protocol, sv); + + if (rval < 0) { + switch (errno) { + case EPROTONOSUPPORT: + case EOPNOTSUPP: + case EAFNOSUPPORT: + ttype = TCONF; + break; + default: + ttype = TBROK; + } + + tst_brkm(ttype | TERRNO, NULL, + "%s:%d: socketpair(%d, %d, %d, %p) failed", + file, lineno, domain, type, protocol, sv); + } + + return rval; +} + +int safe_getsockopt(const char *file, const int lineno, int sockfd, int level, + int optname, void *optval, socklen_t *optlen) +{ + int rval = getsockopt(sockfd, level, optname, optval, optlen); + + if (!rval) + return 0; + + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: getsockopt(%d, %d, %d, %p, %p) failed", + file, lineno, sockfd, level, optname, optval, optlen); + + return rval; +} + +int safe_setsockopt(const char *file, const int lineno, int sockfd, int level, + int optname, const void *optval, socklen_t optlen) +{ + int rval; + + rval = setsockopt(sockfd, level, optname, optval, optlen); + + if (rval) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: setsockopt(%d, %d, %d, %p, %d) failed", + file, lineno, sockfd, level, optname, optval, optlen); + } + + return rval; +} + +ssize_t safe_send(const char *file, const int lineno, char len_strict, + int sockfd, const void *buf, size_t len, int flags) +{ + ssize_t rval; + + rval = send(sockfd, buf, len, flags); + + if (rval == -1 || (len_strict && (size_t)rval != len)) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: send(%d, %p, %zu, %d) failed", + file, lineno, sockfd, buf, len, flags); + } + + return rval; +} + +ssize_t safe_sendto(const char *file, const int lineno, char len_strict, + int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + ssize_t rval; + char res[128]; + + rval = sendto(sockfd, buf, len, flags, dest_addr, addrlen); + + if (rval == -1 || (len_strict && (size_t)rval != len)) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: sendto(%d, %p, %zu, %d, %s, %d) failed", + file, lineno, sockfd, buf, len, flags, + tst_sock_addr(dest_addr, addrlen, res, sizeof(res)), + addrlen); + } + + return rval; +} + +ssize_t safe_sendmsg(const char *file, const int lineno, size_t len, + int sockfd, const struct msghdr *msg, int flags) +{ + ssize_t rval; + + rval = sendmsg(sockfd, msg, flags); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: sendmsg(%d, %p, %d) failed", + file, lineno, sockfd, msg, flags); + } + + if (len && (size_t)rval != len) { + tst_brkm(TBROK, NULL, + "%s:%d: sendmsg(%d, %p, %d) ret(%zd) != len(%zu)", + file, lineno, sockfd, msg, flags, rval, len); + } + + return rval; +} + +ssize_t safe_recvmsg(const char *file, const int lineno, size_t len, + int sockfd, struct msghdr *msg, int flags) +{ + ssize_t rval; + + rval = recvmsg(sockfd, msg, flags); + + if (rval == -1) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: recvmsg(%d, %p, %d) failed", + file, lineno, sockfd, msg, flags); + } + + if (len && (size_t)rval != len) { + tst_brkm(TBROK, NULL, + "%s:%d: recvmsg(%d, %p, %d) ret(%zd) != len(%zu)", + file, lineno, sockfd, msg, flags, rval, len); + } + + return rval; + +} + +int safe_bind(const char *file, const int lineno, void (cleanup_fn)(void), + int socket, const struct sockaddr *address, + socklen_t address_len) +{ + int i; + char buf[128]; + + for (i = 0; i < 120; i++) { + if (!bind(socket, address, address_len)) + return 0; + + if (errno != EADDRINUSE) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: bind(%d, %s, %d) failed", file, lineno, + socket, tst_sock_addr(address, address_len, + buf, sizeof(buf)), + address_len); + return -1; + } + + if ((i + 1) % 10 == 0) { + tst_resm(TINFO, "address is in use, waited %3i sec", + i + 1); + } + + sleep(1); + } + + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: Failed to bind(%d, %s, %d) after 120 retries", file, + lineno, socket, + tst_sock_addr(address, address_len, buf, sizeof(buf)), + address_len); + return -1; +} + +int safe_listen(const char *file, const int lineno, void (cleanup_fn)(void), + int socket, int backlog) +{ + int rval; + + rval = listen(socket, backlog); + + if (rval < 0) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: listen(%d, %d) failed", file, lineno, socket, + backlog); + } + + return rval; +} + +int safe_accept(const char *file, const int lineno, void (cleanup_fn)(void), + int sockfd, struct sockaddr *addr, socklen_t *addrlen) +{ + int rval; + + rval = accept(sockfd, addr, addrlen); + + if (rval < 0) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: accept(%d, %p, %d) failed", file, lineno, + sockfd, addr, *addrlen); + } + + return rval; +} + +int safe_connect(const char *file, const int lineno, void (cleanup_fn)(void), + int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + int rval; + char buf[128]; + + rval = connect(sockfd, addr, addrlen); + + if (rval < 0) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: connect(%d, %s, %d) failed", file, lineno, + sockfd, tst_sock_addr(addr, addrlen, buf, + sizeof(buf)), addrlen); + } + + return rval; +} + +int safe_getsockname(const char *file, const int lineno, + void (cleanup_fn)(void), int sockfd, struct sockaddr *addr, + socklen_t *addrlen) +{ + int rval; + char buf[128]; + + rval = getsockname(sockfd, addr, addrlen); + + if (rval < 0) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: getsockname(%d, %s, %d) failed", file, lineno, + sockfd, tst_sock_addr(addr, *addrlen, buf, + sizeof(buf)), *addrlen); + } + + return rval; +} + +int safe_gethostname(const char *file, const int lineno, + char *name, size_t size) +{ + int rval = gethostname(name, size); + + if (rval < 0) { + tst_brkm(TBROK | TERRNO, NULL, + "%s:%d: gethostname(%p, %zu) failed", + file, lineno, name, size); + } + + return rval; +} + +/* + * @return port in network byte order. + */ +unsigned short tst_get_unused_port(const char *file, const int lineno, + void (cleanup_fn)(void), unsigned short family, int type) +{ + int sock; + socklen_t slen; + struct sockaddr_storage _addr; + struct sockaddr *addr = (struct sockaddr *)&_addr; + struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; + + switch (family) { + case AF_INET: + addr4->sin_family = AF_INET; + addr4->sin_port = 0; + addr4->sin_addr.s_addr = INADDR_ANY; + slen = sizeof(*addr4); + break; + + case AF_INET6: + addr6->sin6_family = AF_INET6; + addr6->sin6_port = 0; + addr6->sin6_addr = in6addr_any; + slen = sizeof(*addr6); + break; + + default: + tst_brkm(TBROK, cleanup_fn, + "%s:%d: unknown family", file, lineno); + return -1; + } + + sock = socket(addr->sa_family, type, 0); + if (sock < 0) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: socket failed", file, lineno); + return -1; + } + + if (bind(sock, addr, slen) < 0) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: bind failed", file, lineno); + return -1; + } + + if (getsockname(sock, addr, &slen) == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: getsockname failed", file, lineno); + return -1; + } + + if (close(sock) == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: close failed", file, lineno); + return -1; + } + + switch (family) { + case AF_INET: + return addr4->sin_port; + case AF_INET6: + return addr6->sin6_port; + default: + return -1; + } +} diff --git a/src/kernel/tests/lib/safe_pthread.c b/src/kernel/tests/lib/safe_pthread.c new file mode 100644 index 0000000..2866aa5 --- /dev/null +++ b/src/kernel/tests/lib/safe_pthread.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Oracle and/or its affiliates. All Rights Reserved. + */ + +#include <pthread.h> +#include <stdio.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" + +int safe_pthread_create(const char *file, const int lineno, + pthread_t *thread_id, const pthread_attr_t *attr, + void *(*thread_fn)(void *), void *arg) +{ + int rval; + + rval = pthread_create(thread_id, attr, thread_fn, arg); + + if (rval) { + tst_brk_(file, lineno, TBROK, + "pthread_create(%p,%p,%p,%p) failed: %s", thread_id, + attr, thread_fn, arg, tst_strerrno(rval)); + } + + return rval; +} + +int safe_pthread_join(const char *file, const int lineno, + pthread_t thread_id, void **retval) +{ + int rval; + + rval = pthread_join(thread_id, retval); + + if (rval) { + tst_brk_(file, lineno, TBROK, + "pthread_join(..., %p) failed: %s", + retval, tst_strerrno(rval)); + } + + return rval; +} diff --git a/src/kernel/tests/lib/safe_stdio.c b/src/kernel/tests/lib/safe_stdio.c new file mode 100644 index 0000000..966a039 --- /dev/null +++ b/src/kernel/tests/lib/safe_stdio.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2013 Cyril Hrubis <chrubis@suse.cz> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define _GNU_SOURCE +#include <stdarg.h> +#include <stdio.h> +#include <errno.h> +#include "test.h" +#include "safe_stdio_fn.h" + +FILE *safe_fopen(const char *file, const int lineno, void (cleanup_fn)(void), + const char *path, const char *mode) +{ + FILE *f = fopen(path, mode); + + if (f == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: fopen(%s,%s) failed", + file, lineno, path, mode); + } + + return f; +} + +int safe_fclose(const char *file, const int lineno, void (cleanup_fn)(void), + FILE *f) +{ + int ret; + + ret = fclose(f); + + if (ret) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: fclose(%p) failed", file, lineno, f); + } + + return ret; +} + +int safe_asprintf(const char *file, const int lineno, void (cleanup_fn)(void), + char **strp, const char *fmt, ...) +{ + int ret; + va_list va; + + va_start(va, fmt); + ret = vasprintf(strp, fmt, va); + va_end(va); + + if (ret < 0) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: asprintf(%s,...) failed", file, lineno, fmt); + } + + return ret; +} + +FILE *safe_popen(const char *file, const int lineno, void (cleanup_fn)(void), + const char *command, const char *type) +{ + FILE *stream; + const int saved_errno = errno; + + errno = 0; + stream = popen(command, type); + + if (stream == NULL) { + if (errno != 0) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: popen(%s,%s) failed", + file, lineno, command, type); + } else { + tst_brkm(TBROK, cleanup_fn, + "%s:%d: popen(%s,%s) failed: Out of memory", + file, lineno, command, type); + } + } + + errno = saved_errno; + + return stream; +} diff --git a/src/kernel/tests/lib/self_exec.c b/src/kernel/tests/lib/self_exec.c new file mode 100644 index 0000000..de7d095 --- /dev/null +++ b/src/kernel/tests/lib/self_exec.c @@ -0,0 +1,225 @@ +/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: t -*- */ +/* + * self_exec.c: self_exec magic required to run child functions on uClinux + * + * Copyright (C) 2005 Paul J.Y. Lahaie <pjlahaie-at-steamballoon.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * This software was produced by Steamballoon Incorporated + * 55 Byward Market Square, 2nd Floor North, Ottawa, ON K1N 9C3, Canada + */ + +#define _GNU_SOURCE /* for asprintf */ + +#include "config.h" + +#ifdef UCLINUX + +#include <stdarg.h> +#include <string.h> +#include <stdio.h> +#include "test.h" +#include "safe_macros.h" + +/* Set from parse_opts.c: */ +char *child_args; /* Arguments to child when -C is used */ + +static char *start_cwd; /* Stores the starting directory for self_exec */ + +int asprintf(char **app, const char *fmt, ...) +{ + va_list ptr; + int rv; + char *p; + + /* + * First iteration - find out size of buffer required and allocate it. + */ + va_start(ptr, fmt); + rv = vsnprintf(NULL, 0, fmt, ptr); + va_end(ptr); + + p = malloc(++rv); /* allocate the buffer */ + *app = p; + if (!p) { + return -1; + } + + /* + * Second iteration - actually produce output. + */ + va_start(ptr, fmt); + rv = vsnprintf(p, rv, fmt, ptr); + va_end(ptr); + + return rv; +} + +void maybe_run_child(void (*child) (), const char *fmt, ...) +{ + va_list ap; + char *child_dir; + char *p, *tok; + int *iptr, i, j; + char *s; + char **sptr; + char *endptr; + + /* Store the current directory for later use. */ + start_cwd = getcwd(NULL, 0); + + if (child_args) { + char *args = strdup(child_args); + + child_dir = strtok(args, ","); + if (strlen(child_dir) == 0) { + tst_brkm(TBROK, NULL, + "Could not get directory from -C option"); + return; + } + + va_start(ap, fmt); + + for (p = fmt; *p; p++) { + tok = strtok(NULL, ","); + if (!tok || strlen(tok) == 0) { + tst_brkm(TBROK, NULL, + "Invalid argument to -C option"); + return; + } + + switch (*p) { + case 'd': + iptr = va_arg(ap, int *); + i = strtol(tok, &endptr, 10); + if (*endptr != '\0') { + tst_brkm(TBROK, NULL, + "Invalid argument to -C option"); + return; + } + *iptr = i; + break; + case 'n': + j = va_arg(ap, int); + i = strtol(tok, &endptr, 10); + if (*endptr != '\0') { + tst_brkm(TBROK, NULL, + "Invalid argument to -C option"); + return; + } + if (j != i) { + va_end(ap); + free(args); + return; + } + break; + case 's': + s = va_arg(ap, char *); + if (!strncpy(s, tok, strlen(tok) + 1)) { + tst_brkm(TBROK, NULL, + "Could not strncpy for -C option"); + return; + } + break; + case 'S': + sptr = va_arg(ap, char **); + *sptr = strdup(tok); + if (!*sptr) { + tst_brkm(TBROK, NULL, + "Could not strdup for -C option"); + return; + } + break; + default: + tst_brkm(TBROK, NULL, + "Format string option %c not implemented", + *p); + return; + } + } + + va_end(ap); + free(args); + SAFE_CHDIR(NULL, child_dir); + + (*child) (); + tst_resm(TWARN, "Child function returned unexpectedly"); + /* Exit here? or exit silently? */ + } +} + +int self_exec(const char *argv0, const char *fmt, ...) +{ + va_list ap; + char *p; + char *tmp_cwd; + char *arg; + int ival; + char *str; + + if ((tmp_cwd = getcwd(NULL, 0)) == NULL) { + tst_resm(TBROK, "Could not getcwd()"); + return -1; + } + + arg = strdup(tmp_cwd); + if (arg == NULL) { + tst_resm(TBROK, "Could not produce self_exec string"); + return -1; + } + + va_start(ap, fmt); + + for (p = fmt; *p; p++) { + switch (*p) { + case 'd': + case 'n': + ival = va_arg(ap, int); + if (asprintf(&arg, "%s,%d", arg, ival) < 0) { + tst_resm(TBROK, + "Could not produce self_exec string"); + return -1; + } + break; + case 's': + case 'S': + str = va_arg(ap, char *); + if (asprintf(&arg, "%s,%s", arg, str) < 0) { + tst_resm(TBROK, + "Could not produce self_exec string"); + return -1; + } + break; + default: + tst_resm(TBROK, + "Format string option %c not implemented", *p); + return -1; + break; + } + } + + va_end(ap); + + if (chdir(start_cwd) < 0) { + tst_resm(TBROK, "Could not change to %s for self_exec", + start_cwd); + return -1; + } + + return execlp(argv0, argv0, "-C", arg, (char *)NULL); +} + +#endif /* UCLINUX */ diff --git a/src/kernel/tests/lib/signame.h b/src/kernel/tests/lib/signame.h new file mode 100644 index 0000000..d420458 --- /dev/null +++ b/src/kernel/tests/lib/signame.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2014 Fujitsu Ltd. + * Author: Xiaoguang Wang <wangxg.fnst@cn.fujitsu.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +const char *tst_strsig(int sig) +{ + static const struct pair signal_pairs[] = { + PAIR(SIGHUP) + PAIR(SIGINT) + PAIR(SIGQUIT) + PAIR(SIGILL) + #ifdef SIGTRAP + PAIR(SIGTRAP) + #endif + + #ifdef SIGIOT + /* SIGIOT same as SIGABRT */ + STRPAIR(SIGABRT, "SIGIOT/SIGABRT") + #else + PAIR(SIGABRT) + #endif + + #ifdef SIGEMT + PAIR(SIGEMT) + #endif + #ifdef SIGBUS + PAIR(SIGBUS) + #endif + PAIR(SIGFPE) + PAIR(SIGKILL) + PAIR(SIGUSR1) + PAIR(SIGSEGV) + PAIR(SIGUSR2) + PAIR(SIGPIPE) + PAIR(SIGALRM) + PAIR(SIGTERM) + #ifdef SIGSTKFLT + PAIR(SIGSTKFLT) + #endif + PAIR(SIGCHLD) + PAIR(SIGCONT) + PAIR(SIGSTOP) + PAIR(SIGTSTP) + PAIR(SIGTTIN) + PAIR(SIGTTOU) + #ifdef SIGURG + PAIR(SIGURG) + #endif + #ifdef SIGXCPU + PAIR(SIGXCPU) + #endif + #ifdef SIGXFSZ + PAIR(SIGXFSZ) + #endif + #ifdef SIGVTALRM + PAIR(SIGVTALRM) + #endif + #ifdef SIGPROF + PAIR(SIGPROF) + #endif + #ifdef SIGWINCH + PAIR(SIGWINCH) + #endif + + #if defined(SIGIO) && defined(SIGPOLL) + /* SIGPOLL same as SIGIO */ + STRPAIR(SIGIO, "SIGIO/SIGPOLL") + #elif defined(SIGIO) + PAIR(SIGIO) + #elif defined(SIGPOLL) + PAIR(SIGPOLL) + #endif + + #ifdef SIGINFO + PAIR(SIGINFO) + #endif + #ifdef SIGLOST + PAIR(SIGLOST) + #endif + #ifdef SIGPWR + PAIR(SIGPWR) + #endif + #if defined(SIGSYS) + /* + * According to signal(7)'s manpage, SIGUNUSED is synonymous + * with SIGSYS on most architectures. + */ + STRPAIR(SIGSYS, "SIGSYS/SIGUNUSED") + #endif + }; + + PAIR_LOOKUP(signal_pairs, sig); +}; diff --git a/src/kernel/tests/lib/tlibio.c b/src/kernel/tests/lib/tlibio.c new file mode 100644 index 0000000..cc110d1 --- /dev/null +++ b/src/kernel/tests/lib/tlibio.c @@ -0,0 +1,2161 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ + */ +/* + * + * Lib i/o + * + * This file contains several functions to doing reads and writes. + * It was written so that a single function could be called in a test + * program and only a io type field value would have to change to + * do different types of io. There is even a couple of functions that + * will allow you to parse a string to determine the iotype. + * + * This file contains functions for writing/reading to/from open files + * Prototypes: + * + * Functions declared in this module - see individual function code for + * usage comments: + * + * int stride_bounds(int offset, int stride, int nstrides, + * int bytes_per_stride, int *min, int *max); + + * int lio_write_buffer(int fd, int method, char *buffer, int size, + * char **errmsg, long wrd); + * int lio_read_buffer(int fd, int method, char *buffer, int size, + * char **errmsg, long wrd); + * + * #ifdef CRAY + * int lio_wait4asyncio(int method, int fd, struct iosw **statptr) + * int lio_check_asyncio(char *io_type, int size, struct iosw *status) + * #endif + * #ifdef sgi + * int lio_wait4asyncio(int method, int fd, aiocb_t *aiocbp) + * int lio_check_asyncio(char *io_type, int size, aiocb_t *aiocbp, int method) + * #endif + * + * int lio_parse_io_arg1(char *string) + * void lio_help1(char *prefix); + * + * int lio_parse_io_arg2(char *string, char **badtoken) + * void lio_help2(char *prefix); + * + * int lio_set_debug(int level); + * + * char Lio_SysCall[]; + * struct lio_info_type Lio_info1[]; + * struct lio_info_type Lio_info2[]; + * + * Author : Richard Logan + * + */ + +#ifdef __linux__ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#define _LARGEFILE64_SOURCE +#endif +#include "config.h" +#include <stdio.h> +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/param.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/file.h> +#include <signal.h> +#include <stdint.h> +#ifdef CRAY +#include <sys/secparm.h> +#include <sys/iosw.h> +#include <sys/listio.h> +#else +/* for linux or sgi */ +#include <sys/uio.h> /* readv(2)/writev(2) */ +#include <string.h> +#endif +#if defined(__linux__) || defined(__sun) || defined(__hpux) || defined(_AIX) +#if !defined(UCLINUX) && !defined(__UCLIBC__) +#include <aio.h> +#endif +#endif +#include <stdlib.h> /* atoi, abs */ + +#include "tlibio.h" /* defines LIO* marcos */ +#include "random_range.h" + +#ifndef PATH_MAX +#define PATH_MAX MAXPATHLEN +#endif + +#if 0 /* disabled until it's needed -- roehrich 6/11/97 */ +#define BUG1_workaround 1 /* Work around a condition where aio_return gives + * a value of zero but there is no errno followup + * and the read/write operation actually did its + * job. spr/pv 705244 + */ +#endif + + +/* + * Define the structure as used in lio_parse_arg1 and lio_help1 + */ +struct lio_info_type Lio_info1[] = { + {"s", LIO_IO_SYNC, "sync i/o"}, + {"p", LIO_IO_ASYNC | LIO_WAIT_SIGACTIVE, + "async i/o using a loop to wait for a signal"}, + {"b", LIO_IO_ASYNC | LIO_WAIT_SIGPAUSE, "async i/o using pause"}, + {"a", LIO_IO_ASYNC | LIO_WAIT_RECALL, + "async i/o using recall/aio_suspend"}, +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + {"r", + LIO_RANDOM | LIO_IO_TYPES | LIO_WAIT_TYPES, + "random sync i/o types and wait methods"}, + {"R", + LIO_RANDOM | LIO_IO_ATYPES | LIO_WAIT_ATYPES, + "random i/o types and wait methods"}, +#else + {"r", + LIO_RANDOM | LIO_IO_TYPES | LIO_WAIT_TYPES, + "random i/o types and wait methods"}, + {"R", + LIO_RANDOM | LIO_IO_TYPES | LIO_WAIT_TYPES, + "random i/o types and wait methods"}, +#endif + {"l", LIO_IO_SLISTIO | LIO_WAIT_RECALL, "single stride sync listio"}, + {"L", LIO_IO_ALISTIO | LIO_WAIT_RECALL, + "single stride async listio using recall"}, + {"X", LIO_IO_ALISTIO | LIO_WAIT_SIGPAUSE, + "single stride async listio using pause"}, + {"v", LIO_IO_SYNCV, "single buffer sync readv/writev"}, + {"P", LIO_IO_SYNCP, "sync pread/pwrite"}, +}; + +/* + * Define the structure used by lio_parse_arg2 and lio_help2 + */ +struct lio_info_type Lio_info2[] = { + {"sync", LIO_IO_SYNC, "sync i/o (read/write)"}, + {"async", LIO_IO_ASYNC, "async i/o (reada/writea/aio_read/aio_write)"}, + {"slistio", LIO_IO_SLISTIO, "single stride sync listio"}, + {"alistio", LIO_IO_ALISTIO, "single stride async listio"}, + {"syncv", LIO_IO_SYNCV, "single buffer sync readv/writev"}, + {"syncp", LIO_IO_SYNCP, "pread/pwrite"}, + {"active", LIO_WAIT_ACTIVE, "spin on status/control values"}, + {"recall", LIO_WAIT_RECALL, + "use recall(2)/aio_suspend(3) to wait for i/o to complete"}, + {"sigactive", LIO_WAIT_SIGACTIVE, "spin waiting for signal"}, + {"sigpause", LIO_WAIT_SIGPAUSE, "call pause(2) to wait for signal"}, +/* nowait is a touchy thing, it's an accident that this implementation worked at all. 6/27/97 roehrich */ +/* { "nowait", LIO_WAIT_NONE, "do not wait for async io to complete" },*/ + {"random", LIO_RANDOM, "set random bit"}, + {"randomall", + LIO_RANDOM | LIO_IO_TYPES | LIO_WAIT_TYPES, + "all random i/o types and wait methods (except nowait)"}, +}; + +char Lio_SysCall[PATH_MAX]; /* string containing last i/o system call */ + +static volatile int Received_signal = 0; /* number of signals received */ +static volatile int Rec_signal; +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) +static volatile int Received_callback = 0; /* number of callbacks received */ +static volatile int Rec_callback; +#endif +static char Errormsg[500]; +static int Debug_level = 0; + +/*********************************************************************** + * stride_bounds() + * + * Determine the bounds of a strided request, normalized to offset. Returns + * the number of bytes needed to satisfy the request, and optionally sets + * *min and *max to the mininum and maximum bytes referenced, normalized + * around offset. + * + * Returns -1 on error - the only possible error conditions are illegal values + * for nstrides and/or bytes_per_stride - both parameters must be >= 0. + * + * (maule, 11/16/95) + ***********************************************************************/ + +int stride_bounds(int offset, int stride, int nstrides, int bytes_per_stride, + int *min, int *max) +{ + int nbytes, min_byte, max_byte; + + /* + * sanity checks ... + */ + + if (nstrides < 0 || bytes_per_stride < 0) { + return -1; + } + + if (stride == 0) { + stride = bytes_per_stride; + } + + /* + * Determine the # of bytes needed to satisfy the request. This + * value, along with the offset argument, determines the min and max + * bytes referenced. + */ + + nbytes = abs(stride) * (nstrides - 1) + bytes_per_stride; + + if (stride < 0) { + max_byte = offset + bytes_per_stride - 1; + min_byte = max_byte - nbytes + 1; + } else { + min_byte = offset; + max_byte = min_byte + nbytes - 1; + } + + if (min != NULL) { + *min = min_byte; + } + + if (max != NULL) { + *max = max_byte; + } + + return nbytes; +} + +/*********************************************************************** + * This function will allow someone to set the debug level. + ***********************************************************************/ +int lio_set_debug(int level) +{ + int old; + + old = Debug_level; + Debug_level = level; + return old; +} + +/*********************************************************************** + * This function will parse a string and return desired io-method. + * Only the first character of the string is used. + * + * This function does not provide for meaningful option arguments, + * but it supports current growfiles/btlk interface. + * + * (rrl 04/96) + ***********************************************************************/ +int lio_parse_io_arg1(char *string) +{ + unsigned int ind; + int found = 0; + int mask = 0; + + /* + * Determine if token is a valid string. + */ + for (ind = 0; ind < sizeof(Lio_info1) / sizeof(struct lio_info_type); + ind++) { + if (strcmp(string, Lio_info1[ind].token) == 0) { + mask |= Lio_info1[ind].bits; + found = 1; + break; + } + } + + if (found == 0) { + return -1; + } + + return mask; + +} + +/*********************************************************************** + * This function will print a help message describing the characters + * that can be parsed by lio_parse_io_arg1(). + * They will be printed one per line. + * (rrl 04/96) + ***********************************************************************/ +void lio_help1(char *prefix) +{ + unsigned int ind; + + for (ind = 0; ind < sizeof(Lio_info1) / sizeof(struct lio_info_type); + ind++) { + printf("%s %s : %s\n", prefix, Lio_info1[ind].token, + Lio_info1[ind].desc); + } + + return; +} + +/*********************************************************************** + * This function will parse a string and return the desired io-method. + * This function will take a comma separated list of io type and wait + * method tokens as defined in Lio_info2[]. If a token does not match + * any of the tokens in Lio_info2[], it will be coverted to a number. + * If it was a number, those bits are also set. + * + * (rrl 04/96) + ***********************************************************************/ +int lio_parse_io_arg2(char *string, char **badtoken) +{ + char *token = string; + char *cc = token; + char savecc; + int found; + int mask = 0; + + int tmp; + unsigned int ind; + char chr; + + if (token == NULL) + return -1; + + for (;;) { + for (; ((*cc != ',') && (*cc != '\0')); cc++) ; + savecc = *cc; + *cc = '\0'; + + found = 0; + + /* + * Determine if token is a valid string or number and if + * so, add the bits to the mask. + */ + for (ind = 0; + ind < sizeof(Lio_info2) / sizeof(struct lio_info_type); + ind++) { + if (strcmp(token, Lio_info2[ind].token) == 0) { + mask |= Lio_info2[ind].bits; + found = 1; + break; + } + } + + /* + * If token does not match one of the defined tokens, determine + * if it is a number, if so, add the bits. + */ + if (!found) { + if (sscanf(token, "%i%c", &tmp, &chr) == 1) { + mask |= tmp; + found = 1; + } + } + + *cc = savecc; + + if (!found) { /* token is not valid */ + if (badtoken != NULL) + *badtoken = token; + return (-1); + } + + if (savecc == '\0') + break; + + token = ++cc; + } + + return mask; +} + +/*********************************************************************** + * This function will print a help message describing the tokens + * that can be parsed by lio_parse_io_arg2(). + * It will print them one per line. + * + * (rrl 04/96) + ***********************************************************************/ +void lio_help2(char *prefix) +{ + unsigned int ind; + + for (ind = 0; ind < sizeof(Lio_info2) / sizeof(struct lio_info_type); + ind++) { + printf("%s %s : %s\n", prefix, Lio_info2[ind].token, + Lio_info2[ind].desc); + } + return; +} + +/*********************************************************************** + * This is an internal signal handler. + * If the handler is called, it will increment the Received_signal + * global variable. + ***********************************************************************/ +static void lio_async_signal_handler(int sig) +{ + if (Debug_level) + printf + ("DEBUG %s/%d: received signal %d, a signal caught %d times\n", + __FILE__, __LINE__, sig, Received_signal + 1); + + Received_signal++; + + return; +} + +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) +/*********************************************************************** + * This is an internal callback handler. + * If the handler is called, it will increment the Received_callback + * global variable. + ***********************************************************************/ +static void lio_async_callback_handler(union sigval sigval) +{ + if (Debug_level) + printf + ("DEBUG %s/%d: received callback, nbytes=%ld, a callback called %d times\n", + __FILE__, __LINE__, (long)sigval.sival_int, + Received_callback + 1); + + Received_callback++; + + return; +} +#endif /* sgi */ + +/*********************************************************************** + * lio_random_methods + * This function will randomly choose an io type and wait method + * from set of io types and wait methods. Since this information + * is stored in a bitmask, it randomly chooses an io type from + * the io type bits specified and does the same for wait methods. + * + * Return Value + * This function will return a value with all non choosen io type + * and wait method bits cleared. The LIO_RANDOM bit is also + * cleared. All other bits are left unchanged. + * + * (rrl 04/96) + ***********************************************************************/ +int lio_random_methods(long curr_mask) +{ + int mask = 0; + + /* remove random select, io type, and wait method bits from curr_mask */ + mask = curr_mask & (~(LIO_IO_TYPES | LIO_WAIT_TYPES | LIO_RANDOM)); + + /* randomly select io type from specified io types */ + mask = mask | random_bit(curr_mask & LIO_IO_TYPES); + + /* randomly select wait methods from specified wait methods */ + mask = mask | random_bit(curr_mask & LIO_WAIT_TYPES); + + return mask; +} + +static void wait4sync_io(int fd, int read) +{ + fd_set s; + FD_ZERO(&s); + FD_SET(fd, &s); + + select(fd + 1, read ? &s : NULL, read ? NULL : &s, NULL, NULL); +} + +/*********************************************************************** + * Generic write function + * This function can be used to do a write using write(2), writea(2), + * aio_write(3), writev(2), pwrite(2), + * or single stride listio(2)/lio_listio(3). + * By setting the desired bits in the method + * bitmask, the caller can control the type of write and the wait method + * that will be used. If no io type bits are set, write will be used. + * + * If async io was attempted and no wait method bits are set then the + * wait method is: recall(2) for writea(2) and listio(2); aio_suspend(3) for + * aio_write(3) and lio_listio(3). + * + * If multiple wait methods are specified, + * only one wait method will be used. The order is predetermined. + * + * If the call specifies a signal and one of the two signal wait methods, + * a signal handler for the signal is set. This will reset an already + * set handler for this signal. + * + * If the LIO_RANDOM method bit is set, this function will randomly + * choose a io type and wait method from bits in the method argument. + * + * If an error is encountered, an error message will be generated + * in a internal static buffer. If errmsg is not NULL, it will + * be updated to point to the static buffer, allowing the caller + * to print the error message. + * + * Return Value + * If a system call fails, -errno is returned. + * If LIO_WAIT_NONE bit is set, the return value is the return value + * of the system call. + * If the io did not fail, the amount of data written is returned. + * If the size the system call say was written is different + * then what was asked to be written, errmsg is updated for + * this error condition. The return value is still the amount + * the system call says was written. + * + * (rrl 04/96) + ***********************************************************************/ +int lio_write_buffer(int fd, /* open file descriptor */ + int method, /* contains io type and wait method bitmask */ + char *buffer, /* pointer to buffer */ + int size, /* the size of the io */ + int sig, /* signal to use if async io */ + char **errmsg, /* char pointer that will be updated to point to err message */ + long wrd) /* to allow future features, use zero for now */ +{ + int ret = 0; /* syscall return or used to get random method */ + char *io_type; /* Holds string of type of io */ + int omethod = method; + int listio_cmd; /* Holds the listio/lio_listio cmd */ +#ifdef CRAY + struct listreq request; /* Used when a listio is wanted */ + struct iosw status, *statptr[1]; +#else + /* for linux or sgi */ + struct iovec iov; /* iovec for writev(2) */ +#endif +#if defined (sgi) + aiocb_t aiocbp; /* POSIX aio control block */ + aiocb_t *aiolist[1]; /* list of aio control blocks for lio_listio */ + off64_t poffset; /* pwrite(2) offset */ +#endif +#if defined(__linux__) && !defined(__UCLIBC__) + struct aiocb aiocbp; /* POSIX aio control block */ + struct aiocb *aiolist[1]; /* list of aio control blocks for lio_listio */ + off64_t poffset; /* pwrite(2) offset */ +#endif + /* + * If LIO_RANDOM bit specified, get new method randomly. + */ + if (method & LIO_RANDOM) { + if (Debug_level > 3) + printf("DEBUG %s/%d: method mask to choose from: %#o\n", + __FILE__, __LINE__, method); + method = lio_random_methods(method); + if (Debug_level > 2) + printf("DEBUG %s/%d: random chosen method %#o\n", + __FILE__, __LINE__, method); + } + + if (errmsg != NULL) + *errmsg = Errormsg; + + Rec_signal = Received_signal; /* get the current number of signals received */ +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + Rec_callback = Received_callback; /* get the current number of callbacks received */ +#endif + +#ifdef CRAY + memset(&status, 0x00, sizeof(struct iosw)); + memset(&request, 0x00, sizeof(struct listreq)); + statptr[0] = &status; +#else + /* for linux or sgi */ + memset(&iov, 0x00, sizeof(struct iovec)); + iov.iov_base = buffer; + iov.iov_len = size; +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) +#if defined(sgi) + memset(&aiocbp, 0x00, sizeof(aiocb_t)); +#else + memset(&aiocbp, 0x00, sizeof(struct aiocb)); +#endif + aiocbp.aio_fildes = fd; + aiocbp.aio_nbytes = size; + aiocbp.aio_buf = buffer; +/* aiocbp.aio_offset = lseek( fd, 0, SEEK_CUR ); -- set below */ + aiocbp.aio_sigevent.sigev_notify = SIGEV_NONE; + aiocbp.aio_sigevent.sigev_signo = 0; +#ifdef sgi + aiocbp.aio_sigevent.sigev_func = NULL; + aiocbp.aio_sigevent.sigev_value.sival_int = 0; +#elif defined(__linux__) && !defined(__UCLIBC__) + aiocbp.aio_sigevent.sigev_notify_function = NULL; + aiocbp.aio_sigevent.sigev_notify_attributes = 0; +#endif + aiolist[0] = &aiocbp; + + if ((ret = lseek(fd, 0, SEEK_CUR)) == -1) { + ret = 0; + /* If there is an error and it is not ESPIPE then kick out the error. + * If the fd is a fifo then we have to make sure that + * lio_random_methods() didn't select pwrite/pread; if it did then + * switch to write/read. + */ + if (errno == ESPIPE) { + if (method & LIO_IO_SYNCP) { + if (omethod & LIO_RANDOM) { + method &= ~LIO_IO_SYNCP; + method |= LIO_IO_SYNC; + if (Debug_level > 2) + printf + ("DEBUG %s/%d: random chosen method switched to %#o for fifo\n", + __FILE__, __LINE__, + method); + } else if (Debug_level) { + printf + ("DEBUG %s/%d: pwrite will fail when it writes to a fifo\n", + __FILE__, __LINE__); + } + } + /* else: let it ride */ + } else { + sprintf(Errormsg, + "%s/%d lseek(fd=%d,0,SEEK_CUR) failed, errno=%d %s", + __FILE__, __LINE__, fd, errno, strerror(errno)); + return -errno; + } + } +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + poffset = (off64_t) ret; +#endif + aiocbp.aio_offset = ret; + +#endif + + /* + * If the LIO_USE_SIGNAL bit is not set, only use the signal + * if the LIO_WAIT_SIGPAUSE or the LIO_WAIT_SIGACTIVE bits are bit. + * Otherwise there is not necessary a signal handler to trap + * the signal. + */ + if (sig && !(method & LIO_USE_SIGNAL) && !(method & LIO_WAIT_SIGTYPES)) { + + sig = 0; /* ignore signal parameter */ + } +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + if (sig && (method & LIO_WAIT_CBTYPES)) + sig = 0; /* ignore signal parameter */ +#endif + + /* + * only setup signal hander if sig was specified and + * a sig wait method was specified. + * Doing this will change the handler for this signal. The + * old signal handler will not be restored. + *** restoring the signal handler could be added *** + */ + + if (sig && (method & LIO_WAIT_SIGTYPES)) { +#ifdef CRAY + sigctl(SCTL_REG, sig, lio_async_signal_handler); +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + aiocbp.aio_sigevent.sigev_notify = SIGEV_SIGNAL; + aiocbp.aio_sigevent.sigev_signo = sig; + sigset(sig, lio_async_signal_handler); +#endif /* sgi */ + } +#if defined(sgi) + else if (method & LIO_WAIT_CBTYPES) { + /* sival_int just has to be something that I can use + * to identify the callback, and "size" happens to be handy... + */ + aiocbp.aio_sigevent.sigev_notify = SIGEV_CALLBACK; + aiocbp.aio_sigevent.sigev_func = lio_async_callback_handler; + aiocbp.aio_sigevent.sigev_value.sival_int = size; + } +#endif +#if defined(__linux__) && !defined(__UCLIBC__) + else if (method & LIO_WAIT_CBTYPES) { + /* sival_int just has to be something that I can use + * to identify the callback, and "size" happens to be handy... + */ + aiocbp.aio_sigevent.sigev_notify = SIGEV_THREAD; + aiocbp.aio_sigevent.sigev_notify_function = + lio_async_callback_handler; + aiocbp.aio_sigevent.sigev_notify_attributes = + (void *)(uintptr_t) size; + } +#endif + /* + * Determine the system call that will be called and produce + * the string of the system call and place it in Lio_SysCall. + * Also update the io_type char pointer to give brief description + * of system call. Execute the system call and check for + * system call failure. If sync i/o, return the number of + * bytes written/read. + */ + + if ((method & LIO_IO_SYNC) + || (method & (LIO_IO_TYPES | LIO_IO_ATYPES)) == 0) { + /* + * write(2) is used if LIO_IO_SYNC bit is set or not none + * of the LIO_IO_TYPES bits are set (default). + */ + + sprintf(Lio_SysCall, "write(%d, buf, %d)", fd, size); + io_type = "write"; + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + while (1) { + if (((ret = write(fd, buffer, size)) == -1) + && errno != EAGAIN && errno != EINTR) { + sprintf(Errormsg, + "%s/%d write(%d, buf, %d) ret:-1, errno=%d %s", + __FILE__, __LINE__, fd, size, errno, + strerror(errno)); + return -errno; + } + + if (ret != -1) { + if (ret != size) { + sprintf(Errormsg, + "%s/%d write(%d, buf, %d) returned=%d", + __FILE__, __LINE__, + fd, size, ret); + size -= ret; + buffer += ret; + } else { + if (Debug_level > 1) + printf + ("DEBUG %s/%d: write completed without error (ret %d)\n", + __FILE__, __LINE__, ret); + + return ret; + } + } + wait4sync_io(fd, 0); + } + + } + + else if (method & LIO_IO_ASYNC) { +#ifdef CRAY + sprintf(Lio_SysCall, + "writea(%d, buf, %d, &status, %d)", fd, size, sig); + io_type = "writea"; + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + sigoff(); + if ((ret = writea(fd, buffer, size, &status, sig)) == -1) { + sprintf(Errormsg, + "%s/%d writea(%d, buf, %d, &stat, %d) ret:-1, errno=%d %s", + __FILE__, __LINE__, + fd, size, sig, errno, strerror(errno)); + sigon(); + return -errno; + } +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + sprintf(Lio_SysCall, + "aio_write(fildes=%d, buf, nbytes=%d, signo=%d)", fd, + size, sig); + io_type = "aio_write"; + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + if (sig) + sighold(sig); + if ((ret = aio_write(&aiocbp)) == -1) { + sprintf(Errormsg, + "%s/%d aio_write(fildes=%d, buf, nbytes=%d, signo=%d) ret:-1, errno=%d %s", + __FILE__, __LINE__, + fd, size, sig, errno, strerror(errno)); + if (sig) + sigrelse(sig); + return -errno; + } +#endif + } + /* LIO_IO_ASYNC */ + else if (method & LIO_IO_SLISTIO) { +#ifdef CRAY + request.li_opcode = LO_WRITE; + request.li_fildes = fd; + request.li_buf = buffer; + request.li_nbyte = size; + request.li_status = &status; + request.li_signo = sig; + request.li_nstride = 0; + request.li_filstride = 0; + request.li_memstride = 0; + + listio_cmd = LC_WAIT; + io_type = "listio(2) sync write"; + + sprintf(Lio_SysCall, + "listio(LC_WAIT, &req, 1) LO_WRITE, fd:%d, nbyte:%d", + fd, size); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + sigoff(); + if (listio(listio_cmd, &request, 1) == -1) { + sprintf(Errormsg, + "%s/%d %s failed, fd:%d, nbyte:%d errno=%d %s", + __FILE__, __LINE__, Lio_SysCall, fd, size, + errno, strerror(errno)); + sigon(); + return -errno; + } + + if (Debug_level > 1) + printf("DEBUG %s/%d: %s did not return -1\n", + __FILE__, __LINE__, Lio_SysCall); + + ret = lio_check_asyncio(io_type, size, &status); + return ret; + +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + + aiocbp.aio_lio_opcode = LIO_WRITE; + listio_cmd = LIO_WAIT; + io_type = "lio_listio(3) sync write"; + + sprintf(Lio_SysCall, + "lio_listio(LIO_WAIT, aiolist, 1, NULL) LIO_WRITE, fd:%d, nbyte:%d, sig:%d", + fd, size, sig); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + if (sig) + sighold(sig); + if (lio_listio(listio_cmd, aiolist, 1, NULL) == -1) { + sprintf(Errormsg, + "%s/%d %s failed, fd:%d, nbyte:%d errno=%d %s", + __FILE__, __LINE__, Lio_SysCall, fd, size, + errno, strerror(errno)); + if (sig) + sigrelse(sig); + return -errno; + } + + if (Debug_level > 1) + printf("DEBUG %s/%d: %s did not return -1\n", + __FILE__, __LINE__, Lio_SysCall); + + ret = lio_check_asyncio(io_type, size, &aiocbp, method); + return ret; +#endif + } + /* LIO_IO_SLISTIO */ + else if (method & LIO_IO_ALISTIO) { +#ifdef CRAY + request.li_opcode = LO_WRITE; + request.li_fildes = fd; + request.li_buf = buffer; + request.li_nbyte = size; + request.li_status = &status; + request.li_signo = sig; + request.li_nstride = 0; + request.li_filstride = 0; + request.li_memstride = 0; + + listio_cmd = LC_START; + io_type = "listio(2) async write"; + + sprintf(Lio_SysCall, + "listio(LC_START, &req, 1) LO_WRITE, fd:%d, nbyte:%d", + fd, size); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + sigoff(); + if (listio(listio_cmd, &request, 1) == -1) { + sprintf(Errormsg, + "%s/%d %s failed, fd:%d, nbyte:%d errno=%d %s", + __FILE__, __LINE__, Lio_SysCall, fd, size, + errno, strerror(errno)); + sigon(); + return -errno; + } +#endif +#if defined (sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + aiocbp.aio_lio_opcode = LIO_WRITE; + listio_cmd = LIO_NOWAIT; + io_type = "lio_listio(3) async write"; + + sprintf(Lio_SysCall, + "lio_listio(LIO_NOWAIT, aiolist, 1, NULL) LIO_WRITE, fd:%d, nbyte:%d", + fd, size); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + if (sig) + sighold(sig); + if (lio_listio(listio_cmd, aiolist, 1, NULL) == -1) { + sprintf(Errormsg, + "%s/%d %s failed, fd:%d, nbyte:%d errno=%d %s", + __FILE__, __LINE__, Lio_SysCall, fd, size, + errno, strerror(errno)); + if (sig) + sigrelse(sig); + return -errno; + } +#endif + } + /* LIO_IO_ALISTIO */ +#ifndef CRAY + else if (method & LIO_IO_SYNCV) { + io_type = "writev(2)"; + + sprintf(Lio_SysCall, "writev(%d, &iov, 1) nbyte:%d", fd, size); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + if ((ret = writev(fd, &iov, 1)) == -1) { + sprintf(Errormsg, + "%s/%d writev(%d, iov, 1) nbyte:%d ret:-1, errno=%d %s", + __FILE__, __LINE__, fd, size, errno, + strerror(errno)); + return -errno; + } + + if (ret != size) { + sprintf(Errormsg, + "%s/%d writev(%d, iov, 1) nbyte:%d returned=%d", + __FILE__, __LINE__, fd, size, ret); + } else if (Debug_level > 1) + printf + ("DEBUG %s/%d: writev completed without error (ret %d)\n", + __FILE__, __LINE__, ret); + + return ret; + } /* LIO_IO_SYNCV */ +#endif + +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + else if (method & LIO_IO_SYNCP) { + io_type = "pwrite(2)"; + + sprintf(Lio_SysCall, + "pwrite(%d, buf, %d, %lld)", fd, size, + (long long)poffset); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + if ((ret = pwrite(fd, buffer, size, poffset)) == -1) { + sprintf(Errormsg, + "%s/%d pwrite(%d, buf, %d, %lld) ret:-1, errno=%d %s", + __FILE__, __LINE__, fd, size, + (long long)poffset, errno, strerror(errno)); + return -errno; + } + + if (ret != size) { + sprintf(Errormsg, + "%s/%d pwrite(%d, buf, %d, %lld) returned=%d", + __FILE__, __LINE__, + fd, size, (long long)poffset, ret); + } else if (Debug_level > 1) + printf + ("DEBUG %s/%d: pwrite completed without error (ret %d)\n", + __FILE__, __LINE__, ret); + + return ret; + } /* LIO_IO_SYNCP */ +#endif + + else { + printf("DEBUG %s/%d: No I/O method chosen\n", __FILE__, + __LINE__); + return -1; + } + + /* + * wait for async io to complete. + */ +#ifdef CRAY + ret = lio_wait4asyncio(method, fd, statptr); +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + ret = lio_wait4asyncio(method, fd, &aiocbp); +#endif + + /* + * If there was an error waiting for async i/o to complete, + * return the error value (errno) to the caller. + * Note: Errormsg should already have been updated. + */ + if (ret < 0) { + return ret; + } + + /* + * If i/o was not waited for (may not have been completed at this time), + * return the size that was requested. + */ + if (ret == 1) + return size; + + /* + * check that async io was successful. + * Note: if the there was an system call failure, -errno + * was returned and Errormsg should already have been updated. + * If amount i/o was different than size, Errormsg should already + * have been updated but the actual i/o size if returned. + */ + +#ifdef CRAY + ret = lio_check_asyncio(io_type, size, &status); +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + ret = lio_check_asyncio(io_type, size, &aiocbp, method); +#endif + + return ret; +} /* end of lio_write_buffer */ + +/*********************************************************************** + * Generic read function + * This function can be used to do a read using read(2), reada(2), + * aio_read(3), readv(2), pread(2), + * or single stride listio(2)/lio_listio(3). + * By setting the desired bits in the method + * bitmask, the caller can control the type of read and the wait method + * that will be used. If no io type bits are set, read will be used. + * + * If async io was attempted and no wait method bits are set then the + * wait method is: recall(2) for reada(2) and listio(2); aio_suspend(3) for + * aio_read(3) and lio_listio(3). + * + * If multiple wait methods are specified, + * only one wait method will be used. The order is predetermined. + * + * If the call specifies a signal and one of the two signal wait methods, + * a signal handler for the signal is set. This will reset an already + * set handler for this signal. + * + * If the LIO_RANDOM method bit is set, this function will randomly + * choose a io type and wait method from bits in the method argument. + * + * If an error is encountered, an error message will be generated + * in a internal static buffer. If errmsg is not NULL, it will + * be updated to point to the static buffer, allowing the caller + * to print the error message. + * + * Return Value + * If a system call fails, -errno is returned. + * If LIO_WAIT_NONE bit is set, the return value is the return value + * of the system call. + * If the io did not fail, the amount of data written is returned. + * If the size the system call say was written is different + * then what was asked to be written, errmsg is updated for + * this error condition. The return value is still the amount + * the system call says was written. + * + * (rrl 04/96) + ***********************************************************************/ +int lio_read_buffer(int fd, /* open file descriptor */ + int method, /* contains io type and wait method bitmask*/ + char *buffer, /* pointer to buffer */ + int size, /* the size of the io */ + int sig, /* signal to use if async io */ + char **errmsg, /* char pointer that will be updated to point to err message */ + long wrd) /* to allow future features, use zero for now */ +{ + int ret = 0; /* syscall return or used to get random method */ + char *io_type; /* Holds string of type of io */ + int listio_cmd; /* Holds the listio/lio_listio cmd */ + int omethod = method; +#ifdef CRAY + struct listreq request; /* Used when a listio is wanted */ + struct iosw status, *statptr[1]; +#else + /* for linux or sgi */ + struct iovec iov; /* iovec for readv(2) */ +#endif +#ifdef sgi + aiocb_t aiocbp; /* POSIX aio control block */ + aiocb_t *aiolist[1]; /* list of aio control blocks for lio_listio */ + off64_t poffset; /* pread(2) offset */ +#endif +#if defined (__linux__) && !defined(__UCLIBC__) + struct aiocb aiocbp; /* POSIX aio control block */ + struct aiocb *aiolist[1]; /* list of aio control blocks for lio_listio */ + off64_t poffset; /* pread(2) offset */ +#endif + + /* + * If LIO_RANDOM bit specified, get new method randomly. + */ + if (method & LIO_RANDOM) { + if (Debug_level > 3) + printf("DEBUG %s/%d: method mask to choose from: %#o\n", + __FILE__, __LINE__, method); + method = lio_random_methods(method); + if (Debug_level > 2) + printf("DEBUG %s/%d: random chosen method %#o\n", + __FILE__, __LINE__, method); + } + + if (errmsg != NULL) + *errmsg = Errormsg; + + Rec_signal = Received_signal; /* get the current number of signals received */ +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + Rec_callback = Received_callback; /* get the current number of callbacks received */ +#endif + +#ifdef CRAY + memset(&status, 0x00, sizeof(struct iosw)); + memset(&request, 0x00, sizeof(struct listreq)); + statptr[0] = &status; +#else + /* for linux or sgi */ + memset(&iov, 0x00, sizeof(struct iovec)); + iov.iov_base = buffer; + iov.iov_len = size; +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) +#if defined(sgi) + memset(&aiocbp, 0x00, sizeof(aiocb_t)); +#else + memset(&aiocbp, 0x00, sizeof(struct aiocb)); +#endif + aiocbp.aio_fildes = fd; + aiocbp.aio_nbytes = size; + aiocbp.aio_buf = buffer; +/* aiocbp.aio_offset = lseek( fd, 0, SEEK_CUR ); -- set below */ + aiocbp.aio_sigevent.sigev_notify = SIGEV_NONE; + aiocbp.aio_sigevent.sigev_signo = 0; +#ifdef sgi + aiocbp.aio_sigevent.sigev_func = NULL; + aiocbp.aio_sigevent.sigev_value.sival_int = 0; +#elif defined(__linux__) && !defined(__UCLIBC__) + aiocbp.aio_sigevent.sigev_notify_function = NULL; + aiocbp.aio_sigevent.sigev_notify_attributes = 0; +#endif + aiolist[0] = &aiocbp; + + if ((ret = lseek(fd, 0, SEEK_CUR)) == -1) { + ret = 0; + /* If there is an error and it is not ESPIPE then kick out the error. + * If the fd is a fifo then we have to make sure that + * lio_random_methods() didn't select pwrite/pread; if it did then + * switch to write/read. + */ + if (errno == ESPIPE) { + if (method & LIO_IO_SYNCP) { + if (omethod & LIO_RANDOM) { + method &= ~LIO_IO_SYNCP; + method |= LIO_IO_SYNC; + if (Debug_level > 2) + printf + ("DEBUG %s/%d: random chosen method switched to %#o for fifo\n", + __FILE__, __LINE__, + method); + } else if (Debug_level) { + printf + ("DEBUG %s/%d: pread will fail when it reads from a fifo\n", + __FILE__, __LINE__); + } + } + /* else: let it ride */ + } else { + sprintf(Errormsg, + "%s/%d lseek(fd=%d,0,SEEK_CUR) failed, errno=%d %s", + __FILE__, __LINE__, fd, errno, strerror(errno)); + return -errno; + } + } +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + poffset = (off64_t) ret; +#endif + aiocbp.aio_offset = ret; + +#endif + + /* + * If the LIO_USE_SIGNAL bit is not set, only use the signal + * if the LIO_WAIT_SIGPAUSE or the LIO_WAIT_SIGACTIVE bits are set. + * Otherwise there is not necessarily a signal handler to trap + * the signal. + */ + if (sig && !(method & LIO_USE_SIGNAL) && !(method & LIO_WAIT_SIGTYPES)) { + + sig = 0; /* ignore signal parameter */ + } +#if defined(sgi) || (defined(__linux__)&& !defined(__UCLIBC__)) + if (sig && (method & LIO_WAIT_CBTYPES)) + sig = 0; /* ignore signal parameter */ +#endif + + /* + * only setup signal hander if sig was specified and + * a sig wait method was specified. + * Doing this will change the handler for this signal. The + * old signal handler will not be restored. + *** restoring the signal handler could be added *** + */ + + if (sig && (method & LIO_WAIT_SIGTYPES)) { +#ifdef CRAY + sigctl(SCTL_REG, sig, lio_async_signal_handler); +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + aiocbp.aio_sigevent.sigev_notify = SIGEV_SIGNAL; + aiocbp.aio_sigevent.sigev_signo = sig; + sigset(sig, lio_async_signal_handler); +#endif /* CRAY */ + } +#if defined(sgi) + else if (method & LIO_WAIT_CBTYPES) { + aiocbp.aio_sigevent.sigev_notify = SIGEV_CALLBACK; + aiocbp.aio_sigevent.sigev_func = lio_async_callback_handler; + /* sival_int just has to be something that I can use + * to identify the callback, and "size" happens to be handy... + */ + aiocbp.aio_sigevent.sigev_value.sival_int = size; + } +#endif +#if defined(__linux__) && !defined(__UCLIBC__) + else if (method & LIO_WAIT_CBTYPES) { + aiocbp.aio_sigevent.sigev_notify = SIGEV_THREAD; + aiocbp.aio_sigevent.sigev_notify_function = + lio_async_callback_handler; + /* sival_int just has to be something that I can use + * to identify the callback, and "size" happens to be handy... + */ + aiocbp.aio_sigevent.sigev_notify_attributes = + (void *)(uintptr_t) size; + } +#endif + + /* + * Determine the system call that will be called and produce + * the string of the system call and place it in Lio_SysCall. + * Also update the io_type char pointer to give brief description + * of system call. Execute the system call and check for + * system call failure. If sync i/o, return the number of + * bytes written/read. + */ + + if ((method & LIO_IO_SYNC) + || (method & (LIO_IO_TYPES | LIO_IO_ATYPES)) == 0) { + /* + * read(2) is used if LIO_IO_SYNC bit is set or not none + * of the LIO_IO_TYPES bits are set (default). + */ + + sprintf(Lio_SysCall, "read(%d, buf, %d)", fd, size); + io_type = "read"; + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + while (1) { + if (((ret = read(fd, buffer, size)) == -1) + && errno != EINTR && errno != EAGAIN) { + sprintf(Errormsg, + "%s/%d read(%d, buf, %d) ret:-1, errno=%d %s", + __FILE__, __LINE__, fd, size, errno, + strerror(errno)); + return -errno; + } + + if (ret == 0) + return 0; + if (ret != -1) { + if (ret != size) { + sprintf(Errormsg, + "%s/%d read(%d, buf, %d) returned=%d", + __FILE__, __LINE__, + fd, size, ret); + size -= ret; + buffer += ret; + } else { + if (Debug_level > 1) + printf + ("DEBUG %s/%d: read completed without error (ret %d)\n", + __FILE__, __LINE__, ret); + + return ret; + } + } + wait4sync_io(fd, 1); + } + + } + + else if (method & LIO_IO_ASYNC) { +#ifdef CRAY + sprintf(Lio_SysCall, + "reada(%d, buf, %d, &status, %d)", fd, size, sig); + io_type = "reada"; + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + sigoff(); + if ((ret = reada(fd, buffer, size, &status, sig)) == -1) { + sprintf(Errormsg, + "%s/%d reada(%d, buf, %d, &stat, %d) ret:-1, errno=%d %s", + __FILE__, __LINE__, + fd, size, sig, errno, strerror(errno)); + sigon(); + return -errno; + } +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + sprintf(Lio_SysCall, + "aio_read(fildes=%d, buf, nbytes=%d, signo=%d)", fd, + size, sig); + io_type = "aio_read"; + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + if (sig) + sighold(sig); + if ((ret = aio_read(&aiocbp)) == -1) { + sprintf(Errormsg, + "%s/%d aio_read(fildes=%d, buf, nbytes=%d, signo=%d) ret:-1, errno=%d %s", + __FILE__, __LINE__, + fd, size, sig, errno, strerror(errno)); + if (sig) + sigrelse(sig); + return -errno; + } +#endif + } + /* LIO_IO_ASYNC */ + else if (method & LIO_IO_SLISTIO) { +#ifdef CRAY + request.li_opcode = LO_READ; + request.li_fildes = fd; + request.li_buf = buffer; + request.li_nbyte = size; + request.li_status = &status; + request.li_signo = sig; + request.li_nstride = 0; + request.li_filstride = 0; + request.li_memstride = 0; + + listio_cmd = LC_WAIT; + io_type = "listio(2) sync read"; + + sprintf(Lio_SysCall, + "listio(LC_WAIT, &req, 1) LO_READ, fd:%d, nbyte:%d", + fd, size); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + sigoff(); + if (listio(listio_cmd, &request, 1) == -1) { + sprintf(Errormsg, + "%s/%d %s failed, fd:%d, nbyte:%d errno=%d %s", + __FILE__, __LINE__, Lio_SysCall, fd, size, + errno, strerror(errno)); + sigon(); + return -errno; + } + + if (Debug_level > 1) + printf("DEBUG %s/%d: %s did not return -1\n", + __FILE__, __LINE__, Lio_SysCall); + + ret = lio_check_asyncio(io_type, size, &status); + return ret; +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + aiocbp.aio_lio_opcode = LIO_READ; + listio_cmd = LIO_WAIT; + io_type = "lio_listio(3) sync read"; + + sprintf(Lio_SysCall, + "lio_listio(LIO_WAIT, aiolist, 1, NULL) LIO_READ, fd:%d, nbyte:%d", + fd, size); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + if (sig) + sighold(sig); + if (lio_listio(listio_cmd, aiolist, 1, NULL) == -1) { + sprintf(Errormsg, + "%s/%d %s failed, fd:%d, nbyte:%d errno=%d %s", + __FILE__, __LINE__, Lio_SysCall, fd, size, + errno, strerror(errno)); + if (sig) + sigrelse(sig); + return -errno; + } + + if (Debug_level > 1) + printf("DEBUG %s/%d: %s did not return -1\n", + __FILE__, __LINE__, Lio_SysCall); + + ret = lio_check_asyncio(io_type, size, &aiocbp, method); + return ret; +#endif + } + /* LIO_IO_SLISTIO */ + else if (method & LIO_IO_ALISTIO) { +#ifdef CRAY + request.li_opcode = LO_READ; + request.li_fildes = fd; + request.li_buf = buffer; + request.li_nbyte = size; + request.li_status = &status; + request.li_signo = sig; + request.li_nstride = 0; + request.li_filstride = 0; + request.li_memstride = 0; + + listio_cmd = LC_START; + io_type = "listio(2) async read"; + + sprintf(Lio_SysCall, + "listio(LC_START, &req, 1) LO_READ, fd:%d, nbyte:%d", + fd, size); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + sigoff(); + if (listio(listio_cmd, &request, 1) == -1) { + sprintf(Errormsg, + "%s/%d %s failed, fd:%d, nbyte:%d errno=%d %s", + __FILE__, __LINE__, Lio_SysCall, fd, size, + errno, strerror(errno)); + sigon(); + return -errno; + } +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + aiocbp.aio_lio_opcode = LIO_READ; + listio_cmd = LIO_NOWAIT; + io_type = "lio_listio(3) async read"; + + sprintf(Lio_SysCall, + "lio_listio(LIO_NOWAIT, aiolist, 1, NULL) LIO_READ, fd:%d, nbyte:%d", + fd, size); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + + if (sig) + sighold(sig); + if (lio_listio(listio_cmd, aiolist, 1, NULL) == -1) { + sprintf(Errormsg, + "%s/%d %s failed, fd:%d, nbyte:%d errno=%d %s", + __FILE__, __LINE__, Lio_SysCall, fd, size, + errno, strerror(errno)); + if (sig) + sigrelse(sig); + return -errno; + } +#endif + } + /* LIO_IO_ALISTIO */ +#ifndef CRAY + else if (method & LIO_IO_SYNCV) { + io_type = "readv(2)"; + + sprintf(Lio_SysCall, "readv(%d, &iov, 1) nbyte:%d", fd, size); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + if ((ret = readv(fd, &iov, 1)) == -1) { + sprintf(Errormsg, + "%s/%d readv(%d, iov, 1) nbyte:%d ret:-1, errno=%d %s", + __FILE__, __LINE__, fd, size, errno, + strerror(errno)); + return -errno; + } + + if (ret != size) { + sprintf(Errormsg, + "%s/%d readv(%d, iov, 1) nbyte:%d returned=%d", + __FILE__, __LINE__, fd, size, ret); + } else if (Debug_level > 1) + printf + ("DEBUG %s/%d: readv completed without error (ret %d)\n", + __FILE__, __LINE__, ret); + + return ret; + } /* LIO_IO_SYNCV */ +#endif + +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + else if (method & LIO_IO_SYNCP) { + io_type = "pread(2)"; + + sprintf(Lio_SysCall, + "pread(%d, buf, %d, %lld)", fd, size, + (long long)poffset); + + if (Debug_level) { + printf("DEBUG %s/%d: %s\n", __FILE__, __LINE__, + Lio_SysCall); + } + if ((ret = pread(fd, buffer, size, poffset)) == -1) { + sprintf(Errormsg, + "%s/%d pread(%d, buf, %d, %lld) ret:-1, errno=%d %s", + __FILE__, __LINE__, fd, size, + (long long)poffset, errno, strerror(errno)); + return -errno; + } + + if (ret != size) { + sprintf(Errormsg, + "%s/%d pread(%d, buf, %d, %lld) returned=%d", + __FILE__, __LINE__, + fd, size, (long long)poffset, ret); + } else if (Debug_level > 1) + printf + ("DEBUG %s/%d: pread completed without error (ret %d)\n", + __FILE__, __LINE__, ret); + + return ret; + } /* LIO_IO_SYNCP */ +#endif + + else { + printf("DEBUG %s/%d: No I/O method chosen\n", __FILE__, + __LINE__); + return -1; + } + + /* + * wait for async io to complete. + * Note: Sync io should have returned prior to getting here. + */ +#ifdef CRAY + ret = lio_wait4asyncio(method, fd, statptr); +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + ret = lio_wait4asyncio(method, fd, &aiocbp); +#endif + + /* + * If there was an error waiting for async i/o to complete, + * return the error value (errno) to the caller. + * Note: Errormsg should already have been updated. + */ + if (ret < 0) { + return ret; + } + + /* + * If i/o was not waited for (may not have been completed at this time), + * return the size that was requested. + */ + if (ret == 1) + return size; + + /* + * check that async io was successful. + * Note: if the there was an system call failure, -errno + * was returned and Errormsg should already have been updated. + * If amount i/o was different than size, Errormsg should already + * have been updated but the actual i/o size if returned. + */ + +#ifdef CRAY + ret = lio_check_asyncio(io_type, size, &status); +#endif +#if defined(sgi) || (defined(__linux__) && !defined(__UCLIBC__)) + ret = lio_check_asyncio(io_type, size, &aiocbp, method); +#endif + + return ret; +} /* end of lio_read_buffer */ + +#if !defined(__sun) && !defined(__hpux) && !defined(_AIX) +/*********************************************************************** + * This function will check that async io was successful. + * It can also be used to check sync listio since it uses the + * same method. + * + * Return Values + * If status.sw_error is set, -status.sw_error is returned. + * Otherwise sw_count's field value is returned. + * + * (rrl 04/96) + ***********************************************************************/ +#ifdef CRAY +int lio_check_asyncio(char *io_type, int size, struct iosw *status) +#elif defined(sgi) +int lio_check_asyncio(char *io_type, int size, aiocb_t * aiocbp, int method) +#elif defined(__linux__) && !defined(__UCLIBC__) +int lio_check_asyncio(char *io_type, int size, struct aiocb *aiocbp, int method) +{ + int ret; + +#ifdef CRAY + if (status->sw_error) { + sprintf(Errormsg, + "%s/%d %s, sw_error set = %d %s, sw_count = %d", + __FILE__, __LINE__, io_type, + status->sw_error, strerror(status->sw_error), + status->sw_count); + return -status->sw_error; + } else if (status->sw_count != size) { + sprintf(Errormsg, + "%s/%d %s, sw_count not as expected(%d), but actual:%d", + __FILE__, __LINE__, io_type, size, status->sw_count); + } else if (Debug_level > 1) { + printf + ("DEBUG %s/%d: %s completed without error (sw_error == 0, sw_count == %d)\n", + __FILE__, __LINE__, io_type, status->sw_count); + } + + return status->sw_count; + +#else + + int cnt = 1; + + /* The I/O may have been synchronous with signal completion. It doesn't + * make sense, but the combination could be generated. Release the + * completion signal here otherwise it'll hang around and bite us + * later. + */ + if (aiocbp->aio_sigevent.sigev_notify == SIGEV_SIGNAL) + sigrelse(aiocbp->aio_sigevent.sigev_signo); + + ret = aio_error(aiocbp); + + while (ret == EINPROGRESS) { + ret = aio_error(aiocbp); + ++cnt; + } + if (cnt > 1) { + sprintf(Errormsg, + "%s/%d %s, aio_error had to loop on EINPROGRESS, cnt=%d; random method %#o; sigev_notify=%s", + __FILE__, __LINE__, io_type, cnt, method, + (aiocbp->aio_sigevent.sigev_notify == + SIGEV_SIGNAL ? "signal" : aiocbp->aio_sigevent. + sigev_notify == SIGEV_NONE ? "none" : +#ifdef SIGEV_CALLBACK + aiocbp->aio_sigevent.sigev_notify == + SIGEV_CALLBACK ? "callback" : +#endif + aiocbp->aio_sigevent.sigev_notify == + SIGEV_THREAD ? "thread" : "unknown")); + return -ret; + } + + if (ret != 0) { + sprintf(Errormsg, + "%s/%d %s, aio_error = %d %s; random method %#o", + __FILE__, __LINE__, io_type, + ret, strerror(ret), method); + return -ret; + } + ret = aio_return(aiocbp); + if (ret != size) { + sprintf(Errormsg, + "%s/%d %s, aio_return not as expected(%d), but actual:%d", + __FILE__, __LINE__, io_type, size, ret); + +#ifdef BUG1_workaround + if (ret == 0) { + ret = size; + if (Debug_level > 1) { + printf + ("WARN %s/%d: %s completed with bug1_workaround (aio_error == 0, aio_return now == %d)\n", + __FILE__, __LINE__, io_type, ret); + } + } +#endif /* BUG1_workaround */ + + } else if (Debug_level > 1) { + printf + ("DEBUG %s/%d: %s completed without error (aio_error == 0, aio_return == %d)\n", + __FILE__, __LINE__, io_type, ret); + } + + return ret; + +#endif +} /* end of lio_check_asyncio */ +#endif + +/*********************************************************************** + * + * This function will wait for async io to complete. + * If multiple wait methods are specified, the order is predetermined + * to LIO_WAIT_RECALL, + * LIO_WAIT_ACTIVE, LIO_WAIT_SIGPAUSE, LIO_WAIT_SIGACTIVE, + * then LIO_WAIT_NONE. + * + * If no wait method was specified the default wait method is: recall(2) + * or aio_suspend(3), as appropriate. + * + * Return Values + * <0: errno of failed recall + * 0 : async io was completed + * 1 : async was not waited for, io may not have completed. + * + * (rrl 04/96) + ***********************************************************************/ +#ifdef CRAY +int lio_wait4asyncio(int method, int fd, struct iosw **statptr) +#elif defined(sgi) +int lio_wait4asyncio(int method, int fd, aiocb_t * aiocbp) +#elif defined(__linux__) && !defined(__UCLIBC__) +int lio_wait4asyncio(int method, int fd, struct aiocb *aiocbp) +{ + int cnt; +#ifdef sgi + int ret; + const aiocb_t *aioary[1]; +#endif +#if defined(__linux__)&& !defined(__UCLIBC__) + int ret; + const struct aiocb *aioary[1]; +#endif + + if ((method & LIO_WAIT_RECALL) +#if defined(sgi) || (defined(__linux__)&& !defined(__UCLIBC__)) + || (method & LIO_WAIT_CBSUSPEND) + || (method & LIO_WAIT_SIGSUSPEND) +#endif + || ((method & LIO_WAIT_TYPES) == 0)) { + /* + * If method has LIO_WAIT_RECALL bit set or method does + * not have any wait method bits set (default), use recall/aio_suspend. + */ +#ifdef CRAY + if (Debug_level > 2) + printf("DEBUG %s/%d: wait method : recall\n", __FILE__, + __LINE__); + sigon(); + if (recall(fd, 1, statptr)) { + sprintf(Errormsg, + "%s/%d recall(%d, 1, stat) failed, errno:%d %s", + __FILE__, __LINE__, fd, errno, strerror(errno)); + return -errno; + } +#else + if (Debug_level > 2) + printf + ("DEBUG %s/%d: wait method : aio_suspend, sigev_notify=%s\n", + __FILE__, __LINE__, + (aiocbp->aio_sigevent.sigev_notify == + SIGEV_SIGNAL ? "signal" : aiocbp->aio_sigevent. + sigev_notify == SIGEV_NONE ? "none" : +#ifdef SIGEV_CALLBACK + aiocbp->aio_sigevent.sigev_notify == + SIGEV_CALLBACK ? "callback" : +#endif + aiocbp->aio_sigevent.sigev_notify == + SIGEV_THREAD ? "thread" : "unknown")); + + aioary[0] = aiocbp; + ret = aio_suspend(aioary, 1, NULL); + if ((ret == -1) && (errno == EINTR)) { + if (aiocbp->aio_sigevent.sigev_notify == SIGEV_SIGNAL) { + if (Debug_level > 2) { + printf + ("DEBUG %s/%d: aio_suspend received EINTR, sigev_notify=SIGEV_SIGNAL -- ok\n", + __FILE__, __LINE__); + } + } else { + sprintf(Errormsg, + "%s/%d aio_suspend received EINTR, sigev_notify=%s, not ok\n", + __FILE__, __LINE__, + (aiocbp->aio_sigevent.sigev_notify == + SIGEV_SIGNAL ? "signal" : aiocbp-> + aio_sigevent.sigev_notify == + SIGEV_NONE ? "none" : +#ifdef SIGEV_CALLBACK + aiocbp->aio_sigevent.sigev_notify == + SIGEV_CALLBACK ? "callback" : +#endif + aiocbp->aio_sigevent.sigev_notify == + SIGEV_THREAD ? "thread" : "unknown")); + return -errno; + } + } else if (ret) { + sprintf(Errormsg, + "%s/%d aio_suspend(fildes=%d, aioary, 1, NULL) failed, errno:%d %s", + __FILE__, __LINE__, fd, errno, strerror(errno)); + return -errno; + } +#endif + + } else if (method & LIO_WAIT_ACTIVE) { + if (Debug_level > 2) + printf("DEBUG %s/%d: wait method : active\n", __FILE__, + __LINE__); +#ifdef CRAY + sigon(); + /* + * loop until sw_flag, sw_count or sw_error field elements + * change to non-zero. + */ + cnt = 0; + while ((*statptr)->sw_flag == 0 && + (*statptr)->sw_count == 0 && (*statptr)->sw_error == 0) { + cnt++; + } +#else + /* loop while aio_error() returns EINPROGRESS */ + cnt = 0; + while (1) { + ret = aio_error(aiocbp); + if (ret != EINPROGRESS) { + break; + } + ++cnt; + } + +#endif + if (Debug_level > 5 && cnt && (cnt % 50) == 0) + printf("DEBUG %s/%d: wait active cnt = %d\n", + __FILE__, __LINE__, cnt); + + } else if (method & LIO_WAIT_SIGPAUSE) { + if (Debug_level > 2) + printf("DEBUG %s/%d: wait method : sigpause\n", + __FILE__, __LINE__); +#ifdef sgi + /* note: don't do the sigon() for CRAY in this case. why? -- roehrich 6/11/97 */ + if (aiocbp->aio_sigevent.sigev_notify == SIGEV_SIGNAL) + sigrelse(aiocbp->aio_sigevent.sigev_signo); + else { + printf("DEBUG %s/%d: sigev_notify != SIGEV_SIGNAL\n", + __FILE__, __LINE__); + return -1; + } +#endif + pause(); + + } else if (method & LIO_WAIT_SIGACTIVE) { + if (Debug_level > 2) + printf("DEBUG %s/%d: wait method : sigactive\n", + __FILE__, __LINE__); +#ifdef CRAY + sigon(); +#else + if (aiocbp->aio_sigevent.sigev_notify == SIGEV_SIGNAL) + sigrelse(aiocbp->aio_sigevent.sigev_signo); + else { + printf("DEBUG %s/%d: sigev_notify != SIGEV_SIGNAL\n", + __FILE__, __LINE__); + return -1; + } +#endif + /* loop waiting for signal */ + while (Received_signal == Rec_signal) { +#ifdef CRAY + sigon(); +#else + sigrelse(aiocbp->aio_sigevent.sigev_signo); +#endif + } + + } else if (method & LIO_WAIT_NONE) { + if (Debug_level > 2) + printf("DEBUG %s/%d: wait method : none\n", __FILE__, + __LINE__); + /* It's broken because the aiocb/iosw is an automatic variable in + * lio_{read,write}_buffer, so when the function returns and the + * I/O completes there will be nowhere to write the I/O status. + * It doesn't cause a problem on unicos--probably because of some + * compiler quirk, or an accident. It causes POSIX async I/O + * to core dump some threads. spr/pv 705909. 6/27/97 roehrich + */ + sprintf(Errormsg, + "%s/%d LIO_WAIT_NONE was selected (this is broken)\n", + __FILE__, __LINE__); +#ifdef CRAY + sigon(); +#endif +/* return 1;*/ + return -1; + } else { + if (Debug_level > 2) + printf("DEBUG %s/%d: no wait method was chosen\n", + __FILE__, __LINE__); + return -1; + } + + return 0; + +} /* end of lio_wait4asyncio */ + +#endif /* ifndef linux */ +#endif + +#if UNIT_TEST +/*********************************************************************** + * The following code is provided as unit test. + * Just define add "-DUNIT_TEST=1" to the cc line. + * + * (rrl 04/96) + ***********************************************************************/ +struct unit_info_t { + int method; + int sig; + char *str; +} Unit_info[] = { + { + LIO_IO_SYNC, 0, "sync io"}, { + LIO_IO_SYNCV, 0, "sync readv/writev"}, { + LIO_IO_SYNCP, 0, "sync pread/pwrite"}, { + LIO_IO_ASYNC, 0, "async io, def wait"}, { + LIO_IO_SLISTIO, 0, "sync listio"}, { + LIO_IO_ALISTIO, 0, "async listio, def wait"}, { + LIO_IO_ASYNC | LIO_WAIT_ACTIVE, 0, "async active"}, { + LIO_IO_ASYNC | LIO_WAIT_RECALL, 0, "async recall/suspend"}, { + LIO_IO_ASYNC | LIO_WAIT_SIGPAUSE, SIGUSR1, "async sigpause"}, { + LIO_IO_ASYNC | LIO_WAIT_SIGACTIVE, SIGUSR1, "async sigactive"}, { + LIO_IO_ALISTIO | LIO_WAIT_ACTIVE, 0, "async listio active"}, { + LIO_IO_ALISTIO | LIO_WAIT_RECALL, 0, "async listio recall"}, { + LIO_IO_ALISTIO | LIO_WAIT_SIGACTIVE, SIGUSR1, "async listio sigactive"}, + { + LIO_IO_ALISTIO | LIO_WAIT_SIGPAUSE, SIGUSR1, "async listio sigpause"}, + { + LIO_IO_ASYNC, SIGUSR2, "async io, def wait, sigusr2"}, { +LIO_IO_ALISTIO, SIGUSR2, "async listio, def wait, sigusr2"},}; + +int main(argc, argv) +int argc; +char **argv; +{ + extern char *optarg; + extern int optind; + + int fd; + char *err; + char buffer[4096]; + int size = 4096; + int ret; + int ind; + int iter = 3; + int method; + int exit_status = 0; + int c; + int i; + char *symbols = NULL; + int die_on_err = 0; + + while ((c = getopt(argc, argv, "s:di:")) != -1) { + switch (c) { + case 's': + symbols = optarg; + break; + case 'd': + ++die_on_err; + break; + case 'i': + iter = atoi(optarg); + break; + } + } + + if ((fd = + open("unit_test_file", O_CREAT | O_RDWR | O_TRUNC, 0777)) == -1) { + perror + ("open(unit_test_file, O_CREAT|O_RDWR|O_TRUNC, 0777) failed"); + exit(1); + } + + Debug_level = 9; + + if (symbols != NULL) { + if ((method = lio_parse_io_arg2(symbols, &err)) == -1) { + printf + ("lio_parse_io_arg2(%s, &err) failed, bad token starting at %s\n", + symbols, err); + if (die_on_err) + exit(1); + } else + printf("lio_parse_io_arg2(%s, &err) returned %#o\n", + symbols, method); + + exit_status = 0; + for (ind = 0; ind < iter; ind++) { + memset(buffer, 'A', 4096); + if (lseek(fd, 0, 0) == -1) { + printf("lseek(fd,0,0), %d, failed, errno %d\n", + __LINE__, errno); + ++exit_status; + } + if ((ret = lio_write_buffer(fd, method, buffer, + size, SIGUSR1, &err, + 0)) != size) { + printf + ("lio_write_buffer returned -1, err = %s\n", + err); + } else + printf("lio_write_buffer returned %d\n", ret); + + memset(buffer, 'B', 4096); + if (lseek(fd, 0, 0) == -1) { + printf("lseek(fd,0,0), %d, failed, errno %d\n", + __LINE__, errno); + ++exit_status; + } + if ((ret = lio_read_buffer(fd, method, buffer, + size, SIGUSR2, &err, + 0)) != size) { + printf + ("lio_read_buffer returned -1, err = %s\n", + err); + } else + printf("lio_read_buffer returned %d\n", ret); + + for (i = 0; i < 4096; ++i) { + if (buffer[i] != 'A') { + printf(" buffer[%d] = %d\n", i, + buffer[i]); + ++exit_status; + break; + } + } + + if (exit_status) + exit(exit_status); + + } + + unlink("unit_test_file"); + exit(0); + } + + for (ind = 0; ind < sizeof(Unit_info) / sizeof(struct unit_info_t); + ind++) { + + printf("\n********* write %s ***************\n", + Unit_info[ind].str); + if (lseek(fd, 0, 0) == -1) { + printf("lseek(fd,0,0), %d, failed, errno %d\n", + __LINE__, errno); + ++exit_status; + } + + memset(buffer, 'A', 4096); + if ((ret = lio_write_buffer(fd, Unit_info[ind].method, buffer, + size, Unit_info[ind].sig, &err, + 0)) != size) { + printf + (">>>>> lio_write_buffer(fd,0%x,buffer,%d,%d,err,0) returned -1,\n err = %s\n", + Unit_info[ind].method, size, Unit_info[ind].sig, + err); + ++exit_status; + if (die_on_err) + exit(exit_status); + } else { + printf("lio_write_buffer returned %d\n", ret); + } + + printf("\n********* read %s ***************\n", + Unit_info[ind].str); + if (lseek(fd, 0, 0) == -1) { + printf("lseek(fd,0,0), %d, failed, errno %d\n", + __LINE__, errno); + ++exit_status; + } + memset(buffer, 'B', 4096); + if ((ret = lio_read_buffer(fd, Unit_info[ind].method, buffer, + size, Unit_info[ind].sig, &err, + 0)) != size) { + printf + (">>>>> lio_read_buffer(fd,0%x,buffer,%d,%d,err,0) returned -1,\n err = %s\n", + Unit_info[ind].method, size, Unit_info[ind].sig, + err); + ++exit_status; + if (die_on_err) + exit(exit_status); + } else { + printf("lio_read_buffer returned %d\n", ret); + } + + for (i = 0; i < 4096; ++i) { + if (buffer[i] != 'A') { + printf(" buffer[%d] = %d\n", i, buffer[i]); + ++exit_status; + if (die_on_err) + exit(exit_status); + break; + } + } + + fflush(stdout); + fflush(stderr); + sleep(1); + + } + + unlink("unit_test_file"); + + exit(exit_status); +} +#endif diff --git a/src/kernel/tests/lib/tst_af_alg.c b/src/kernel/tests/lib/tst_af_alg.c new file mode 100644 index 0000000..d3895a8 --- /dev/null +++ b/src/kernel/tests/lib/tst_af_alg.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2019 Google LLC + */ + +#include <errno.h> +#include <stdlib.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_af_alg.h" +#include "lapi/socket.h" + +int tst_alg_create(void) +{ + TEST(socket(AF_ALG, SOCK_SEQPACKET, 0)); + if (TST_RET >= 0) + return TST_RET; + if (TST_ERR == EAFNOSUPPORT) + tst_brk(TCONF, "kernel doesn't support AF_ALG"); + tst_brk(TBROK | TTERRNO, "unexpected error creating AF_ALG socket"); + return -1; +} + +void tst_alg_bind_addr(int algfd, const struct sockaddr_alg *addr) +{ + TEST(bind(algfd, (const struct sockaddr *)addr, sizeof(*addr))); + if (TST_RET == 0) + return; + if (TST_ERR == ENOENT) { + tst_brk(TCONF, "kernel doesn't support %s algorithm '%s'", + addr->salg_type, addr->salg_name); + } + tst_brk(TBROK | TTERRNO, + "unexpected error binding AF_ALG socket to %s algorithm '%s'", + addr->salg_type, addr->salg_name); +} + +static void init_sockaddr_alg(struct sockaddr_alg *addr, + const char *algtype, const char *algname) +{ + memset(addr, 0, sizeof(*addr)); + + addr->salg_family = AF_ALG; + + strncpy((char *)addr->salg_type, algtype, sizeof(addr->salg_type)); + if (addr->salg_type[sizeof(addr->salg_type) - 1] != '\0') + tst_brk(TBROK, "algorithm type too long: '%s'", algtype); + + strncpy((char *)addr->salg_name, algname, sizeof(addr->salg_name)); + if (addr->salg_name[sizeof(addr->salg_name) - 1] != '\0') + tst_brk(TBROK, "algorithm name too long: '%s'", algname); +} + +void tst_alg_bind(int algfd, const char *algtype, const char *algname) +{ + struct sockaddr_alg addr; + + init_sockaddr_alg(&addr, algtype, algname); + + tst_alg_bind_addr(algfd, &addr); +} + +bool tst_have_alg(const char *algtype, const char *algname) +{ + int algfd; + struct sockaddr_alg addr; + bool have_alg = true; + + algfd = tst_alg_create(); + + init_sockaddr_alg(&addr, algtype, algname); + + TEST(bind(algfd, (const struct sockaddr *)&addr, sizeof(addr))); + if (TST_RET != 0) { + if (TST_ERR != ENOENT) { + tst_brk(TBROK | TTERRNO, + "unexpected error binding AF_ALG socket to %s algorithm '%s'", + algtype, algname); + } + have_alg = false; + } + + close(algfd); + return have_alg; +} + +void tst_require_alg(const char *algtype, const char *algname) +{ + int algfd = tst_alg_create(); + + tst_alg_bind(algfd, algtype, algname); + + close(algfd); +} + +void tst_alg_setkey(int algfd, const uint8_t *key, unsigned int keylen) +{ + uint8_t *keybuf = NULL; + unsigned int i; + + if (key == NULL) { + /* generate a random key */ + keybuf = SAFE_MALLOC(keylen); + for (i = 0; i < keylen; i++) + keybuf[i] = rand(); + key = keybuf; + } + TEST(setsockopt(algfd, SOL_ALG, ALG_SET_KEY, key, keylen)); + if (TST_RET != 0) { + tst_brk(TBROK | TTERRNO, + "unexpected error setting key (len=%u)", keylen); + } + free(keybuf); +} + +int tst_alg_accept(int algfd) +{ + TEST(accept(algfd, NULL, NULL)); + if (TST_RET < 0) { + tst_brk(TBROK | TTERRNO, + "unexpected error accept()ing AF_ALG request socket"); + } + return TST_RET; +} + +int tst_alg_setup(const char *algtype, const char *algname, + const uint8_t *key, unsigned int keylen) +{ + int algfd = tst_alg_create(); + + tst_alg_bind(algfd, algtype, algname); + + if (keylen != 0) + tst_alg_setkey(algfd, key, keylen); + + return algfd; +} + +int tst_alg_setup_reqfd(const char *algtype, const char *algname, + const uint8_t *key, unsigned int keylen) +{ + int algfd = tst_alg_setup(algtype, algname, key, keylen); + int reqfd = tst_alg_accept(algfd); + + close(algfd); + return reqfd; +} + +void tst_alg_sendmsg(int reqfd, const void *data, size_t datalen, + const struct tst_alg_sendmsg_params *params) +{ + struct iovec iov = { + .iov_base = (void *)data, + .iov_len = datalen, + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_flags = params->msg_flags, + }; + size_t controllen; + uint8_t *control; + struct cmsghdr *cmsg; + struct af_alg_iv *alg_iv; + + if (params->encrypt && params->decrypt) + tst_brk(TBROK, "Both encrypt and decrypt are specified"); + + controllen = 0; + if (params->encrypt || params->decrypt) + controllen += CMSG_SPACE(sizeof(uint32_t)); + if (params->ivlen) + controllen += CMSG_SPACE(sizeof(struct af_alg_iv) + + params->ivlen); + if (params->assoclen) + controllen += CMSG_SPACE(sizeof(uint32_t)); + + control = SAFE_MALLOC(controllen); + memset(control, 0, controllen); + msg.msg_control = control; + msg.msg_controllen = controllen; + cmsg = CMSG_FIRSTHDR(&msg); + + if (params->encrypt || params->decrypt) { + cmsg->cmsg_level = SOL_ALG; + cmsg->cmsg_type = ALG_SET_OP; + cmsg->cmsg_len = CMSG_LEN(sizeof(uint32_t)); + *(uint32_t *)CMSG_DATA(cmsg) = + params->encrypt ? ALG_OP_ENCRYPT : ALG_OP_DECRYPT; + cmsg = CMSG_NXTHDR(&msg, cmsg); + } + if (params->ivlen) { + cmsg->cmsg_level = SOL_ALG; + cmsg->cmsg_type = ALG_SET_IV; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct af_alg_iv) + + params->ivlen); + alg_iv = (struct af_alg_iv *)CMSG_DATA(cmsg); + alg_iv->ivlen = params->ivlen; + memcpy(alg_iv->iv, params->iv, params->ivlen); + cmsg = CMSG_NXTHDR(&msg, cmsg); + } + if (params->assoclen) { + cmsg->cmsg_level = SOL_ALG; + cmsg->cmsg_type = ALG_SET_AEAD_ASSOCLEN; + cmsg->cmsg_len = CMSG_LEN(sizeof(uint32_t)); + *(uint32_t *)CMSG_DATA(cmsg) = params->assoclen; + cmsg = CMSG_NXTHDR(&msg, cmsg); + } + + SAFE_SENDMSG(datalen, reqfd, &msg, 0); +} diff --git a/src/kernel/tests/lib/tst_ansi_color.c b/src/kernel/tests/lib/tst_ansi_color.c new file mode 100644 index 0000000..1c29268 --- /dev/null +++ b/src/kernel/tests/lib/tst_ansi_color.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017 Petr Vorel <pvorel@suse.cz> + */ + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#include "tst_res_flags.h" +#include "tst_ansi_color.h" + +char* tst_ttype2color(int ttype) +{ + switch (TTYPE_RESULT(ttype)) { + case TPASS: + return ANSI_COLOR_GREEN; + break; + case TFAIL: + return ANSI_COLOR_RED; + break; + case TBROK: + return ANSI_COLOR_RED; + break; + case TCONF: + return ANSI_COLOR_YELLOW; + break; + case TWARN: + return ANSI_COLOR_MAGENTA; + break; + case TINFO: + return ANSI_COLOR_BLUE; + break; + default: + return ""; + } +} + +int tst_color_enabled(int fd) +{ + static int color; + + if (color) + return color - 1; + + char *env = getenv("LTP_COLORIZE_OUTPUT"); + + if (env) { + if (!strcmp(env, "n") || !strcmp(env, "0")) + color = 1; + + if (!strcmp(env, "y") || !strcmp(env, "1")) + color = 2; + + return color - 1; + } + + if (isatty(fd) == 0) + color = 1; + else + color = 2; + + return color - 1; +} diff --git a/src/kernel/tests/lib/tst_assert.c b/src/kernel/tests/lib/tst_assert.c new file mode 100644 index 0000000..9b8ebc1 --- /dev/null +++ b/src/kernel/tests/lib/tst_assert.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020 FUJITSU LIMITED. All rights reserved. + * Author: Yang Xu <xuyang2018.jy@cn.fujitsu.com> + * Copyright (c) 2020 Cyril Hrubis <chrubis@suse.cz> + */ +#include <stdio.h> +#define TST_NO_DEFAULT_MAIN +#include "tst_assert.h" +#include "tst_test.h" + +void tst_assert_int(const char *file, const int lineno, const char *path, int val) +{ + int sys_val; + + safe_file_scanf(file, lineno, NULL, path, "%d", &sys_val); + + if (val == sys_val) { + tst_res_(file, lineno, TPASS, "%s = %d", path, val); + return; + } + + tst_res_(file, lineno, TFAIL, "%s != %d got %d", path, val, sys_val); +} + +void tst_assert_ulong(const char *file, const int lineno, const char *path, unsigned long val) +{ + unsigned long sys_val; + + safe_file_scanf(file, lineno, NULL, path, "%lu", &sys_val); + + if (val == sys_val) { + tst_res_(file, lineno, TPASS, "%s = %lu", path, val); + return; + } + + tst_res_(file, lineno, TFAIL, "%s != %lu got %lu", path, val, sys_val); +} + +void tst_assert_file_int(const char *file, const int lineno, const char *path, const char *prefix, int val) +{ + int sys_val; + char fmt[1024]; + + snprintf(fmt, sizeof(fmt), "%s%%d", prefix); + file_lines_scanf(file, lineno, NULL, 1, path, fmt, &sys_val); + + if (val == sys_val) { + tst_res_(file, lineno, TPASS, "%s %s = %d", path, prefix, sys_val); + return; + } + + tst_res_(file, lineno, TFAIL, "%s %s != %d got %d", path, prefix, val, sys_val); +} + +void tst_assert_str(const char *file, const int lineno, const char *path, const char *val) +{ + char sys_val[1024]; + + safe_file_scanf(file, lineno, NULL, path, "%1024s", sys_val); + if (!strcmp(val, sys_val)) { + tst_res_(file, lineno, TPASS, "%s = '%s'", path, val); + return; + } + + tst_res_(file, lineno, TFAIL, "%s != '%s' got '%s'", path, val, sys_val); +} + +void tst_assert_file_str(const char *file, const int lineno, const char *path, const char *prefix, const char *val) +{ + char sys_val[1024]; + char fmt[2048]; + + snprintf(fmt, sizeof(fmt), "%s: %%1024s", prefix); + file_lines_scanf(file, lineno, NULL, 1, path, fmt, sys_val); + + if (!strcmp(val, sys_val)) { + tst_res_(file, lineno, TPASS, "%s %s = '%s'", path, prefix, sys_val); + return; + } + + tst_res_(file, lineno, TFAIL, "%s %s != '%s' got '%s'", path, prefix, val, sys_val); +} diff --git a/src/kernel/tests/lib/tst_buffers.c b/src/kernel/tests/lib/tst_buffers.c new file mode 100644 index 0000000..b8b597a --- /dev/null +++ b/src/kernel/tests/lib/tst_buffers.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2019 Cyril Hrubis <chrubis@suse.cz> + */ + +#include <sys/mman.h> +#include <stdlib.h> +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" + +struct map { + void *addr; + size_t size; + size_t buf_shift; + struct map *next; +}; + +static struct map *maps; + +static void setup_canary(struct map *map) +{ + size_t i; + char *buf = map->addr; + + for (i = 0; i < map->buf_shift/2; i++) { + char c = random(); + buf[map->buf_shift - i - 1] = c; + buf[i] = c; + } +} + +static void check_canary(struct map *map) +{ + size_t i; + char *buf = map->addr; + + for (i = 0; i < map->buf_shift/2; i++) { + if (buf[map->buf_shift - i - 1] != buf[i]) { + tst_res(TWARN, + "pid %i: buffer modified address %p[%zi]", + getpid(), (char*)map->addr + map->buf_shift, -i-1); + } + } +} + +void *tst_alloc(size_t size) +{ + size_t page_size = getpagesize(); + unsigned int pages = (size / page_size) + !!(size % page_size) + 1; + void *ret; + struct map *map = SAFE_MALLOC(sizeof(struct map)); + static int print_msg = 1; + + if (print_msg) { + tst_res(TINFO, "Test is using guarded buffers"); + print_msg = 0; + } + + ret = SAFE_MMAP(NULL, page_size * pages, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + + mprotect(ret + (pages-1) * page_size, page_size, PROT_NONE); + + map->addr = ret; + map->size = pages * page_size; + map->next = maps; + maps = map; + + if (size % page_size) + map->buf_shift = page_size - (size % page_size); + else + map->buf_shift = 0; + + setup_canary(map); + + return ret + map->buf_shift; +} + +static int count_iovec(int *sizes) +{ + int ret = 0; + + while (sizes[ret++] != -1); + + return ret - 1; +} + +struct iovec *tst_iovec_alloc(int sizes[]) +{ + int i, cnt = count_iovec(sizes); + struct iovec *iovec; + + if (cnt <= 0) + return NULL; + + iovec = tst_alloc(sizeof(struct iovec) * cnt); + + for (i = 0; i < cnt; i++) { + if (sizes[i]) { + iovec[i].iov_base = tst_alloc(sizes[i]); + iovec[i].iov_len = sizes[i]; + } else { + iovec[i].iov_base = NULL; + iovec[i].iov_base = 0; + } + } + + return iovec; +} + +void tst_buffers_alloc(struct tst_buffers bufs[]) +{ + unsigned int i; + + for (i = 0; bufs[i].ptr; i++) { + if (bufs[i].size) + *((void**)bufs[i].ptr) = tst_alloc(bufs[i].size); + else + *((void**)bufs[i].ptr) = tst_iovec_alloc(bufs[i].iov_sizes); + } +} + +char *tst_strdup(const char *str) +{ + size_t len = strlen(str); + char *ret = tst_alloc(len + 1); + return strcpy(ret, str); +} + +void tst_free_all(void) +{ + struct map *i = maps; + + while (i) { + struct map *j = i; + check_canary(i); + SAFE_MUNMAP(i->addr, i->size); + i = i->next; + free(j); + } + + maps = NULL; +} diff --git a/src/kernel/tests/lib/tst_capability.c b/src/kernel/tests/lib/tst_capability.c new file mode 100644 index 0000000..1fa0e49 --- /dev/null +++ b/src/kernel/tests/lib/tst_capability.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2019 Richard Palethorpe <rpalethorpe@suse.com> + */ + +#include <string.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_capability.h" + +#include "lapi/syscalls.h" + +int tst_capget(struct tst_cap_user_header *hdr, + struct tst_cap_user_data *data) +{ + return tst_syscall(__NR_capget, hdr, data); +} + +int tst_capset(struct tst_cap_user_header *hdr, + const struct tst_cap_user_data *data) +{ + return tst_syscall(__NR_capset, hdr, data); +} + +static void do_cap_drop(uint32_t *set, uint32_t mask, const struct tst_cap *cap) +{ + if (*set & mask) { + tst_res(TINFO, "Dropping %s(%d)", cap->name, cap->id); + *set &= ~mask; + } +} + +static void do_cap_req(uint32_t *permitted, uint32_t *effective, uint32_t mask, + const struct tst_cap *cap) +{ + if (!(*permitted & mask)) + tst_brk(TCONF, "Need %s(%d)", cap->name, cap->id); + + if (!(*effective & mask)) { + tst_res(TINFO, "Permitting %s(%d)", cap->name, cap->id); + *effective |= mask; + } +} + +void tst_cap_action(struct tst_cap *cap) +{ + struct tst_cap_user_header hdr = { + .version = 0x20080522, + .pid = tst_syscall(__NR_gettid), + }; + struct tst_cap_user_data cur[2] = { {0} }; + struct tst_cap_user_data new[2] = { {0} }; + uint32_t act = cap->action; + uint32_t *pE = &new[CAP_TO_INDEX(cap->id)].effective; + uint32_t *pP = &new[CAP_TO_INDEX(cap->id)].permitted; + uint32_t mask = CAP_TO_MASK(cap->id); + + if (tst_capget(&hdr, cur)) + tst_brk(TBROK | TTERRNO, "tst_capget()"); + + memcpy(new, cur, sizeof(new)); + + switch (act) { + case TST_CAP_DROP: + do_cap_drop(pE, mask, cap); + break; + case TST_CAP_REQ: + do_cap_req(pP, pE, mask, cap); + break; + default: + tst_brk(TBROK, "Unrecognised action %d", cap->action); + } + + if (!memcmp(cur, new, sizeof(new))) + return; + + if (tst_capset(&hdr, new)) + tst_brk(TBROK | TERRNO, "tst_capset(%s)", cap->name); +} + +void tst_cap_setup(struct tst_cap *caps, unsigned int action_mask) +{ + struct tst_cap *cap; + + for (cap = caps; cap->action; cap++) { + if (cap->action & action_mask) + tst_cap_action(cap); + } +} diff --git a/src/kernel/tests/lib/tst_cgroup.c b/src/kernel/tests/lib/tst_cgroup.c new file mode 100644 index 0000000..ba413d8 --- /dev/null +++ b/src/kernel/tests/lib/tst_cgroup.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020 Red Hat, Inc. + * Copyright (c) 2020 Li Wang <liwang@redhat.com> + */ + +#define TST_NO_DEFAULT_MAIN + +#include <stdio.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <fcntl.h> +#include <unistd.h> + +#include "tst_test.h" +#include "tst_safe_macros.h" +#include "tst_safe_stdio.h" +#include "tst_cgroup.h" +#include "tst_device.h" + +static enum tst_cgroup_ver tst_cg_ver; +static int clone_children; + +static int tst_cgroup_check(const char *cgroup) +{ + char line[PATH_MAX]; + FILE *file; + int cg_check = 0; + + file = SAFE_FOPEN("/proc/filesystems", "r"); + while (fgets(line, sizeof(line), file)) { + if (strstr(line, cgroup) != NULL) { + cg_check = 1; + break; + } + } + SAFE_FCLOSE(file); + + return cg_check; +} + +enum tst_cgroup_ver tst_cgroup_version(void) +{ + enum tst_cgroup_ver cg_ver; + + if (tst_cgroup_check("cgroup2")) { + if (!tst_is_mounted("cgroup2") && tst_is_mounted("cgroup")) + cg_ver = TST_CGROUP_V1; + else + cg_ver = TST_CGROUP_V2; + + goto out; + } + + if (tst_cgroup_check("cgroup")) + cg_ver = TST_CGROUP_V1; + + if (!cg_ver) + tst_brk(TCONF, "Cgroup is not configured"); + +out: + return cg_ver; +} + +static void tst_cgroup1_mount(const char *name, const char *option, + const char *mnt_path, const char *new_path) +{ + char knob_path[PATH_MAX]; + if (tst_is_mounted(mnt_path)) + goto out; + + SAFE_MKDIR(mnt_path, 0777); + if (mount(name, mnt_path, "cgroup", 0, option) == -1) { + if (errno == ENODEV) { + if (rmdir(mnt_path) == -1) + tst_res(TWARN | TERRNO, "rmdir %s failed", mnt_path); + tst_brk(TCONF, + "Cgroup v1 is not configured in kernel"); + } + tst_brk(TBROK | TERRNO, "mount %s", mnt_path); + } + + /* + * We should assign one or more memory nodes to cpuset.mems and + * cpuset.cpus, otherwise, echo $$ > tasks gives “ENOSPC: no space + * left on device” when trying to use cpuset. + * + * Or, setting cgroup.clone_children to 1 can help in automatically + * inheriting memory and node setting from parent cgroup when a + * child cgroup is created. + */ + if (strcmp(option, "cpuset") == 0) { + sprintf(knob_path, "%s/cgroup.clone_children", mnt_path); + SAFE_FILE_SCANF(knob_path, "%d", &clone_children); + SAFE_FILE_PRINTF(knob_path, "%d", 1); + } +out: + SAFE_MKDIR(new_path, 0777); + + tst_res(TINFO, "Cgroup(%s) v1 mount at %s success", option, mnt_path); +} + +static void tst_cgroup2_mount(const char *mnt_path, const char *new_path) +{ + if (tst_is_mounted(mnt_path)) + goto out; + + SAFE_MKDIR(mnt_path, 0777); + if (mount("cgroup2", mnt_path, "cgroup2", 0, NULL) == -1) { + if (errno == ENODEV) { + if (rmdir(mnt_path) == -1) + tst_res(TWARN | TERRNO, "rmdir %s failed", mnt_path); + tst_brk(TCONF, + "Cgroup v2 is not configured in kernel"); + } + tst_brk(TBROK | TERRNO, "mount %s", mnt_path); + } + +out: + SAFE_MKDIR(new_path, 0777); + + tst_res(TINFO, "Cgroup v2 mount at %s success", mnt_path); +} + +static void tst_cgroupN_umount(const char *mnt_path, const char *new_path) +{ + FILE *fp; + int fd; + char s_new[BUFSIZ], s[BUFSIZ], value[BUFSIZ]; + char knob_path[PATH_MAX]; + + if (!tst_is_mounted(mnt_path)) + return; + + /* Move all processes in task(v2: cgroup.procs) to its parent node. */ + if (tst_cg_ver & TST_CGROUP_V1) + sprintf(s, "%s/tasks", mnt_path); + if (tst_cg_ver & TST_CGROUP_V2) + sprintf(s, "%s/cgroup.procs", mnt_path); + + fd = open(s, O_WRONLY); + if (fd == -1) + tst_res(TWARN | TERRNO, "open %s", s); + + if (tst_cg_ver & TST_CGROUP_V1) + snprintf(s_new, BUFSIZ, "%s/tasks", new_path); + if (tst_cg_ver & TST_CGROUP_V2) + snprintf(s_new, BUFSIZ, "%s/cgroup.procs", new_path); + + fp = fopen(s_new, "r"); + if (fp == NULL) + tst_res(TWARN | TERRNO, "fopen %s", s_new); + if ((fd != -1) && (fp != NULL)) { + while (fgets(value, BUFSIZ, fp) != NULL) + if (write(fd, value, strlen(value) - 1) + != (ssize_t)strlen(value) - 1) + tst_res(TWARN | TERRNO, "write %s", s); + } + if (tst_cg_ver & TST_CGROUP_V1) { + sprintf(knob_path, "%s/cpuset.cpus", mnt_path); + if (!access(knob_path, F_OK)) { + sprintf(knob_path, "%s/cgroup.clone_children", mnt_path); + SAFE_FILE_PRINTF(knob_path, "%d", clone_children); + } + } + if (fd != -1) + close(fd); + if (fp != NULL) + fclose(fp); + if (rmdir(new_path) == -1) + tst_res(TWARN | TERRNO, "rmdir %s", new_path); + if (umount(mnt_path) == -1) + tst_res(TWARN | TERRNO, "umount %s", mnt_path); + if (rmdir(mnt_path) == -1) + tst_res(TWARN | TERRNO, "rmdir %s", mnt_path); + + if (tst_cg_ver & TST_CGROUP_V1) + tst_res(TINFO, "Cgroup v1 unmount success"); + if (tst_cg_ver & TST_CGROUP_V2) + tst_res(TINFO, "Cgroup v2 unmount success"); +} + +struct tst_cgroup_path { + char *mnt_path; + char *new_path; + struct tst_cgroup_path *next; +}; + +static struct tst_cgroup_path *tst_cgroup_paths; + +static void tst_cgroup_set_path(const char *cgroup_dir) +{ + char cgroup_new_dir[PATH_MAX]; + struct tst_cgroup_path *tst_cgroup_path, *a; + + if (!cgroup_dir) + tst_brk(TBROK, "Invalid cgroup dir, plese check cgroup_dir"); + + sprintf(cgroup_new_dir, "%s/ltp_%d", cgroup_dir, rand()); + + /* To store cgroup path in the 'path' list */ + tst_cgroup_path = SAFE_MALLOC(sizeof(struct tst_cgroup_path)); + tst_cgroup_path->mnt_path = SAFE_MALLOC(strlen(cgroup_dir) + 1); + tst_cgroup_path->new_path = SAFE_MALLOC(strlen(cgroup_new_dir) + 1); + tst_cgroup_path->next = NULL; + + if (!tst_cgroup_paths) { + tst_cgroup_paths = tst_cgroup_path; + } else { + a = tst_cgroup_paths; + do { + if (!a->next) { + a->next = tst_cgroup_path; + break; + } + a = a->next; + } while (a); + } + + sprintf(tst_cgroup_path->mnt_path, "%s", cgroup_dir); + sprintf(tst_cgroup_path->new_path, "%s", cgroup_new_dir); +} + +static char *tst_cgroup_get_path(const char *cgroup_dir) +{ + struct tst_cgroup_path *a; + + if (!tst_cgroup_paths) + return NULL; + + a = tst_cgroup_paths; + + while (strcmp(a->mnt_path, cgroup_dir) != 0){ + if (!a->next) { + tst_res(TINFO, "%s is not found", cgroup_dir); + return NULL; + } + a = a->next; + }; + + return a->new_path; +} + +static void tst_cgroup_del_path(const char *cgroup_dir) +{ + struct tst_cgroup_path *a, *b; + + if (!tst_cgroup_paths) + return; + + a = b = tst_cgroup_paths; + + while (strcmp(b->mnt_path, cgroup_dir) != 0) { + if (!b->next) { + tst_res(TINFO, "%s is not found", cgroup_dir); + return; + } + a = b; + b = b->next; + }; + + if (b == tst_cgroup_paths) + tst_cgroup_paths = b->next; + else + a->next = b->next; + + free(b->mnt_path); + free(b->new_path); + free(b); +} + +void tst_cgroup_mount(enum tst_cgroup_ctrl ctrl, const char *cgroup_dir) +{ + char *cgroup_new_dir; + char knob_path[PATH_MAX]; + + tst_cg_ver = tst_cgroup_version(); + + tst_cgroup_set_path(cgroup_dir); + cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); + + if (tst_cg_ver & TST_CGROUP_V1) { + switch(ctrl) { + case TST_CGROUP_MEMCG: + tst_cgroup1_mount("memcg", "memory", cgroup_dir, cgroup_new_dir); + break; + case TST_CGROUP_CPUSET: + tst_cgroup1_mount("cpusetcg", "cpuset", cgroup_dir, cgroup_new_dir); + break; + default: + tst_brk(TBROK, "Invalid cgroup controller: %d", ctrl); + } + } + + if (tst_cg_ver & TST_CGROUP_V2) { + tst_cgroup2_mount(cgroup_dir, cgroup_new_dir); + + switch(ctrl) { + case TST_CGROUP_MEMCG: + sprintf(knob_path, "%s/cgroup.subtree_control", cgroup_dir); + SAFE_FILE_PRINTF(knob_path, "%s", "+memory"); + break; + case TST_CGROUP_CPUSET: + tst_brk(TCONF, "Cgroup v2 hasn't achieve cpuset subsystem"); + break; + default: + tst_brk(TBROK, "Invalid cgroup controller: %d", ctrl); + } + } +} + +void tst_cgroup_umount(const char *cgroup_dir) +{ + char *cgroup_new_dir; + + cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); + tst_cgroupN_umount(cgroup_dir, cgroup_new_dir); + tst_cgroup_del_path(cgroup_dir); +} + +void tst_cgroup_set_knob(const char *cgroup_dir, const char *knob, long value) +{ + char *cgroup_new_dir; + char knob_path[PATH_MAX]; + + cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); + sprintf(knob_path, "%s/%s", cgroup_new_dir, knob); + SAFE_FILE_PRINTF(knob_path, "%ld", value); +} + +void tst_cgroup_move_current(const char *cgroup_dir) +{ + if (tst_cg_ver & TST_CGROUP_V1) + tst_cgroup_set_knob(cgroup_dir, "tasks", getpid()); + + if (tst_cg_ver & TST_CGROUP_V2) + tst_cgroup_set_knob(cgroup_dir, "cgroup.procs", getpid()); +} + +void tst_cgroup_mem_set_maxbytes(const char *cgroup_dir, long memsz) +{ + if (tst_cg_ver & TST_CGROUP_V1) + tst_cgroup_set_knob(cgroup_dir, "memory.limit_in_bytes", memsz); + + if (tst_cg_ver & TST_CGROUP_V2) + tst_cgroup_set_knob(cgroup_dir, "memory.max", memsz); +} + +int tst_cgroup_mem_swapacct_enabled(const char *cgroup_dir) +{ + char *cgroup_new_dir; + char knob_path[PATH_MAX]; + + cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); + + if (tst_cg_ver & TST_CGROUP_V1) { + sprintf(knob_path, "%s/%s", + cgroup_new_dir, "/memory.memsw.limit_in_bytes"); + + if ((access(knob_path, F_OK) == -1)) { + if (errno == ENOENT) + tst_res(TCONF, "memcg swap accounting is disabled"); + else + tst_brk(TBROK | TERRNO, "failed to access %s", knob_path); + } else { + return 1; + } + } + + if (tst_cg_ver & TST_CGROUP_V2) { + sprintf(knob_path, "%s/%s", + cgroup_new_dir, "/memory.swap.max"); + + if ((access(knob_path, F_OK) == -1)) { + if (errno == ENOENT) + tst_res(TCONF, "memcg swap accounting is disabled"); + else + tst_brk(TBROK | TERRNO, "failed to access %s", knob_path); + } else { + return 1; + } + } + + return 0; +} + +void tst_cgroup_mem_set_maxswap(const char *cgroup_dir, long memsz) +{ + if (tst_cg_ver & TST_CGROUP_V1) + tst_cgroup_set_knob(cgroup_dir, "memory.memsw.limit_in_bytes", memsz); + + if (tst_cg_ver & TST_CGROUP_V2) + tst_cgroup_set_knob(cgroup_dir, "memory.swap.max", memsz); +} + +void tst_cgroup_cpuset_read_files(const char *cgroup_dir, const char *filename, char *retbuf) +{ + int fd; + char *cgroup_new_dir; + char knob_path[PATH_MAX]; + + cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); + + /* + * try either '/dev/cpuset/XXXX' or '/dev/cpuset/cpuset.XXXX' + * please see Documentation/cgroups/cpusets.txt from kernel src + * for details + */ + sprintf(knob_path, "%s/%s", cgroup_new_dir, filename); + fd = open(knob_path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) { + sprintf(knob_path, "%s/cpuset.%s", + cgroup_new_dir, filename); + fd = SAFE_OPEN(knob_path, O_RDONLY); + } else + tst_brk(TBROK | TERRNO, "open %s", knob_path); + } + + if (read(fd, retbuf, sizeof(retbuf)) < 0) + tst_brk(TBROK | TERRNO, "read %s", knob_path); + + close(fd); +} + +void tst_cgroup_cpuset_write_files(const char *cgroup_dir, const char *filename, const char *buf) +{ + int fd; + char *cgroup_new_dir; + char knob_path[PATH_MAX]; + + cgroup_new_dir = tst_cgroup_get_path(cgroup_dir); + + /* + * try either '/dev/cpuset/XXXX' or '/dev/cpuset/cpuset.XXXX' + * please see Documentation/cgroups/cpusets.txt from kernel src + * for details + */ + sprintf(knob_path, "%s/%s", cgroup_new_dir, filename); + fd = open(knob_path, O_WRONLY); + if (fd == -1) { + if (errno == ENOENT) { + sprintf(knob_path, "%s/cpuset.%s", cgroup_new_dir, filename); + fd = SAFE_OPEN(knob_path, O_WRONLY); + } else + tst_brk(TBROK | TERRNO, "open %s", knob_path); + } + + SAFE_WRITE(1, fd, buf, strlen(buf)); + + close(fd); +} diff --git a/src/kernel/tests/lib/tst_checkpoint.c b/src/kernel/tests/lib/tst_checkpoint.c new file mode 100644 index 0000000..5e5b114 --- /dev/null +++ b/src/kernel/tests/lib/tst_checkpoint.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2015 Cyril Hrubis <chrubis@suse.cz> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdint.h> +#include <limits.h> +#include <errno.h> +#include <sys/syscall.h> +#include <linux/futex.h> + +#include "test.h" +#include "safe_macros.h" +#include "lapi/futex.h" + +#define DEFAULT_MSEC_TIMEOUT 10000 + +futex_t *tst_futexes; +unsigned int tst_max_futexes; + +void tst_checkpoint_init(const char *file, const int lineno, + void (*cleanup_fn)(void)) +{ + int fd; + unsigned int page_size; + + if (tst_futexes) { + tst_brkm(TBROK, cleanup_fn, + "%s: %d checkpoints already initialized", + file, lineno); + return; + } + + /* + * The parent test process is responsible for creating the temporary + * directory and therefore must pass non-zero cleanup (to remove the + * directory if something went wrong). + * + * We cannot do this check unconditionally because if we need to init + * the checkpoint from a binary that was started by exec() the + * tst_tmpdir_created() will return false because the tmpdir was + * created by parent. In this case we expect the subprogram can call + * the init as a first function with NULL as cleanup function. + */ + if (cleanup_fn && !tst_tmpdir_created()) { + tst_brkm(TBROK, cleanup_fn, + "%s:%d You have to create test temporary directory " + "first (call tst_tmpdir())", file, lineno); + return; + } + + page_size = getpagesize(); + + fd = SAFE_OPEN(cleanup_fn, "checkpoint_futex_base_file", + O_RDWR | O_CREAT, 0666); + + SAFE_FTRUNCATE(cleanup_fn, fd, page_size); + + tst_futexes = SAFE_MMAP(cleanup_fn, NULL, page_size, + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + tst_max_futexes = page_size / sizeof(uint32_t); + + SAFE_CLOSE(cleanup_fn, fd); +} + +int tst_checkpoint_wait(unsigned int id, unsigned int msec_timeout) +{ + struct timespec timeout; + int ret; + + if (id >= tst_max_futexes) { + errno = EOVERFLOW; + return -1; + } + + timeout.tv_sec = msec_timeout/1000; + timeout.tv_nsec = (msec_timeout%1000) * 1000000; + + do { + ret = syscall(SYS_futex, &tst_futexes[id], FUTEX_WAIT, + tst_futexes[id], &timeout); + } while (ret == -1 && errno == EINTR); + + return ret; +} + +int tst_checkpoint_wake(unsigned int id, unsigned int nr_wake, + unsigned int msec_timeout) +{ + unsigned int msecs = 0, waked = 0; + + if (id >= tst_max_futexes) { + errno = EOVERFLOW; + return -1; + } + + for (;;) { + waked += syscall(SYS_futex, &tst_futexes[id], FUTEX_WAKE, + INT_MAX, NULL); + + if (waked == nr_wake) + break; + + usleep(1000); + msecs++; + + if (msecs >= msec_timeout) { + errno = ETIMEDOUT; + return -1; + } + } + + return 0; +} + +void tst_safe_checkpoint_wait(const char *file, const int lineno, + void (*cleanup_fn)(void), unsigned int id, + unsigned int msec_timeout) +{ + int ret; + + if (!msec_timeout) + msec_timeout = DEFAULT_MSEC_TIMEOUT; + + ret = tst_checkpoint_wait(id, msec_timeout); + + if (ret) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: tst_checkpoint_wait(%u, %i)", + file, lineno, id, msec_timeout); + } +} + +void tst_safe_checkpoint_wake(const char *file, const int lineno, + void (*cleanup_fn)(void), unsigned int id, + unsigned int nr_wake) +{ + int ret = tst_checkpoint_wake(id, nr_wake, DEFAULT_MSEC_TIMEOUT); + + if (ret) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "%s:%d: tst_checkpoint_wake(%u, %u, %i)", + file, lineno, id, nr_wake, DEFAULT_MSEC_TIMEOUT); + } +} diff --git a/src/kernel/tests/lib/tst_checksum.c b/src/kernel/tests/lib/tst_checksum.c new file mode 100644 index 0000000..903bf3d --- /dev/null +++ b/src/kernel/tests/lib/tst_checksum.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (c) 2018 Oracle and/or its affiliates. All Rights Reserved. */ + +#include "tst_checksum.h" + +static const uint32_t crc32c_table[] = { + 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, + 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, + 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, + 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, + 0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, + 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, + 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, + 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, + 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, + 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, + 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, + 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, + 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, + 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a, + 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, + 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, + 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, + 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, + 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, + 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, + 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, + 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, + 0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, + 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, + 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, + 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, + 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, + 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, + 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, + 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6, + 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, + 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829, + 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, + 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, + 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, + 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, + 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, + 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, + 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, + 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, + 0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, + 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, + 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, + 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, + 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, + 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, + 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, + 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, + 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, + 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f, + 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, + 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, + 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, + 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, + 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, + 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, + 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, + 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, + 0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, + 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, + 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, + 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, + 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, + 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351, +}; + +uint32_t tst_crc32c(uint8_t *buf, size_t buf_len) +{ + uint32_t crc = 0xffffffff; + + while (buf_len--) + crc = crc32c_table[(crc ^ (*buf++)) & 0xff] ^ (crc >> 8); + + return ~crc; +} diff --git a/src/kernel/tests/lib/tst_clocks.c b/src/kernel/tests/lib/tst_clocks.c new file mode 100644 index 0000000..cdcb9fc --- /dev/null +++ b/src/kernel/tests/lib/tst_clocks.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz> + */ + +#include <time.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_timer.h" +#include "tst_clocks.h" +#include "lapi/syscalls.h" +#include "lapi/posix_clocks.h" + +typedef int (*mysyscall)(clockid_t clk_id, void *ts); + +int syscall_supported_by_kernel(long sysnr) +{ + int ret; + + ret = syscall(sysnr, 0, NULL); + if (ret == -1 && errno == ENOSYS) + return 0; + + return 1; +} + +int tst_clock_getres(clockid_t clk_id, struct timespec *res) +{ + static struct tst_ts tts = { 0, }; + static mysyscall func; + int ret; + +#if (__NR_clock_getres_time64 != __LTP__NR_INVALID_SYSCALL) + if (!func && syscall_supported_by_kernel(__NR_clock_getres_time64)) { + func = sys_clock_getres64; + tts.type = TST_KERN_TIMESPEC; + } +#endif + + if (!func && syscall_supported_by_kernel(__NR_clock_getres)) { + func = sys_clock_getres; + tts.type = TST_KERN_OLD_TIMESPEC; + } + + if (!func) { + tst_res(TCONF, "clock_getres() not available"); + errno = ENOSYS; + return -1; + } + + ret = func(clk_id, tst_ts_get(&tts)); + res->tv_sec = tst_ts_get_sec(tts); + res->tv_nsec = tst_ts_get_nsec(tts); + return ret; +} + +int tst_clock_gettime(clockid_t clk_id, struct timespec *ts) +{ + static struct tst_ts tts = { 0, }; + static mysyscall func; + int ret; + +#if (__NR_clock_gettime64 != __LTP__NR_INVALID_SYSCALL) + if (!func && syscall_supported_by_kernel(__NR_clock_gettime64)) { + func = sys_clock_gettime64; + tts.type = TST_KERN_TIMESPEC; + } +#endif + + if (!func && syscall_supported_by_kernel(__NR_clock_gettime)) { + func = sys_clock_gettime; + tts.type = TST_KERN_OLD_TIMESPEC; + } + + if (!func) { + tst_res(TCONF, "clock_gettime() not available"); + errno = ENOSYS; + return -1; + } + + ret = func(clk_id, tst_ts_get(&tts)); + ts->tv_sec = tst_ts_get_sec(tts); + ts->tv_nsec = tst_ts_get_nsec(tts); + return ret; +} + +int tst_clock_settime(clockid_t clk_id, struct timespec *ts) +{ + static struct tst_ts tts = { 0, }; + static mysyscall func; + +#if (__NR_clock_settime64 != __LTP__NR_INVALID_SYSCALL) + if (!func && syscall_supported_by_kernel(__NR_clock_settime64)) { + func = sys_clock_settime64; + tts.type = TST_KERN_TIMESPEC; + } +#endif + + if (!func && syscall_supported_by_kernel(__NR_clock_settime)) { + func = sys_clock_settime; + tts.type = TST_KERN_OLD_TIMESPEC; + } + + if (!func) { + tst_res(TCONF, "clock_settime() not available"); + errno = ENOSYS; + return -1; + } + + tst_ts_set_sec(&tts, ts->tv_sec); + tst_ts_set_nsec(&tts, ts->tv_nsec); + return func(clk_id, tst_ts_get(&tts)); +} + +const char *tst_clock_name(clockid_t clk_id) +{ + switch (clk_id) { + case CLOCK_REALTIME: + return "CLOCK_REALTIME"; + case CLOCK_MONOTONIC: + return "CLOCK_MONOTONIC"; + case CLOCK_PROCESS_CPUTIME_ID: + return "CLOCK_PROCESS_CPUTIME_ID"; + case CLOCK_THREAD_CPUTIME_ID: + return "CLOCK_THREAD_CPUTIME_ID"; + case CLOCK_MONOTONIC_RAW: + return "CLOCK_MONOTONIC_RAW"; + case CLOCK_REALTIME_COARSE: + return "CLOCK_REALTIME_COARSE"; + case CLOCK_MONOTONIC_COARSE: + return "CLOCK_MONOTONIC_COARSE"; + case CLOCK_BOOTTIME: + return "CLOCK_BOOTTIME"; + case CLOCK_REALTIME_ALARM: + return "CLOCK_REALTIME_ALARM"; + case CLOCK_BOOTTIME_ALARM: + return "CLOCK_BOOTTIME_ALARM"; + case CLOCK_TAI: + return "CLOCK_TAI"; + default: + return "INVALID/UNKNOWN CLOCK"; + } +} diff --git a/src/kernel/tests/lib/tst_cmd.c b/src/kernel/tests/lib/tst_cmd.c new file mode 100644 index 0000000..7446249 --- /dev/null +++ b/src/kernel/tests/lib/tst_cmd.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. + * Copyright (c) 2020 Petr Vorel <pvorel@suse.cz> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Alexey Kodanev <alexey.kodanev@oracle.com> + */ + +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> +#include "test.h" +#include "tst_cmd.h" + +#define OPEN_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) +#define OPEN_FLAGS (O_WRONLY | O_APPEND | O_CREAT) + +int tst_cmd_fds_(void (cleanup_fn)(void), + const char *const argv[], + int stdout_fd, + int stderr_fd, + enum tst_cmd_flags flags) +{ + int rc; + + if (argv == NULL || argv[0] == NULL) { + tst_brkm(TBROK, cleanup_fn, + "argument list is empty at %s:%d", __FILE__, __LINE__); + return -1; + } + + /* + * The tst_sig() install poisoned signal handlers for all signals the + * test is not expected to get. + * + * So we temporarily disable the handler for sigchild we get after our + * child exits so that we don't have to disable it in each test that + * uses this interface. + */ + void *old_handler = signal(SIGCHLD, SIG_DFL); + + char path[PATH_MAX]; + + if (tst_get_path(argv[0], path, sizeof(path))) { + if (flags & TST_CMD_TCONF_ON_MISSING) + tst_brkm(TCONF, cleanup_fn, "Couldn't find '%s' in $PATH at %s:%d", argv[0], + __FILE__, __LINE__); + else + return 255; + } + + pid_t pid = vfork(); + if (pid == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, "vfork failed at %s:%d", + __FILE__, __LINE__); + return -1; + } + if (!pid) { + /* redirecting stdout and stderr if needed */ + if (stdout_fd != -1) { + close(STDOUT_FILENO); + dup2(stdout_fd, STDOUT_FILENO); + } + + if (stderr_fd != -1) { + close(STDERR_FILENO); + dup2(stderr_fd, STDERR_FILENO); + } + + execvp(argv[0], (char *const *)argv); + _exit(254); + } + + int ret = -1; + if (waitpid(pid, &ret, 0) != pid) { + tst_brkm(TBROK | TERRNO, cleanup_fn, "waitpid failed at %s:%d", + __FILE__, __LINE__); + return -1; + } + + signal(SIGCHLD, old_handler); + + if (!WIFEXITED(ret)) { + tst_brkm(TBROK, cleanup_fn, "failed to exec cmd '%s' at %s:%d", + argv[0], __FILE__, __LINE__); + return -1; + } + + rc = WEXITSTATUS(ret); + + if (!(flags & TST_CMD_PASS_RETVAL) && rc) { + tst_brkm(TBROK, cleanup_fn, + "'%s' exited with a non-zero code %d at %s:%d", + argv[0], rc, __FILE__, __LINE__); + return -1; + } + + return rc; +} + +int tst_cmd_(void (cleanup_fn)(void), + const char *const argv[], + const char *stdout_path, + const char *stderr_path, + enum tst_cmd_flags flags) +{ + int stdout_fd = -1; + int stderr_fd = -1; + int rc; + + if (stdout_path != NULL) { + stdout_fd = open(stdout_path, + OPEN_FLAGS, OPEN_MODE); + + if (stdout_fd == -1) + tst_resm(TWARN | TERRNO, + "open() on %s failed at %s:%d", + stdout_path, __FILE__, __LINE__); + } + + if (stderr_path != NULL) { + stderr_fd = open(stderr_path, + OPEN_FLAGS, OPEN_MODE); + + if (stderr_fd == -1) + tst_resm(TWARN | TERRNO, + "open() on %s failed at %s:%d", + stderr_path, __FILE__, __LINE__); + } + + rc = tst_cmd_fds(cleanup_fn, argv, stdout_fd, stderr_fd, flags); + + if ((stdout_fd != -1) && (close(stdout_fd) == -1)) + tst_resm(TWARN | TERRNO, + "close() on %s failed at %s:%d", + stdout_path, __FILE__, __LINE__); + + if ((stderr_fd != -1) && (close(stderr_fd) == -1)) + tst_resm(TWARN | TERRNO, + "close() on %s failed at %s:%d", + stderr_path, __FILE__, __LINE__); + + return rc; +} + +int tst_system(const char *command) +{ + int ret = 0; + + /* + *Temporarily disable SIGCHLD of user defined handler, so the + *system(3) function will not cause unexpected SIGCHLD signal + *callback function for test cases. + */ + void *old_handler = signal(SIGCHLD, SIG_DFL); + + ret = system(command); + + signal(SIGCHLD, old_handler); + return ret; +} diff --git a/src/kernel/tests/lib/tst_coredump.c b/src/kernel/tests/lib/tst_coredump.c new file mode 100644 index 0000000..83aa2c3 --- /dev/null +++ b/src/kernel/tests/lib/tst_coredump.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2019 Red Hat, Inc. + */ + +#define TST_NO_DEFAULT_MAIN + +#include <sys/time.h> +#include <sys/resource.h> + +#include "tst_test.h" +#include "tst_coredump.h" + +void tst_no_corefile(int verbose) +{ + struct rlimit new_r, old_r; + + SAFE_GETRLIMIT(RLIMIT_CORE, &old_r); + if (old_r.rlim_max >= 1 || geteuid() == 0) { + /* + * 1 is a special value, that disables core-to-pipe. + * At the same time it is small enough value for + * core-to-file, so it skips creating cores as well. + */ + new_r.rlim_cur = 1; + new_r.rlim_max = 1; + SAFE_SETRLIMIT(RLIMIT_CORE, &new_r); + + if (verbose) { + tst_res(TINFO, + "Avoid dumping corefile for process(pid=%d)", + getpid()); + } + } +} diff --git a/src/kernel/tests/lib/tst_cpu.c b/src/kernel/tests/lib/tst_cpu.c new file mode 100644 index 0000000..033155e --- /dev/null +++ b/src/kernel/tests/lib/tst_cpu.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2012 Fujitsu Ltd. + * Author: Wanlong Gao <gaowanlong@cn.fujitsu.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdlib.h> +#include <unistd.h> +#include "test.h" +#include "safe_macros.h" + +long tst_ncpus(void) +{ + long ncpus = -1; +#ifdef _SC_NPROCESSORS_ONLN + ncpus = SAFE_SYSCONF(NULL, _SC_NPROCESSORS_ONLN); +#else + tst_brkm(TBROK, NULL, "could not determine number of CPUs online"); +#endif + return ncpus; +} + +long tst_ncpus_conf(void) +{ + long ncpus_conf = -1; +#ifdef _SC_NPROCESSORS_CONF + ncpus_conf = SAFE_SYSCONF(NULL, _SC_NPROCESSORS_CONF); +#else + tst_brkm(TBROK, NULL, "could not determine number of CPUs configured"); +#endif + return ncpus_conf; +} + +#define KERNEL_MAX "/sys/devices/system/cpu/kernel_max" + +long tst_ncpus_max(void) +{ + long ncpus_max = -1; + struct stat buf; + + /* sched_getaffinity() and sched_setaffinity() cares about number of + * possible CPUs the OS or hardware can support, which can be larger + * than what sysconf(_SC_NPROCESSORS_CONF) currently provides + * (by enumarating /sys/devices/system/cpu/cpu* entries). + * + * Use /sys/devices/system/cpu/kernel_max, if available. This + * represents NR_CPUS-1, a compile time option which specifies + * "maximum number of CPUs which this kernel will support". + * This should provide cpu mask size large enough for any purposes. */ + if (stat(KERNEL_MAX, &buf) == 0) { + SAFE_FILE_SCANF(NULL, KERNEL_MAX, "%ld", &ncpus_max); + /* this is maximum CPU index allowed by the kernel + * configuration, so # of cpus allowed by config is +1 */ + ncpus_max++; + } else { + /* fall back to _SC_NPROCESSORS_CONF */ + ncpus_max = tst_ncpus_conf(); + } + return ncpus_max; +} diff --git a/src/kernel/tests/lib/tst_crypto.c b/src/kernel/tests/lib/tst_crypto.c new file mode 100644 index 0000000..685e087 --- /dev/null +++ b/src/kernel/tests/lib/tst_crypto.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2018 Richard Palethorpe <rpalethorpe@suse.com> + * Nicolai Stange <nstange@suse.de> + */ + +#include <errno.h> +#include <stdio.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_crypto.h" +#include "tst_netlink.h" + +void tst_crypto_open(struct tst_crypto_session *ses) +{ + TEST(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_CRYPTO)); + if (TST_RET < 0 && TST_ERR == EPROTONOSUPPORT) + tst_brk(TCONF | TTERRNO, "NETLINK_CRYPTO is probably disabled"); + + if (TST_RET < 0) { + tst_brk(TBROK | TTERRNO, + "socket(AF_NETLINK, SOCK_DGRAM, NETLINK_CRYPTO)"); + } + + ses->fd = TST_RET; + ses->seq_num = 0; +} + +void tst_crypto_close(struct tst_crypto_session *ses) +{ + SAFE_CLOSE(ses->fd); +} + +static int tst_crypto_recv_ack(struct tst_crypto_session *ses) +{ + uint32_t len; + char buf[BUFSIZ]; + struct nlmsghdr *nh; + + len = SAFE_NETLINK_RECV(ses->fd, buf, sizeof(buf)); + + for (nh = (struct nlmsghdr *) buf; + NLMSG_OK(nh, len); + nh = NLMSG_NEXT(nh, len)) { + if (nh->nlmsg_seq != ses->seq_num) { + tst_brk(TBROK, + "Message out of sequence; type=0%hx, seq_num=%u (not %u)", + nh->nlmsg_type, nh->nlmsg_seq, ses->seq_num); + } + + /* Acks use the error message type with error number set to + * zero. Ofcourse we could also receive an actual error. + */ + if (nh->nlmsg_type == NLMSG_ERROR) + return ((struct nlmsgerr *)NLMSG_DATA(nh))->error; + + tst_brk(TBROK, "Unexpected message type; type=0x%hx, seq_num=%u", + nh->nlmsg_type, nh->nlmsg_seq); + } + + tst_brk(TBROK, "Empty message from netlink socket?"); + + return ENODATA; +} + +int tst_crypto_add_alg(struct tst_crypto_session *ses, + const struct crypto_user_alg *alg) +{ + struct nlmsghdr nh = { + .nlmsg_len = sizeof(struct nlmsghdr) + sizeof(*alg), + .nlmsg_type = CRYPTO_MSG_NEWALG, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .nlmsg_seq = ++(ses->seq_num), + .nlmsg_pid = 0, + }; + + SAFE_NETLINK_SEND(ses->fd, &nh, alg); + + return tst_crypto_recv_ack(ses); +} + +int tst_crypto_del_alg(struct tst_crypto_session *ses, + const struct crypto_user_alg *alg) +{ + unsigned int i = 0; + struct nlmsghdr nh = { + .nlmsg_len = sizeof(struct nlmsghdr) + sizeof(*alg), + .nlmsg_type = CRYPTO_MSG_DELALG, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .nlmsg_pid = 0, + }; + + while (1) { + nh.nlmsg_seq = ++(ses->seq_num), + + SAFE_NETLINK_SEND(ses->fd, &nh, alg); + + TEST(tst_crypto_recv_ack(ses)); + if (TST_RET != -EBUSY || i >= ses->retries) + break; + + if (usleep(1) && errno != EINTR) + tst_brk(TBROK | TERRNO, "usleep(1)"); + + ++i; + } + + return TST_RET; +} diff --git a/src/kernel/tests/lib/tst_device.c b/src/kernel/tests/lib/tst_device.c new file mode 100644 index 0000000..0df8efe --- /dev/null +++ b/src/kernel/tests/lib/tst_device.c @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2014 Cyril Hrubis chrubis@suse.cz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <uapi_xloop.h> +#include <stdint.h> +#include <inttypes.h> +#include <sys/sysmacros.h> +#include "lapi/syscalls.h" +#include "test.h" +#include "safe_macros.h" + +#ifndef XLOOP_CTL_GET_FREE +# define XLOOP_CTL_GET_FREE 0x4C82 +#endif + +#define XLOOP_CONTROL_FILE "/dev/xloop-control" + +#define DEV_FILE "test_dev.img" +#define DEV_SIZE_MB 256u + +static char dev_path[1024]; +static int device_acquired; +static unsigned long prev_dev_sec_write; + +static const char *dev_variants[] = { + "/dev/xloop%i", + "/dev/xloop/%i", + "/dev/block/xloop%i" +}; + +static int set_dev_path(int dev, char *path, size_t path_len) +{ + unsigned int i; + struct stat st; + + for (i = 0; i < ARRAY_SIZE(dev_variants); i++) { + snprintf(path, path_len, dev_variants[i], dev); + + if (stat(path, &st) == 0 && S_ISBLK(st.st_mode)) + return 1; + } + + return 0; +} + +int tst_find_free_xloopdev(char *path, size_t path_len) +{ + int ctl_fd, dev_fd, rc, i; + struct xloop_info xloopinfo; + char buf[1024]; + + /* since Linux 3.1 */ + ctl_fd = open(XLOOP_CONTROL_FILE, O_RDWR); + + if (ctl_fd > 0) { + rc = ioctl(ctl_fd, XLOOP_CTL_GET_FREE); + close(ctl_fd); + if (rc >= 0) { + if (path) + set_dev_path(rc, path, path_len); + tst_resm(TINFO, "Found free device %d '%s'", + rc, path ?: ""); + return rc; + } + tst_resm(TINFO, "Couldn't find free xloop device"); + return -1; + } + + switch (errno) { + case ENOENT: + break; + case EACCES: + tst_resm(TINFO | TERRNO, + "Not allowed to open " XLOOP_CONTROL_FILE ". " + "Are you root?"); + break; + default: + tst_resm(TBROK | TERRNO, "Failed to open " XLOOP_CONTROL_FILE); + } + + /* + * Older way is to iterate over /dev/xloop%i and /dev/xloop/%i and try + * XLOOP_GET_STATUS ioctl() which fails for free xloop devices. + */ + for (i = 0; i < 256; i++) { + + if (!set_dev_path(i, buf, sizeof(buf))) + continue; + + dev_fd = open(buf, O_RDONLY); + + if (dev_fd < 0) + continue; + + if (ioctl(dev_fd, XLOOP_GET_STATUS, &xloopinfo) == 0) { + tst_resm(TINFO, "Device '%s' in use", buf); + } else { + if (errno != ENXIO) + continue; + tst_resm(TINFO, "Found free device '%s'", buf); + close(dev_fd); + if (path != NULL) { + strncpy(path, buf, path_len); + path[path_len-1] = '\0'; + } + return i; + } + + close(dev_fd); + } + + tst_resm(TINFO, "No free devices found"); + + return -1; +} + +int tst_attach_device(const char *dev, const char *file) +{ + int dev_fd, file_fd; + struct xloop_info xloopinfo; + + dev_fd = open(dev, O_RDWR); + if (dev_fd < 0) { + tst_resm(TWARN | TERRNO, "open('%s', O_RDWR) failed", dev); + return 1; + } + + file_fd = open(file, O_RDWR); + if (file_fd < 0) { + tst_resm(TWARN | TERRNO, "open('%s', O_RDWR) failed", file); + close(dev_fd); + return 1; + } + + if (ioctl(dev_fd, XLOOP_SET_FD, file_fd) < 0) { + close(dev_fd); + close(file_fd); + tst_resm(TWARN | TERRNO, "ioctl(%s, XLOOP_SET_FD, %s) failed", + dev, file); + return 1; + } + + /* Old mkfs.btrfs use XLOOP_GET_STATUS instead of backing_file to get + * associated filename, so we need to set up the device by calling + * XLOOP_SET_FD and XLOOP_SET_STATUS. + */ + memset(&xloopinfo, 0, sizeof(xloopinfo)); + strcpy(xloopinfo.xlo_name, file); + + if (ioctl(dev_fd, XLOOP_SET_STATUS, &xloopinfo)) { + close(dev_fd); + close(file_fd); + tst_resm(TWARN | TERRNO, + "ioctl(%s, XLOOP_SET_STATUS, %s) failed", dev, file); + return 1; + } + + close(dev_fd); + close(file_fd); + return 0; +} + +int tst_detach_device_by_fd(const char *dev, int dev_fd) +{ + int ret, i; + + /* keep trying to clear XLOOPDEV until we get ENXIO, a quick succession + * of attach/detach might not give udev enough time to complete */ + for (i = 0; i < 40; i++) { + ret = ioctl(dev_fd, XLOOP_CLR_FD, 0); + + if (ret && (errno == ENXIO)) + return 0; + + if (ret && (errno != EBUSY)) { + tst_resm(TWARN, + "ioctl(%s, XLOOP_CLR_FD, 0) unexpectedly failed with: %s", + dev, tst_strerrno(errno)); + return 1; + } + + usleep(50000); + } + + tst_resm(TWARN, + "ioctl(%s, XLOOP_CLR_FD, 0) no ENXIO for too long", dev); + return 1; +} + +int tst_detach_device(const char *dev) +{ + int dev_fd, ret; + + dev_fd = open(dev, O_RDONLY); + if (dev_fd < 0) { + tst_resm(TWARN | TERRNO, "open(%s) failed", dev); + return 1; + } + + ret = tst_detach_device_by_fd(dev, dev_fd); + close(dev_fd); + return ret; +} + +int tst_dev_sync(int fd) +{ + return syscall(__NR_syncfs, fd); +} + +const char *tst_acquire_xloop_device(unsigned int size, const char *filename) +{ + unsigned int acq_dev_size = MAX(size, DEV_SIZE_MB); + + if (tst_prealloc_file(filename, 1024 * 1024, acq_dev_size)) { + tst_resm(TWARN | TERRNO, "Failed to create %s", filename); + return NULL; + } + + if (tst_find_free_xloopdev(dev_path, sizeof(dev_path)) == -1) + return NULL; + + if (tst_attach_device(dev_path, filename)) + return NULL; + + return dev_path; +} + +const char *tst_acquire_device__(unsigned int size) +{ + int fd; + const char *dev; + struct stat st; + unsigned int acq_dev_size; + uint64_t ltp_dev_size; + + acq_dev_size = MAX(size, DEV_SIZE_MB); + + dev = getenv("LTP_DEV"); + + if (dev) { + tst_resm(TINFO, "Using test device LTP_DEV='%s'", dev); + + if (stat(dev, &st)) { + tst_resm(TWARN | TERRNO, "stat() failed"); + return NULL; + } + + if (!S_ISBLK(st.st_mode)) { + tst_resm(TWARN, "%s is not a block device", dev); + return NULL; + } + + fd = open(dev, O_RDONLY); + if (fd < 0) { + tst_resm(TWARN | TERRNO, + "open(%s, O_RDONLY) failed", dev); + return NULL; + } + + if (ioctl(fd, BLKGETSIZE64, <p_dev_size)) { + tst_resm(TWARN | TERRNO, + "ioctl(fd, BLKGETSIZE64, ...) failed"); + close(fd); + return NULL; + } + + if (close(fd)) { + tst_resm(TWARN | TERRNO, + "close(fd) failed"); + return NULL; + } + + ltp_dev_size = ltp_dev_size/1024/1024; + + if (acq_dev_size <= ltp_dev_size) + return dev; + + tst_resm(TINFO, "Skipping $LTP_DEV size %"PRIu64"MB, requested size %uMB", + ltp_dev_size, acq_dev_size); + } + + dev = tst_acquire_xloop_device(acq_dev_size, DEV_FILE); + + if (dev) + device_acquired = 1; + + return dev; +} + +const char *tst_acquire_device_(void (cleanup_fn)(void), unsigned int size) +{ + const char *device; + + if (device_acquired) { + tst_brkm(TBROK, cleanup_fn, "Device already acquired"); + return NULL; + } + + if (!tst_tmpdir_created()) { + tst_brkm(TBROK, cleanup_fn, + "Cannot acquire device without tmpdir() created"); + return NULL; + } + + device = tst_acquire_device__(size); + + if (!device) { + tst_brkm(TBROK, cleanup_fn, "Failed to acquire device"); + return NULL; + } + + return device; +} + +int tst_release_device(const char *dev) +{ + int ret; + + if (!device_acquired) + return 0; + + /* + * Loop device was created -> we need to detach it. + * + * The file image is deleted in tst_rmdir(); + */ + ret = tst_detach_device(dev); + + device_acquired = 0; + + return ret; +} + +int tst_clear_device(const char *dev) +{ + if (tst_fill_file(dev, 0, 1024, 512)) { + tst_resm(TWARN, "Failed to clear 512k block on %s", dev); + return 1; + } + + return 0; +} + +int tst_umount(const char *path) +{ + int err, ret, i; + + for (i = 0; i < 50; i++) { + ret = umount(path); + err = errno; + + if (!ret) + return 0; + + if (err != EBUSY) { + tst_resm(TWARN, "umount('%s') failed with %s", + path, tst_strerrno(err)); + errno = err; + return ret; + } + + tst_resm(TINFO, "umount('%s') failed with %s, try %2i...", + path, tst_strerrno(err), i+1); + + if (i == 0) { + tst_resm(TINFO, "Likely gvfsd-trash is probing newly " + "mounted fs, kill it to speed up tests."); + } + + usleep(100000); + } + + tst_resm(TWARN, "Failed to umount('%s') after 50 retries", path); + errno = err; + return -1; +} + +int tst_is_mounted(const char *path) +{ + char line[PATH_MAX]; + FILE *file; + int ret = 0; + + file = SAFE_FOPEN(NULL, "/proc/mounts", "r"); + + while (fgets(line, sizeof(line), file)) { + if (strstr(line, path) != NULL) { + ret = 1; + break; + } + } + + SAFE_FCLOSE(NULL, file); + + if (!ret) + tst_resm(TINFO, "No device is mounted at %s", path); + + return ret; +} + +int tst_is_mounted_at_tmpdir(const char *path) +{ + char cdir[PATH_MAX], mpath[PATH_MAX]; + int ret; + + if (!getcwd(cdir, PATH_MAX)) { + tst_resm(TWARN | TERRNO, "Failed to find current directory"); + return 0; + } + + ret = snprintf(mpath, PATH_MAX, "%s/%s", cdir, path); + if (ret < 0 || ret >= PATH_MAX) { + tst_resm(TWARN | TERRNO, + "snprintf() should have returned %d instead of %d", + PATH_MAX, ret); + return 0; + } + + return tst_is_mounted(mpath); +} + +int find_stat_file(const char *dev, char *path, size_t path_len) +{ + const char *devname = strrchr(dev, '/') + 1; + + snprintf(path, path_len, "/sys/block/%s/stat", devname); + + if (!access(path, F_OK)) + return 1; + + DIR *dir = SAFE_OPENDIR(NULL, "/sys/block/"); + struct dirent *ent; + + while ((ent = readdir(dir))) { + snprintf(path, path_len, "/sys/block/%s/%s/stat", ent->d_name, devname); + + if (!access(path, F_OK)) { + SAFE_CLOSEDIR(NULL, dir); + return 1; + } + } + + SAFE_CLOSEDIR(NULL, dir); + return 0; +} + +unsigned long tst_dev_bytes_written(const char *dev) +{ + unsigned long dev_sec_write = 0, dev_bytes_written, io_ticks = 0; + char dev_stat_path[1024]; + + if (!find_stat_file(dev, dev_stat_path, sizeof(dev_stat_path))) + tst_brkm(TCONF, NULL, "Test device stat file: %s not found", + dev_stat_path); + + SAFE_FILE_SCANF(NULL, dev_stat_path, + "%*s %*s %*s %*s %*s %*s %lu %*s %*s %lu", + &dev_sec_write, &io_ticks); + + if (!io_ticks) + tst_brkm(TCONF, NULL, "Test device stat file: %s broken", + dev_stat_path); + + dev_bytes_written = (dev_sec_write - prev_dev_sec_write) * 512; + + prev_dev_sec_write = dev_sec_write; + + return dev_bytes_written; +} + +void tst_find_backing_dev(const char *path, char *dev) +{ + char fmt[20]; + struct stat buf; + FILE *file; + char line[PATH_MAX]; + char *pre = NULL; + char *next = NULL; + + if (stat(path, &buf) < 0) + tst_brkm(TWARN | TERRNO, NULL, "stat() failed"); + + snprintf(fmt, sizeof(fmt), "%u:%u", major(buf.st_dev), minor(buf.st_dev)); + file = SAFE_FOPEN(NULL, "/proc/self/mountinfo", "r"); + + while (fgets(line, sizeof(line), file)) { + if (strstr(line, fmt) != NULL) { + pre = strstr(line, " - "); + pre = strtok_r(pre, " ", &next); + pre = strtok_r(NULL, " ", &next); + pre = strtok_r(NULL, " ", &next); + strcpy(dev, pre); + break; + } + } + + SAFE_FCLOSE(NULL, file); + + if (stat(dev, &buf) < 0) + tst_brkm(TWARN | TERRNO, NULL, "stat(%s) failed", dev); + + if (S_ISBLK(buf.st_mode) != 1) + tst_brkm(TCONF, NULL, "dev(%s) isn't a block dev", dev); +} diff --git a/src/kernel/tests/lib/tst_dir_is_empty.c b/src/kernel/tests/lib/tst_dir_is_empty.c new file mode 100644 index 0000000..43764ee --- /dev/null +++ b/src/kernel/tests/lib/tst_dir_is_empty.c @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 Oracle and/or its affiliates. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Alexey Kodanev <alexey.kodanev@oracle.com> + * + */ + +#include <string.h> +#include <sys/types.h> +#include <dirent.h> + +#include "test.h" +#include "safe_macros.h" + +int tst_dir_is_empty_(void (cleanup_fn)(void), const char *name, int verbose) +{ + struct dirent *entry; + DIR *dir = SAFE_OPENDIR(cleanup_fn, name); + int ret = 1; + + while ((entry = SAFE_READDIR(cleanup_fn, dir)) != NULL) { + const char *file = entry->d_name; + + if (!strcmp(file, "..") || !strcmp(file, ".")) + continue; + + if (verbose) + tst_resm(TINFO, "found a file: %s", file); + ret = 0; + break; + } + + SAFE_CLOSEDIR(cleanup_fn, dir); + + return ret; +} diff --git a/src/kernel/tests/lib/tst_fill_file.c b/src/kernel/tests/lib/tst_fill_file.c new file mode 100644 index 0000000..8047200 --- /dev/null +++ b/src/kernel/tests/lib/tst_fill_file.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Stanislav Kholmanskikh <stanislav.kholmanskikh@oracle.com> + * + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include "lapi/fallocate.h" + +#include "test.h" + +int tst_fill_fd(int fd, char pattern, size_t bs, size_t bcount) +{ + size_t i; + char *buf; + + /* Filling a memory buffer with provided pattern */ + buf = malloc(bs); + if (buf == NULL) + return -1; + + for (i = 0; i < bs; i++) + buf[i] = pattern; + + /* Filling the file */ + for (i = 0; i < bcount; i++) { + if (write(fd, buf, bs) != (ssize_t)bs) { + free(buf); + return -1; + } + } + + free(buf); + + return 0; +} + +int tst_prealloc_size_fd(int fd, size_t bs, size_t bcount) +{ + int ret; + + errno = 0; + ret = fallocate(fd, 0, 0, bs * bcount); + + if (ret && errno == ENOSPC) + return ret; + + if (ret) + ret = tst_fill_fd(fd, 0, bs, bcount); + + return ret; +} + +int tst_fill_file(const char *path, char pattern, size_t bs, size_t bcount) +{ + int fd; + + fd = open(path, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR); + if (fd < 0) + return -1; + + if (tst_fill_fd(fd, pattern, bs, bcount)) { + close(fd); + unlink(path); + return -1; + } + + if (close(fd) < 0) { + unlink(path); + + return -1; + } + + return 0; +} + +int tst_prealloc_file(const char *path, size_t bs, size_t bcount) +{ + int fd; + + fd = open(path, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR); + if (fd < 0) + return -1; + + if (tst_prealloc_size_fd(fd, bs, bcount)) { + close(fd); + unlink(path); + return -1; + } + + if (close(fd) < 0) { + unlink(path); + return -1; + } + + return 0; +} diff --git a/src/kernel/tests/lib/tst_fill_fs.c b/src/kernel/tests/lib/tst_fill_fs.c new file mode 100644 index 0000000..121dd2f --- /dev/null +++ b/src/kernel/tests/lib/tst_fill_fs.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz> + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/statvfs.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_fs.h" + +void tst_fill_fs(const char *path, int verbose) +{ + int i = 0; + char file[PATH_MAX]; + char buf[4096]; + size_t len; + ssize_t ret; + int fd; + struct statvfs fi; + statvfs(path, &fi); + + for (;;) { + len = random() % (1024 * 102400); + + snprintf(file, sizeof(file), "%s/file%i", path, i++); + + if (verbose) + tst_res(TINFO, "Creating file %s size %zu", file, len); + + fd = open(file, O_WRONLY | O_CREAT, 0700); + if (fd == -1) { + if (errno != ENOSPC) + tst_brk(TBROK | TERRNO, "open()"); + + tst_res(TINFO | TERRNO, "open()"); + return; + } + + while (len) { + ret = write(fd, buf, MIN(len, sizeof(buf))); + + if (ret < 0) { + /* retry on ENOSPC to make sure filesystem is really full */ + if (errno == ENOSPC && len >= fi.f_bsize/2) { + SAFE_FSYNC(fd); + len /= 2; + continue; + } + + SAFE_CLOSE(fd); + + if (errno != ENOSPC) + tst_brk(TBROK | TERRNO, "write()"); + + tst_res(TINFO | TERRNO, "write()"); + return; + } + + len -= ret; + } + + SAFE_CLOSE(fd); + } +} diff --git a/src/kernel/tests/lib/tst_fs_has_free.c b/src/kernel/tests/lib/tst_fs_has_free.c new file mode 100644 index 0000000..e82dfa8 --- /dev/null +++ b/src/kernel/tests/lib/tst_fs_has_free.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014 Fujitsu Ltd. + * Author: Xiaoguang Wang <wangxg.fnst@cn.fujitsu.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * DESCRIPTION + * Check if the mounted file system has enough free space, + * if it is, tst_fs_has_free() returns 1, otherwise 0. + */ + +#include <stdint.h> +#include <sys/vfs.h> +#include "test.h" +#include "tst_fs.h" + +int tst_fs_has_free_(void (*cleanup)(void), const char *path, + unsigned int size, unsigned int mult) +{ + struct statfs sf; + + if (statfs(path, &sf)) { + tst_brkm(TBROK | TERRNO, cleanup, + "tst_fs_has_free: failed to statfs(%s)", path); + return 0; + } + + if ((uint64_t)sf.f_bavail * sf.f_bsize >= (uint64_t)size * mult) + return 1; + + return 0; +} diff --git a/src/kernel/tests/lib/tst_fs_link_count.c b/src/kernel/tests/lib/tst_fs_link_count.c new file mode 100644 index 0000000..860510d --- /dev/null +++ b/src/kernel/tests/lib/tst_fs_link_count.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2014 Fujitsu Ltd. + * Author: Xiaoguang Wang <wangxg.fnst@cn.fujitsu.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "test.h" +#include "usctest.h" +#include "safe_macros.h" + +#define MAX_SANE_HARD_LINKS 65535 + +/* + * filesystems whose subdir limit is less than MAX_SANE_HARD_LINKS + * XXX: we cannot filter ext4 out, because ext2/ext3/ext4 have the + * same magic number + */ +const long subdir_limit_whitelist[] = { + TST_EXT2_OLD_MAGIC, TST_EXT234_MAGIC, TST_MINIX_MAGIC, + TST_MINIX_MAGIC2, TST_MINIX2_MAGIC, TST_MINIX2_MAGIC2, + TST_MINIX3_MAGIC, TST_UDF_MAGIC, TST_SYSV2_MAGIC, + TST_SYSV4_MAGIC, TST_UFS_MAGIC, TST_UFS2_MAGIC, + TST_F2FS_MAGIC, TST_NILFS_MAGIC, TST_EXOFS_MAGIC +}; + +int tst_fs_fill_hardlinks_(void (*cleanup) (void), const char *dir) +{ + unsigned int i, j; + char base_filename[PATH_MAX], link_filename[PATH_MAX]; + struct stat s; + + if (stat(dir, &s) == -1 && errno == ENOENT) + SAFE_MKDIR(cleanup, dir, 0744); + + SAFE_STAT(cleanup, dir, &s); + if (!S_ISDIR(s.st_mode)) { + tst_brkm(TBROK, cleanup, "%s is not directory", dir); + return 0; + } + + sprintf(base_filename, "%s/testfile0", dir); + SAFE_TOUCH(cleanup, base_filename, 0644, NULL); + + for (i = 1; i < MAX_SANE_HARD_LINKS; i++) { + sprintf(link_filename, "%s/testfile%d", dir, i); + + if (link(base_filename, link_filename) == 0) + continue; + + switch (errno) { + case EMLINK: + SAFE_STAT(cleanup, base_filename, &s); + if (s.st_nlink != i) { + tst_brkm(TBROK, cleanup, "wrong number of " + "hard links for %s have %i, should be" + " %d", base_filename, + (int)s.st_nlink, i); + return 0; + } else { + tst_resm(TINFO, "the maximum number of hard " + "links to %s is hit: %d", + base_filename, (int)s.st_nlink); + return s.st_nlink; + } + case ENOSPC: + case EDQUOT: + tst_resm(TINFO | TERRNO, "link(%s, %s) failed", + base_filename, link_filename); + goto max_hardlinks_cleanup; + default: + tst_brkm(TBROK, cleanup, "link(%s, %s) failed " + "unexpectedly: %s", base_filename, + link_filename, strerror(errno)); + return 0; + } + } + + tst_resm(TINFO, "Failed reach the hardlinks limit"); + +max_hardlinks_cleanup: + for (j = 0; j < i; j++) { + sprintf(link_filename, "%s/testfile%d", dir, j); + SAFE_UNLINK(cleanup, link_filename); + } + + return 0; +} + +int tst_fs_fill_subdirs_(void (*cleanup) (void), const char *dir) +{ + unsigned int i, j, whitelist_size; + char dirname[PATH_MAX]; + struct stat s; + long fs_type; + + if (stat(dir, &s) == -1 && errno == ENOENT) + SAFE_MKDIR(cleanup, dir, 0744); + + SAFE_STAT(cleanup, dir, &s); + if (!S_ISDIR(s.st_mode)) { + tst_brkm(TBROK, cleanup, "%s is not directory", dir); + return 0; + } + + /* for current kernel, subdir limit is not availiable for all fs */ + fs_type = tst_fs_type(cleanup, dir); + + whitelist_size = ARRAY_SIZE(subdir_limit_whitelist); + for (i = 0; i < whitelist_size; i++) { + if (fs_type == subdir_limit_whitelist[i]) + break; + } + if (i == whitelist_size) { + tst_resm(TINFO, "subdir limit is not availiable for " + "%s filesystem", tst_fs_type_name(fs_type)); + return 0; + } + + for (i = 0; i < MAX_SANE_HARD_LINKS; i++) { + sprintf(dirname, "%s/testdir%d", dir, i); + + if (mkdir(dirname, 0755) == 0) + continue; + + switch (errno) { + case EMLINK: + SAFE_STAT(cleanup, dir, &s); + /* + * i+2 because there are two links to each newly + * created directory (the '.' and link from parent dir) + */ + if (s.st_nlink != (i + 2)) { + tst_brkm(TBROK, cleanup, "%s link counts have" + "%d, should be %d", dir, + (int)s.st_nlink, i + 2); + return 0; + } else { + tst_resm(TINFO, "the maximum subdirectories in " + "%s is hit: %d", dir, (int)s.st_nlink); + return s.st_nlink; + } + case ENOSPC: + case EDQUOT: + tst_resm(TINFO | TERRNO, "mkdir(%s, 0755) failed", + dirname); + goto max_subdirs_cleanup; + default: + tst_brkm(TBROK, cleanup, "mkdir(%s, 0755) failed " + "unexpectedly: %s", dirname, + strerror(errno)); + return 0; + } + + } + + tst_resm(TINFO, "Failed reach the subdirs limit on %s filesystem", + tst_fs_type_name(fs_type)); + +max_subdirs_cleanup: + for (j = 0; j < i; j++) { + sprintf(dirname, "%s/testdir%d", dir, j); + SAFE_RMDIR(cleanup, dirname); + } + + return 0; +} diff --git a/src/kernel/tests/lib/tst_fs_setup.c b/src/kernel/tests/lib/tst_fs_setup.c new file mode 100644 index 0000000..54ea370 --- /dev/null +++ b/src/kernel/tests/lib/tst_fs_setup.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <dirent.h> +#include <errno.h> +#include <sys/mount.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_fs.h" + +#define TST_FS_SETUP_OVERLAYFS_MSG "overlayfs is not configured in this kernel" +#define TST_FS_SETUP_OVERLAYFS_CONFIG "lowerdir="OVL_LOWER",upperdir="OVL_UPPER",workdir="OVL_WORK + +void create_overlay_dirs(void) +{ + DIR *dir = opendir(OVL_LOWER); + if (dir == NULL) { + SAFE_MKDIR(OVL_LOWER, 0755); + SAFE_MKDIR(OVL_UPPER, 0755); + SAFE_MKDIR(OVL_WORK, 0755); + SAFE_MKDIR(OVL_MNT, 0755); + return; + } + closedir(dir); +} + +int mount_overlay(const char *file, const int lineno, int skip) +{ + int ret; + + create_overlay_dirs(); + ret = mount("overlay", OVL_MNT, "overlay", 0, + TST_FS_SETUP_OVERLAYFS_CONFIG); + if (ret == 0) + return 0; + + if (errno == ENODEV) { + if (skip) { + tst_brk(TCONF, "%s:%d: " TST_FS_SETUP_OVERLAYFS_MSG, + file, lineno); + } else { + tst_res(TINFO, "%s:%d: " TST_FS_SETUP_OVERLAYFS_MSG, + file, lineno); + } + } else { + tst_brk(TBROK | TERRNO, "overlayfs mount failed"); + } + return ret; +} diff --git a/src/kernel/tests/lib/tst_fs_type.c b/src/kernel/tests/lib/tst_fs_type.c new file mode 100644 index 0000000..1d0ac96 --- /dev/null +++ b/src/kernel/tests/lib/tst_fs_type.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2005-2014 Linux Test Project + * + * Cyril Hrubis <chrubis@suse.cz> 2014 + * Michal Simek <monstr@monstr.eu> 2009 + * Kumar Gala <galak@kernel.crashing.org> 2007 + * Ricky Ng-Adam <rngadam@yahoo.com> 2005 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <sys/vfs.h> +#include "test.h" +#include "tst_fs.h" + +long tst_fs_type_(void (*cleanup)(void), const char *path) +{ + struct statfs sbuf; + + if (statfs(path, &sbuf)) { + tst_brkm(TBROK | TERRNO, cleanup, + "tst_fs_type: Failed to statfs(%s)", path); + return 0; + } + + return sbuf.f_type; +} + +const char *tst_fs_type_name(long f_type) +{ + switch (f_type) { + case TST_TMPFS_MAGIC: + return "TMPFS"; + case TST_NFS_MAGIC: + return "NFS"; + case TST_V9FS_MAGIC: + return "9P"; + case TST_RAMFS_MAGIC: + return "RAMFS"; + case TST_BTRFS_MAGIC: + return "BTRFS"; + case TST_XFS_MAGIC: + return "XFS"; + case TST_EXT2_OLD_MAGIC: + return "EXT2"; + case TST_EXT234_MAGIC: + return "EXT2/EXT3/EXT4"; + case TST_MINIX_MAGIC: + case TST_MINIX_MAGIC2: + case TST_MINIX2_MAGIC: + case TST_MINIX2_MAGIC2: + case TST_MINIX3_MAGIC: + return "MINIX"; + case TST_UDF_MAGIC: + return "UDF"; + case TST_SYSV2_MAGIC: + case TST_SYSV4_MAGIC: + return "SYSV"; + case TST_UFS_MAGIC: + case TST_UFS2_MAGIC: + return "UFS"; + case TST_F2FS_MAGIC: + return "F2FS"; + case TST_NILFS_MAGIC: + return "NILFS"; + case TST_EXOFS_MAGIC: + return "EXOFS"; + case TST_OVERLAYFS_MAGIC: + return "OVERLAYFS"; + default: + return "Unknown"; + } +} diff --git a/src/kernel/tests/lib/tst_get_bad_addr.c b/src/kernel/tests/lib/tst_get_bad_addr.c new file mode 100644 index 0000000..098e72b --- /dev/null +++ b/src/kernel/tests/lib/tst_get_bad_addr.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/mman.h> +#include "test.h" +#include "tst_get_bad_addr.h" + +void *tst_get_bad_addr(void (*cleanup_fn) (void)) +{ + void *bad_addr; + + bad_addr = mmap(0, 1, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + if (bad_addr == MAP_FAILED) + tst_brkm(TBROK, cleanup_fn, "mmap() failed to get bad address"); + + return bad_addr; +} diff --git a/src/kernel/tests/lib/tst_hugepage.c b/src/kernel/tests/lib/tst_hugepage.c new file mode 100644 index 0000000..1d0e62e --- /dev/null +++ b/src/kernel/tests/lib/tst_hugepage.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2019 Red Hat, Inc. + */ + +#define TST_NO_DEFAULT_MAIN + +#include "tst_test.h" +#include "tst_hugepage.h" + +unsigned long tst_hugepages; +char *nr_opt; +char *Hopt; + +size_t tst_get_hugepage_size(void) +{ + if (access(PATH_HUGEPAGES, F_OK)) + return 0; + + return SAFE_READ_MEMINFO("Hugepagesize:") * 1024; +} + +unsigned long tst_request_hugepages(unsigned long hpages) +{ + unsigned long val, max_hpages; + + if (access(PATH_HUGEPAGES, F_OK)) { + tst_hugepages = 0; + goto out; + } + + if (nr_opt) + tst_hugepages = SAFE_STRTOL(nr_opt, 1, LONG_MAX); + else + tst_hugepages = hpages; + + SAFE_FILE_PRINTF("/proc/sys/vm/drop_caches", "3"); + max_hpages = SAFE_READ_MEMINFO("MemFree:") / SAFE_READ_MEMINFO("Hugepagesize:"); + + if (tst_hugepages > max_hpages) { + tst_res(TINFO, "Requested number(%lu) of hugepages is too large, " + "limiting to 80%% of the max hugepage count %lu", + tst_hugepages, max_hpages); + tst_hugepages = max_hpages * 0.8; + + if (tst_hugepages < 1) + goto out; + } + + tst_sys_conf_save("?/proc/sys/vm/nr_hugepages"); + SAFE_FILE_PRINTF(PATH_NR_HPAGES, "%lu", tst_hugepages); + SAFE_FILE_SCANF(PATH_NR_HPAGES, "%lu", &val); + if (val != tst_hugepages) + tst_brk(TCONF, "nr_hugepages = %lu, but expect %lu. " + "Not enough hugepages for testing.", + val, tst_hugepages); + + tst_res(TINFO, "%lu hugepage(s) reserved", tst_hugepages); +out: + return tst_hugepages; +} diff --git a/src/kernel/tests/lib/tst_ioctl.c b/src/kernel/tests/lib/tst_ioctl.c new file mode 100644 index 0000000..364220b --- /dev/null +++ b/src/kernel/tests/lib/tst_ioctl.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <linux/fs.h> + +#define TST_NO_DEFAULT_MAIN + +#include "tst_test.h" + +int tst_fibmap(const char *filename) +{ + /* test if FIBMAP ioctl is supported */ + int fd, block = 0; + + fd = open(filename, O_RDWR | O_CREAT, 0666); + if (fd < 0) { + tst_res(TWARN | TERRNO, + "open(%s, O_RDWR | O_CREAT, 0666) failed", filename); + return -1; + } + + if (ioctl(fd, FIBMAP, &block)) { + tst_res(TINFO | TERRNO, "FIBMAP ioctl is NOT supported"); + close(fd); + return 1; + } + tst_res(TINFO, "FIBMAP ioctl is supported"); + + if (close(fd)) { + tst_res(TWARN | TERRNO, "close(fd) failed"); + return -1; + } + return 0; +} diff --git a/src/kernel/tests/lib/tst_kconfig.c b/src/kernel/tests/lib/tst_kconfig.c new file mode 100644 index 0000000..d49187b --- /dev/null +++ b/src/kernel/tests/lib/tst_kconfig.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2018 Cyril Hrubis <chrubis@suse.cz> + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <sys/utsname.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_kconfig.h" + +static const char *kconfig_path(char *path_buf, size_t path_buf_len) +{ + const char *path = getenv("KCONFIG_PATH"); + struct utsname un; + + if (path) { + if (!access(path, F_OK)) + return path; + + tst_res(TWARN, "KCONFIG_PATH='%s' does not exist", path); + } + + if (!access("/proc/config.gz", F_OK)) + return "/proc/config.gz"; + + uname(&un); + + /* Debian and derivatives */ + snprintf(path_buf, path_buf_len, "/boot/config-%s", un.release); + + if (!access(path_buf, F_OK)) + return path_buf; + + /* Clear Linux */ + snprintf(path_buf, path_buf_len, "/lib/kernel/config-%s", un.release); + + if (!access(path_buf, F_OK)) + return path_buf; + + tst_res(TINFO, "Couldn't locate kernel config!"); + + return NULL; +} + +static char is_gzip; + +static FILE *open_kconfig(void) +{ + FILE *fp; + char buf[1064]; + char path_buf[1024]; + const char *path = kconfig_path(path_buf, sizeof(path_buf)); + + if (!path) + return NULL; + + tst_res(TINFO, "Parsing kernel config '%s'", path); + + is_gzip = !!strstr(path, ".gz"); + + if (is_gzip) { + snprintf(buf, sizeof(buf), "zcat '%s'", path); + fp = popen(buf, "r"); + } else { + fp = fopen(path, "r"); + } + + if (!fp) + tst_brk(TBROK | TERRNO, "Failed to open '%s'", path); + + return fp; +} + +static void close_kconfig(FILE *fp) +{ + if (is_gzip) + pclose(fp); + else + fclose(fp); +} + +struct match { + /* match len, string length up to \0 or = */ + size_t len; + /* if set part of conf string after = */ + const char *val; + /* if set the config option was matched already */ + int match; +}; + +static int is_set(const char *str, const char *val) +{ + size_t vlen = strlen(val); + + while (isspace(*str)) + str++; + + if (strncmp(str, val, vlen)) + return 0; + + switch (str[vlen]) { + case ' ': + case '\n': + case '\0': + return 1; + break; + default: + return 0; + } +} + +static inline int match(struct match *match, const char *conf, + struct tst_kconfig_res *result, const char *line) +{ + if (match->match) + return 0; + + const char *cfg = strstr(line, "CONFIG_"); + + if (!cfg) + return 0; + + if (strncmp(cfg, conf, match->len)) + return 0; + + const char *val = &cfg[match->len]; + + switch (cfg[match->len]) { + case '=': + break; + case ' ': + if (is_set(val, "is not set")) { + result->match = 'n'; + goto match; + } + /* fall through */ + default: + return 0; + } + + if (is_set(val, "=y")) { + result->match = 'y'; + goto match; + } + + if (is_set(val, "=m")) { + result->match = 'm'; + goto match; + } + + result->match = 'v'; + result->value = strndup(val+1, strlen(val)-2); + +match: + match->match = 1; + return 1; +} + +void tst_kconfig_read(const char *const *kconfigs, + struct tst_kconfig_res results[], size_t cnt) +{ + struct match matches[cnt]; + FILE *fp; + unsigned int i, j; + char buf[1024]; + + for (i = 0; i < cnt; i++) { + const char *val = strchr(kconfigs[i], '='); + + if (strncmp("CONFIG_", kconfigs[i], 7)) + tst_brk(TBROK, "Invalid config string '%s'", kconfigs[i]); + + matches[i].match = 0; + matches[i].len = strlen(kconfigs[i]); + + if (val) { + matches[i].val = val + 1; + matches[i].len -= strlen(val); + } + + results[i].match = 0; + results[i].value = NULL; + } + + fp = open_kconfig(); + if (!fp) + tst_brk(TBROK, "Cannot parse kernel .config"); + + while (fgets(buf, sizeof(buf), fp)) { + for (i = 0; i < cnt; i++) { + if (match(&matches[i], kconfigs[i], &results[i], buf)) { + for (j = 0; j < cnt; j++) { + if (matches[j].match) + break; + } + + if (j == cnt) + goto exit; + } + } + + } + +exit: + close_kconfig(fp); +} + +static size_t array_len(const char *const kconfigs[]) +{ + size_t i = 0; + + while (kconfigs[++i]); + + return i; +} + +static int compare_res(struct tst_kconfig_res *res, const char *kconfig, + char match, const char *val) +{ + if (res->match != match) { + tst_res(TINFO, "Needs kernel %s, have %c", kconfig, res->match); + return 1; + } + + if (match != 'v') + return 0; + + if (strcmp(res->value, val)) { + tst_res(TINFO, "Needs kernel %s, have %s", kconfig, res->value); + return 1; + } + + return 0; +} + +void tst_kconfig_check(const char *const kconfigs[]) +{ + size_t cnt = array_len(kconfigs); + struct tst_kconfig_res results[cnt]; + unsigned int i; + int abort_test = 0; + + tst_kconfig_read(kconfigs, results, cnt); + + for (i = 0; i < cnt; i++) { + if (results[i].match == 0) { + tst_res(TINFO, "Missing kernel %s", kconfigs[i]); + abort_test = 1; + continue; + } + + if (results[i].match == 'n') { + tst_res(TINFO, "Kernel %s is not set", kconfigs[i]); + abort_test = 1; + continue; + } + + const char *val = strchr(kconfigs[i], '='); + + if (val) { + char match = 'v'; + val++; + + if (!strcmp(val, "y")) + match = 'y'; + + if (!strcmp(val, "m")) + match = 'm'; + + if (compare_res(&results[i], kconfigs[i], match, val)) + abort_test = 1; + + } + + free(results[i].value); + } + + if (abort_test) + tst_brk(TCONF, "Aborting due to unsuitable kernel config, see above!"); +} diff --git a/src/kernel/tests/lib/tst_kernel.c b/src/kernel/tests/lib/tst_kernel.c new file mode 100644 index 0000000..57fa4b2 --- /dev/null +++ b/src/kernel/tests/lib/tst_kernel.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/personality.h> +#include <sys/utsname.h> +#include "test.h" +#include "tst_kernel.h" + +static int get_kernel_bits_from_uname(struct utsname *buf) +{ + if (uname(buf)) { + tst_brkm(TBROK | TERRNO, NULL, "uname()"); + return -1; + } + + return strstr(buf->machine, "64") ? 64 : 32; +} + +int tst_kernel_bits(void) +{ + struct utsname buf; + int kernel_bits = get_kernel_bits_from_uname(&buf); + + if (kernel_bits == -1) + return -1; + + /* + * ARM64 (aarch64) defines 32-bit compatibility modes as + * armv8l and armv8b (little and big endian). + * s390x is 64bit but not contain 64 in the words. + */ + if (!strcmp(buf.machine, "armv8l") || !strcmp(buf.machine, "armv8b") + || !strcmp(buf.machine, "s390x")) + kernel_bits = 64; + +#ifdef __ANDROID__ + /* Android's bionic libc sets the PER_LINUX32 personality for all 32-bit + * programs. This will cause buf.machine to report as i686 even though + * the kernel itself is 64-bit. + */ + if (!strcmp(buf.machine, "i686") && + (personality(0xffffffff) & PER_MASK) == PER_LINUX32) { + /* Set the personality back to the default. */ + if (personality(PER_LINUX) == -1) { + tst_brkm(TBROK | TERRNO, NULL, "personality()"); + return -1; + } + + /* Redo the uname check without the PER_LINUX32 personality to + * determine the actual kernel bits value. + */ + kernel_bits = get_kernel_bits_from_uname(&buf); + if (kernel_bits == -1) + return -1; + + /* Set the personality back to PER_LINUX32. */ + if (personality(PER_LINUX32) == -1) { + tst_brkm(TBROK | TERRNO, NULL, "personality()"); + return -1; + } + } +#endif /* __ANDROID__ */ + + tst_resm(TINFO, "uname.machine=%s kernel is %ibit", + buf.machine, kernel_bits); + + return kernel_bits; +} + +int tst_check_driver(const char *name) +{ +#ifndef __ANDROID__ + const char * const argv[] = { "modprobe", "-n", name, NULL }; + int res = tst_cmd_(NULL, argv, "/dev/null", "/dev/null", + TST_CMD_PASS_RETVAL); + + /* 255 - it looks like modprobe not available */ + return (res == 255) ? 0 : res; +#else + /* Android modprobe may not have '-n', or properly installed + * module.*.bin files to determine built-in drivers. Assume + * all drivers are available. + */ + return 0; +#endif +} diff --git a/src/kernel/tests/lib/tst_kvercmp.c b/src/kernel/tests/lib/tst_kvercmp.c new file mode 100644 index 0000000..5d56e30 --- /dev/null +++ b/src/kernel/tests/lib/tst_kvercmp.c @@ -0,0 +1,195 @@ +/* + * Copyright (c) International Business Machines Corp., 2003 + * AUTHOR: Paul Larson <plars@linuxtestproject.org> + * Copyright (c) 2016 Cyril Hrubis <chrubis@suse.cz> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <limits.h> +#include <sys/utsname.h> +#include "test.h" + +#define OSRELEASE_PATH "/etc/os-release" + +static char *parse_digit(const char *str, int *d) +{ + unsigned long v; + char *end; + + v = strtoul(str, &end, 10); + if (str == end) + return NULL; + + if (v > INT_MAX) + return NULL; + + *d = v; + + return end; +} + +int tst_parse_kver(const char *str_kver, int *v1, int *v2, int *v3) +{ + const char *str = str_kver; + + *v1 = 0; + *v2 = 0; + *v3 = 0; + + if (!(str = parse_digit(str, v1))) + return 1; + + if (*(str++) != '.') + return 1; + + if (!(str = parse_digit(str, v2))) + return 1; + + /* + * Check for a short version e.g '2.4' + */ + if (*str == ' ' || *str == '\0') + return 0; + + if (*(str++) != '.') + return 1; + + /* + * Ignore rest of the string in order not to break on versions as + * 4.8.1-52-default. + */ + if (!parse_digit(str, v3)) + return 1; + + return 0; +} + +int tst_kvcmp(const char *cur_kver, int r1, int r2, int r3) +{ + int a1, a2, a3; + int testver, currver; + + if (tst_parse_kver(cur_kver, &a1, &a2, &a3)) { + tst_resm(TWARN, + "Invalid kernel version %s, expected %%d.%%d.%%d", + cur_kver); + } + + testver = (r1 << 16) + (r2 << 8) + r3; + currver = (a1 << 16) + (a2 << 8) + a3; + + return currver - testver; +} + +int tst_kvercmp(int r1, int r2, int r3) +{ + struct utsname uval; + + uname(&uval); + + return tst_kvcmp(uval.release, r1, r2, r3); +} + +int tst_kvexcmp(const char *tst_exv, const char *cur_ver) +{ + int c1 = 0, c2 = 0, c3 = 0, c4 = 0, c5 = 0; + int t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0; + int ret; + + sscanf(cur_ver, "%d.%d.%d-%d.%d", &c1, &c2, &c3, &c4, &c5); + sscanf(tst_exv, "%d.%d.%d-%d.%d", &t1, &t2, &t3, &t4, &t5); + + if ((ret = c1 - t1)) + return ret; + if ((ret = c2 - t2)) + return ret; + if ((ret = c3 - t3)) + return ret; + if ((ret = c4 - t4)) + return ret; + + return c5 - t5; +} + +const char *tst_kvcmp_distname(const char *kver) +{ + static char distname[64]; + char *ret = distname; + char *p = distname; + + if (strstr(kver, ".el5uek")) + return "OL5UEK"; + + if (strstr(kver, ".el5")) + return "RHEL5"; + + if (strstr(kver, ".el6uek")) + return "OL6UEK"; + + if (strstr(kver, ".el6")) + return "RHEL6"; + + if (access(OSRELEASE_PATH, F_OK) != -1) { + SAFE_FILE_LINES_SCANF(NULL, OSRELEASE_PATH, "ID=%s", distname); + + if (p[0] == '"') { + ret = distname + 1; + p = ret; + } + + while (*p) { + if (*p == '"') { + *p = 0; + break; + } + *p = toupper((unsigned char)*p); + p++; + } + + return ret; + } + + return NULL; +} + +int tst_kvercmp2(int r1, int r2, int r3, struct tst_kern_exv *vers) +{ + int i; + const char *kver; + struct utsname uval; + const char *cur_dist_name; + + uname(&uval); + kver = uval.release; + cur_dist_name = tst_kvcmp_distname(kver); + + if (cur_dist_name == NULL) + return tst_kvercmp(r1, r2, r3); + + for (i = 0; vers[i].dist_name; i++) { + if (!strcmp(vers[i].dist_name, cur_dist_name)) { + tst_resm(TINFO, "Detected %s using kernel version %s", + cur_dist_name, kver); + return tst_kvexcmp(vers[i].extra_ver, kver); + } + } + + return tst_kvcmp(kver, r1, r2, r3); +} diff --git a/src/kernel/tests/lib/tst_lockdown.c b/src/kernel/tests/lib/tst_lockdown.c new file mode 100644 index 0000000..e7c1981 --- /dev/null +++ b/src/kernel/tests/lib/tst_lockdown.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define TST_NO_DEFAULT_MAIN + +#include <stdio.h> +#include <stdlib.h> +#include <sys/mount.h> + +#include "tst_test.h" +#include "tst_safe_macros.h" +#include "tst_safe_stdio.h" +#include "tst_lockdown.h" + +int tst_lockdown_enabled(void) +{ + char line[BUFSIZ]; + FILE *file; + + if (access(PATH_LOCKDOWN, F_OK) != 0) { + tst_res(TINFO, "Unable to determine system lockdown state"); + return 0; + } + + file = SAFE_FOPEN(PATH_LOCKDOWN, "r"); + if (!fgets(line, sizeof(line), file)) + tst_brk(TBROK | TERRNO, "fgets %s", PATH_LOCKDOWN); + SAFE_FCLOSE(file); + + return (strstr(line, "[none]") == NULL); +} diff --git a/src/kernel/tests/lib/tst_memutils.c b/src/kernel/tests/lib/tst_memutils.c new file mode 100644 index 0000000..f134d90 --- /dev/null +++ b/src/kernel/tests/lib/tst_memutils.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020 SUSE LLC <mdoucha@suse.cz> + */ + +#include <unistd.h> +#include <limits.h> +#include <sys/sysinfo.h> +#include <stdlib.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" + +#define BLOCKSIZE (16 * 1024 * 1024) + +void tst_pollute_memory(size_t maxsize, int fillchar) +{ + size_t i, map_count = 0, safety = 0, blocksize = BLOCKSIZE; + void **map_blocks; + struct sysinfo info; + + SAFE_SYSINFO(&info); + safety = 4096 * SAFE_SYSCONF(_SC_PAGESIZE) / info.mem_unit; + + if (info.freeswap > safety) + safety = 0; + + /* Not enough free memory to avoid invoking OOM killer */ + if (info.freeram <= safety) + return; + + if (!maxsize) + maxsize = SIZE_MAX; + + if (info.freeram - safety < maxsize / info.mem_unit) + maxsize = (info.freeram - safety) * info.mem_unit; + + blocksize = MIN(maxsize, blocksize); + map_count = maxsize / blocksize; + map_blocks = SAFE_MALLOC(map_count * sizeof(void *)); + + /* + * Keep allocating until the first failure. The address space may be + * too fragmented or just smaller than maxsize. + */ + for (i = 0; i < map_count; i++) { + map_blocks[i] = mmap(NULL, blocksize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (map_blocks[i] == MAP_FAILED) { + map_count = i; + break; + } + + memset(map_blocks[i], fillchar, blocksize); + } + + for (i = 0; i < map_count; i++) + SAFE_MUNMAP(map_blocks[i], blocksize); + + free(map_blocks); +} diff --git a/src/kernel/tests/lib/tst_mkfs.c b/src/kernel/tests/lib/tst_mkfs.c new file mode 100644 index 0000000..38b2e71 --- /dev/null +++ b/src/kernel/tests/lib/tst_mkfs.c @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2013-2016 Cyril Hrubis <chrubis@suse.cz> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "test.h" +#include "ltp_priv.h" +#include "tst_mkfs.h" +#include "tst_device.h" + +#define OPTS_MAX 32 + +void tst_mkfs_(const char *file, const int lineno, void (cleanup_fn)(void), + const char *dev, const char *fs_type, + const char *const fs_opts[], const char *const extra_opts[]) +{ + int i, pos = 1, ret; + char mkfs[64]; + const char *argv[OPTS_MAX] = {mkfs}; + char fs_opts_str[1024] = ""; + char extra_opts_str[1024] = ""; + + if (!dev) { + tst_brkm(TBROK, cleanup_fn, + "%s:%d: No device specified", file, lineno); + return; + } + + if (!fs_type) { + tst_brkm(TBROK, cleanup_fn, + "%s:%d: No fs_type specified", file, lineno); + return; + } + + snprintf(mkfs, sizeof(mkfs), "mkfs.%s", fs_type); + + if (fs_opts) { + for (i = 0; fs_opts[i]; i++) { + argv[pos++] = fs_opts[i]; + + if (pos + 2 > OPTS_MAX) { + tst_brkm(TBROK, cleanup_fn, + "%s:%d: Too much mkfs options", + file, lineno); + return; + } + + if (i) + strcat(fs_opts_str, " "); + strcat(fs_opts_str, fs_opts[i]); + } + } + + argv[pos++] = dev; + + if (extra_opts) { + for (i = 0; extra_opts[i]; i++) { + argv[pos++] = extra_opts[i]; + + if (pos + 1 > OPTS_MAX) { + tst_brkm(TBROK, cleanup_fn, + "%s:%d: Too much mkfs options", file, lineno); + return; + } + + if (i) + strcat(extra_opts_str, " "); + strcat(extra_opts_str, extra_opts[i]); + } + } + + argv[pos] = NULL; + + if (tst_clear_device(dev)) + tst_brkm(TBROK, cleanup_fn, "tst_clear_device() failed"); + + tst_resm(TINFO, "Formatting %s with %s opts='%s' extra opts='%s'", + dev, fs_type, fs_opts_str, extra_opts_str); + ret = tst_cmd(cleanup_fn, argv, "/dev/null", NULL, TST_CMD_PASS_RETVAL | + TST_CMD_TCONF_ON_MISSING); + + switch (ret) { + case 0: + break; + case 255: + tst_brkm(TCONF, cleanup_fn, + "%s:%d: %s not found in $PATH", file, lineno, mkfs); + break; + default: + tst_brkm(TBROK, cleanup_fn, + "%s:%d: %s failed with %i", file, lineno, mkfs, ret); + } +} + +const char *tst_dev_fs_type(void) +{ + const char *fs_type; + + fs_type = getenv("LTP_DEV_FS_TYPE"); + + if (fs_type) + return fs_type; + + return DEFAULT_FS_TYPE; +} diff --git a/src/kernel/tests/lib/tst_module.c b/src/kernel/tests/lib/tst_module.c new file mode 100644 index 0000000..eda6187 --- /dev/null +++ b/src/kernel/tests/lib/tst_module.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2013 Oracle and/or its affiliates. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Alexey Kodanev <alexey.kodanev@oracle.com> + * + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "test.h" +#include "ltp_priv.h" +#include "old_module.h" + +void tst_module_exists(void (cleanup_fn)(void), + const char *mod_name, char **mod_path) +{ + /* check current working directory */ + if (access(mod_name, F_OK) == 0) { + if (mod_path != NULL) + *mod_path = strdup(mod_name); + return; + } + char *buf = NULL; + int err = -1; + /* check LTP installation path */ + const char *ltproot = getenv("LTPROOT"); + if (ltproot != NULL) { + if (asprintf(&buf, "%s/testcases/bin/%s", + ltproot, mod_name) == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "asprintf failed at %s:%d", + __FILE__, __LINE__); + return; + } + err = access(buf, F_OK); + } + /* check start working directory */ + if (err == -1 && tst_tmpdir_created()) { + free(buf); + if (asprintf(&buf, "%s/%s", tst_get_startwd(), + mod_name) == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "asprintf failed at %s:%d", + __FILE__, __LINE__); + return; + } + err = access(buf, F_OK); + } + + if (err != 0) { + free(buf); + tst_brkm(TCONF, cleanup_fn, "Failed to find module '%s'", + mod_name); + return; + } + + if (mod_path != NULL) + *mod_path = buf; + else + free(buf); +} + +void tst_module_load(void (cleanup_fn)(void), + const char *mod_name, char *const argv[]) +{ + char *mod_path = NULL; + tst_module_exists(cleanup_fn, mod_name, &mod_path); + + const int offset = 2; /* command name & module path */ + int size = 0; + while (argv && argv[size]) + ++size; + size += offset; + const char *mod_argv[size + 1]; /* + NULL in the end */ + mod_argv[size] = NULL; + mod_argv[0] = "insmod"; + mod_argv[1] = mod_path; + + int i; + for (i = offset; i < size; ++i) + mod_argv[i] = argv[i - offset]; + + tst_cmd(cleanup_fn, mod_argv, NULL, NULL, 0); + free(mod_path); +} + +void tst_module_unload(void (cleanup_fn)(void), const char *mod_name) +{ + int i, rc; + + const char *const argv[] = { "rmmod", mod_name, NULL }; + + rc = 1; + for (i = 0; i < 50; i++) { + rc = tst_cmd(NULL, argv, "/dev/null", "/dev/null", + TST_CMD_PASS_RETVAL); + if (!rc) + break; + + usleep(20000); + } + + if (rc) { + tst_brkm(TBROK, cleanup_fn, + "could not unload %s module", mod_name); + } +} diff --git a/src/kernel/tests/lib/tst_net.c b/src/kernel/tests/lib/tst_net.c new file mode 100644 index 0000000..8a589b0 --- /dev/null +++ b/src/kernel/tests/lib/tst_net.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017-2019 Petr Vorel <pvorel@suse.cz> + * Copyright (c) 2019 Martin Doucha <mdoucha@suse.cz> + */ + +#include <errno.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_net.h" +#include "tst_private.h" + +void tst_print_svar(const char *name, const char *val) +{ + if (name && val) + printf("export %s=\"%s\"\n", name, val); +} + +void tst_print_svar_change(const char *name, const char *val) +{ + if (name && val) + printf("export %s=\"${%s:-%s}\"\n", name, name, val); +} + +/* + * Function bit_count is from ipcalc project, ipcalc.c. + */ +static int tst_bit_count(uint32_t i) +{ + int c = 0; + unsigned int seen_one = 0; + + while (i > 0) { + if (i & 1) { + seen_one = 1; + c++; + } else { + if (seen_one) + return -1; + } + i >>= 1; + } + + return c; +} + +/* + * Function mask2prefix is from ipcalc project, ipcalc.c. + */ +static int tst_mask2prefix(struct in_addr mask) +{ + return tst_bit_count(ntohl(mask.s_addr)); +} + +/* + * Function ipv4_mask_to_int is from ipcalc project, ipcalc.c. + */ +static int tst_ipv4_mask_to_int(const char *prefix) +{ + int ret; + struct in_addr in; + + ret = inet_pton(AF_INET, prefix, &in); + if (ret == 0) + return -1; + + return tst_mask2prefix(in); +} + +/* + * Function safe_atoi is from ipcalc project, ipcalc.c. + */ +static int tst_safe_atoi(const char *s, int *ret_i) +{ + char *x = NULL; + long l; + + errno = 0; + l = strtol(s, &x, 0); + + if (!x || x == s || *x || errno) + return errno > 0 ? -errno : -EINVAL; + + if ((long)(int)l != l) + return -ERANGE; + + *ret_i = (int)l; + + return 0; +} + +/* + * Function get_prefix use code from ipcalc project, str_to_prefix/ipcalc.c. + */ +int tst_get_prefix(const char *ip_str, int is_ipv6) +{ + char *prefix_str = NULL; + int prefix = -1, r; + + prefix_str = strchr(ip_str, '/'); + if (!prefix_str) + return -1; + + *(prefix_str++) = '\0'; + + if (!is_ipv6 && strchr(prefix_str, '.')) + prefix = tst_ipv4_mask_to_int(prefix_str); + else { + r = tst_safe_atoi(prefix_str, &prefix); + if (r != 0) + tst_brk_comment("conversion error: '%s' is not integer", + prefix_str); + } + + if (prefix < 0 || ((is_ipv6 && prefix > MAX_IPV6_PREFIX) || + (!is_ipv6 && prefix > MAX_IPV4_PREFIX))) + tst_brk_comment("bad %s prefix: %s", is_ipv6 ? "IPv6" : "IPv4", + prefix_str); + + return prefix; +} + +void tst_get_in_addr(const char *ip_str, struct in_addr *ip) +{ + if (inet_pton(AF_INET, ip_str, ip) <= 0) + tst_brk_comment("bad IPv4 address: '%s'", ip_str); +} + +void tst_get_in6_addr(const char *ip_str, struct in6_addr *ip6) +{ + if (inet_pton(AF_INET6, ip_str, ip6) <= 0) + tst_brk_comment("bad IPv6 address: '%s'", ip_str); +} + +socklen_t tst_get_connect_address(int sock, struct sockaddr_storage *addr) +{ + struct sockaddr_in *inet_ptr; + struct sockaddr_in6 *inet6_ptr; + size_t tmp_size; + socklen_t ret = sizeof(*addr); + + SAFE_GETSOCKNAME(sock, (struct sockaddr*)addr, &ret); + + /* Sanitize wildcard addresses */ + switch (addr->ss_family) { + case AF_INET: + inet_ptr = (struct sockaddr_in*)addr; + + switch (ntohl(inet_ptr->sin_addr.s_addr)) { + case INADDR_ANY: + case INADDR_BROADCAST: + inet_ptr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + break; + } + + break; + + case AF_INET6: + inet6_ptr = (struct sockaddr_in6*)addr; + tmp_size = sizeof(struct in6_addr); + + if (!memcmp(&inet6_ptr->sin6_addr, &in6addr_any, tmp_size)) { + memcpy(&inet6_ptr->sin6_addr, &in6addr_loopback, + tmp_size); + } + + break; + } + + return ret; +} + +void tst_init_sockaddr_inet(struct sockaddr_in *sa, const char *ip_str, uint16_t port) +{ + memset(sa, 0, sizeof(struct sockaddr_in)); + sa->sin_family = AF_INET; + sa->sin_port = htons(port); + tst_get_in_addr(ip_str, &sa->sin_addr); +} + +void tst_init_sockaddr_inet_bin(struct sockaddr_in *sa, uint32_t ip_val, uint16_t port) +{ + memset(sa, 0, sizeof(struct sockaddr_in)); + sa->sin_family = AF_INET; + sa->sin_port = htons(port); + sa->sin_addr.s_addr = htonl(ip_val); +} + +void tst_init_sockaddr_inet6(struct sockaddr_in6 *sa, const char *ip_str, uint16_t port) +{ + memset(sa, 0, sizeof(struct sockaddr_in6)); + sa->sin6_family = AF_INET6; + sa->sin6_port = htons(port); + tst_get_in6_addr(ip_str, &sa->sin6_addr); +} + +void tst_init_sockaddr_inet6_bin(struct sockaddr_in6 *sa, const struct in6_addr *ip_val, uint16_t port) +{ + memset(sa, 0, sizeof(struct sockaddr_in6)); + sa->sin6_family = AF_INET6; + sa->sin6_port = htons(port); + memcpy(&sa->sin6_addr, ip_val, sizeof(struct in6_addr)); +} + +void safe_getaddrinfo(const char *file, const int lineno, const char *src_addr, + const char *port, const struct addrinfo *hints, + struct addrinfo **addr_info) +{ + int err = getaddrinfo(src_addr, port, hints, addr_info); + + if (err) + tst_brk(TBROK, "%s:%d: getaddrinfo failed, %s", file, lineno, + gai_strerror(err)); + + if (!*addr_info) + tst_brk(TBROK, "%s:%d: failed to get the address", file, lineno); +} diff --git a/src/kernel/tests/lib/tst_parse_opts.c b/src/kernel/tests/lib/tst_parse_opts.c new file mode 100644 index 0000000..94970e1 --- /dev/null +++ b/src/kernel/tests/lib/tst_parse_opts.c @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 Cyril Hrubis chrubis@suse.cz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "test.h" +#include "ltp_priv.h" + +void tst_parse_opts(int argc, char *argv[], const option_t *user_optarg, + void (*user_help)(void)) +{ + const char *msg; + + msg = parse_opts(argc, argv, user_optarg, user_help); + + if (msg) + tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg); +} diff --git a/src/kernel/tests/lib/tst_path_has_mnt_flags.c b/src/kernel/tests/lib/tst_path_has_mnt_flags.c new file mode 100644 index 0000000..154bf41 --- /dev/null +++ b/src/kernel/tests/lib/tst_path_has_mnt_flags.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014 Fujitsu Ltd. + * Author: Xing Gu <gux.fnst@cn.fujitsu.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <unistd.h> +#include <mntent.h> +#include <stdio.h> +#include <string.h> +#include "test.h" + +/* + * Check whether a path is on a filesystem that is mounted with + * specified flags. + */ +int tst_path_has_mnt_flags_(void (cleanup_fn)(void), + const char *path, const char *flags[]) +{ + struct mntent *mnt; + size_t prefix_max = 0, prefix_len; + int flags_matched = 0; + FILE *f; + int i; + char *tmpdir = NULL; + + /* + * Default parameter is test temporary directory + */ + if (path == NULL) + path = tmpdir = tst_get_tmpdir(); + + if (access(path, F_OK) == -1) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "tst_path_has_mnt_flags: path %s doesn't exist", path); + return -1; + } + + f = setmntent("/proc/mounts", "r"); + if (f == NULL) { + tst_brkm(TBROK | TERRNO, cleanup_fn, + "tst_path_has_mnt_flags: failed to open /proc/mounts"); + return -1; + } + + while ((mnt = getmntent(f))) { + /* ignore duplicit record for root fs */ + if (!strcmp(mnt->mnt_fsname, "rootfs")) + continue; + + prefix_len = strlen(mnt->mnt_dir); + + if (strncmp(path, mnt->mnt_dir, prefix_len) == 0 + && prefix_len > prefix_max) { + prefix_max = prefix_len; + flags_matched = 0; + i = 0; + + while (flags[i] != NULL) { + if (hasmntopt(mnt, flags[i]) != NULL) + flags_matched++; + i++; + } + } + } + + endmntent(f); + + free(tmpdir); + + return flags_matched; +} diff --git a/src/kernel/tests/lib/tst_pid.c b/src/kernel/tests/lib/tst_pid.c new file mode 100644 index 0000000..9568cc9 --- /dev/null +++ b/src/kernel/tests/lib/tst_pid.c @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) International Business Machines Corp., 2009 + * Copyright (c) 2014 Oracle and/or its affiliates. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <fcntl.h> +#include <limits.h> +#include <sys/types.h> +#include "test.h" +#include "tst_pid.h" +#include "old_safe_file_ops.h" + +#define PID_MAX_PATH "/proc/sys/kernel/pid_max" + +pid_t tst_get_unused_pid_(void (*cleanup_fn) (void)) +{ + pid_t pid; + + SAFE_FILE_SCANF(cleanup_fn, PID_MAX_PATH, "%d", &pid); + + return pid; +} + +int tst_get_free_pids_(void (*cleanup_fn) (void)) +{ + FILE *f; + int rc, used_pids, max_pids; + + f = popen("ps -eT | wc -l", "r"); + if (!f) { + tst_resm(TBROK, "Could not run 'ps' to calculate used " "pids"); + return -1; + } + rc = fscanf(f, "%i", &used_pids); + pclose(f); + + if (rc != 1 || used_pids < 0) { + tst_resm(TBROK, "Could not read output of 'ps' to " + "calculate used pids"); + return -1; + } + + SAFE_FILE_SCANF(cleanup_fn, PID_MAX_PATH, "%d", &max_pids); + + /* max_pids contains the maximum PID + 1, + * used_pids contains used PIDs + 1, + * so this additional '1' is eliminated by the substraction */ + return max_pids - used_pids; +} diff --git a/src/kernel/tests/lib/tst_process_state.c b/src/kernel/tests/lib/tst_process_state.c new file mode 100644 index 0000000..11790c9 --- /dev/null +++ b/src/kernel/tests/lib/tst_process_state.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012-2014 Cyril Hrubis chrubis@suse.cz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> + +#include "test.h" +#include "tst_process_state.h" + +int tst_process_state_wait(const char *file, const int lineno, + void (*cleanup_fn)(void), pid_t pid, + const char state, unsigned int msec_timeout) +{ + char proc_path[128], cur_state; + unsigned int msecs = 0; + + snprintf(proc_path, sizeof(proc_path), "/proc/%i/stat", pid); + + for (;;) { + safe_file_scanf(file, lineno, cleanup_fn, proc_path, + "%*i %*s %c", &cur_state); + + if (state == cur_state) + break; + + usleep(1000); + msecs += 1; + + if (msec_timeout && msecs >= msec_timeout) { + errno = ETIMEDOUT; + return -1; + } + } + + return 0; +} + +int tst_process_state_wait2(pid_t pid, const char state) +{ + char proc_path[128], cur_state; + + snprintf(proc_path, sizeof(proc_path), "/proc/%i/stat", pid); + + for (;;) { + FILE *f = fopen(proc_path, "r"); + if (!f) { + fprintf(stderr, "Failed to open '%s': %s\n", + proc_path, strerror(errno)); + return 1; + } + + if (fscanf(f, "%*i %*s %c", &cur_state) != 1) { + fclose(f); + fprintf(stderr, "Failed to read '%s': %s\n", + proc_path, strerror(errno)); + return 1; + } + fclose(f); + + if (state == cur_state) + return 0; + + usleep(10000); + } +} diff --git a/src/kernel/tests/lib/tst_res.c b/src/kernel/tests/lib/tst_res.c new file mode 100644 index 0000000..c35f41b --- /dev/null +++ b/src/kernel/tests/lib/tst_res.c @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * AUTHOR : Kent Rogers (from Dave Fenner's original) + * CO-PILOT : Rich Logan + * DATE STARTED : 05/01/90 (rewritten 1/96) + * Copyright (c) 2009-2016 Cyril Hrubis <chrubis@suse.cz> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ + */ + +#define _GNU_SOURCE + +#include <pthread.h> +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "test.h" +#include "safe_macros.h" +#include "usctest.h" +#include "ltp_priv.h" +#include "tst_ansi_color.h" + +long TEST_RETURN; +int TEST_ERRNO; +void *TST_RET_PTR; + +#define VERBOSE 1 +#define NOPASS 3 +#define DISCARD 4 + +#define MAXMESG 80 /* max length of internal messages */ +#define USERMESG 2048 /* max length of user message */ +#define TRUE 1 +#define FALSE 0 + +/* + * EXPAND_VAR_ARGS - Expand the variable portion (arg_fmt) of a result + * message into the specified string. + * + * NOTE (garrcoop): arg_fmt _must_ be the last element in each function + * argument list that employs this. + */ +#define EXPAND_VAR_ARGS(buf, arg_fmt, buf_len) do {\ + va_list ap; \ + assert(arg_fmt != NULL); \ + va_start(ap, arg_fmt); \ + vsnprintf(buf, buf_len, arg_fmt, ap); \ + va_end(ap); \ + assert(strlen(buf) > 0); \ +} while (0) + +#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP +# ifdef __ANDROID__ +# define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP \ + PTHREAD_RECURSIVE_MUTEX_INITIALIZER +# else +/* MUSL: http://www.openwall.com/lists/musl/2017/02/20/5 */ +# define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP { {PTHREAD_MUTEX_RECURSIVE} } +# endif +#endif + +static pthread_mutex_t tmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; + +static void check_env(void); +static void tst_condense(int tnum, int ttype, const char *tmesg); +static void tst_print(const char *tcid, int tnum, int ttype, const char *tmesg); + +static int T_exitval = 0; /* exit value used by tst_exit() */ +static int passed_cnt; +static int T_mode = VERBOSE; /* flag indicating print mode: VERBOSE, */ + /* NOPASS, DISCARD */ + +static char Warn_mesg[MAXMESG]; /* holds warning messages */ + +/* + * These are used for condensing output when NOT in verbose mode. + */ +static int Buffered = FALSE; /* TRUE if condensed output is currently */ + /* buffered (i.e. not yet printed) */ +static char *Last_tcid; /* previous test case id */ +static int Last_num; /* previous test case number */ +static int Last_type; /* previous test result type */ +static char *Last_mesg; /* previous test result message */ + +int tst_count = 0; + +/* + * These globals must be defined in the test. + */ +extern char *TCID; /* Test case identifier from the test source */ +extern int TST_TOTAL; /* Total number of test cases from the test */ + + +struct pair { + const char *name; + int val; +}; + +#define PAIR(def) [def] = {.name = #def, .val = def}, +#define STRPAIR(key, value) [key] = {.name = value, .val = key}, + +#define PAIR_LOOKUP(pair_arr, idx) do { \ + if (idx < 0 || (size_t)idx >= ARRAY_SIZE(pair_arr) || \ + pair_arr[idx].name == NULL) \ + return "???"; \ + return pair_arr[idx].name; \ +} while (0) + +const char *strttype(int ttype) +{ + static const struct pair ttype_pairs[] = { + PAIR(TPASS) + PAIR(TFAIL) + PAIR(TBROK) + PAIR(TCONF) + PAIR(TWARN) + PAIR(TINFO) + }; + + PAIR_LOOKUP(ttype_pairs, TTYPE_RESULT(ttype)); +} + +#include "errnos.h" +#include "signame.h" + +static void tst_res__(const char *file, const int lineno, int ttype, + const char *arg_fmt, ...) +{ + pthread_mutex_lock(&tmutex); + + char tmesg[USERMESG]; + int len = 0; + int ttype_result = TTYPE_RESULT(ttype); + + if (file && (ttype_result != TPASS && ttype_result != TINFO)) + len = sprintf(tmesg, "%s:%d: ", file, lineno); + EXPAND_VAR_ARGS(tmesg + len, arg_fmt, USERMESG - len); + + /* + * Save the test result type by ORing ttype into the current exit + * value (used by tst_exit()). + */ + T_exitval |= ttype_result; + + if (ttype_result == TPASS) + passed_cnt++; + + check_env(); + + /* + * Set the test case number and print the results, depending on the + * display type. + */ + if (ttype_result == TWARN || ttype_result == TINFO) { + tst_print(TCID, 0, ttype, tmesg); + } else { + if (tst_count < 0) + tst_print(TCID, 0, TWARN, + "tst_res(): tst_count < 0 is not valid"); + + /* + * Process each display type. + */ + switch (T_mode) { + case DISCARD: + break; + case NOPASS: /* filtered by tst_print() */ + tst_condense(tst_count + 1, ttype, tmesg); + break; + default: /* VERBOSE */ + tst_print(TCID, tst_count + 1, ttype, tmesg); + break; + } + + tst_count++; + } + + pthread_mutex_unlock(&tmutex); +} + +static void tst_condense(int tnum, int ttype, const char *tmesg) +{ + int ttype_result = TTYPE_RESULT(ttype); + + /* + * If this result is the same as the previous result, return. + */ + if (Buffered == TRUE) { + if (strcmp(Last_tcid, TCID) == 0 && Last_type == ttype_result && + strcmp(Last_mesg, tmesg) == 0) + return; + + /* + * This result is different from the previous result. First, + * print the previous result. + */ + tst_print(Last_tcid, Last_num, Last_type, Last_mesg); + free(Last_tcid); + free(Last_mesg); + } + + /* + * If a file was specified, print the current result since we have no + * way of retaining the file contents for comparing with future + * results. Otherwise, buffer the current result info for next time. + */ + Last_tcid = malloc(strlen(TCID) + 1); + strcpy(Last_tcid, TCID); + Last_num = tnum; + Last_type = ttype_result; + Last_mesg = malloc(strlen(tmesg) + 1); + strcpy(Last_mesg, tmesg); + Buffered = TRUE; +} + +void tst_old_flush(void) +{ + NO_NEWLIB_ASSERT("Unknown", 0); + + pthread_mutex_lock(&tmutex); + + /* + * Print out last line if in NOPASS mode. + */ + if (Buffered == TRUE && T_mode == NOPASS) { + tst_print(Last_tcid, Last_num, Last_type, Last_mesg); + Buffered = FALSE; + } + + fflush(stdout); + + pthread_mutex_unlock(&tmutex); +} + +static void tst_print(const char *tcid, int tnum, int ttype, const char *tmesg) +{ + int err = errno; + const char *type; + int ttype_result = TTYPE_RESULT(ttype); + char message[USERMESG]; + size_t size = 0; + + /* + * Save the test result type by ORing ttype into the current exit value + * (used by tst_exit()). This is already done in tst_res(), but is + * also done here to catch internal warnings. For internal warnings, + * tst_print() is called directly with a case of TWARN. + */ + T_exitval |= ttype_result; + + /* + * If output mode is DISCARD, or if the output mode is NOPASS and this + * result is not one of FAIL, BROK, or WARN, just return. This check + * is necessary even though we check for DISCARD mode inside of + * tst_res(), since occasionally we get to this point without going + * through tst_res() (e.g. internal TWARN messages). + */ + if (T_mode == DISCARD || (T_mode == NOPASS && ttype_result != TFAIL && + ttype_result != TBROK + && ttype_result != TWARN)) + return; + + /* + * Build the result line and print it. + */ + type = strttype(ttype); + + if (T_mode == VERBOSE) { + size += snprintf(message + size, sizeof(message) - size, + "%-8s %4d ", tcid, tnum); + } else { + size += snprintf(message + size, sizeof(message) - size, + "%-8s %4d ", tcid, tnum); + } + + if (size >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + if (tst_color_enabled(STDOUT_FILENO)) + size += snprintf(message + size, sizeof(message) - size, + "%s%s%s : %s", tst_ttype2color(ttype), type, ANSI_COLOR_RESET, tmesg); + else + size += snprintf(message + size, sizeof(message) - size, + "%s : %s", type, tmesg); + + if (size >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + if (ttype & TERRNO) { + size += snprintf(message + size, sizeof(message) - size, + ": errno=%s(%i): %s", tst_strerrno(err), + err, strerror(err)); + } + + if (size >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + if (ttype & TTERRNO) { + size += snprintf(message + size, sizeof(message) - size, + ": TEST_ERRNO=%s(%i): %s", + tst_strerrno(TEST_ERRNO), (int)TEST_ERRNO, + strerror(TEST_ERRNO)); + } + + if (size >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + if (ttype & TRERRNO) { + err = TEST_RETURN < 0 ? -(int)TEST_RETURN : (int)TEST_RETURN; + size += snprintf(message + size, sizeof(message) - size, + ": TEST_RETURN=%s(%i): %s", + tst_strerrno(err), err, strerror(err)); + } + + if (size + 1 >= sizeof(message)) { + printf("%s: %i: line too long\n", __func__, __LINE__); + abort(); + } + + message[size] = '\n'; + message[size + 1] = '\0'; + + fputs(message, stdout); +} + +static void check_env(void) +{ + static int first_time = 1; + char *value; + + if (!first_time) + return; + + first_time = 0; + + /* BTOUTPUT not defined, use default */ + if ((value = getenv(TOUTPUT)) == NULL) { + T_mode = VERBOSE; + return; + } + + if (strcmp(value, TOUT_NOPASS_S) == 0) { + T_mode = NOPASS; + return; + } + + if (strcmp(value, TOUT_DISCARD_S) == 0) { + T_mode = DISCARD; + return; + } + + T_mode = VERBOSE; + return; +} + +void tst_exit(void) +{ + NO_NEWLIB_ASSERT("Unknown", 0); + + pthread_mutex_lock(&tmutex); + + tst_old_flush(); + + T_exitval &= ~TINFO; + + if (T_exitval == TCONF && passed_cnt) + T_exitval &= ~TCONF; + + exit(T_exitval); +} + +pid_t tst_fork(void) +{ + pid_t child; + + NO_NEWLIB_ASSERT("Unknown", 0); + + tst_old_flush(); + + child = fork(); + if (child == 0) + T_exitval = 0; + + return child; +} + +void tst_record_childstatus(void (*cleanup)(void), pid_t child) +{ + int status, ttype_result; + + NO_NEWLIB_ASSERT("Unknown", 0); + + SAFE_WAITPID(cleanup, child, &status, 0); + + if (WIFEXITED(status)) { + ttype_result = WEXITSTATUS(status); + ttype_result = TTYPE_RESULT(ttype_result); + T_exitval |= ttype_result; + + if (ttype_result == TPASS) + tst_resm(TINFO, "Child process returned TPASS"); + + if (ttype_result & TFAIL) + tst_resm(TINFO, "Child process returned TFAIL"); + + if (ttype_result & TBROK) + tst_resm(TINFO, "Child process returned TBROK"); + + if (ttype_result & TCONF) + tst_resm(TINFO, "Child process returned TCONF"); + + } else { + tst_brkm(TBROK, cleanup, "child process(%d) killed by " + "unexpected signal %s(%d)", child, + tst_strsig(WTERMSIG(status)), WTERMSIG(status)); + } +} + +pid_t tst_vfork(void) +{ + NO_NEWLIB_ASSERT("Unknown", 0); + + tst_old_flush(); + return vfork(); +} + +/* + * Make tst_brk reentrant so that one can call the SAFE_* macros from within + * user-defined cleanup functions. + */ +static int tst_brk_entered = 0; + +static void tst_brk__(const char *file, const int lineno, int ttype, + void (*func)(void), const char *arg_fmt, ...) +{ + pthread_mutex_lock(&tmutex); + + char tmesg[USERMESG]; + int ttype_result = TTYPE_RESULT(ttype); + + EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); + + /* + * Only FAIL, BROK, CONF, and RETR are supported by tst_brk(). + */ + if (ttype_result != TFAIL && ttype_result != TBROK && + ttype_result != TCONF) { + sprintf(Warn_mesg, "%s: Invalid Type: %d. Using TBROK", + __func__, ttype_result); + tst_print(TCID, 0, TWARN, Warn_mesg); + /* Keep TERRNO, TTERRNO, etc. */ + ttype = (ttype & ~ttype_result) | TBROK; + } + + tst_res__(file, lineno, ttype, "%s", tmesg); + if (tst_brk_entered == 0) { + if (ttype_result == TCONF) { + tst_res__(file, lineno, ttype, + "Remaining cases not appropriate for " + "configuration"); + } else if (ttype_result == TBROK) { + tst_res__(file, lineno, TBROK, + "Remaining cases broken"); + } + } + + /* + * If no cleanup function was specified, just return to the caller. + * Otherwise call the specified function. + */ + if (func != NULL) { + tst_brk_entered++; + (*func) (); + tst_brk_entered--; + } + if (tst_brk_entered == 0) + tst_exit(); + + pthread_mutex_unlock(&tmutex); +} + +void tst_resm_(const char *file, const int lineno, int ttype, + const char *arg_fmt, ...) +{ + char tmesg[USERMESG]; + + EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); + + if (tst_test) + tst_res_(file, lineno, ttype, "%s", tmesg); + else + tst_res__(file, lineno, ttype, "%s", tmesg); +} + +typedef void (*tst_res_func_t)(const char *file, const int lineno, + int ttype, const char *fmt, ...); + +void tst_resm_hexd_(const char *file, const int lineno, int ttype, + const void *buf, size_t size, const char *arg_fmt, ...) +{ + char tmesg[USERMESG]; + static const size_t symb_num = 2; /* xx */ + static const size_t size_max = 16; + size_t offset; + size_t i; + char *pmesg = tmesg; + tst_res_func_t res_func; + + if (tst_test) + res_func = tst_res_; + else + res_func = tst_res__; + + EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); + offset = strlen(tmesg); + + if (size > size_max || size == 0 || + (offset + size * (symb_num + 1)) >= USERMESG) + res_func(file, lineno, ttype, "%s", tmesg); + else + pmesg += offset; + + for (i = 0; i < size; ++i) { + /* add space before byte except first one */ + if (pmesg != tmesg) + *(pmesg++) = ' '; + + sprintf(pmesg, "%02x", ((unsigned char *)buf)[i]); + pmesg += symb_num; + if ((i + 1) % size_max == 0 || i + 1 == size) { + res_func(file, lineno, ttype, "%s", tmesg); + pmesg = tmesg; + } + } +} + +void tst_brkm_(const char *file, const int lineno, int ttype, + void (*func)(void), const char *arg_fmt, ...) +{ + char tmesg[USERMESG]; + + EXPAND_VAR_ARGS(tmesg, arg_fmt, USERMESG); + + if (tst_test) { + if (func) { + tst_brk_(file, lineno, TBROK, + "Non-NULL cleanup in newlib!"); + } + + tst_brk_(file, lineno, ttype, "%s", tmesg); + } else { + tst_brk__(file, lineno, ttype, func, "%s", tmesg); + } + + /* Shouldn't be reached, but fixes build time warnings about noreturn. */ + abort(); +} + +void tst_require_root(void) +{ + NO_NEWLIB_ASSERT("Unknown", 0); + + if (geteuid() != 0) + tst_brkm(TCONF, NULL, "Test needs to be run as root"); +} diff --git a/src/kernel/tests/lib/tst_resource.c b/src/kernel/tests/lib/tst_resource.c new file mode 100644 index 0000000..0b9b381 --- /dev/null +++ b/src/kernel/tests/lib/tst_resource.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2012 Cyril Hrubis chrubis@suse.cz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <pthread.h> +#include "test.h" +#include "old_resource.h" +#include "ltp_priv.h" + +#ifndef PATH_MAX +#ifdef MAXPATHLEN +#define PATH_MAX MAXPATHLEN +#else +#define PATH_MAX 1024 +#endif +#endif + +static pthread_mutex_t tmutex = PTHREAD_MUTEX_INITIALIZER; +static char dataroot[PATH_MAX]; +extern char *TCID; + +static void tst_dataroot_init(void) +{ + const char *ltproot = getenv("LTPROOT"); + char curdir[PATH_MAX]; + const char *startdir; + int ret; + + /* 1. if LTPROOT is set, use $LTPROOT/testcases/data/$TCID + * 2. else if startwd is set by tst_tmpdir(), use $STARWD/datafiles + * 3. else use $CWD/datafiles */ + if (ltproot) { + ret = snprintf(dataroot, PATH_MAX, "%s/testcases/data/%s", + ltproot, TCID); + } else { + startdir = tst_get_startwd(); + if (startdir[0] == 0) { + if (getcwd(curdir, PATH_MAX) == NULL) { + tst_brkm(TBROK | TERRNO, NULL, + "tst_dataroot getcwd"); + return; + } + startdir = curdir; + } + ret = snprintf(dataroot, PATH_MAX, "%s/datafiles", startdir); + } + + if (ret < 0 || ret >= PATH_MAX) + tst_brkm(TBROK, NULL, "tst_dataroot snprintf: %d", ret); +} + +const char *tst_dataroot(void) +{ + if (dataroot[0] == 0) { + pthread_mutex_lock(&tmutex); + if (dataroot[0] == 0) + tst_dataroot_init(); + pthread_mutex_unlock(&tmutex); + } + return dataroot; +} + +static int file_copy(const char *file, const int lineno, + void (*cleanup_fn)(void), const char *path, + const char *filename, const char *dest) +{ + size_t len = strlen(path) + strlen(filename) + 2; + char buf[len]; + + snprintf(buf, sizeof(buf), "%s/%s", path, filename); + + /* check if file exists */ + if (access(buf, R_OK)) + return 0; + + safe_cp(file, lineno, cleanup_fn, buf, dest); + + return 1; +} + +void tst_resource_copy(const char *file, const int lineno, + void (*cleanup_fn)(void), + const char *filename, const char *dest) +{ + if (!tst_tmpdir_created()) { + tst_brkm(TBROK, cleanup_fn, + "Temporary directory doesn't exist at %s:%d", + file, lineno); + return; + } + + if (dest == NULL) + dest = "."; + + const char *ltproot = getenv("LTPROOT"); + const char *dataroot = tst_dataroot(); + + /* look for data files in $LTP_DATAROOT, $LTPROOT/testcases/bin + * and $CWD */ + if (file_copy(file, lineno, cleanup_fn, dataroot, filename, dest)) + return; + + if (ltproot != NULL) { + char buf[strlen(ltproot) + 64]; + + snprintf(buf, sizeof(buf), "%s/testcases/bin", ltproot); + + if (file_copy(file, lineno, cleanup_fn, buf, filename, dest)) + return; + } + + /* try directory test started in as last resort */ + const char *startwd = tst_get_startwd(); + if (file_copy(file, lineno, cleanup_fn, startwd, filename, dest)) + return; + + tst_brkm(TBROK, cleanup_fn, "Failed to copy resource '%s' at %s:%d", + filename, file, lineno); +} diff --git a/src/kernel/tests/lib/tst_safe_macros.c b/src/kernel/tests/lib/tst_safe_macros.c new file mode 100644 index 0000000..25c37df --- /dev/null +++ b/src/kernel/tests/lib/tst_safe_macros.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz> + */ + +#define _GNU_SOURCE +#include <unistd.h> +#include <errno.h> +#include <sched.h> +#include <sys/ptrace.h> +#include "config.h" +#ifdef HAVE_SYS_FANOTIFY_H +# include <sys/fanotify.h> +#endif +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "lapi/setns.h" +#include "tst_safe_macros.h" +#include "lapi/personality.h" + +int safe_setpgid(const char *file, const int lineno, pid_t pid, pid_t pgid) +{ + int rval; + + rval = setpgid(pid, pgid); + if (rval) { + tst_brk(TBROK | TERRNO, + "%s:%d: setpgid(%i, %i) failed", + file, lineno, pid, pgid); + } + + return rval; +} + +pid_t safe_getpgid(const char *file, const int lineno, pid_t pid) +{ + pid_t pgid; + + pgid = getpgid(pid); + if (pgid == -1) { + tst_brk(TBROK | TERRNO, + "%s:%d: getpgid(%i) failed", file, lineno, pid); + } + + return pgid; +} + +int safe_fanotify_init(const char *file, const int lineno, + unsigned int flags, unsigned int event_f_flags) +{ + int rval; + +#ifdef HAVE_SYS_FANOTIFY_H + rval = fanotify_init(flags, event_f_flags); + + if (rval == -1) { + if (errno == ENOSYS) { + tst_brk(TCONF, + "fanotify is not configured in this kernel."); + } + tst_brk(TBROK | TERRNO, + "%s:%d: fanotify_init() failed", file, lineno); + } +#else + tst_brk(TCONF, "Header <sys/fanotify.h> is not present"); +#endif /* HAVE_SYS_FANOTIFY_H */ + + return rval; +} + +int safe_personality(const char *filename, unsigned int lineno, + unsigned long persona) +{ + int prev_persona = personality(persona); + + if (prev_persona < 0) { + tst_brk_(filename, lineno, TBROK | TERRNO, + "persona(%ld) failed", persona); + } + + return prev_persona; +} + +int safe_setregid(const char *file, const int lineno, + gid_t rgid, gid_t egid) +{ + int rval; + + rval = setregid(rgid, egid); + if (rval == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, + "setregid(%li, %li) failed", + (long)rgid, (long)egid); + } + + return rval; +} + + +int safe_setreuid(const char *file, const int lineno, + uid_t ruid, uid_t euid) +{ + int rval; + + rval = setreuid(ruid, euid); + if (rval == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, + "setreuid(%li, %li) failed", + (long)ruid, (long)euid); + } + + return rval; +} + + +int safe_sigaction(const char *file, const int lineno, + int signum, const struct sigaction *act, + struct sigaction *oldact) +{ + int rval; + + rval = sigaction(signum, act, oldact); + + if (rval == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, + "sigaction(%s (%d), %p, %p) failed", + tst_strsig(signum), signum, act, oldact); + } + + return rval; +} + +void safe_sigaddset(const char *file, const int lineno, + sigset_t *sigs, int signo) +{ + int rval; + + rval = sigaddset(sigs, signo); + if (rval == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, + "sigaddset() %s (%i) failed", + tst_strsig(signo), signo); + } +} + +void safe_sigdelset(const char *file, const int lineno, + sigset_t *sigs, int signo) +{ + int rval; + + rval = sigdelset(sigs, signo); + if (rval == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, + "sigdelset() %s (%i) failed", + tst_strsig(signo), signo); + } +} + +void safe_sigemptyset(const char *file, const int lineno, + sigset_t *sigs) +{ + int rval; + + rval = sigemptyset(sigs); + if (rval == -1) + tst_brk_(file, lineno, TBROK | TERRNO, "sigemptyset() failed"); +} + +void safe_sigfillset(const char *file, const int lineno, + sigset_t *sigs) +{ + int rval; + + rval = sigfillset(sigs); + if (rval == -1) + tst_brk_(file, lineno, TBROK | TERRNO, "sigfillset() failed"); +} + +static const char *strhow(int how) +{ + switch (how) { + case SIG_BLOCK: + return "SIG_BLOCK"; + case SIG_UNBLOCK: + return "SIG_UNBLOCK"; + case SIG_SETMASK: + return "SIG_SETMASK"; + default: + return "???"; + } +} + +void safe_sigprocmask(const char *file, const int lineno, + int how, sigset_t *set, sigset_t *oldset) +{ + int rval; + + rval = sigprocmask(how, set, oldset); + if (rval == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, + "sigprocmask(%s, %p, %p)", strhow(how), set, oldset); + } +} + +void safe_sigwait(const char *file, const int lineno, + sigset_t *set, int *sig) +{ + int rval; + + rval = sigwait(set, sig); + if (rval != 0) { + errno = rval; + tst_brk_(file, lineno, TBROK, "sigwait(%p, %p)", set, sig); + } +} + +struct group *safe_getgrnam(const char *file, const int lineno, + const char *name) +{ + struct group *rval; + + errno = 0; + rval = getgrnam(name); + if (rval == NULL) { + tst_brk_(file, lineno, TBROK | TERRNO, + "getgrnam(%s) failed", name); + } + + return rval; +} + +struct group *safe_getgrnam_fallback(const char *file, const int lineno, + const char *name, const char *fallback) +{ + struct group *rval; + + errno = 0; + rval = getgrnam(name); + if (rval == NULL) { + tst_res_(file, lineno, TINFO, + "getgrnam(%s) failed - try fallback %s", + name, fallback); + rval = safe_getgrnam(file, lineno, fallback); + } + + return rval; +} + +struct group *safe_getgrgid(const char *file, const int lineno, gid_t gid) +{ + struct group *rval; + + errno = 0; + rval = getgrgid(gid); + if (rval == NULL) { + tst_brk_(file, lineno, TBROK | TERRNO, + "getgrgid(%li) failed", (long)gid); + } + + return rval; +} + +int safe_chroot(const char *file, const int lineno, const char *path) +{ + int rval; + + rval = chroot(path); + if (rval == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, + "chroot(%s) failed", path); + } + + return rval; +} + +void safe_unshare(const char *file, const int lineno, int flags) +{ + int res; + + res = unshare(flags); + if (res == -1) { + if (errno == EINVAL) { + tst_brk_(file, lineno, TCONF | TERRNO, + "unshare(%d) unsupported", flags); + } else { + tst_brk_(file, lineno, TBROK | TERRNO, + "unshare(%d) failed", flags); + } + } +} + +void safe_setns(const char *file, const int lineno, int fd, int nstype) +{ + int ret; + + ret = setns(fd, nstype); + if (ret == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, "setns(%i, %i) failed", + fd, nstype); + } +} + +long tst_safe_ptrace(const char *file, const int lineno, int req, pid_t pid, + void *addr, void *data) +{ + long ret; + + errno = 0; + ret = ptrace(req, pid, addr, data); + + if (ret == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, "ptrace() failed"); + } else if (ret) { + tst_brk_(file, lineno, TBROK | TERRNO, + "Invalid ptrace() return value %ld", ret); + } + + return ret; +} + +int safe_pipe2(const char *file, const int lineno, int fildes[2], int flags) +{ + int ret; + + ret = pipe2(fildes, flags); + if (ret == -1) { + tst_brk_(file, lineno, TBROK | TERRNO, + "pipe2({%d,%d}) failed with flag(%d)", + fildes[0], fildes[1], flags); + } + + return ret; +} diff --git a/src/kernel/tests/lib/tst_safe_sysv_ipc.c b/src/kernel/tests/lib/tst_safe_sysv_ipc.c new file mode 100644 index 0000000..30b5f6e --- /dev/null +++ b/src/kernel/tests/lib/tst_safe_sysv_ipc.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017 Xiao yang <yangx.jy@cn.fujitsu.com> + */ + +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/msg.h> +#include <sys/shm.h> +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_safe_sysv_ipc.h" + +/* + * The IPC_STAT, IPC_SET and IPC_RMID can return either 0 or -1. + * + * Linux specific cmds either returns -1 on failure or positive integer + * either index into an kernel array or shared primitive indentifier. + */ +static int ret_check(int cmd, int ret) +{ + switch (cmd) { + case IPC_STAT: + case IPC_SET: + case IPC_RMID: + return ret != 0; + default: + return ret == -1; + } +} + +int safe_msgget(const char *file, const int lineno, key_t key, int msgflg) +{ + int rval; + + rval = msgget(key, msgflg); + if (rval == -1) { + tst_brk(TBROK | TERRNO, "%s:%d: msgget(%i, %x) failed", + file, lineno, (int)key, msgflg); + } + + return rval; +} + +int safe_msgsnd(const char *file, const int lineno, int msqid, const void *msgp, + size_t msgsz, int msgflg) +{ + int rval; + + rval = msgsnd(msqid, msgp, msgsz, msgflg); + if (rval == -1) { + tst_brk(TBROK | TERRNO, + "%s:%d: msgsnd(%i, %p, %zu, %x) failed", + file, lineno, msqid, msgp, msgsz, msgflg); + } + + return rval; +} + +ssize_t safe_msgrcv(const char *file, const int lineno, int msqid, void *msgp, + size_t msgsz, long msgtyp, int msgflg) +{ + ssize_t rval; + + rval = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg); + if (rval == -1) { + tst_brk(TBROK | TERRNO, + "%s:%d: msgrcv(%i, %p, %zu, %li, %x) failed", + file, lineno, msqid, msgp, msgsz, msgtyp, msgflg); + } + + return rval; +} + +int safe_msgctl(const char *file, const int lineno, int msqid, int cmd, + struct msqid_ds *buf) +{ + int rval; + + rval = msgctl(msqid, cmd, buf); + if (ret_check(cmd, rval)) { + tst_brk(TBROK | TERRNO, + "%s:%d: msgctl(%i, %i, %p) = %i failed", + file, lineno, msqid, cmd, buf, rval); + } + + + return rval; +} + +int safe_shmget(const char *file, const int lineno, key_t key, size_t size, + int shmflg) +{ + int rval; + + rval = shmget(key, size, shmflg); + if (rval == -1) { + tst_brk(TBROK | TERRNO, "%s:%d: shmget(%i, %zu, %x) failed", + file, lineno, (int)key, size, shmflg); + } + + return rval; +} + +void *safe_shmat(const char *file, const int lineno, int shmid, + const void *shmaddr, int shmflg) +{ + void *rval; + + rval = shmat(shmid, shmaddr, shmflg); + if (rval == (void *)-1) { + tst_brk(TBROK | TERRNO, "%s:%d: shmat(%i, %p, %x) failed", + file, lineno, shmid, shmaddr, shmflg); + } + + return rval; +} + +int safe_shmdt(const char *file, const int lineno, const void *shmaddr) +{ + int rval; + + rval = shmdt(shmaddr); + if (rval == -1) { + tst_brk(TBROK | TERRNO, "%s:%d: shmdt(%p) failed", + file, lineno, shmaddr); + } + + return rval; +} + +int safe_shmctl(const char *file, const int lineno, int shmid, int cmd, + struct shmid_ds *buf) +{ + int rval; + + rval = shmctl(shmid, cmd, buf); + if (ret_check(cmd, rval)) { + tst_brk(TBROK | TERRNO, + "%s:%d: shmctl(%i, %i, %p) = %i failed", + file, lineno, shmid, cmd, buf, rval); + } + + return rval; +} diff --git a/src/kernel/tests/lib/tst_safe_timerfd.c b/src/kernel/tests/lib/tst_safe_timerfd.c new file mode 100644 index 0000000..ffe7b2e --- /dev/null +++ b/src/kernel/tests/lib/tst_safe_timerfd.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020 Petr Vorel <pvorel@suse.cz> + */ + +#include "tst_safe_timerfd.h" +#include "lapi/timerfd.h" +#include "tst_clocks.h" +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" + +#define TTYPE (errno == ENOTSUP ? TCONF : TBROK) + +int safe_timerfd_create(const char *file, const int lineno, + int clockid, int flags) +{ + int fd; + + fd = timerfd_create(clockid, flags); + if (fd < 0) { + tst_brk(TTYPE | TERRNO, "%s:%d timerfd_create(%s) failed", + file, lineno, tst_clock_name(clockid)); + } + + return fd; +} + +int safe_timerfd_gettime(const char *file, const int lineno, + int fd, struct itimerspec *curr_value) +{ + int rval; + + rval = timerfd_gettime(fd, curr_value); + if (rval != 0) { + tst_brk(TTYPE | TERRNO, "%s:%d timerfd_gettime() failed", + file, lineno); + } + + return rval; +} + +int safe_timerfd_settime(const char *file, const int lineno, + int fd, int flags, + const struct itimerspec *new_value, + struct itimerspec *old_value) +{ + int rval; + + rval = timerfd_settime(fd, flags, new_value, old_value); + if (rval != 0) { + tst_brk(TTYPE | TERRNO, "%s:%d timerfd_settime() failed", + file, lineno); + } + + return rval; +} diff --git a/src/kernel/tests/lib/tst_sig.c b/src/kernel/tests/lib/tst_sig.c new file mode 100644 index 0000000..6d77aea --- /dev/null +++ b/src/kernel/tests/lib/tst_sig.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ + */ + +/* $Id: tst_sig.c,v 1.13 2009/08/28 09:29:01 vapier Exp $ */ + +/***************************************************************************** + OS Testing - Silicon Graphics, Inc. + + FUNCTION IDENTIFIER : tst_sig Set up for unexpected signals. + + AUTHOR : David D. Fenner + + CO-PILOT : Bill Roske + + DATE STARTED : 06/06/90 + + This module may be linked with c-modules requiring unexpected + signal handling. The parameters to tst_sig are as follows: + + fork_flag - set to FORK or NOFORK depending upon whether the + calling program executes a fork() system call. It + is normally the case that the calling program treats + SIGCHLD as an expected signal if fork() is being used. + + handler - a pointer to the unexpected signal handler to + be executed after an unexpected signal has been + detected. If handler is set to DEF_HANDLER, a + default handler is used. This routine should be + declared as function returning an int. + + cleanup - a pointer to a cleanup routine to be executed + by the unexpected signal handler before tst_exit is + called. This parameter is set to NULL if no cleanup + routine is required. An external variable, T_cleanup + is set so that other user-defined handlers have + access to the cleanup routine. This routine should be + declared as returning type void. + +***************************************************************************/ + +#include <errno.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#include "test.h" +#include "lapi/signal.h" + +#define MAXMESG 150 /* size of mesg string sent to tst_res */ + +static void (*T_cleanup) (); + +static void def_handler(); /* default signal handler */ +static void (*tst_setup_signal(int, void (*)(int))) (int); + +/**************************************************************************** + * tst_sig() : set-up to catch unexpected signals. fork_flag is set to NOFORK + * if SIGCHLD is to be an "unexpected signal", otherwise it is set to + * FORK. cleanup points to a cleanup routine to be executed before + * tst_exit is called (cleanup is set to NULL if no cleanup is desired). + * handler is a pointer to the signal handling routine (if handler is + * set to NULL, a default handler is used). + ***************************************************************************/ + +void tst_sig(int fork_flag, void (*handler) (), void (*cleanup) ()) +{ + int sig; +#ifdef _SC_SIGRT_MIN + long sigrtmin, sigrtmax; +#endif + + /* + * save T_cleanup and handler function pointers + */ + T_cleanup = cleanup; /* used by default handler */ + + if (handler == DEF_HANDLER) { + /* use default handler */ + handler = def_handler; + } +#ifdef _SC_SIGRT_MIN + sigrtmin = sysconf(_SC_SIGRT_MIN); + sigrtmax = sysconf(_SC_SIGRT_MAX); +#endif + + /* + * now loop through all signals and set the handlers + */ + + for (sig = 1; sig < NSIG; sig++) { + /* + * SIGKILL is never unexpected. + * SIGCHLD is only unexpected when + * no forking is being done. + * SIGINFO is used for file quotas and should be expected + */ + +#ifdef _SC_SIGRT_MIN + if (sig >= sigrtmin && sig <= sigrtmax) + continue; +#endif + + switch (sig) { + case SIGKILL: + case SIGSTOP: + case SIGCONT: +#if !defined(_SC_SIGRT_MIN) && defined(__SIGRTMIN) && defined(__SIGRTMAX) + /* Ignore all real-time signals */ + case __SIGRTMIN: + case __SIGRTMIN + 1: + case __SIGRTMIN + 2: + case __SIGRTMIN + 3: + case __SIGRTMIN + 4: + case __SIGRTMIN + 5: + case __SIGRTMIN + 6: + case __SIGRTMIN + 7: + case __SIGRTMIN + 8: + case __SIGRTMIN + 9: + case __SIGRTMIN + 10: + case __SIGRTMIN + 11: + case __SIGRTMIN + 12: + case __SIGRTMIN + 13: + case __SIGRTMIN + 14: + case __SIGRTMIN + 15: +/* __SIGRTMIN is 37 on HPPA rather than 32 * + * as on i386, etc. */ +#if !defined(__hppa__) + case __SIGRTMAX - 15: + case __SIGRTMAX - 14: + case __SIGRTMAX - 13: + case __SIGRTMAX - 12: + case __SIGRTMAX - 11: +#endif + case __SIGRTMAX - 10: + case __SIGRTMAX - 9: + case __SIGRTMAX - 8: + case __SIGRTMAX - 7: + case __SIGRTMAX - 6: + case __SIGRTMAX - 5: + case __SIGRTMAX - 4: + case __SIGRTMAX - 3: + case __SIGRTMAX - 2: + case __SIGRTMAX - 1: + case __SIGRTMAX: +#endif +#ifdef SIGSWAP + case SIGSWAP: +#endif /* SIGSWAP */ + +#ifdef SIGCKPT + case SIGCKPT: +#endif +#ifdef SIGRESTART + case SIGRESTART: +#endif + /* + * pthread-private signals SIGPTINTR and SIGPTRESCHED. + * Setting a handler for these signals is disallowed when + * the binary is linked against libpthread. + */ +#ifdef SIGPTINTR + case SIGPTINTR: +#endif /* SIGPTINTR */ +#ifdef SIGPTRESCHED + case SIGPTRESCHED: +#endif /* SIGPTRESCHED */ +#ifdef _SIGRESERVE + case _SIGRESERVE: +#endif +#ifdef _SIGDIL + case _SIGDIL: +#endif +#ifdef _SIGCANCEL + case _SIGCANCEL: +#endif +#ifdef _SIGGFAULT + case _SIGGFAULT: +#endif + break; + + case SIGCHLD: + if (fork_flag == FORK) + continue; + + default: + if (tst_setup_signal(sig, handler) == SIG_ERR) + tst_resm(TWARN | TERRNO, + "signal failed for signal %d", sig); + break; + } + } +} + +/**************************************************************************** + * def_handler() : default signal handler that is invoked when + * an unexpected signal is caught. + ***************************************************************************/ + +static void def_handler(int sig) +{ + /* + * Break remaining test cases, do any cleanup, then exit + */ + tst_brkm(TBROK, T_cleanup, + "unexpected signal %s(%d) received (pid = %d).", + tst_strsig(sig), sig, getpid()); +} + +/* + * tst_setup_signal - A function like signal(), but we have + * control over its personality. + */ +static void (*tst_setup_signal(int sig, void (*handler) (int))) (int) { + struct sigaction my_act, old_act; + int ret; + + my_act.sa_handler = handler; + my_act.sa_flags = SA_RESTART; + sigemptyset(&my_act.sa_mask); + + ret = sigaction(sig, &my_act, &old_act); + + if (ret == 0) + return old_act.sa_handler; + else + return SIG_ERR; +} diff --git a/src/kernel/tests/lib/tst_sig_proc.c b/src/kernel/tests/lib/tst_sig_proc.c new file mode 100644 index 0000000..509418a --- /dev/null +++ b/src/kernel/tests/lib/tst_sig_proc.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016 Linux Test Project + */ + +#include <stdlib.h> +#include <sys/types.h> + +#include "tst_sig_proc.h" + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" + +pid_t create_sig_proc(int sig, int count, unsigned int usec) +{ + pid_t pid, cpid; + + pid = getpid(); + cpid = SAFE_FORK(); + + if (cpid == 0) { + while (count-- > 0) { + usleep(usec); + if (kill(pid, sig) == -1) + break; + } + exit(0); + } + + return cpid; +} diff --git a/src/kernel/tests/lib/tst_status.c b/src/kernel/tests/lib/tst_status.c new file mode 100644 index 0000000..f1affea --- /dev/null +++ b/src/kernel/tests/lib/tst_status.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz> + */ + +#include <sys/types.h> +#include <sys/wait.h> +#include <stdio.h> +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" + +static char buf[32]; + +const char *exited(int status) +{ + snprintf(buf, sizeof(buf), "exited with %i", WEXITSTATUS(status)); + + return buf; +} + +const char *signaled(int status) +{ + snprintf(buf, sizeof(buf), "killed by %s", tst_strsig(status)); + + return buf; +} + +const char *invalid(int status) +{ + snprintf(buf, sizeof(buf), "invalid status 0x%x", status); + + return buf; +} + +const char *tst_strstatus(int status) +{ + if (WIFEXITED(status)) + return exited(status); + + if (WIFSIGNALED(status)) + return signaled(status); + + if (WIFSTOPPED(status)) + return "is stopped"; + + if (WIFCONTINUED(status)) + return "is resumed"; + + return invalid(status); +} diff --git a/src/kernel/tests/lib/tst_supported_fs_types.c b/src/kernel/tests/lib/tst_supported_fs_types.c new file mode 100644 index 0000000..00ede54 --- /dev/null +++ b/src/kernel/tests/lib/tst_supported_fs_types.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz> + */ + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <sys/wait.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_fs.h" + +static const char *const fs_type_whitelist[] = { + "ext2", + "ext3", + "ext4", + "xfs", + "btrfs", + "vfat", + "exfat", + "ntfs", + NULL +}; + +static const char *fs_types[ARRAY_SIZE(fs_type_whitelist)]; + +static int has_mkfs(const char *fs_type) +{ + char buf[128]; + int ret; + + sprintf(buf, "mkfs.%s >/dev/null 2>&1", fs_type); + + ret = tst_system(buf); + + if (WEXITSTATUS(ret) == 127) { + tst_res(TINFO, "mkfs.%s does not exist", fs_type); + return 0; + } + + tst_res(TINFO, "mkfs.%s does exist", fs_type); + return 1; +} + +static int has_kernel_support(const char *fs_type, int flags) +{ + static int fuse_supported = -1; + const char *tmpdir = getenv("TMPDIR"); + char buf[128]; + int ret; + + if (!tmpdir) + tmpdir = "/tmp"; + + mount("/dev/zero", tmpdir, fs_type, 0, NULL); + if (errno != ENODEV) { + tst_res(TINFO, "Kernel supports %s", fs_type); + return 1; + } + + /* Is FUSE supported by kernel? */ + if (fuse_supported == -1) { + ret = open("/dev/fuse", O_RDWR); + if (ret < 0) { + fuse_supported = 0; + } else { + fuse_supported = 1; + SAFE_CLOSE(ret); + } + } + + if (!fuse_supported) + return 0; + + /* Is FUSE implementation installed? */ + sprintf(buf, "mount.%s >/dev/null 2>&1", fs_type); + + ret = tst_system(buf); + if (WEXITSTATUS(ret) == 127) { + tst_res(TINFO, "Filesystem %s is not supported", fs_type); + return 0; + } + + if (flags & TST_FS_SKIP_FUSE) { + tst_res(TINFO, "Skipping FUSE as requested by the test"); + return 0; + } + + tst_res(TINFO, "FUSE does support %s", fs_type); + return 1; +} + +int tst_fs_is_supported(const char *fs_type, int flags) +{ + return has_kernel_support(fs_type, flags) && has_mkfs(fs_type); +} + +const char **tst_get_supported_fs_types(int flags) +{ + unsigned int i, j = 0; + + for (i = 0; fs_type_whitelist[i]; i++) { + if (tst_fs_is_supported(fs_type_whitelist[i], flags)) + fs_types[j++] = fs_type_whitelist[i]; + } + + return fs_types; +} diff --git a/src/kernel/tests/lib/tst_sys_conf.c b/src/kernel/tests/lib/tst_sys_conf.c new file mode 100644 index 0000000..4ad9f8b --- /dev/null +++ b/src/kernel/tests/lib/tst_sys_conf.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2018 Jan Stancek <jstancek@redhat.com> + */ + +#include <limits.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_sys_conf.h" + +static struct tst_sys_conf *save_restore_data; + +void tst_sys_conf_dump(void) +{ + struct tst_sys_conf *i; + + for (i = save_restore_data; i; i = i->next) + tst_res(TINFO, "%s = %s", i->path, i->value); +} + +int tst_sys_conf_save_str(const char *path, const char *value) +{ + struct tst_sys_conf *n = SAFE_MALLOC(sizeof(*n)); + + strncpy(n->path, path, sizeof(n->path)-1); + strncpy(n->value, value, sizeof(n->value)-1); + + n->path[sizeof(n->path) - 1] = 0; + n->value[sizeof(n->value) - 1] = 0; + + n->next = save_restore_data; + save_restore_data = n; + + return 0; +} + +int tst_sys_conf_save(const char *path) +{ + char line[PATH_MAX]; + FILE *fp; + void *ret; + char flag; + + if (!path) + tst_brk(TBROK, "path is empty"); + + flag = path[0]; + if (flag == '?' || flag == '!') + path++; + + if (access(path, F_OK) != 0) { + switch (flag) { + case '?': + tst_res(TINFO, "Path not found: '%s'", path); + break; + case '!': + tst_brk(TBROK|TERRNO, "Path not found: '%s'", path); + break; + default: + tst_brk(TCONF|TERRNO, "Path not found: '%s'", path); + } + return 1; + } + + fp = fopen(path, "r"); + if (fp == NULL) { + if (flag == '?') + return 1; + + tst_brk(TBROK | TERRNO, "Failed to open FILE '%s' for reading", + path); + return 1; + } + + ret = fgets(line, sizeof(line), fp); + fclose(fp); + + if (ret == NULL) { + if (flag == '?') + return 1; + + tst_brk(TBROK | TERRNO, "Failed to read anything from '%s'", + path); + } + + return tst_sys_conf_save_str(path, line); +} + +void tst_sys_conf_restore(int verbose) +{ + struct tst_sys_conf *i; + + for (i = save_restore_data; i; i = i->next) { + if (verbose) { + tst_res(TINFO, "Restoring conf.: %s -> %s\n", + i->path, i->value); + } + FILE_PRINTF(i->path, "%s", i->value); + } +} + diff --git a/src/kernel/tests/lib/tst_taint.c b/src/kernel/tests/lib/tst_taint.c new file mode 100644 index 0000000..49146aa --- /dev/null +++ b/src/kernel/tests/lib/tst_taint.c @@ -0,0 +1,107 @@ +#define TST_NO_DEFAULT_MAIN + +#include "tst_test.h" +#include "tst_taint.h" +#include "tst_safe_stdio.h" + +#define TAINT_FILE "/proc/sys/kernel/tainted" + +static unsigned int taint_mask = -1; + +static unsigned int tst_taint_read(void) +{ + unsigned int val; + + SAFE_FILE_SCANF(TAINT_FILE, "%u", &val); + + return val; +} + +static int tst_taint_check_kver(unsigned int mask) +{ + int r1; + int r2; + int r3 = 0; + + if (mask & TST_TAINT_X) { + r1 = 4; + r2 = 15; + } else if (mask & TST_TAINT_K) { + r1 = 4; + r2 = 0; + } else if (mask & TST_TAINT_L) { + r1 = 3; + r2 = 17; + } else if (mask & TST_TAINT_E) { + r1 = 3; + r2 = 15; + } else if (mask & TST_TAINT_O) { + r1 = 3; + r2 = 2; + } else if (mask & TST_TAINT_I) { + r1 = 2; + r2 = 6; + r3 = 35; + } else if (mask & TST_TAINT_C) { + r1 = 2; + r2 = 6; + r3 = 28; + } else if (mask & TST_TAINT_W) { + r1 = 2; + r2 = 6; + r3 = 26; + } else if (mask & TST_TAINT_A) { + r1 = 2; + r2 = 6; + r3 = 25; + } else if (mask & TST_TAINT_D) { + r1 = 2; + r2 = 6; + r3 = 23; + } else if (mask & TST_TAINT_U) { + r1 = 2; + r2 = 6; + r3 = 21; + } else { + r1 = 2; + r2 = 6; + r3 = 16; + } + + return tst_kvercmp(r1, r2, r3); +} + +void tst_taint_init(unsigned int mask) +{ + unsigned int taint = -1; + + if (mask == 0) + tst_brk(TBROK, "mask is not allowed to be 0"); + + if (tst_taint_check_kver(mask) < 0) + tst_res(TCONF, "Kernel is too old for requested mask"); + + taint_mask = mask; + taint = tst_taint_read(); + + if (taint & TST_TAINT_W) { + tst_res(TCONF, "Ignoring already set kernel warning taint"); + taint_mask &= ~TST_TAINT_W; + } + + if ((taint & taint_mask) != 0) + tst_brk(TBROK, "Kernel is already tainted: %u", taint); +} + + +unsigned int tst_taint_check(void) +{ + unsigned int taint = -1; + + if (taint_mask == (unsigned int) -1) + tst_brk(TBROK, "need to call tst_taint_init() first"); + + taint = tst_taint_read(); + + return (taint & taint_mask); +} diff --git a/src/kernel/tests/lib/tst_test.c b/src/kernel/tests/lib/tst_test.c new file mode 100644 index 0000000..135cd4e --- /dev/null +++ b/src/kernel/tests/lib/tst_test.c @@ -0,0 +1,1387 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2015-2016 Cyril Hrubis <chrubis@suse.cz> + */ + +#include <limits.h> +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <sys/wait.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_device.h" +#include "lapi/futex.h" +#include "lapi/syscalls.h" +#include "tst_ansi_color.h" +#include "tst_safe_stdio.h" +#include "tst_timer_test.h" +#include "tst_clocks.h" +#include "tst_timer.h" +#include "tst_wallclock.h" +#include "tst_sys_conf.h" +#include "tst_kconfig.h" + +#include "old_resource.h" +#include "old_device.h" +#include "old_tmpdir.h" + +/* + * Hack to get TCID defined in newlib tests + */ +const char *TCID __attribute__((weak)); + +#define LINUX_GIT_URL "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=" +#define CVE_DB_URL "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-" + +struct tst_test *tst_test; + +static const char *tid; +static int iterations = 1; +static float duration = -1; +static float timeout_mul = -1; +static pid_t main_pid, lib_pid; +static int mntpoint_mounted; +static int ovl_mounted; +static struct timespec tst_start_time; /* valid only for test pid */ + +struct results { + int passed; + int skipped; + int failed; + int warnings; + unsigned int timeout; +}; + +static struct results *results; + +static int ipc_fd; + +extern void *tst_futexes; +extern unsigned int tst_max_futexes; + +#define IPC_ENV_VAR "LTP_IPC_PATH" + +static char ipc_path[1064]; +const char *tst_ipc_path = ipc_path; + +static char shm_path[1024]; + +int TST_ERR; +long TST_RET; + +static void do_cleanup(void); +static void do_exit(int ret) __attribute__ ((noreturn)); + +static void setup_ipc(void) +{ + size_t size = getpagesize(); + + if (access("/dev/shm", F_OK) == 0) { + snprintf(shm_path, sizeof(shm_path), "/dev/shm/ltp_%s_%d", + tid, getpid()); + } else { + char *tmpdir; + + if (!tst_tmpdir_created()) + tst_tmpdir(); + + tmpdir = tst_get_tmpdir(); + snprintf(shm_path, sizeof(shm_path), "%s/ltp_%s_%d", + tmpdir, tid, getpid()); + free(tmpdir); + } + + ipc_fd = open(shm_path, O_CREAT | O_EXCL | O_RDWR, 0600); + if (ipc_fd < 0) + tst_brk(TBROK | TERRNO, "open(%s)", shm_path); + SAFE_CHMOD(shm_path, 0666); + + SAFE_FTRUNCATE(ipc_fd, size); + + results = SAFE_MMAP(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ipc_fd, 0); + + /* Checkpoints needs to be accessible from processes started by exec() */ + if (tst_test->needs_checkpoints || tst_test->child_needs_reinit) { + sprintf(ipc_path, IPC_ENV_VAR "=%s", shm_path); + putenv(ipc_path); + } else { + SAFE_UNLINK(shm_path); + } + + SAFE_CLOSE(ipc_fd); + + if (tst_test->needs_checkpoints) { + tst_futexes = (char*)results + sizeof(struct results); + tst_max_futexes = (size - sizeof(struct results))/sizeof(futex_t); + } +} + +static void cleanup_ipc(void) +{ + size_t size = getpagesize(); + + if (ipc_fd > 0 && close(ipc_fd)) + tst_res(TWARN | TERRNO, "close(ipc_fd) failed"); + + if (shm_path[0] && !access(shm_path, F_OK) && unlink(shm_path)) + tst_res(TWARN | TERRNO, "unlink(%s) failed", shm_path); + + if (results) { + msync((void*)results, size, MS_SYNC); + munmap((void*)results, size); + results = NULL; + } +} + +void tst_reinit(void) +{ + const char *path = getenv(IPC_ENV_VAR); + size_t size = getpagesize(); + int fd; + + if (!path) + tst_brk(TBROK, IPC_ENV_VAR" is not defined"); + + if (access(path, F_OK)) + tst_brk(TBROK, "File %s does not exist!", path); + + fd = SAFE_OPEN(path, O_RDWR); + + results = SAFE_MMAP(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + tst_futexes = (char*)results + sizeof(struct results); + tst_max_futexes = (size - sizeof(struct results))/sizeof(futex_t); + + SAFE_CLOSE(fd); +} + +static void update_results(int ttype) +{ + if (!results) + return; + + switch (ttype) { + case TCONF: + tst_atomic_inc(&results->skipped); + break; + case TPASS: + tst_atomic_inc(&results->passed); + break; + case TWARN: + tst_atomic_inc(&results->warnings); + break; + case TFAIL: + tst_atomic_inc(&results->failed); + break; + } +} + +static void print_result(const char *file, const int lineno, int ttype, + const char *fmt, va_list va) +{ + char buf[1024]; + char *str = buf; + int ret, size = sizeof(buf), ssize, int_errno, buflen; + const char *str_errno = NULL; + const char *res; + + switch (TTYPE_RESULT(ttype)) { + case TPASS: + res = "PASS"; + break; + case TFAIL: + res = "FAIL"; + break; + case TBROK: + res = "BROK"; + break; + case TCONF: + res = "CONF"; + break; + case TWARN: + res = "WARN"; + break; + case TINFO: + res = "INFO"; + break; + default: + tst_brk(TBROK, "Invalid ttype value %i", ttype); + abort(); + } + + if (ttype & TERRNO) { + str_errno = tst_strerrno(errno); + int_errno = errno; + } + + if (ttype & TTERRNO) { + str_errno = tst_strerrno(TST_ERR); + int_errno = TST_ERR; + } + + if (ttype & TRERRNO) { + int_errno = TST_RET < 0 ? -(int)TST_RET : (int)TST_RET; + str_errno = tst_strerrno(int_errno); + } + + ret = snprintf(str, size, "%s:%i: ", file, lineno); + str += ret; + size -= ret; + + if (tst_color_enabled(STDERR_FILENO)) + ret = snprintf(str, size, "%s%s: %s", tst_ttype2color(ttype), + res, ANSI_COLOR_RESET); + else + ret = snprintf(str, size, "%s: ", res); + str += ret; + size -= ret; + + ssize = size - 2; + ret = vsnprintf(str, size, fmt, va); + str += MIN(ret, ssize); + size -= MIN(ret, ssize); + if (ret >= ssize) { + tst_res_(file, lineno, TWARN, + "Next message is too long and truncated:"); + } else if (str_errno) { + ssize = size - 2; + ret = snprintf(str, size, ": %s (%d)", str_errno, int_errno); + str += MIN(ret, ssize); + size -= MIN(ret, ssize); + if (ret >= ssize) + tst_res_(file, lineno, TWARN, + "Next message is too long and truncated:"); + } + + snprintf(str, size, "\n"); + + /* we might be called from signal handler, so use write() */ + buflen = str - buf + 1; + str = buf; + while (buflen) { + ret = write(STDERR_FILENO, str, buflen); + if (ret <= 0) + break; + + str += ret; + buflen -= ret; + } +} + +void tst_vres_(const char *file, const int lineno, int ttype, + const char *fmt, va_list va) +{ + print_result(file, lineno, ttype, fmt, va); + + update_results(TTYPE_RESULT(ttype)); +} + +void tst_vbrk_(const char *file, const int lineno, int ttype, + const char *fmt, va_list va); + +static void (*tst_brk_handler)(const char *file, const int lineno, int ttype, + const char *fmt, va_list va) = tst_vbrk_; + +static void tst_cvres(const char *file, const int lineno, int ttype, + const char *fmt, va_list va) +{ + if (TTYPE_RESULT(ttype) == TBROK) { + ttype &= ~TTYPE_MASK; + ttype |= TWARN; + } + + print_result(file, lineno, ttype, fmt, va); + update_results(TTYPE_RESULT(ttype)); +} + +static void do_test_cleanup(void) +{ + tst_brk_handler = tst_cvres; + + if (tst_test->cleanup) + tst_test->cleanup(); + + tst_free_all(); + + tst_brk_handler = tst_vbrk_; +} + +void tst_vbrk_(const char *file, const int lineno, int ttype, + const char *fmt, va_list va) +{ + print_result(file, lineno, ttype, fmt, va); + update_results(TTYPE_RESULT(ttype)); + + /* + * The getpid implementation in some C library versions may cause cloned + * test threads to show the same pid as their parent when CLONE_VM is + * specified but CLONE_THREAD is not. Use direct syscall to avoid + * cleanup running in the child. + */ + if (syscall(SYS_getpid) == main_pid) + do_test_cleanup(); + + if (getpid() == lib_pid) + do_exit(TTYPE_RESULT(ttype)); + + exit(TTYPE_RESULT(ttype)); +} + +void tst_res_(const char *file, const int lineno, int ttype, + const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + tst_vres_(file, lineno, ttype, fmt, va); + va_end(va); +} + +void tst_brk_(const char *file, const int lineno, int ttype, + const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + tst_brk_handler(file, lineno, ttype, fmt, va); + va_end(va); +} + +static void check_child_status(pid_t pid, int status) +{ + int ret; + + if (WIFSIGNALED(status)) { + tst_brk(TBROK, "Child (%i) killed by signal %s", + pid, tst_strsig(WTERMSIG(status))); + } + + if (!(WIFEXITED(status))) + tst_brk(TBROK, "Child (%i) exited abnormally", pid); + + ret = WEXITSTATUS(status); + switch (ret) { + case TPASS: + break; + case TBROK: + case TCONF: + tst_brk(ret, "Reported by child (%i)", pid); + break; + default: + tst_brk(TBROK, "Invalid child (%i) exit value %i", pid, ret); + } +} + +void tst_reap_children(void) +{ + int status; + pid_t pid; + + for (;;) { + pid = wait(&status); + + if (pid > 0) { + check_child_status(pid, status); + continue; + } + + if (errno == ECHILD) + break; + + if (errno == EINTR) + continue; + + tst_brk(TBROK | TERRNO, "wait() failed"); + } +} + + +pid_t safe_fork(const char *filename, unsigned int lineno) +{ + pid_t pid; + + if (!tst_test->forks_child) + tst_brk(TBROK, "test.forks_child must be set!"); + + tst_flush(); + + pid = fork(); + if (pid < 0) + tst_brk_(filename, lineno, TBROK | TERRNO, "fork() failed"); + + if (!pid) + atexit(tst_free_all); + + return pid; +} + +static struct option { + char *optstr; + char *help; +} options[] = { + {"h", "-h Prints this help"}, + {"i:", "-i n Execute test n times"}, + {"I:", "-I x Execute test for n seconds"}, + {"C:", "-C ARG Run child process with ARG arguments (used internally)"}, +}; + +static void print_help(void) +{ + unsigned int i; + + fprintf(stderr, "Options\n"); + fprintf(stderr, "-------\n"); + + for (i = 0; i < ARRAY_SIZE(options); i++) + fprintf(stderr, "%s\n", options[i].help); + + if (!tst_test->options) + return; + + for (i = 0; tst_test->options[i].optstr; i++) + fprintf(stderr, "%s\n", tst_test->options[i].help); +} + +static void print_test_tags(void) +{ + unsigned int i; + const struct tst_tag *tags = tst_test->tags; + + if (!tags) + return; + + printf("\nTags\n"); + printf("----\n"); + + for (i = 0; tags[i].name; i++) { + if (!strcmp(tags[i].name, "CVE")) + printf(CVE_DB_URL "%s\n", tags[i].value); + else if (!strcmp(tags[i].name, "linux-git")) + printf(LINUX_GIT_URL "%s\n", tags[i].value); + else + printf("%s: %s\n", tags[i].name, tags[i].value); + } + + printf("\n"); +} + +static void check_option_collision(void) +{ + unsigned int i, j; + struct tst_option *toptions = tst_test->options; + + if (!toptions) + return; + + for (i = 0; toptions[i].optstr; i++) { + for (j = 0; j < ARRAY_SIZE(options); j++) { + if (toptions[i].optstr[0] == options[j].optstr[0]) { + tst_brk(TBROK, "Option collision '%s'", + options[j].help); + } + } + } +} + +static unsigned int count_options(void) +{ + unsigned int i; + + if (!tst_test->options) + return 0; + + for (i = 0; tst_test->options[i].optstr; i++); + + return i; +} + +static void parse_topt(unsigned int topts_len, int opt, char *optarg) +{ + unsigned int i; + struct tst_option *toptions = tst_test->options; + + for (i = 0; i < topts_len; i++) { + if (toptions[i].optstr[0] == opt) + break; + } + + if (i >= topts_len) + tst_brk(TBROK, "Invalid option '%c' (should not happen)", opt); + + if (*toptions[i].arg) + tst_res(TWARN, "Option -%c passed multiple times", opt); + + *(toptions[i].arg) = optarg ? optarg : ""; +} + +/* see self_exec.c */ +#ifdef UCLINUX +extern char *child_args; +#endif + +static void parse_opts(int argc, char *argv[]) +{ + unsigned int i, topts_len = count_options(); + char optstr[2 * ARRAY_SIZE(options) + 2 * topts_len]; + int opt; + + check_option_collision(); + + optstr[0] = 0; + + for (i = 0; i < ARRAY_SIZE(options); i++) + strcat(optstr, options[i].optstr); + + for (i = 0; i < topts_len; i++) + strcat(optstr, tst_test->options[i].optstr); + + while ((opt = getopt(argc, argv, optstr)) > 0) { + switch (opt) { + case '?': + print_help(); + tst_brk(TBROK, "Invalid option"); + break; + case 'h': + print_help(); + print_test_tags(); + exit(0); + case 'i': + iterations = atoi(optarg); + break; + case 'I': + duration = atof(optarg); + break; + case 'C': +#ifdef UCLINUX + child_args = optarg; +#endif + break; + default: + parse_topt(topts_len, opt, optarg); + } + } + + if (optind < argc) + tst_brk(TBROK, "Unexpected argument(s) '%s'...", argv[optind]); +} + +int tst_parse_int(const char *str, int *val, int min, int max) +{ + long rval; + + if (!str) + return 0; + + int ret = tst_parse_long(str, &rval, min, max); + + if (ret) + return ret; + + *val = (int)rval; + return 0; +} + +int tst_parse_long(const char *str, long *val, long min, long max) +{ + long rval; + char *end; + + if (!str) + return 0; + + errno = 0; + rval = strtol(str, &end, 10); + + if (str == end || *end != '\0') + return EINVAL; + + if (errno) + return errno; + + if (rval > max || rval < min) + return ERANGE; + + *val = rval; + return 0; +} + +int tst_parse_float(const char *str, float *val, float min, float max) +{ + double rval; + char *end; + + if (!str) + return 0; + + errno = 0; + rval = strtod(str, &end); + + if (str == end || *end != '\0') + return EINVAL; + + if (errno) + return errno; + + if (rval > (double)max || rval < (double)min) + return ERANGE; + + *val = (float)rval; + return 0; +} + +static void print_colored(const char *str) +{ + if (tst_color_enabled(STDOUT_FILENO)) + printf("%s%s%s", ANSI_COLOR_YELLOW, str, ANSI_COLOR_RESET); + else + printf("%s", str); +} + +static void print_failure_hints(void) +{ + unsigned int i; + const struct tst_tag *tags = tst_test->tags; + + if (!tags) + return; + + int hint_printed = 0; + for (i = 0; tags[i].name; i++) { + if (!strcmp(tags[i].name, "linux-git")) { + if (!hint_printed) { + hint_printed = 1; + printf("\n"); + print_colored("HINT: "); + printf("You _MAY_ be missing kernel fixes, see:\n\n"); + } + + printf(LINUX_GIT_URL "%s\n", tags[i].value); + } + + } + + hint_printed = 0; + for (i = 0; tags[i].name; i++) { + if (!strcmp(tags[i].name, "CVE")) { + if (!hint_printed) { + hint_printed = 1; + printf("\n"); + print_colored("HINT: "); + printf("You _MAY_ be vulnerable to CVE(s), see:\n\n"); + } + + printf(CVE_DB_URL "%s\n", tags[i].value); + } + } +} + +static void do_exit(int ret) +{ + if (results) { + if (results->passed && ret == TCONF) + ret = 0; + + if (results->failed) { + ret |= TFAIL; + print_failure_hints(); + } + + if (results->skipped && !results->passed) + ret |= TCONF; + + if (results->warnings) + ret |= TWARN; + + printf("\nSummary:\n"); + printf("passed %d\n", results->passed); + printf("failed %d\n", results->failed); + printf("skipped %d\n", results->skipped); + printf("warnings %d\n", results->warnings); + } + + do_cleanup(); + + exit(ret); +} + +void check_kver(void) +{ + int v1, v2, v3; + + if (tst_parse_kver(tst_test->min_kver, &v1, &v2, &v3)) { + tst_res(TWARN, + "Invalid kernel version %s, expected %%d.%%d.%%d", + tst_test->min_kver); + } + + if (tst_kvercmp(v1, v2, v3) < 0) { + tst_brk(TCONF, "The test requires kernel %s or newer", + tst_test->min_kver); + } +} + +static int results_equal(struct results *a, struct results *b) +{ + if (a->passed != b->passed) + return 0; + + if (a->failed != b->failed) + return 0; + + if (a->skipped != b->skipped) + return 0; + + return 1; +} + +static int needs_tmpdir(void) +{ + return tst_test->needs_tmpdir || + tst_test->needs_device || + tst_test->mntpoint || + tst_test->resource_files || + tst_test->needs_checkpoints; +} + +static void copy_resources(void) +{ + unsigned int i; + + for (i = 0; tst_test->resource_files[i]; i++) + TST_RESOURCE_COPY(NULL, tst_test->resource_files[i], NULL); +} + +static const char *get_tid(char *argv[]) +{ + char *p; + + if (!argv[0] || !argv[0][0]) { + tst_res(TINFO, "argv[0] is empty!"); + return "ltp_empty_argv"; + } + + p = strrchr(argv[0], '/'); + if (p) + return p+1; + + return argv[0]; +} + +static struct tst_device tdev; +struct tst_device *tst_device; + +static void assert_test_fn(void) +{ + int cnt = 0; + + if (tst_test->test) + cnt++; + + if (tst_test->test_all) + cnt++; + + if (tst_test->sample) + cnt++; + + if (!cnt) + tst_brk(TBROK, "No test function specified"); + + if (cnt != 1) + tst_brk(TBROK, "You can define only one test function"); + + if (tst_test->test && !tst_test->tcnt) + tst_brk(TBROK, "Number of tests (tcnt) must be > 0"); + + if (!tst_test->test && tst_test->tcnt) + tst_brk(TBROK, "You can define tcnt only for test()"); +} + +static int prepare_and_mount_ro_fs(const char *dev, + const char *mntpoint, + const char *fs_type) +{ + char buf[PATH_MAX]; + + if (mount(dev, mntpoint, fs_type, 0, NULL)) { + tst_res(TINFO | TERRNO, "Can't mount %s at %s (%s)", + dev, mntpoint, fs_type); + return 1; + } + + mntpoint_mounted = 1; + + snprintf(buf, sizeof(buf), "%s/dir/", mntpoint); + SAFE_MKDIR(buf, 0777); + + snprintf(buf, sizeof(buf), "%s/file", mntpoint); + SAFE_FILE_PRINTF(buf, "file content"); + SAFE_CHMOD(buf, 0777); + + SAFE_MOUNT(dev, mntpoint, fs_type, MS_REMOUNT | MS_RDONLY, NULL); + + return 0; +} + +static void prepare_and_mount_dev_fs(const char *mntpoint) +{ + const char *flags[] = {"nodev", NULL}; + int mounted_nodev; + + mounted_nodev = tst_path_has_mnt_flags(NULL, flags); + if (mounted_nodev) { + tst_res(TINFO, "tmpdir isn't suitable for creating devices, " + "mounting tmpfs without nodev on %s", mntpoint); + SAFE_MOUNT(NULL, mntpoint, "tmpfs", 0, NULL); + mntpoint_mounted = 1; + } +} + +static void prepare_device(void) +{ + if (tst_test->format_device) { + SAFE_MKFS(tdev.dev, tdev.fs_type, tst_test->dev_fs_opts, + tst_test->dev_extra_opts); + } + + if (tst_test->needs_rofs) { + prepare_and_mount_ro_fs(tdev.dev, tst_test->mntpoint, + tdev.fs_type); + return; + } + + if (tst_test->mount_device) { + SAFE_MOUNT(tdev.dev, tst_test->mntpoint, tdev.fs_type, + tst_test->mnt_flags, tst_test->mnt_data); + mntpoint_mounted = 1; + } +} + +static void do_setup(int argc, char *argv[]) +{ + if (!tst_test) + tst_brk(TBROK, "No tests to run"); + + if (tst_test->tconf_msg) + tst_brk(TCONF, "%s", tst_test->tconf_msg); + + if (tst_test->needs_kconfigs) + tst_kconfig_check(tst_test->needs_kconfigs); + + assert_test_fn(); + + tid = get_tid(argv); + + if (tst_test->sample) + tst_test = tst_timer_test_setup(tst_test); + + parse_opts(argc, argv); + + if (tst_test->needs_root && geteuid() != 0) + tst_brk(TCONF, "Test needs to be run as root"); + + if (tst_test->min_kver) + check_kver(); + + if (tst_test->needs_cmds) { + const char *cmd; + char path[PATH_MAX]; + int i; + + for (i = 0; (cmd = tst_test->needs_cmds[i]); ++i) + if (tst_get_path(cmd, path, sizeof(path))) + tst_brk(TCONF, "Couldn't find '%s' in $PATH", cmd); + } + + if (tst_test->needs_drivers) { + const char *name; + int i; + + for (i = 0; (name = tst_test->needs_drivers[i]); ++i) + if (tst_check_driver(name)) + tst_brk(TCONF, "%s driver not available", name); + } + + if (tst_test->format_device) + tst_test->needs_device = 1; + + if (tst_test->mount_device) { + tst_test->needs_device = 1; + tst_test->format_device = 1; + } + + if (tst_test->all_filesystems) + tst_test->needs_device = 1; + + if (tst_test->request_hugepages) + tst_request_hugepages(tst_test->request_hugepages); + + setup_ipc(); + + if (tst_test->bufs) + tst_buffers_alloc(tst_test->bufs); + + if (needs_tmpdir() && !tst_tmpdir_created()) + tst_tmpdir(); + + if (tst_test->save_restore) { + const char * const *name = tst_test->save_restore; + + while (*name) { + tst_sys_conf_save(*name); + name++; + } + } + + if (tst_test->mntpoint) + SAFE_MKDIR(tst_test->mntpoint, 0777); + + if ((tst_test->needs_devfs || tst_test->needs_rofs || + tst_test->mount_device || tst_test->all_filesystems) && + !tst_test->mntpoint) { + tst_brk(TBROK, "tst_test->mntpoint must be set!"); + } + + if (!!tst_test->needs_rofs + !!tst_test->needs_devfs + + !!tst_test->needs_device > 1) { + tst_brk(TBROK, + "Two or more of needs_{rofs, devfs, device} are set"); + } + + if (tst_test->needs_devfs) + prepare_and_mount_dev_fs(tst_test->mntpoint); + + if (tst_test->needs_rofs) { + /* If we failed to mount read-only tmpfs. Fallback to + * using a device with read-only filesystem. + */ + if (prepare_and_mount_ro_fs(NULL, tst_test->mntpoint, "tmpfs")) { + tst_res(TINFO, "Can't mount tmpfs read-only, " + "falling back to block device..."); + tst_test->needs_device = 1; + tst_test->format_device = 1; + } + } + + if (tst_test->needs_device && !mntpoint_mounted) { + tdev.dev = tst_acquire_device_(NULL, tst_test->dev_min_size); + + if (!tdev.dev) + tst_brk(TCONF, "Failed to acquire device"); + + tst_device = &tdev; + + if (tst_test->dev_fs_type) + tdev.fs_type = tst_test->dev_fs_type; + else + tdev.fs_type = tst_dev_fs_type(); + + if (!tst_test->all_filesystems) + prepare_device(); + } + + if (tst_test->needs_overlay && !tst_test->mount_device) { + tst_brk(TBROK, "tst_test->mount_device must be set"); + } + if (tst_test->needs_overlay && !mntpoint_mounted) { + tst_brk(TBROK, "tst_test->mntpoint must be mounted"); + } + if (tst_test->needs_overlay && !ovl_mounted) { + SAFE_MOUNT_OVERLAY(); + ovl_mounted = 1; + } + + if (tst_test->resource_files) + copy_resources(); + + if (tst_test->restore_wallclock) + tst_wallclock_save(); + + if (tst_test->taint_check) + tst_taint_init(tst_test->taint_check); +} + +static void do_test_setup(void) +{ + main_pid = getpid(); + + if (tst_test->caps) + tst_cap_setup(tst_test->caps, TST_CAP_REQ); + + if (tst_test->setup) + tst_test->setup(); + + if (main_pid != getpid()) + tst_brk(TBROK, "Runaway child in setup()!"); + + if (tst_test->caps) + tst_cap_setup(tst_test->caps, TST_CAP_DROP); +} + +static void do_cleanup(void) +{ + if (ovl_mounted) + SAFE_UMOUNT(OVL_MNT); + + if (mntpoint_mounted) + tst_umount(tst_test->mntpoint); + + if (tst_test->needs_device && tdev.dev) + tst_release_device(tdev.dev); + + if (tst_tmpdir_created()) { + /* avoid munmap() on wrong pointer in tst_rmdir() */ + tst_futexes = NULL; + tst_rmdir(); + } + + tst_sys_conf_restore(0); + + if (tst_test->restore_wallclock) + tst_wallclock_restore(); + + cleanup_ipc(); +} + +static void run_tests(void) +{ + unsigned int i; + struct results saved_results; + + if (!tst_test->test) { + saved_results = *results; + tst_test->test_all(); + + if (getpid() != main_pid) { + exit(0); + } + + tst_reap_children(); + + if (results_equal(&saved_results, results)) + tst_brk(TBROK, "Test haven't reported results!"); + return; + } + + for (i = 0; i < tst_test->tcnt; i++) { + saved_results = *results; + tst_test->test(i); + + if (getpid() != main_pid) { + exit(0); + } + + tst_reap_children(); + + if (results_equal(&saved_results, results)) + tst_brk(TBROK, "Test %i haven't reported results!", i); + } +} + +static unsigned long long get_time_ms(void) +{ + struct timespec ts; + + if (tst_clock_gettime(CLOCK_MONOTONIC, &ts)) + tst_brk(TBROK | TERRNO, "tst_clock_gettime()"); + + return tst_timespec_to_ms(ts); +} + +static void add_paths(void) +{ + char *old_path = getenv("PATH"); + const char *start_dir; + char *new_path; + + start_dir = tst_get_startwd(); + + if (old_path) + SAFE_ASPRINTF(&new_path, "%s::%s", old_path, start_dir); + else + SAFE_ASPRINTF(&new_path, "::%s", start_dir); + + SAFE_SETENV("PATH", new_path, 1); + free(new_path); +} + +static void heartbeat(void) +{ + if (tst_clock_gettime(CLOCK_MONOTONIC, &tst_start_time)) + tst_res(TWARN | TERRNO, "tst_clock_gettime() failed"); + + kill(getppid(), SIGUSR1); +} + +static void testrun(void) +{ + unsigned int i = 0; + unsigned long long stop_time = 0; + int cont = 1; + + heartbeat(); + add_paths(); + do_test_setup(); + + if (duration > 0) + stop_time = get_time_ms() + (unsigned long long)(duration * 1000); + + for (;;) { + cont = 0; + + if (i < (unsigned int)iterations) { + i++; + cont = 1; + } + + if (stop_time && get_time_ms() < stop_time) + cont = 1; + + if (!cont) + break; + + run_tests(); + heartbeat(); + } + + do_test_cleanup(); + exit(0); +} + +static pid_t test_pid; + + +static volatile sig_atomic_t sigkill_retries; + +#define WRITE_MSG(msg) do { \ + if (write(2, msg, sizeof(msg) - 1)) { \ + /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 */ \ + } \ +} while (0) + +static void alarm_handler(int sig LTP_ATTRIBUTE_UNUSED) +{ + WRITE_MSG("Test timeouted, sending SIGKILL!\n"); + kill(-test_pid, SIGKILL); + alarm(5); + + if (++sigkill_retries > 10) { + WRITE_MSG("Cannot kill test processes!\n"); + WRITE_MSG("Congratulation, likely test hit a kernel bug.\n"); + WRITE_MSG("Exitting uncleanly...\n"); + _exit(TFAIL); + } +} + +static void heartbeat_handler(int sig LTP_ATTRIBUTE_UNUSED) +{ + alarm(results->timeout); + sigkill_retries = 0; +} + +static void sigint_handler(int sig LTP_ATTRIBUTE_UNUSED) +{ + if (test_pid > 0) { + WRITE_MSG("Sending SIGKILL to test process...\n"); + kill(-test_pid, SIGKILL); + } +} + +unsigned int tst_timeout_remaining(void) +{ + static struct timespec now; + unsigned int elapsed; + + if (tst_clock_gettime(CLOCK_MONOTONIC, &now)) + tst_res(TWARN | TERRNO, "tst_clock_gettime() failed"); + + elapsed = (tst_timespec_diff_ms(now, tst_start_time) + 500) / 1000; + if (results->timeout > elapsed) + return results->timeout - elapsed; + + return 0; +} + +unsigned int tst_multiply_timeout(unsigned int timeout) +{ + char *mul; + int ret; + + if (timeout_mul == -1) { + mul = getenv("LTP_TIMEOUT_MUL"); + if (mul) { + if ((ret = tst_parse_float(mul, &timeout_mul, 1, 10000))) { + tst_brk(TBROK, "Failed to parse LTP_TIMEOUT_MUL: %s", + tst_strerrno(ret)); + } + } else { + timeout_mul = 1; + } + } + if (timeout_mul < 1) + tst_brk(TBROK, "LTP_TIMEOUT_MUL must to be int >= 1! (%.2f)", + timeout_mul); + + if (timeout < 1) + tst_brk(TBROK, "timeout must to be >= 1! (%d)", timeout); + + return timeout * timeout_mul; +} + +void tst_set_timeout(int timeout) +{ + if (timeout == -1) { + tst_res(TINFO, "Timeout per run is disabled"); + return; + } + + if (timeout < 1) + tst_brk(TBROK, "timeout must to be >= 1! (%d)", timeout); + + results->timeout = tst_multiply_timeout(timeout); + + tst_res(TINFO, "Timeout per run is %uh %02um %02us", + results->timeout/3600, (results->timeout%3600)/60, + results->timeout % 60); + + if (getpid() == lib_pid) + alarm(results->timeout); + else + heartbeat(); +} + +static int fork_testrun(void) +{ + int status; + + if (tst_test->timeout) + tst_set_timeout(tst_test->timeout); + else + tst_set_timeout(300); + + SAFE_SIGNAL(SIGINT, sigint_handler); + + test_pid = fork(); + if (test_pid < 0) + tst_brk(TBROK | TERRNO, "fork()"); + + if (!test_pid) { + SAFE_SIGNAL(SIGALRM, SIG_DFL); + SAFE_SIGNAL(SIGUSR1, SIG_DFL); + SAFE_SIGNAL(SIGINT, SIG_DFL); + SAFE_SETPGID(0, 0); + testrun(); + } + + SAFE_WAITPID(test_pid, &status, 0); + alarm(0); + SAFE_SIGNAL(SIGINT, SIG_DFL); + + if (tst_test->taint_check && tst_taint_check()) { + tst_res(TFAIL, "Kernel is now tainted."); + return TFAIL; + } + + if (WIFEXITED(status) && WEXITSTATUS(status)) + return WEXITSTATUS(status); + + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) { + tst_res(TINFO, "If you are running on slow machine, " + "try exporting LTP_TIMEOUT_MUL > 1"); + tst_brk(TBROK, "Test killed! (timeout?)"); + } + + if (WIFSIGNALED(status)) + tst_brk(TBROK, "Test killed by %s!", tst_strsig(WTERMSIG(status))); + + return 0; +} + +static int run_tcases_per_fs(void) +{ + int ret = 0; + unsigned int i; + const char *const *filesystems = tst_get_supported_fs_types(tst_test->dev_fs_flags); + + if (!filesystems[0]) + tst_brk(TCONF, "There are no supported filesystems"); + + for (i = 0; filesystems[i]; i++) { + + tst_res(TINFO, "Testing on %s", filesystems[i]); + tdev.fs_type = filesystems[i]; + + prepare_device(); + + ret = fork_testrun(); + + if (mntpoint_mounted) { + tst_umount(tst_test->mntpoint); + mntpoint_mounted = 0; + } + + if (ret == TCONF) + continue; + + if (ret == 0) + continue; + + do_exit(ret); + } + + return ret; +} + +unsigned int tst_variant; + +void tst_run_tcases(int argc, char *argv[], struct tst_test *self) +{ + int ret = 0; + unsigned int test_variants = 1; + + lib_pid = getpid(); + tst_test = self; + + do_setup(argc, argv); + + TCID = tid; + + SAFE_SIGNAL(SIGALRM, alarm_handler); + SAFE_SIGNAL(SIGUSR1, heartbeat_handler); + + if (tst_test->test_variants) + test_variants = tst_test->test_variants; + + for (tst_variant = 0; tst_variant < test_variants; tst_variant++) { + if (tst_test->all_filesystems) + ret |= run_tcases_per_fs(); + else + ret |= fork_testrun(); + + if (ret & ~(TCONF)) + goto exit; + } + +exit: + do_exit(ret); +} + + +void tst_flush(void) +{ + int rval; + + rval = fflush(stderr); + if (rval != 0) + tst_brk(TBROK | TERRNO, "fflush(stderr) failed"); + + rval = fflush(stdout); + if (rval != 0) + tst_brk(TBROK | TERRNO, "fflush(stdout) failed"); + +} diff --git a/src/kernel/tests/lib/tst_timer.c b/src/kernel/tests/lib/tst_timer.c new file mode 100644 index 0000000..62d8f90 --- /dev/null +++ b/src/kernel/tests/lib/tst_timer.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015 Cyril Hrubis <chrubis@suse.cz> + */ + +#include <errno.h> + +#define TST_NO_DEFAULT_MAIN + +#include "tst_test.h" +#include "tst_timer.h" +#include "tst_clocks.h" +#include "lapi/posix_clocks.h" + +static struct timespec start_time, stop_time; +static clockid_t clock_id; + +void tst_timer_check(clockid_t clk_id) +{ + if (tst_clock_gettime(clk_id, &start_time)) { + if (errno == EINVAL) { + tst_brk(TCONF, + "Clock id %s(%u) not supported by kernel", + tst_clock_name(clk_id), clk_id); + return; + } + + tst_brk(TBROK | TERRNO, "tst_clock_gettime() failed"); + } +} + +void tst_timer_start(clockid_t clk_id) +{ + clock_id = clk_id; + + if (tst_clock_gettime(clock_id, &start_time)) + tst_res(TWARN | TERRNO, "tst_clock_gettime() failed"); +} + +int tst_timer_expired_ms(long long ms) +{ + struct timespec cur_time; + + if (tst_clock_gettime(clock_id, &cur_time)) + tst_res(TWARN | TERRNO, "tst_clock_gettime() failed"); + + return tst_timespec_diff_ms(cur_time, start_time) >= ms; +} + +void tst_timer_stop(void) +{ + if (tst_clock_gettime(clock_id, &stop_time)) + tst_res(TWARN | TERRNO, "tst_clock_gettime() failed"); +} + +struct timespec tst_timer_elapsed(void) +{ + return tst_timespec_diff(stop_time, start_time); +} diff --git a/src/kernel/tests/lib/tst_timer_test.c b/src/kernel/tests/lib/tst_timer_test.c new file mode 100644 index 0000000..196c512 --- /dev/null +++ b/src/kernel/tests/lib/tst_timer_test.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz> + */ + +#include <sys/prctl.h> +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> +#include <string.h> + +#define TST_NO_DEFAULT_MAIN +#include "tst_test.h" +#include "tst_clocks.h" +#include "tst_timer_test.h" + +#define MAX_SAMPLES 500 + +static const char *scall; +static void (*setup)(void); +static void (*cleanup)(void); +static int (*sample)(int clk_id, long long usec); +static struct tst_test *test; + +static long long *samples; +static unsigned int cur_sample; +static unsigned int monotonic_resolution; +static unsigned int timerslack; +static int virt_env; + +static char *print_frequency_plot; +static char *file_name; +static char *str_sleep_time; +static char *str_sample_cnt; +static int sleep_time = -1; +static int sample_cnt; + +static void print_line(char c, int len) +{ + while (len-- > 0) + fputc(c, stderr); +} + +static unsigned int ceilu(float f) +{ + if (f - (int)f > 0) + return (unsigned int)f + 1; + + return (unsigned int)f; +} + +static unsigned int flooru(float f) +{ + return (unsigned int)f; +} + +static float bucket_len(unsigned int bucket, unsigned int max_bucket, + unsigned int cols) +{ + return 1.00 * bucket * cols / max_bucket; +} + +static const char *table_heading = " Time: us "; + +/* + * Line Header: '10023 | ' + */ +static unsigned int header_len(long long max_sample) +{ + unsigned int l = 1; + + while (max_sample/=10) + l++; + + return MAX(strlen(table_heading) + 2, l + 3); +} + +static void frequency_plot(void) +{ + unsigned int cols = 80; + unsigned int rows = 20; + unsigned int i, buckets[rows]; + long long max_sample = samples[0]; + long long min_sample = samples[cur_sample-1]; + unsigned int line_header_len = header_len(max_sample); + unsigned int plot_line_len = cols - line_header_len; + unsigned int bucket_size; + + memset(buckets, 0, sizeof(buckets)); + + /* + * We work with discrete data buckets smaller than 1 does not make + * sense as well as it's a good idea to keep buckets integer sized + * to avoid scaling artifacts. + */ + bucket_size = MAX(1u, ceilu(1.00 * (max_sample - min_sample)/(rows-1))); + + for (i = 0; i < cur_sample; i++) { + unsigned int bucket; + bucket = flooru(1.00 * (samples[i] - min_sample)/bucket_size); + buckets[bucket]++; + } + + unsigned int max_bucket = buckets[0]; + for (i = 1; i < rows; i++) + max_bucket = MAX(max_bucket, buckets[i]); + + fprintf(stderr, "\n%*s| Frequency\n", line_header_len - 2, table_heading); + + print_line('-', cols); + fputc('\n', stderr); + + unsigned int l, r; + + for (l = 0; l < rows; l++) { + if (buckets[l]) + break; + } + + for (r = rows-1; r > l; r--) { + if (buckets[r]) + break; + } + + for (i = l; i <= r; i++) { + float len = bucket_len(buckets[i], max_bucket, plot_line_len); + + fprintf(stderr, "%*lli | ", + line_header_len - 3, min_sample + bucket_size*i); + print_line('*', len); + + if ((len - (int)len) >= 0.5) + fputc('+', stderr); + else if ((len - (int)len) >= 0.25) + fputc('-', stderr); + else if (len < 0.25 && buckets[i]) + fputc('.', stderr); + + fputc('\n', stderr); + } + + print_line('-', cols); + fputc('\n', stderr); + + float scale = 1.00 * plot_line_len / max_bucket; + + fprintf(stderr, + "%*uus | 1 sample = %.5f '*', %.5f '+', %.5f '-', non-zero '.'\n", + line_header_len - 5, bucket_size, scale, scale * 2, scale * 4); + + fputc('\n', stderr); +} + +void tst_timer_sample(void) +{ + samples[cur_sample++] = tst_timer_elapsed_us(); +} + +static int cmp(const void *a, const void *b) +{ + const long long *aa = a, *bb = b; + + return (*bb - *aa); +} + +/* + * The threshold per one syscall is computed as a sum of: + * + * 400 us - accomodates for context switches, process + * migrations between CPUs on SMP, etc. + * 2*monotonic_resolution - accomodates for granurality of the CLOCK_MONOTONIC + * slack_per_scall - max of 0.1% of the sleep capped on 100ms or + * current->timer_slack_ns, which is slack allowed + * in kernel + * + * The formula for slack_per_scall applies to select() and *poll*() syscalls, + * the futex and *nanosleep() use only the timer_slack_ns, so we are a bit + * less strict here that we could be for these two for longer sleep times... + * + * We also allow for outliners, i.e. add some number to the threshold in case + * that the number of iteration is small. For large enoung number of iterations + * outliners are discarded and averaged out. + */ +static long long compute_threshold(long long requested_us, + unsigned int nsamples) +{ + unsigned int slack_per_scall = MIN(100000, requested_us / 1000); + + slack_per_scall = MAX(slack_per_scall, timerslack); + + return (400 + 2 * monotonic_resolution + slack_per_scall) * nsamples + + 3000/nsamples; +} + +/* + * Returns number of samples to discard. + * + * We set it to either at least 1 if number of samples > 1 or 5%. + */ +static unsigned int compute_discard(unsigned int nsamples) +{ + if (nsamples == 1) + return 0; + + return MAX(1u, nsamples / 20); +} + +static void write_to_file(void) +{ + unsigned int i; + FILE *f; + + if (!file_name) + return; + + f = fopen(file_name, "w"); + + if (!f) { + tst_res(TWARN | TERRNO, + "Failed to open '%s'", file_name); + return; + } + + for (i = 0; i < cur_sample; i++) + fprintf(f, "%lli\n", samples[i]); + + if (fclose(f)) { + tst_res(TWARN | TERRNO, + "Failed to close file '%s'", file_name); + } +} + + +/* + * Timer testing function. + * + * What we do here is: + * + * * Take nsamples measurements of the timer function, the function + * to be sampled is defined in the the actual test. + * + * * We sort the array of samples, then: + * + * - look for outliners which are samples where the sleep time has exceeded + * requested sleep time by an order of magnitude and, at the same time, are + * greater than clock resolution multiplied by three. + * + * - check for samples where the call has woken up too early which is a plain + * old bug + * + * - then we compute truncated mean and compare that with the requested sleep + * time increased by a threshold + */ +void do_timer_test(long long usec, unsigned int nsamples) +{ + long long trunc_mean, median; + unsigned int discard = compute_discard(nsamples); + unsigned int keep_samples = nsamples - discard; + long long threshold = compute_threshold(usec, keep_samples); + int i; + int failed = 0; + + tst_res(TINFO, + "%s sleeping for %llius %u iterations, threshold %.2fus", + scall, usec, nsamples, 1.00 * threshold / (keep_samples)); + + cur_sample = 0; + for (i = 0; i < (int)nsamples; i++) { + if (sample(CLOCK_MONOTONIC, usec)) { + tst_res(TINFO, "sampling function failed, exitting"); + return; + } + } + + qsort(samples, nsamples, sizeof(samples[0]), cmp); + + write_to_file(); + + for (i = 0; samples[i] > 10 * usec && i < (int)nsamples; i++) { + if (samples[i] <= 3 * monotonic_resolution) + break; + } + + if (i > 0) { + tst_res(TINFO, "Found %i outliners in [%lli,%lli] range", + i, samples[0], samples[i-1]); + } + + for (i = nsamples - 1; samples[i] < usec && i > -1; i--); + + if (i < (int)nsamples - 1) { + tst_res(TFAIL, "%s woken up early %u times range: [%lli,%lli]", + scall, nsamples - 1 - i, + samples[i+1], samples[nsamples-1]); + failed = 1; + } + + median = samples[nsamples/2]; + + trunc_mean = 0; + + for (i = discard; i < (int)nsamples; i++) + trunc_mean += samples[i]; + + tst_res(TINFO, + "min %llius, max %llius, median %llius, trunc mean %.2fus (discarded %u)", + samples[nsamples-1], samples[0], median, + 1.00 * trunc_mean / keep_samples, discard); + + if (virt_env) { + tst_res(TINFO, + "Virtualisation detected, skipping oversleep checks"); + } else if (trunc_mean > (nsamples - discard) * usec + threshold) { + tst_res(TFAIL, "%s slept for too long", scall); + + if (!print_frequency_plot) + frequency_plot(); + + failed = 1; + } + + if (print_frequency_plot) + frequency_plot(); + + if (!failed) + tst_res(TPASS, "Measured times are within thresholds"); +} + +static void parse_timer_opts(void); + +static int set_latency(void) +{ + int fd, latency = 0; + + fd = open("/dev/cpu_dma_latency", O_WRONLY); + if (fd < 0) + return fd; + + return write(fd, &latency, sizeof(latency)); +} + +static void timer_setup(void) +{ + struct timespec t; + int ret; + + if (setup) + setup(); + + /* + * Running tests in VM may cause timing issues, disable upper bound + * checks if any hypervisor is detected. + */ + virt_env = tst_is_virt(VIRT_ANY); + tst_clock_getres(CLOCK_MONOTONIC, &t); + + tst_res(TINFO, "CLOCK_MONOTONIC resolution %lins", (long)t.tv_nsec); + + monotonic_resolution = t.tv_nsec / 1000; + timerslack = 50; + +#ifdef PR_GET_TIMERSLACK + ret = prctl(PR_GET_TIMERSLACK); + if (ret < 0) { + tst_res(TINFO, "prctl(PR_GET_TIMERSLACK) = -1, using %uus", + timerslack); + } else { + timerslack = ret / 1000; + tst_res(TINFO, "prctl(PR_GET_TIMERSLACK) = %ius", timerslack); + } +#else + tst_res(TINFO, "PR_GET_TIMERSLACK not defined, using %uus", + timerslack); +#endif /* PR_GET_TIMERSLACK */ + parse_timer_opts(); + + samples = SAFE_MALLOC(sizeof(long long) * MAX(MAX_SAMPLES, sample_cnt)); + if (set_latency() < 0) + tst_res(TINFO, "Failed to set zero latency constraint: %m"); +} + +static void timer_cleanup(void) +{ + free(samples); + + if (cleanup) + cleanup(); +} + +static struct tst_timer_tcase { + long long usec; + unsigned int samples; +} tcases[] = { + {1000, 500}, + {2000, 500}, + {5000, 300}, + {10000, 100}, + {25000, 50}, + {100000, 10}, + {1000000, 2}, +}; + +static void timer_test_fn(unsigned int n) +{ + do_timer_test(tcases[n].usec, tcases[n].samples); +} + +static void single_timer_test(void) +{ + do_timer_test(sleep_time, sample_cnt); +} + +static struct tst_option options[] = { + {"p", &print_frequency_plot, "-p Print frequency plot"}, + {"s:", &str_sleep_time, "-s us Sleep time"}, + {"n:", &str_sample_cnt, "-n uint Number of samples to take"}, + {"f:", &file_name, "-f fname Write measured samples into a file"}, + {NULL, NULL, NULL} +}; + +static void parse_timer_opts(void) +{ + if (str_sleep_time) { + if (tst_parse_int(str_sleep_time, &sleep_time, 0, INT_MAX)) { + tst_brk(TBROK, + "Invalid sleep time '%s'", str_sleep_time); + } + } + + if (str_sample_cnt) { + if (tst_parse_int(str_sample_cnt, &sample_cnt, 1, INT_MAX)) { + tst_brk(TBROK, + "Invalid sample count '%s'", str_sample_cnt); + } + } + + if (str_sleep_time || str_sample_cnt) { + if (sleep_time < 0) + sleep_time = 10000; + + if (!sample_cnt) + sample_cnt = 500; + + long long timeout = sleep_time * sample_cnt / 1000000; + + tst_set_timeout(timeout + timeout/10); + + test->test_all = single_timer_test; + test->test = NULL; + test->tcnt = 0; + } +} + +struct tst_test *tst_timer_test_setup(struct tst_test *timer_test) +{ + setup = timer_test->setup; + cleanup = timer_test->cleanup; + scall = timer_test->scall; + sample = timer_test->sample; + + timer_test->scall = NULL; + timer_test->setup = timer_setup; + timer_test->cleanup = timer_cleanup; + timer_test->test = timer_test_fn; + timer_test->tcnt = ARRAY_SIZE(tcases); + timer_test->sample = NULL; + timer_test->options = options; + + test = timer_test; + + return timer_test; +} diff --git a/src/kernel/tests/lib/tst_tmpdir.c b/src/kernel/tests/lib/tst_tmpdir.c new file mode 100644 index 0000000..0c39eb8 --- /dev/null +++ b/src/kernel/tests/lib/tst_tmpdir.c @@ -0,0 +1,347 @@ +/********************************************************** + * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ + *********************************************************/ + +/********************************************************** + * + * OS Testing - Silicon Graphics, Inc. + * + * FUNCTION NAME : tst_tmpdir, tst_rmdir + * + * FUNCTION TITLE : Create/remove a testing temp dir + * + * SYNOPSIS: + * void tst_tmpdir(); + * void tst_rmdir(); + * + * AUTHOR : Dave Fenner + * + * INITIAL RELEASE : UNICOS 8.0 + * + * DESCRIPTION + * tst_tmpdir() is used to create a unique, temporary testing + * directory, and make it the current working directory. + * tst_rmdir() is used to remove the directory created by + * tst_tmpdir(). + * + * RETURN VALUE + * Neither tst_tmpdir() or tst_rmdir() has a return value. + * + *********************************************************/ +#define _GNU_SOURCE +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> +#include <errno.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <dirent.h> +#include <fcntl.h> + +#include "test.h" +#include "safe_macros.h" +#include "ltp_priv.h" +#include "lapi/futex.h" + +/* + * Define some useful macros. + */ +#define DIR_MODE (S_IRWXU|S_IRWXG|S_IRWXO) + +#ifndef PATH_MAX +#ifdef MAXPATHLEN +#define PATH_MAX MAXPATHLEN +#else +#define PATH_MAX 1024 +#endif +#endif + +/* + * Define global variables. + */ +extern char *TCID; /* defined/initialized in main() */ +static char *TESTDIR = NULL; /* the directory created */ + +static char test_start_work_dir[PATH_MAX]; + +/* lib/tst_checkpoint.c */ +extern futex_t *tst_futexes; + +static int rmobj(const char *obj, char **errmsg); + +int tst_tmpdir_created(void) +{ + return TESTDIR != NULL; +} + +char *tst_get_tmpdir(void) +{ + if (TESTDIR == NULL) { + tst_brkm(TBROK, NULL, "you must call tst_tmpdir() first"); + return NULL; + } + + return strdup(TESTDIR); +} + +const char *tst_get_startwd(void) +{ + return test_start_work_dir; +} + +static int purge_dir(const char *path, char **errptr) +{ + int ret_val = 0; + DIR *dir; + struct dirent *dir_ent; + char dirobj[PATH_MAX]; + static char err_msg[PATH_MAX + 1280]; + + /* Do NOT perform the request if the directory is "/" */ + if (!strcmp(path, "/")) { + if (errptr) { + strcpy(err_msg, "Cannot purge system root directory"); + *errptr = err_msg; + } + + return -1; + } + + errno = 0; + + /* Open the directory to get access to what is in it */ + if (!(dir = opendir(path))) { + if (errptr) { + sprintf(err_msg, + "Cannot open directory %s; errno=%d: %s", + path, errno, tst_strerrno(errno)); + *errptr = err_msg; + } + return -1; + } + + /* Loop through the entries in the directory, removing each one */ + for (dir_ent = readdir(dir); dir_ent; dir_ent = readdir(dir)) { + /* Don't remove "." or ".." */ + if (!strcmp(dir_ent->d_name, ".") + || !strcmp(dir_ent->d_name, "..")) + continue; + + /* Recursively remove the current entry */ + sprintf(dirobj, "%s/%s", path, dir_ent->d_name); + if (rmobj(dirobj, errptr) != 0) + ret_val = -1; + } + + closedir(dir); + return ret_val; +} + +static int rmobj(const char *obj, char **errmsg) +{ + int ret_val = 0; + struct stat statbuf; + static char err_msg[PATH_MAX + 1280]; + int fd; + + fd = open(obj, O_DIRECTORY | O_NOFOLLOW); + if (fd >= 0) { + close(fd); + ret_val = purge_dir(obj, errmsg); + + /* If there were problems removing an entry, don't attempt to + remove the directory itself */ + if (ret_val == -1) + return -1; + + /* Get the link count, now that all the entries have been removed */ + if (lstat(obj, &statbuf) < 0) { + if (errmsg != NULL) { + sprintf(err_msg, + "lstat(%s) failed; errno=%d: %s", obj, + errno, tst_strerrno(errno)); + *errmsg = err_msg; + } + return -1; + } + + /* Remove the directory itself */ + if (statbuf.st_nlink >= 3) { + /* The directory is linked; unlink() must be used */ + if (unlink(obj) < 0) { + if (errmsg != NULL) { + sprintf(err_msg, + "unlink(%s) failed; errno=%d: %s", + obj, errno, tst_strerrno(errno)); + *errmsg = err_msg; + } + return -1; + } + } else { + /* The directory is not linked; remove() can be used */ + if (remove(obj) < 0) { + if (errmsg != NULL) { + sprintf(err_msg, + "remove(%s) failed; errno=%d: %s", + obj, errno, tst_strerrno(errno)); + *errmsg = err_msg; + } + return -1; + } + } + } else { + if (unlink(obj) < 0) { + if (errmsg != NULL) { + sprintf(err_msg, + "unlink(%s) failed; errno=%d: %s", obj, + errno, tst_strerrno(errno)); + *errmsg = err_msg; + } + return -1; + } + } + + return 0; +} + +void tst_tmpdir(void) +{ + char template[PATH_MAX]; + char *env_tmpdir; + char *errmsg, *c; + + /* + * Create a template for the temporary directory. Use the + * environment variable TMPDIR if it is available, otherwise + * use our default TEMPDIR. + */ + env_tmpdir = getenv("TMPDIR"); + if (env_tmpdir) { + c = strchr(env_tmpdir, '/'); + /* + * Now we force environment variable TMPDIR to be an absolute + * pathname, which dose not make much sense, but it will + * greatly simplify code in tst_rmdir(). + */ + if (c != env_tmpdir) { + tst_brkm(TBROK, NULL, "You must specify an absolute " + "pathname for environment variable TMPDIR"); + return; + } + snprintf(template, PATH_MAX, "%s/%.3sXXXXXX", env_tmpdir, TCID); + } else { + snprintf(template, PATH_MAX, "%s/%.3sXXXXXX", TEMPDIR, TCID); + } + + /* Make the temporary directory in one shot using mkdtemp. */ + if (mkdtemp(template) == NULL) { + tst_brkm(TBROK | TERRNO, NULL, + "%s: mkdtemp(%s) failed", __func__, template); + return; + } + + if ((TESTDIR = strdup(template)) == NULL) { + tst_brkm(TBROK | TERRNO, NULL, + "%s: strdup(%s) failed", __func__, template); + return; + } + + SAFE_CHOWN(NULL, TESTDIR, -1, getgid()); + + SAFE_CHMOD(NULL, TESTDIR, DIR_MODE); + + if (getcwd(test_start_work_dir, sizeof(test_start_work_dir)) == NULL) { + tst_resm(TINFO, "Failed to record test working dir"); + test_start_work_dir[0] = '\0'; + } + + /* + * Change to the temporary directory. If the chdir() fails, issue + * TBROK messages for all test cases, attempt to remove the + * directory (if it was created), and exit. If the removal also + * fails, also issue a TWARN message. + */ + if (chdir(TESTDIR) == -1) { + tst_resm(TERRNO, "%s: chdir(%s) failed", __func__, TESTDIR); + + /* Try to remove the directory */ + if (rmobj(TESTDIR, &errmsg) == -1) { + tst_resm(TWARN, "%s: rmobj(%s) failed: %s", + __func__, TESTDIR, errmsg); + } + + tst_exit(); + } +} + +void tst_rmdir(void) +{ + char *errmsg; + + /* + * Check that TESTDIR is not NULL. + */ + if (TESTDIR == NULL) { + tst_resm(TWARN, + "%s: TESTDIR was NULL; no removal attempted", + __func__); + return; + } + + /* + * Unmap the backend file. + * This is needed to overcome the NFS "silly rename" feature. + */ + if (tst_futexes) { + msync((void *)tst_futexes, getpagesize(), MS_SYNC); + munmap((void *)tst_futexes, getpagesize()); + } + + /* + * Attempt to remove the "TESTDIR" directory, using rmobj(). + */ + if (rmobj(TESTDIR, &errmsg) == -1) { + tst_resm(TWARN, "%s: rmobj(%s) failed: %s", + __func__, TESTDIR, errmsg); + } +} + +void tst_purge_dir(const char *path) +{ + char *err; + + if (purge_dir(path, &err)) + tst_brkm(TBROK, NULL, "%s: %s", __func__, err); +} diff --git a/src/kernel/tests/lib/tst_virt.c b/src/kernel/tests/lib/tst_virt.c new file mode 100644 index 0000000..53d33e6 --- /dev/null +++ b/src/kernel/tests/lib/tst_virt.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2013 Linux Test Project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Further, this software is distributed without any warranty that it + * is free of the rightful claim of any third person regarding + * infringement or the like. Any license provided herein, whether + * implied or otherwise, applies only to this software file. Patent + * licenses, if any, provided herein do not apply to combinations of + * this program with other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include <unistd.h> +#include "test.h" +#include "safe_macros.h" + +static int is_kvm(void) +{ + FILE *cpuinfo; + char line[64]; + int found; + + /* this doesn't work with custom -cpu values, since there's + * no easy, reasonable or reliable way to work around those */ + cpuinfo = SAFE_FOPEN(NULL, "/proc/cpuinfo", "r"); + found = 0; + while (fgets(line, sizeof(line), cpuinfo) != NULL) { + if (strstr(line, "QEMU Virtual CPU")) { + found = 1; + break; + } + } + + SAFE_FCLOSE(NULL, cpuinfo); + return found; +} + +static int is_xen(void) +{ + char hypervisor_type[4]; + + if (access("/proc/xen", F_OK) == 0) + return 1; + + if (access("/sys/hypervisor/type", F_OK) == 0) { + SAFE_FILE_SCANF(NULL, "/sys/hypervisor/type", "%3s", + hypervisor_type); + return strncmp("xen", hypervisor_type, + sizeof(hypervisor_type)) == 0; + } + + return 0; +} + +static int try_systemd_detect_virt(void) +{ + FILE *f; + char virt_type[64]; + int ret; + + /* See tst_cmd.c */ + void *old_handler = signal(SIGCHLD, SIG_DFL); + + f = popen("systemd-detect-virt", "r"); + if (!f) { + signal(SIGCHLD, old_handler); + return 0; + } + + if (!fgets(virt_type, sizeof(virt_type), f)) + virt_type[0] = '\0'; + + ret = pclose(f); + + signal(SIGCHLD, old_handler); + + /* + * systemd-detect-virt not found by shell or no virtualization detected + * (systemd-detect-virt returns non-zero) + */ + if (ret < 0 || (WIFEXITED(ret) && WEXITSTATUS(ret) == 127)) + return -1; + + if (ret) + return 0; + + if (!strncmp("kvm", virt_type, 3)) + return VIRT_KVM; + + if (!strncmp("xen", virt_type, 3)) + return VIRT_XEN; + + return VIRT_OTHER; +} + +int tst_is_virt(int virt_type) +{ + int ret = try_systemd_detect_virt(); + + if (ret >= 0) { + if (virt_type == VIRT_ANY) + return ret != 0; + else + return ret == virt_type; + } + + switch (virt_type) { + case VIRT_ANY: + return is_xen() || is_kvm(); + case VIRT_XEN: + return is_xen(); + case VIRT_KVM: + return is_kvm(); + case VIRT_OTHER: + return 0; + } + + tst_brkm(TBROK, NULL, "invalid virt_type flag: %d", virt_type); + return -1; +} diff --git a/src/kernel/tests/lib/tst_wallclock.c b/src/kernel/tests/lib/tst_wallclock.c new file mode 100644 index 0000000..282d6ad --- /dev/null +++ b/src/kernel/tests/lib/tst_wallclock.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2019 Linaro Limited. All rights reserved. + * Author: Rafael David Tinoco <rafael.tinoco@linaro.org> + */ + +#include <errno.h> + +#define TST_NO_DEFAULT_MAIN + +#include "tst_test.h" +#include "tst_timer.h" +#include "tst_clocks.h" +#include "tst_wallclock.h" +#include "lapi/posix_clocks.h" + +static struct timespec real_begin, mono_begin; + +static int clock_saved; + +void tst_wallclock_save(void) +{ + /* save initial monotonic time to restore it when needed */ + + if (tst_clock_gettime(CLOCK_REALTIME, &real_begin)) + tst_brk(TBROK | TERRNO, "tst_clock_gettime() realtime failed"); + + if (tst_clock_gettime(CLOCK_MONOTONIC_RAW, &mono_begin)) { + if (errno == EINVAL) { + tst_brk(TCONF | TERRNO, + "tst_clock_gettime() didn't support CLOCK_MONOTONIC_RAW"); + } + + tst_brk(TBROK | TERRNO, "tst_clock_gettime() monotonic failed"); + } + + clock_saved = 1; +} + +void tst_wallclock_restore(void) +{ + static struct timespec mono_end, elapsed, adjust; + + if (!clock_saved) + return; + + clock_saved = 0; + + if (tst_clock_gettime(CLOCK_MONOTONIC_RAW, &mono_end)) + tst_brk(TBROK | TERRNO, "tst_clock_gettime() monotonic failed"); + + elapsed = tst_timespec_diff(mono_end, mono_begin); + + adjust = tst_timespec_add(real_begin, elapsed); + + /* restore realtime clock based on monotonic delta */ + + if (tst_clock_settime(CLOCK_REALTIME, &adjust)) + tst_brk(TBROK | TERRNO, "tst_clock_settime() realtime failed"); +} |