/****************************************************************************** * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. * iSulad licensed under the Mulan PSL v1. * You can use this software according to the terms and conditions of the Mulan PSL v1. * You may obtain a copy of Mulan PSL v1 at: * http://license.coscl.org.cn/MulanPSL * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR * PURPOSE. * See the Mulan PSL v1 for more details. * Author: tanyifeng * Create: 2018-11-08 * Description: provide console definition ******************************************************************************/ #include #include #include #include #include #include #include #include "console.h" #include "securec.h" #include "mainloop.h" #include "log.h" #include "utils.h" #include "constants.h" static ssize_t fifo_write_function(void *context, const void *data, size_t len) { ssize_t ret; int fd; fd = *(int *)context; ret = util_write_nointr(fd, data, len); // Ignore EAGAIN to prevent hang, do not report error if (errno == EAGAIN) { return (ssize_t)len; } if ((ret <= 0) || (ret != (ssize_t)len)) { ERROR("Failed to write %d: %s", fd, strerror(errno)); return -1; } return ret; } static ssize_t fd_write_function(void *context, const void *data, size_t len) { ssize_t ret; ret = util_write_nointr(*(int *)context, data, len); if ((ret <= 0) || (ret != (ssize_t)len)) { ERROR("Failed to write: %s", strerror(errno)); return -1; } return ret; } /* console cb tty fifoin */ static int console_cb_tty_stdin_with_escape(int fd, uint32_t events, void *cbdata, struct epoll_descr *descr) { struct tty_state *ts = cbdata; char c; int ret = 0; ssize_t r_ret, w_ret; if (fd != ts->stdin_reader) { ret = 1; goto out; } r_ret = util_read_nointr(ts->stdin_reader, &c, 1); if (r_ret <= 0) { ret = 1; goto out; } if (ts->tty_exit != -1) { if (c == ts->tty_exit && !(ts->saw_tty_exit)) { ts->saw_tty_exit = 1; goto out; } if (c == 'q' && ts->saw_tty_exit) { ret = 1; goto out; } ts->saw_tty_exit = 0; } if (ts->stdin_writer.context && ts->stdin_writer.write_func) { w_ret = ts->stdin_writer.write_func(ts->stdin_writer.context, &c, 1); if ((w_ret <= 0) || (w_ret != r_ret)) { ret = 1; goto out; } } out: return ret; } static int console_writer_write_data(const struct io_write_wrapper *writer, const char *buf, ssize_t len) { ssize_t ret; if (writer == NULL || writer->context == NULL || writer->write_func == NULL || len <= 0) { return 0; } ret = writer->write_func(writer->context, buf, (size_t)len); if (ret <= 0 || ret != len) { ERROR("failed to write, error:%s", strerror(errno)); return -1; } return 0; } /* console cb tty fifoin */ static int console_cb_stdio_copy(int fd, uint32_t events, void *cbdata, struct epoll_descr *descr) { struct tty_state *ts = cbdata; char *buf = NULL; size_t buf_len = MAX_MSG_BUFFER_SIZE; int ret = 0; ssize_t r_ret; buf = util_common_calloc_s(buf_len); if (buf == NULL) { ERROR("Out of memory"); return -1; } if (fd == ts->sync_fd) { ret = 1; goto out; } if (fd != ts->stdin_reader && fd != ts->stdout_reader && fd != ts->stderr_reader) { ret = 1; goto out; } r_ret = util_read_nointr(fd, buf, buf_len - 1); if (r_ret <= 0) { ret = 1; goto out; } if (fd == ts->stdin_reader) { if (console_writer_write_data(&ts->stdin_writer, buf, r_ret) != 0) { ret = 1; goto out; } } if (fd == ts->stdout_reader) { if (console_writer_write_data(&ts->stdout_writer, buf, r_ret) != 0) { ret = 1; goto out; } } if (fd == ts->stderr_reader) { if (console_writer_write_data(&ts->stderr_writer, buf, r_ret) != 0) { ret = 1; goto out; } } out: free(buf); return ret; } /* console fifo name */ int console_fifo_name(const char *rundir, const char *subpath, const char *stdflag, char *fifo_name, size_t fifo_name_sz, char *fifo_path, size_t fifo_path_sz, bool do_mkdirp) { int ret = 0; int nret = 0; nret = sprintf_s(fifo_path, fifo_path_sz, "%s/%s/", rundir, subpath); if (nret < 0) { ERROR("FIFO path:%s/%s/ is too long.", rundir, subpath); ret = -1; goto out; } if (do_mkdirp) { ret = util_mkdir_p(fifo_path, CONSOLE_FIFO_DIRECTORY_MODE); if (ret < 0) { COMMAND_ERROR("Unable to create console fifo directory %s: %s.", fifo_path, strerror(errno)); goto out; } } nret = sprintf_s(fifo_name, fifo_name_sz, "%s/%s/%s-fifo", rundir, subpath, stdflag); if (nret < 0) { ERROR("FIFO name %s/%s/%s-fifo is too long.", rundir, subpath, stdflag); ret = -1; goto out; } out: return ret; } /* console fifo create */ int console_fifo_create(const char *fifo_path) { int ret; ret = mknod(fifo_path, S_IFIFO | S_IRUSR | S_IWUSR, (dev_t)0); if (ret < 0 && errno != EEXIST) { ERROR("Failed to mknod monitor fifo %s: %s.", fifo_path, strerror(errno)); return -1; } return 0; } /* console fifo delete */ int console_fifo_delete(const char *fifo_path) { char *ret = NULL; char real_path[PATH_MAX + 1] = { 0x00 }; if (fifo_path == NULL || strlen(fifo_path) > PATH_MAX) { ERROR("Invalid input!"); return -1; } if (strlen(fifo_path) == 0) { return 0; } ret = realpath(fifo_path, real_path); if (ret == NULL) { if (errno != ENOENT) { ERROR("Failed to get real path: %s", fifo_path); return -1; } return 0; } if (unlink(real_path) && errno != ENOENT) { WARN("Unlink %s failed", real_path); return -1; } return 0; } /* console fifo open */ int console_fifo_open(const char *fifo_path, int *fdout, int flags) { int fd = 0; fd = util_open(fifo_path, O_RDONLY | O_NONBLOCK, (mode_t)0); if (fd < 0) { ERROR("Failed to open fifo %s to send message: %s.", fifo_path, strerror(errno)); return -1; } *fdout = fd; return 0; } /* console fifo open withlock */ int console_fifo_open_withlock(const char *fifo_path, int *fdout, int flags) { int fd = 0; struct flock lk; fd = util_open(fifo_path, flags, 0); if (fd < 0) { WARN("Failed to open fifo %s to send message: %s.", fifo_path, strerror(errno)); return -1; } lk.l_type = F_WRLCK; lk.l_whence = SEEK_SET; lk.l_start = 0; lk.l_len = 0; if (fcntl(fd, F_SETLK, &lk) != 0) { /* another console instance is already running, don't start up */ DEBUG("Another console instance already running with path : %s.", fifo_path); close(fd); return -1; } *fdout = fd; return 0; } /* console fifo close */ void console_fifo_close(int fd) { close(fd); } /* setup tios */ int setup_tios(int fd, struct termios *curr_tios) { struct termios tmptios; int ret = 0; if (!isatty(fd)) { ERROR("Specified fd: '%d' is not a tty", fd); return -1; } if (tcgetattr(fd, curr_tios)) { ERROR("Failed to get current terminal settings"); ret = -1; goto out; } tmptios = *curr_tios; cfmakeraw(&tmptios); tmptios.c_oflag |= OPOST; if (tcsetattr(fd, TCSAFLUSH, &tmptios)) { ERROR("Set terminal settings failed"); ret = -1; goto out; } out: return ret; } static void client_console_tty_state_close(struct epoll_descr *descr, const struct tty_state *ts) { if (ts->stdin_reader >= 0) { epoll_loop_del_handler(descr, ts->stdin_reader); } if (ts->stdout_reader >= 0) { epoll_loop_del_handler(descr, ts->stdout_reader); } if (ts->stderr_reader >= 0) { epoll_loop_del_handler(descr, ts->stderr_reader); } } /* console loop */ /* data direction: */ /* read stdinfd, write fifoinfd */ /* read fifooutfd, write stdoutfd */ /* read stderrfd, write stderrfd */ int client_console_loop(int stdinfd, int stdoutfd, int stderrfd, int fifoinfd, int fifooutfd, int fifoerrfd, int tty_exit, bool tty) { int ret; struct epoll_descr descr; struct tty_state ts; ret = epoll_loop_open(&descr); if (ret) { ERROR("Create epoll_loop error"); return ret; } ts.tty_exit = tty_exit; ts.saw_tty_exit = 0; ts.sync_fd = -1; ts.stdin_reader = -1; ts.stdout_reader = -1; ts.stderr_reader = -1; if (fifoinfd >= 0) { ts.stdin_reader = stdinfd; ts.stdin_writer.context = &fifoinfd; ts.stdin_writer.write_func = fd_write_function; if (tty) { ret = epoll_loop_add_handler(&descr, ts.stdin_reader, console_cb_tty_stdin_with_escape, &ts); if (ret) { INFO("Add handler for stdinfd faied. with error %s", strerror(errno)); } } else { ret = epoll_loop_add_handler(&descr, ts.stdin_reader, console_cb_stdio_copy, &ts); if (ret) { INFO("Add handler for stdinfd faied. with error %s", strerror(errno)); } } } if (fifooutfd >= 0) { ts.stdout_reader = fifooutfd; ts.stdout_writer.context = &stdoutfd; ts.stdout_writer.write_func = fd_write_function; ret = epoll_loop_add_handler(&descr, ts.stdout_reader, console_cb_stdio_copy, &ts); if (ret) { ERROR("Add handler for masterfd failed"); goto err_out; } } if (fifoerrfd >= 0) { ts.stderr_reader = fifoerrfd; ts.stderr_writer.context = &stderrfd; ts.stderr_writer.write_func = fd_write_function; ret = epoll_loop_add_handler(&descr, ts.stderr_reader, console_cb_stdio_copy, &ts); if (ret) { ERROR("Add handler for masterfd failed"); goto err_out; } } ret = epoll_loop(&descr, -1); if (ret) { ERROR("Epoll_loop error"); goto err_out; } ret = 0; err_out: client_console_tty_state_close(&descr, &ts); epoll_loop_close(&descr); return ret; } /* console loop copy */ static int console_loop_io_copy(int sync_fd, const int *srcfds, struct io_write_wrapper *writers, size_t len) { int ret = 0; size_t i = 0; struct epoll_descr descr; struct tty_state *ts = NULL; if (len > (SIZE_MAX / sizeof(struct tty_state)) - 1) { ERROR("Invalid io size"); return -1; } ts = util_common_calloc_s(sizeof(struct tty_state) * (len + 1)); if (ts == NULL) { ERROR("Out of memory"); return -1; } ret = epoll_loop_open(&descr); if (ret) { ERROR("Create epoll_loop error"); free(ts); return ret; } for (i = 0; i < len; i++) { // Reusing ts.stdout_reader and ts.stdout_writer for coping io ts[i].stdout_reader = srcfds[i]; ts[i].stdout_writer.context = writers[i].context; ts[i].stdout_writer.write_func = writers[i].write_func; ts[i].sync_fd = -1; ret = epoll_loop_add_handler(&descr, ts[i].stdout_reader, console_cb_stdio_copy, &ts[i]); if (ret != 0) { ERROR("Add handler for masterfd failed"); goto err_out; } } if (sync_fd >= 0) { ts[i].sync_fd = sync_fd; epoll_loop_add_handler(&descr, ts[i].sync_fd, console_cb_stdio_copy, &ts[i]); if (ret) { ERROR("Add handler for syncfd failed"); goto err_out; } } ret = epoll_loop(&descr, -1); if (ret != 0) { ERROR("Epoll_loop error"); goto err_out; } err_out: for (i = 0; i < (len + 1); i++) { epoll_loop_del_handler(&descr, ts[i].stdout_reader); } epoll_loop_close(&descr); free(ts); return ret; } struct io_copy_thread_arg { struct io_copy_arg *copy_arg; bool detach; size_t len; int sync_fd; sem_t wait_sem; }; static int io_copy_init_fds(size_t len, int **infds, int **outfds, int **srcfds, struct io_write_wrapper **writers) { size_t i; if (len > SIZE_MAX / sizeof(struct io_write_wrapper)) { ERROR("Invalid arguments"); return -1; } *srcfds = util_common_calloc_s(sizeof(int) * len); if (*srcfds == NULL) { ERROR("Out of memory"); return -1; } *infds = util_common_calloc_s(sizeof(int) * len); if (*infds == NULL) { ERROR("Out of memory"); return -1; } for (i = 0; i < len; i++) { (*infds)[i] = -1; } *outfds = util_common_calloc_s(sizeof(int) * len); if (*outfds == NULL) { ERROR("Out of memory"); return -1; } for (i = 0; i < len; i++) { (*outfds)[i] = -1; } *writers = util_common_calloc_s(sizeof(struct io_write_wrapper) * len); if (*writers == NULL) { ERROR("Out of memory"); return -1; } return 0; } static int io_copy_make_srcfds(size_t len, struct io_copy_arg *copy_arg, int *infds, int *srcfds) { size_t i; for (i = 0; i < len; i++) { if (copy_arg[i].srctype == IO_FIFO) { if (console_fifo_open((const char *)copy_arg[i].src, &(infds[i]), O_RDONLY | O_NONBLOCK)) { ERROR("failed to open console fifo."); return -1; } srcfds[i] = infds[i]; } else if (copy_arg[i].srctype == IO_FD) { srcfds[i] = *(int *)(copy_arg[i].src); } else { ERROR("Got invalid src fd type"); return -1; } } return 0; } static int io_copy_make_dstfds(size_t len, struct io_copy_arg *copy_arg, int *outfds, struct io_write_wrapper *writers) { size_t i; for (i = 0; i < len; i++) { if (copy_arg[i].dsttype == IO_FIFO) { if (console_fifo_open_withlock((const char *)copy_arg[i].dst, &outfds[i], O_RDWR | O_NONBLOCK)) { ERROR("Failed to open console fifo."); return -1; } writers[i].context = &outfds[i]; writers[i].write_func = fifo_write_function; } else if (copy_arg[i].dsttype == IO_FD) { writers[i].context = copy_arg[i].dst; writers[i].write_func = fd_write_function; } else if (copy_arg[i].dsttype == IO_FUNC) { struct io_write_wrapper *io_write = copy_arg[i].dst; writers[i].context = io_write->context; writers[i].write_func = io_write->write_func; writers[i].close_func = io_write->close_func; } else { ERROR("Got invalid dst fd type"); return -1; } } return 0; } static void io_copy_thread_cleanup(struct io_write_wrapper *writers, struct io_copy_thread_arg *thread_arg, int *infds, int *outfds, int *srcfds, size_t len) { size_t i = 0; for (i = 0; i < len; i++) { if (writers != NULL && writers[i].close_func != NULL) { (void)writers[i].close_func(writers[i].context, NULL); } } free(srcfds); for (i = 0; i < len; i++) { if ((infds != NULL) && (infds[i] >= 0)) { console_fifo_close(infds[i]); } if ((outfds != NULL) && (outfds[i] >= 0)) { console_fifo_close(outfds[i]); } } free(infds); free(outfds); free(writers); } static void *io_copy_thread_main(void *arg) { int ret = -1; struct io_copy_thread_arg *thread_arg = (struct io_copy_thread_arg *)arg; struct io_copy_arg *copy_arg = thread_arg->copy_arg; size_t len = 0; int *infds = NULL; int *outfds = NULL; // recored fds to close int *srcfds = NULL; struct io_write_wrapper *writers = NULL; int sync_fd = thread_arg->sync_fd; bool posted = false; if (thread_arg->detach) { ret = pthread_detach(pthread_self()); if (ret != 0) { CRIT("Set thread detach fail"); goto err; } } (void)prctl(PR_SET_NAME, "IoCopy"); len = thread_arg->len; if (io_copy_init_fds(len, &infds, &outfds, &srcfds, &writers) != 0) { goto err; } if (io_copy_make_srcfds(len, copy_arg, infds, srcfds) != 0) { goto err; } if (io_copy_make_dstfds(len, copy_arg, outfds, writers) != 0) { goto err; } sem_post(&thread_arg->wait_sem); posted = true; (void)console_loop_io_copy(sync_fd, srcfds, writers, len); err: if (!posted) { sem_post(&thread_arg->wait_sem); } io_copy_thread_cleanup(writers, thread_arg, infds, outfds, srcfds, len); return NULL; } int start_io_copy_thread(int sync_fd, bool detach, struct io_copy_arg *copy_arg, size_t len, pthread_t *tid) { int res = 0; struct io_copy_thread_arg thread_arg; if (copy_arg == NULL || len == 0) { return 0; } thread_arg.detach = detach; thread_arg.copy_arg = copy_arg; thread_arg.len = len; thread_arg.sync_fd = sync_fd; if (sem_init(&thread_arg.wait_sem, 0, 0)) { ERROR("Failed to init start semaphore"); return -1; } res = pthread_create(tid, NULL, io_copy_thread_main, (void *)(&thread_arg)); if (res != 0) { CRIT("Thread creation failed"); return -1; } sem_wait(&thread_arg.wait_sem); sem_destroy(&thread_arg.wait_sem); return 0; }