/* * Copyright(c) 2012-2019 Intel Corporation * SPDX-License-Identifier: BSD-3-Clause-Clear */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "cas_lib.h" #include "extended_err_msg.h" #include "cas_lib_utils.h" #include "safeclib/safe_str_lib.h" #include #include #include #include #include extern cas_printf_t cas_printf; #define IOCTL_RETRIES 3 /* this is how many times ioctl is called */ #define VT100_CLEARLINE "[K" #define ESCAPE 0x1b #define CARRIAGE_RETURN 0xd #define INVALID_DIRTY_NO ((uint64_t) (-1)) /* file descriptors for pipe */ /* 1 is writing end, 0 is reading end of a pipe */ int fdspipe[2]; /* these must be global for handling signals */ volatile static int interrupted = 0; /*!< signal was caught, interrupt the *!< management operation now! */ static int finished = 0; /*!< if management operation has finished *!< (so that progressbar drawing thread *!< can either display "100%" or exit quietly */ static int device_id = 0; /*!< id of caching device to which management *!< operation that is underway, applies */ /** * Default signal handling function exists so that SIGINT wan't interrupt management * operations in unpredicted/disallowed way. */ void sig_handler_default(int x) { static int inter_counter = 0; inter_counter++; if (inter_counter>4) { cas_printf(LOG_ERR, "Can't interrupt CAS management process\n"); } } /** * If management operation was interrupted due to user action (SIGINT) */ int was_ioctl_interrupted() { return interrupted; } void sig_handler_interrupt_flushing(int x) { struct kcas_interrupt_flushing cmd_info; int fd = open(CTRL_DEV_PATH, 0); close(fdspipe[1]); interrupted = 1; if (fd < 0) { cas_printf(LOG_ERR, "Device " CTRL_DEV_PATH " not found\n"); return; } memset(&cmd_info, 0, sizeof(cmd_info)); cmd_info.cache_id = device_id; int res = run_ioctl(fd, KCAS_IOCTL_INTERRUPT_FLUSHING, &cmd_info); close(fd); if (!res) { set_default_sig_handler(); } } /** * print current backtrace */ void dump_stack() { const int sym_max = 512; void *sym_buf[sym_max]; int nsym; nsym = backtrace(sym_buf, sym_max); backtrace_symbols_fd(sym_buf, nsym, 2); } /** * Sad CAS :( * dump stack to allow debugging. */ void segv_handler_default(int i) { cas_printf(LOG_ERR, "Segmentation fault\n"); dump_stack(); exit(EXIT_FAILURE); } /** * register default signal handling function */ void set_default_sig_handler() { signal(SIGINT, sig_handler_default); signal(SIGSEGV, segv_handler_default); } /** * handle errors of cafe c library (wrong parameters passed) */ static void safe_lib_constraint_handler(const char *msg, void *ptr, errno_t error) { cas_printf(LOG_ERR, "Safe C lib error\n"); if (msg) { cas_printf(LOG_ERR, "%s (%d)\n", msg, error); } dump_stack(); exit(EXIT_FAILURE); } /** * Register constraint handler for safe_string_library */ void set_safe_lib_constraint_handler() { set_mem_constraint_handler_s(safe_lib_constraint_handler); set_str_constraint_handler_s(safe_lib_constraint_handler); } int _open_ctrl_device(int quiet) { int fd; fd = open(CTRL_DEV_PATH, 0); if (fd < 0) { if (!quiet) { cas_printf(LOG_ERR, "Device " CTRL_DEV_PATH " not found\n"); cas_printf(LOG_INFO, "Is the kernel module loaded?\n"); } return -1; } return fd; } int open_ctrl_device_quiet() { return _open_ctrl_device(true); } /** * calls open on control device; returns either error (-1) or a valid file descriptor */ int open_ctrl_device() { return _open_ctrl_device(false); } /** * @brief print spinning wheel */ void print_progress_indicator(float prog, struct progress_status *ps) { static const char prog_indicator[] = { '|', '/', '-', '\\', '|', '/', '-', '\\'}; /*!< set of characters implementing "spinning wheel" progress indicator */ static int max_i = ARRAY_SIZE(prog_indicator); static int i = 0; printf("%c%s... [%c]%c" VT100_CLEARLINE, CARRIAGE_RETURN, ps->friendly_name, prog_indicator[i], ESCAPE); if (50 < prog) { /* we're almost there. Ignore all signals at this stage */ set_default_sig_handler(); } i = (i + 1) % max_i; fflush(stdout); } /** * @brief print progress bar once * @param prog degree of progress (0-100) * @param ps structure holding status between progressbar and caller */ void print_progress_bar(float prog, struct progress_status *ps) { /* constants affecting look of progressbar/activity indicator */ static const char progress_full = '='; /*!< represents progress_step of progress */ static const char progress_partial = '-';/*!< represents progress of more than 0 *!< but less than progress_step */ static const char progress_empty = ' '; /*!< progressbar didn't reach here */ static const char delimiter_left = '['; /*!< left delimiter of progress bar */ static const char delimiter_right = ']';/*!< right delimiter of progress bar */ static const int progress_step = 2; /*!< progress step - percentage of progress to *!< be represented by one character. i.e. if *!< progress stepis set to 2, entire *!< progressbar is 50 chars wide+2 chars for *!< delimiters */ int i, remaining_m; time_t elapsed, remaining_s; printf("%c%s... ", CARRIAGE_RETURN, ps->friendly_name); /* carriage return and "name of op"*/ putchar(delimiter_left); /* make sure, progressbar always moves forward and never backward */ if (prog < ps->progress_accumulated) { prog = ps->progress_accumulated; } else { ps->progress_accumulated = prog; } /* print actual progress bar */ for (i = progress_step; i <= prog; i += progress_step){ putchar(progress_full); } if (((int)prog) % progress_step) { putchar(progress_partial); i += progress_step; } for (; i <= 100; i += progress_step){ putchar(progress_empty); } elapsed = time(NULL) - ps->time_started; remaining_s = ((100 - prog) * elapsed) / (prog ?: 1); remaining_m = remaining_s / 60; remaining_s -= remaining_m * 60; if (remaining_m) { /* ESCAPE VT100_CLEARLINE is terminal control sequence to clear "rest * of the line */ printf("%c %3.1f%% [%dm%02lds remaining]%c" VT100_CLEARLINE, delimiter_right, prog, remaining_m, remaining_s, ESCAPE); } else { printf("%c %3.1f%% [%lds remaining]%c" VT100_CLEARLINE, delimiter_right, prog, remaining_s, ESCAPE); } fflush(stdout); } /** * @brief either print a progressbar or spinning wheel depending on prog */ void print_progress_bar_or_indicator(float prog, struct progress_status *ps) { if (0.01 > prog || 99.99 < prog) { print_progress_indicator(prog, ps); } else { print_progress_bar(prog, ps); } } /** * initialize progressbar structure; */ void init_progress_bar(struct progress_status *ps) { if (NULL != ps) { memset(ps, 0, sizeof(*ps)); ps->dirty_clines_curr = INVALID_DIRTY_NO; ps->dirty_clines_initial = INVALID_DIRTY_NO; ps->time_started = time(NULL); } } void get_core_flush_progress(int fd, int cache_id, int core_id, float *prog) { struct kcas_core_info cmd_info; memset(&cmd_info, 0, sizeof(cmd_info)); cmd_info.cache_id = cache_id; cmd_info.core_id = core_id; if (0 == ioctl(fd, KCAS_IOCTL_CORE_INFO, &cmd_info)) { *prog = calculate_flush_progress(cmd_info.info.dirty, cmd_info.info.flushed); } } void get_cache_flush_progress(int fd, int cache_id, float *prog) { struct kcas_cache_info cmd_info; memset(&cmd_info, 0, sizeof(cmd_info)); cmd_info.cache_id = cache_id; if (0 == ioctl(fd, KCAS_IOCTL_CACHE_INFO, &cmd_info)) { *prog = calculate_flush_progress(cmd_info.info.dirty, cmd_info.info.flushed); } } /** * pthread thread handling function - runs during proper ioctl execution. Prints command progress */ void *print_command_progress(void *th_arg) { static const int show_progressbar_after = 2; /*!< threshold in seconds */ int do_print_progress_bar = 0; int mseconds = 0; /*< milliseconds */ int fd; float prog = 0.; struct progress_status *ps = th_arg; /* argument of command progress of which is monitored */ /*1,2,0 are descriptors of stdout, err and in respectively*/ int running_tty = isatty(1) && isatty(2) && isatty(0); struct sigaction new_action, old_action; fd = open(CTRL_DEV_PATH, 0); if (fd < 0) { cas_printf(LOG_ERR, "Device " CTRL_DEV_PATH " not found\n"); return NULL; /* FAILURE; */ } device_id = ps->cache_id; sigaction(SIGINT, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) { new_action.sa_handler = sig_handler_interrupt_flushing; sigemptyset(&new_action.sa_mask); new_action.sa_flags = 0; sigaction(SIGINT, &new_action, NULL); } sched_yield(); while (1) { struct pollfd pfd; struct timespec ts; sigset_t sigmask; int ppoll_res; sigemptyset(&sigmask); ts.tv_sec = 1; ts.tv_nsec = 0; pfd.fd = fdspipe[0]; pfd.events = POLLIN | POLLRDHUP; ppoll_res = ppoll(&pfd, 1, &ts, &sigmask); if (ppoll_res < 0) { if (ENOMEM == errno) { sleep(1); /* ppoll call failed due to insufficient memory */ } else if (EINTR == errno) { interrupted = 1; } else { /* other error conditions are EFAULT or EINVAL * cannot happen in realistic conditions, * and are likely to refer to OS errors, which * cannot possibly be handled. Perform abortion. */ cas_printf(LOG_ERR, "Failed ppoll"); abort(); } } mseconds += 1000; if (interrupted) { /* if flushing is interrupted by signal, don't proceed with displaying * any kind of progress bar. if bar was previously printed, * print indicator instead */ if (do_print_progress_bar) { print_progress_indicator(100, ps); } break; } else if (finished) { if (do_print_progress_bar) { print_progress_bar_or_indicator(100., ps); } break; } if (ps->core_id == OCF_CORE_ID_INVALID) { get_cache_flush_progress(fd, ps->cache_id, &prog); } else { get_core_flush_progress(fd, ps->cache_id, ps->core_id, &prog); } /* it is normal that ioctl to get statistics * fails from time to time. Most common cases * of it are: * - during --start-cache when cache isn't added * - during --stopping-cache, when progress is * supposed to read "100%", but cache is actually * already removed and its stopping progress can't * be queried at all. */ if (mseconds >= show_progressbar_after * 1000 && running_tty && prog < 50) { do_print_progress_bar = 1; } if (do_print_progress_bar) { print_progress_bar_or_indicator(prog, ps); } } close(fdspipe[0]); close(fd); /* if progressbar was displayed at least one, clear line */ if (do_print_progress_bar) { printf("%c%c" VT100_CLEARLINE, CARRIAGE_RETURN, ESCAPE); } fflush(stdout); return NULL; } /* * Run ioctl in a way that displays progressbar (if flushing operation takes longer) * Catch SIGINT signal. * @param friendly_name name of management operation that shall * be displayed in command prompt */ int run_ioctl_interruptible(int fd, int command, void *cmd, char *friendly_name, int cache_id, int core_id) { pthread_t thread; int ioctl_res; struct progress_status ps; sigset_t sigset; init_progress_bar(&ps); ps.friendly_name = friendly_name; ps.cache_id = cache_id; ps.core_id = core_id; if (pipe(fdspipe)) { cas_printf(LOG_ERR,"Failed to allocate pipes.\n"); return -1; } interrupted = 0; sigemptyset(&sigset); sigaddset(&sigset, SIGINT); pthread_sigmask(SIG_BLOCK, &sigset, NULL); pthread_create(&thread, 0, print_command_progress, &ps); ioctl_res = run_ioctl(fd, command, cmd); if (!interrupted) { close(fdspipe[1]); } finished = 1; pthread_join(thread, 0); return ioctl_res; } /* * @brief ioctl wrapper that retries ioctl attempts within one second timeouts * @param[in] fd as for IOCTL(2) * @param[in] command as for IOCTL(2) * @param[inout] cmd_info as for IOCTL(2) */ int run_ioctl(int fd, int command, void *cmd) { int i, ret; struct timespec timeout = { .tv_sec = 1, .tv_nsec = 0, }; for (i = 0; i < IOCTL_RETRIES; i++) { ret = ioctl(fd, command, cmd); if (ret < 0) { if (interrupted) { return -EINTR; } if (EINTR == errno) { return -EINTR; } else if (EBUSY == errno) { int nret = nanosleep(&timeout, NULL); if (nret) { return -EINTR; } } else { return ret; } } else { break; } } return ret; } int create_pipe_pair(FILE **intermediate_file) { /* 1 is writing end, 0 is reading end of a pipe */ int pipefd[2]; if (pipe(pipefd)) { cas_printf(LOG_ERR,"Failed to create unidirectional pipe.\n"); return FAILURE; } intermediate_file[0] = fdopen(pipefd[0], "r"); if (!intermediate_file[0]) { cas_printf(LOG_ERR,"Failed to open reading end of an unidirectional pipe.\n"); close(pipefd[0]); close(pipefd[1]); return FAILURE; } intermediate_file[1] = fdopen(pipefd[1], "w"); if (!intermediate_file[1]) { cas_printf(LOG_ERR,"Failed to open reading end of an unidirectional pipe.\n"); fclose(intermediate_file[0]); close(pipefd[1]); return FAILURE; } return SUCCESS; }