/****************************************************************************** * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. * iSulad licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: * http://license.coscl.org.cn/MulanPSL2 * 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 v2 for more details. * Author: tanyifeng * Create: 2018-11-1 * Description: provide container utils functions *******************************************************************************/ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "isula_libutils/log.h" #include "isula_libutils/json_common.h" int mem_realloc(void **newptr, size_t newsize, void *oldptr, size_t oldsize) { void *tmp = NULL; if (newptr == NULL || newsize == 0) { goto err_out; } tmp = util_common_calloc_s(newsize); if (tmp == NULL) { ERROR("Failed to malloc memory"); goto err_out; } if (oldptr != NULL) { (void)memcpy(tmp, oldptr, (newsize < oldsize) ? newsize : oldsize); (void)memset(oldptr, 0, oldsize); free(oldptr); } *newptr = tmp; return 0; err_out: return -1; } static int util_read_pipe(int pipe_fd, char **out_buf, size_t *out_buf_size, size_t *out_real_size) { int ret = 0; char *tmp = NULL; char *buffer = *out_buf; size_t old_size = *out_buf_size; size_t real_size = *out_real_size; size_t new_size = 0; ssize_t read_size = 0; if (buffer == NULL) { new_size = PIPE_BUF + 1; buffer = util_common_calloc_s(new_size); if (buffer == NULL) { ERROR("Memory out"); ret = -1; goto out; } *out_buf_size = new_size; *out_buf = buffer; *out_real_size = 0; } else { if (old_size - real_size < PIPE_BUF + 1) { if (old_size > (SIZE_MAX - PIPE_BUF) - 1) { ERROR("Memory out"); ret = -1; goto out; } new_size = old_size + PIPE_BUF + 1; ret = mem_realloc((void *)(&tmp), new_size, (void *)buffer, old_size); if (ret != 0) { ERROR("Memory out"); ret = -1; goto out; } buffer = tmp; *out_buf_size = new_size; *out_buf = buffer; } } read_size = util_read_nointr(pipe_fd, buffer + real_size, PIPE_BUF); if (read_size > 0) { *out_real_size = real_size + (size_t)read_size; ret = 0; goto out; } else if (read_size < 0 && errno == EAGAIN) { ret = 0; goto out; } else { ret = -1; goto out; } out: return ret; } #ifndef PR_SET_MM #define PR_SET_MM 35 #endif #ifndef PR_SET_MM_MAP #define PR_SET_MM_MAP 14 #endif static bool util_dir_skip_current(const struct dirent *pdirent) { if (strcmp(pdirent->d_name, ".") == 0) { return true; } if (strcmp(pdirent->d_name, "..") == 0) { return true; } return false; } static bool util_is_std_fileno(int fd) { return fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO; } int util_check_inherited(bool closeall, int fd_to_ignore) { struct dirent *pdirent = NULL; int fd, fddir; DIR *directory = NULL; restart: directory = opendir("/proc/self/fd"); if (directory == NULL) { WARN("Failed to open directory: %m."); return -1; } fddir = dirfd(directory); pdirent = readdir(directory); for (; pdirent != NULL; pdirent = readdir(directory)) { if (util_dir_skip_current(pdirent)) { continue; } if (util_safe_int(pdirent->d_name, &fd) < 0) { continue; } if (util_is_std_fileno(fd) || fd == fddir || fd == fd_to_ignore) { continue; } if (closeall) { if (fd >= 0) { close(fd); fd = -1; } if (directory != NULL) { closedir(directory); directory = NULL; } goto restart; } } closedir(directory); return 0; } static int sig_num(const char *sig) { int n; if (util_safe_int(sig, &n) < 0) { return -1; } return n; } int util_sig_parse(const char *sig_name) { size_t n; const struct signame signames[] = SIGNAL_MAP_DEFAULT; if (sig_name == NULL) { return -1; } if (isdigit(*sig_name)) { return sig_num(sig_name); } else if (strncasecmp(sig_name, "sig", 3) == 0) { sig_name += 3; for (n = 0; n < sizeof(signames) / sizeof(signames[0]); n++) { if (strcasecmp(signames[n].name, sig_name) == 0) { return signames[n].num; } } } else { for (n = 0; n < sizeof(signames) / sizeof(signames[0]); n++) { if (strcasecmp(signames[n].name, sig_name) == 0) { return signames[n].num; } } } return -1; } bool util_check_signal_valid(int sig) { size_t i; const struct signame signames[] = SIGNAL_MAP_DEFAULT; for (i = 0; i < sizeof(signames) / sizeof(signames[0]); i++) { if (signames[i].num == sig) { return true; } } return false; } void *util_smart_calloc_s(size_t unit_size, size_t count) { if (unit_size == 0) { return NULL; } if (count > (MAX_MEMORY_SIZE / unit_size)) { return NULL; } return calloc(count, unit_size); } void *util_common_calloc_s(size_t size) { if (size == 0 || size > MAX_MEMORY_SIZE) { return NULL; } return calloc((size_t)1, size); } char *util_strdup_s(const char *src) { char *dst = NULL; if (src == NULL) { return NULL; } dst = strdup(src); if (dst == NULL) { abort(); } return dst; } int wait_for_pid(pid_t pid) { int st; int nret = 0; rep: nret = waitpid(pid, &st, 0); if (nret == -1) { if (errno == EINTR) { goto rep; } return -1; } if (nret != pid) { goto rep; } if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { return -1; } return 0; } int wait_for_pid_status(pid_t pid) { int st; int nret = 0; rep: nret = waitpid(pid, &st, 0); if (nret == -1) { if (errno == EINTR) { goto rep; } return -1; } if (nret != pid) { goto rep; } return st; } /* * if errmsg contain with 'not found'/'no such' error, set exit_code 127 * if errmsg contain with 'permission denied' error, set exit_code 126 */ void util_contain_errmsg(const char *errmsg, int *exit_code) { if (errmsg == NULL || exit_code == NULL) { return; } if (strcasestr(errmsg, "executable file not found") || strcasestr(errmsg, "no such file or directory") || strcasestr(errmsg, "system cannot find the file specified")) { *exit_code = 127; } else if (strcasestr(errmsg, "permission denied")) { *exit_code = 126; } else if (strcasestr(errmsg, "not a directory")) { *exit_code = 127; } return; } char *util_short_digest(const char *digest) { #define SHORT_DIGEST_LEN 12 char short_digest[SHORT_DIGEST_LEN + 1] = { 0 }; size_t start_pos = 0; if (digest == NULL) { return NULL; } if (!util_valid_digest(digest)) { ERROR("invalid digest %s", digest); return NULL; } if (strstr(digest, SHA256_PREFIX) == digest) { start_pos = strlen(SHA256_PREFIX); } (void)memcpy(short_digest, digest + start_pos, SHORT_DIGEST_LEN); short_digest[SHORT_DIGEST_LEN] = 0; return util_strdup_s(short_digest); } char *util_full_digest(const char *digest) { int nret = 0; char full_digest[PATH_MAX] = { 0 }; if (digest == NULL) { ERROR("invalid NULL digest"); return NULL; } nret = snprintf(full_digest, sizeof(full_digest), "%s%s", SHA256_PREFIX, digest); if (nret < 0 || (size_t)nret >= sizeof(full_digest)) { ERROR("digest too long failed"); return NULL; } return util_strdup_s(full_digest); } /* util_stat2proc() makes sure it can handle arbitrary executable file basenames * for `cmd', i.e. those with embedded whitespace or embedded ')'s. * Such names confuse %s (see scanf(3)), so the string is split and %39c * is used instead. (except for embedded ')' "(%[^)]c)" would work. */ proc_t *util_stat2proc(const char *s, size_t len) { int num; proc_t *p = NULL; char *tmp = NULL; if (s == NULL) { return NULL; } if (len == 0) { return NULL; } tmp = strrchr(s, ')'); /* split into "PID (cmd" and "" */ if (tmp == NULL) { return NULL; } *tmp = '\0'; /* replace trailing ')' with NUL */ p = util_common_calloc_s(sizeof(proc_t)); if (p == NULL) { return NULL; } /* parse these two strings separately, skipping the leading "(". */ /* comm[16] in kernel */ num = sscanf(s, "%d (%15c", &p->pid, p->cmd); if (num != 2) { ERROR("Call sscanf error: %s", errno ? strerror(errno) : ""); free(p); return NULL; } num = sscanf(tmp + 2, /* skip space after ')' too */ "%c " "%d %d %d %d %d " "%lu %lu %lu %lu %lu " "%Lu %Lu %Lu %Lu " /* utime stime cutime cstime */ "%ld %ld %ld %ld " "%Lu ", /* start_time */ &p->state, &p->ppid, &p->pgrp, &p->session, &p->tty, &p->tpgid, &p->flags, &p->min_flt, &p->cmin_flt, &p->maj_flt, &p->cmaj_flt, &p->utime, &p->stime, &p->cutime, &p->cstime, &p->priority, &p->nice, &p->timeout, &p->it_real_value, &p->start_time); if (num != 20) { // max arg to read ERROR("Call sscanf error: %s", errno ? strerror(errno) : ""); free(p); return NULL; } if (p->tty == 0) { p->tty = -1; /* the old notty val, update elsewhere bef. moving to 0 */ } return p; } bool util_process_alive(pid_t pid, unsigned long long start_time) { int sret = 0; bool alive = true; proc_t *pid_info = NULL; char filename[PATH_MAX] = { 0 }; char sbuf[1024] = { 0 }; /* bufs for stat */ if (pid == 0) { return false; } sret = kill(pid, 0); if (sret < 0 && errno == ESRCH) { return false; } sret = snprintf(filename, sizeof(filename), "/proc/%d/stat", pid); if (sret < 0 || (size_t)sret >= sizeof(filename)) { ERROR("Failed to sprintf filename"); goto out; } if ((util_file2str(filename, sbuf, sizeof(sbuf))) == -1) { ERROR("Failed to read pidfile %s", filename); alive = false; goto out; } pid_info = util_stat2proc(sbuf, sizeof(sbuf)); if (pid_info == NULL) { ERROR("Failed to get proc stat info"); alive = false; goto out; } if (start_time != pid_info->start_time) { alive = false; } out: free(pid_info); return alive; } static void set_stderr_buf(char **stderr_buf, const char *format, ...) { char errbuf[BUFSIZ + 1] = { 0 }; char *jerr = NULL; UTIL_FREE_AND_SET_NULL(*stderr_buf); va_list argp; va_start(argp, format); int nret = vsnprintf(errbuf, BUFSIZ, format, argp); va_end(argp); if (nret < 0 || nret >= BUFSIZ) { return; } *stderr_buf = json_marshal_string(errbuf, strlen(errbuf), NULL, &jerr); if (*stderr_buf == NULL) { *stderr_buf = util_strdup_s(errbuf); } free(jerr); } static int open_devnull(void) { int fd = util_open("/dev/null", O_RDWR, 0); if (fd < 0) { ERROR("Can't open /dev/null"); } return fd; } static int null_stdin(void) { int ret = -1; int fd = -1; fd = open_devnull(); if (fd >= 0) { ret = dup2(fd, STDIN_FILENO); close(fd); if (ret < 0) { return -1; } } return ret; } static inline bool deal_with_result_of_waitpid_nomsg(char **stderr_msg, size_t errmsg_len) { if (*stderr_msg == NULL || strlen(*stderr_msg) == 0 || errmsg_len == 0) { return true; } return false; } static bool deal_with_result_of_waitpid(int status, char **stderr_msg, size_t errmsg_len) { int signal; bool nomsg = false; if (stderr_msg == NULL) { ERROR("Invalid arguments"); return false; } nomsg = deal_with_result_of_waitpid_nomsg(stderr_msg, errmsg_len); if (status < 0) { if (nomsg) { set_stderr_buf(stderr_msg, "Failed to wait exec cmd process"); } return false; } if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 0) { return true; } if (nomsg) { set_stderr_buf(stderr_msg, "Command exit with status: %d", WEXITSTATUS(status)); } } else if (WIFSIGNALED((unsigned int)status)) { signal = WTERMSIG(status); if (nomsg) { set_stderr_buf(stderr_msg, "Command exit with signal: %d", signal); } } else if (WIFSTOPPED(status)) { signal = WSTOPSIG(status); if (nomsg) { set_stderr_buf(stderr_msg, "Command stop with signal: %d", signal); } } else { if (nomsg) { set_stderr_buf(stderr_msg, "Command exit with unknown status: %d", status); } } return false; } static void marshal_stderr_msg(char **buffer, size_t *real_size) { char *jerr = NULL; char *tmp_err = NULL; char *stderr_buffer = *buffer; size_t stderr_real_size = *real_size; if (stderr_buffer != NULL && strlen(stderr_buffer) > 0 && stderr_real_size > 0) { tmp_err = json_marshal_string(stderr_buffer, strlen(stderr_buffer), NULL, &jerr); if (tmp_err != NULL) { free(stderr_buffer); *buffer = tmp_err; *real_size = strlen(tmp_err); } free(jerr); } } bool util_exec_top_cmd(exec_top_func_t cb_func, char **args, const char *pid_args, size_t args_len, char **stdout_msg, char **stderr_msg) { bool ret = false; char *stdout_buffer = NULL; char *stderr_buffer = NULL; size_t stdout_buf_size = 0; size_t stderr_buf_size = 0; size_t stdout_real_size = 0; size_t stderr_real_size = 0; int stdout_close_flag = 0; int stderr_close_flag = 0; int err_fd[2] = { -1, -1 }; int out_fd[2] = { -1, -1 }; pid_t pid = 0; int status = 0; if (pipe2(err_fd, O_CLOEXEC | O_NONBLOCK) != 0) { ERROR("Failed to create pipe"); set_stderr_buf(&stderr_buffer, "Failed to create pipe"); goto out; } if (pipe2(out_fd, O_CLOEXEC | O_NONBLOCK) != 0) { ERROR("Failed to create pipe"); set_stderr_buf(&stderr_buffer, "Failed to create pipe"); close(err_fd[0]); close(err_fd[1]); goto out; } pid = fork(); if (pid == (pid_t) - 1) { ERROR("Failed to fork()"); set_stderr_buf(&stderr_buffer, "Failed to fork()"); close(err_fd[0]); close(err_fd[1]); close(out_fd[0]); close(out_fd[1]); goto out; } if (pid == (pid_t)0) { int nret = 0; nret = null_stdin(); if (nret < 0) { WARN("Failed to set stdin to /dev/null"); } // child process, dup2 out_fd[1] to stdout close(out_fd[0]); dup2(out_fd[1], STDOUT_FILENO); // child process, dup2 err_fd[1] to stderr close(err_fd[0]); dup2(err_fd[1], STDERR_FILENO); /* become session leader */ nret = setsid(); if (nret < 0) { COMMAND_ERROR("Failed to set process %d as group leader", getpid()); } cb_func(args, pid_args, args_len); } close(err_fd[1]); close(out_fd[1]); for (;;) { if (stdout_close_flag == 0) { stdout_close_flag = util_read_pipe(out_fd[0], &stdout_buffer, &stdout_buf_size, &stdout_real_size); } if (stderr_close_flag == 0) { stderr_close_flag = util_read_pipe(err_fd[0], &stderr_buffer, &stderr_buf_size, &stderr_real_size); } if (stdout_close_flag != 0 && stderr_close_flag != 0) { break; } usleep_nointerupt(1000); } marshal_stderr_msg(&stderr_buffer, &stderr_real_size); status = wait_for_pid_status(pid); ret = deal_with_result_of_waitpid(status, &stderr_buffer, stderr_real_size); close(err_fd[0]); close(out_fd[0]); out: *stdout_msg = stdout_buffer; *stderr_msg = stderr_buffer; return ret; } static void close_pipes_fd(int *pipes, size_t pipe_size) { size_t i = 0; for (i = 0; i < pipe_size; i++) { if (pipes[i] >= 0) { close(pipes[i]); pipes[i] = -1; } } } bool util_exec_cmd(exec_func_t cb_func, void *args, const char *stdin_msg, char **stdout_msg, char **stderr_msg) { bool ret = false; char *stdout_buffer = NULL; char *stderr_buffer = NULL; size_t stdout_buf_size = 0; size_t stderr_buf_size = 0; size_t stdout_real_size = 0; size_t stderr_real_size = 0; int stdout_close_flag = 0; int stderr_close_flag = 0; int err_fd[2] = { -1, -1 }; int out_fd[2] = { -1, -1 }; int in_fd[2] = {-1, -1}; pid_t pid = 0; int status = 0; if (pipe2(in_fd, O_CLOEXEC | O_NONBLOCK) != 0) { ERROR("Failed to create stdin pipe"); set_stderr_buf(&stderr_buffer, "Failed to create stdin pipe"); goto out; } if (pipe2(err_fd, O_CLOEXEC | O_NONBLOCK) != 0) { ERROR("Failed to create pipe"); set_stderr_buf(&stderr_buffer, "Failed to create pipe"); close_pipes_fd(in_fd, 2); goto out; } if (pipe2(out_fd, O_CLOEXEC | O_NONBLOCK) != 0) { ERROR("Failed to create pipe"); set_stderr_buf(&stderr_buffer, "Failed to create pipe"); close_pipes_fd(in_fd, 2); close_pipes_fd(err_fd, 2); goto out; } pid = fork(); if (pid == (pid_t) - 1) { ERROR("Failed to fork()"); set_stderr_buf(&stderr_buffer, "Failed to fork()"); close_pipes_fd(in_fd, 2); close_pipes_fd(err_fd, 2); close_pipes_fd(out_fd, 2); goto out; } if (pid == (pid_t)0) { int nret = 0; close(in_fd[1]); if (in_fd[0] != STDIN_FILENO) { dup2(in_fd[0], STDIN_FILENO); } else { if (fcntl(in_fd[0], F_SETFD, 0) != 0) { fprintf(stderr, "Failed to remove FD_CLOEXEC from fd."); exit(127); } } close(in_fd[0]); // child process, dup2 out_fd[1] to stdout close(out_fd[0]); dup2(out_fd[1], STDOUT_FILENO); // child process, dup2 err_fd[1] to stderr close(err_fd[0]); dup2(err_fd[1], STDERR_FILENO); /* become session leader */ nret = setsid(); if (nret < 0) { COMMAND_ERROR("Failed to set process %d as group leader", getpid()); } cb_func(args); } /* parent */ close(err_fd[1]); err_fd[1] = -1; close(out_fd[1]); out_fd[1] = -1; close(in_fd[0]); in_fd[0] = -1; if (stdin_msg != NULL) { size_t len = strlen(stdin_msg); if (util_write_nointr(in_fd[1], stdin_msg, len) != len) { WARN("Write instr: %s failed", stdin_msg); } } close(in_fd[1]); in_fd[1] = -1; for (;;) { if (stdout_close_flag == 0) { stdout_close_flag = util_read_pipe(out_fd[0], &stdout_buffer, &stdout_buf_size, &stdout_real_size); } if (stderr_close_flag == 0) { stderr_close_flag = util_read_pipe(err_fd[0], &stderr_buffer, &stderr_buf_size, &stderr_real_size); } if (stdout_close_flag != 0 && stderr_close_flag != 0) { break; } usleep_nointerupt(1000); } marshal_stderr_msg(&stderr_buffer, &stderr_real_size); status = wait_for_pid_status(pid); ret = deal_with_result_of_waitpid(status, &stderr_buffer, stderr_real_size); close(err_fd[0]); close(out_fd[0]); out: *stdout_msg = stdout_buffer; *stderr_msg = stderr_buffer; return ret; } char **get_backtrace(void) { #define BACKTRACE_SIZE 16 int addr_cnts; void *buffer[BACKTRACE_SIZE]; char **syms = NULL; addr_cnts = backtrace(buffer, BACKTRACE_SIZE); if (addr_cnts <= 0) { return NULL; } syms = backtrace_symbols(buffer, addr_cnts); if (syms == NULL) { return NULL; } return syms; } static long long get_time_unit(int unit) { long long u[255] = { 0 }; u['M'] = Time_Milli; u['s'] = Time_Second; u['m'] = Time_Minute; u['h'] = Time_Hour; return u[unit]; } static int get_time_ns(long long *pns, long long unit) { if (unit == 0) { return -1; } if (INT64_MAX / *pns >= unit) { *pns *= unit; return 0; } return -1; } int util_parse_time_str_to_nanoseconds(const char *value, int64_t *nanoseconds) { int ret = 0; long long tmp = 0; char unit = 0; size_t len = 0; char *num_str = NULL; if (value == NULL || nanoseconds == NULL) { return -1; } if (util_reg_match("^([0-9]+)+(ms|s|m|h)$", value) != 0) { return -1; } num_str = util_strdup_s(value); len = strlen(value); if (strstr(value, "ms") == NULL) { unit = *(value + len - 1); *(num_str + len - 1) = '\0'; } else { unit = 'M'; *(num_str + len - 2) = '\0'; } ret = util_safe_llong(num_str, &tmp); if (ret < 0) { ERROR("Illegal unsigned integer: %s", num_str); ret = -1; goto out; } if (tmp == 0) { goto out; } ret = get_time_ns(&tmp, get_time_unit(unit)); if (ret != 0) { ERROR("failed get nano seconds for %s", num_str); } *nanoseconds = (int64_t)tmp; out: free(num_str); return ret; } /* isulad: get starttime of process pid */ proc_t *util_get_process_proc_info(pid_t pid) { int sret = 0; proc_t *pid_info = NULL; char filename[PATH_MAX] = { 0 }; char sbuf[1024] = { 0 }; /* bufs for stat */ sret = snprintf(filename, sizeof(filename), "/proc/%d/stat", pid); if (sret < 0 || (size_t)sret >= sizeof(filename)) { ERROR("Failed to sprintf filename"); goto out; } if ((util_file2str(filename, sbuf, sizeof(sbuf))) == -1) { ERROR("Failed to read pidfile %s", filename); goto out; } pid_info = util_stat2proc(sbuf, sizeof(sbuf)); if (pid_info == NULL) { ERROR("Failed to get proc stat info"); goto out; } out: return pid_info; } int util_env_set_val(char ***penv, const size_t *penv_len, const char *key, size_t key_len, const char *newkv) { size_t i = 0; char **env = NULL; size_t env_len = 0; if (penv == NULL || penv_len == NULL || key == NULL || newkv == NULL) { return -1; } env = *penv; env_len = *penv_len; for (i = 0; i < env_len; i++) { size_t elen = strlen(env[i]); if (key_len < elen && (strncmp(key, env[i], key_len) == 0) && (env[i][key_len] == '=')) { free(env[i]); env[i] = util_strdup_s(newkv); if (env[i] == NULL) { ERROR("out of memory"); return -1; } return 0; } } /* can not find key env, return error. */ return -1; } int util_env_insert(char ***penv, size_t *penv_len, const char *key, size_t key_len, const char *newkv) { char **env = NULL; size_t env_len = 0; char **temp = NULL; int ret = 0; if (penv == NULL || penv_len == NULL || key == NULL || newkv == NULL) { return -1; } if (util_env_set_val(penv, penv_len, key, key_len, newkv) == 0) { return 0; } env = *penv; env_len = *penv_len; if (env_len > (SIZE_MAX / sizeof(char *)) - 1) { ERROR("Failed to realloc memory for envionment variables"); return -1; } ret = mem_realloc((void **)(&temp), (env_len + 1) * sizeof(char *), env, env_len * sizeof(char *)); if (ret != 0) { ERROR("Failed to realloc memory for envionment variables"); return -1; } env = temp; env[env_len] = util_strdup_s(newkv); env_len++; *penv = env; *penv_len = env_len; return 0; } char *util_env_get_val(char **env, size_t env_len, const char *key, size_t key_len) { size_t i = 0; if (key == NULL || env == NULL) { return NULL; } for (i = 0; i < env_len; i++) { size_t elen = strlen(env[i]); if (key_len < elen && !strncmp(key, env[i], key_len) && env[i][key_len] == '=') { return util_strdup_s(env[i] + key_len + 1); } } return NULL; } int util_parse_user_remap(const char *user_remap, unsigned int *host_uid, unsigned int *host_gid, unsigned int *size) { int ret = 0; size_t args_len = 0; char **items = NULL; if (user_remap == NULL || host_uid == NULL || host_gid == NULL || size == NULL) { COMMAND_ERROR("Out of memory"); return -1; } items = util_string_split(user_remap, ':'); if (items == NULL) { COMMAND_ERROR("split user remap '%s' failed", user_remap); ret = -1; goto out; } args_len = util_array_len((const char **)items); switch (args_len) { case 3: ret = util_safe_uint(items[0], host_uid); if (ret) { COMMAND_ERROR("Invalid host uid for '%s', uid must be unsigned int", user_remap); break; } ret = util_safe_uint(items[1], host_gid); if (ret) { COMMAND_ERROR("Invalid host gid for '%s', gid must be unsigned int", user_remap); break; } ret = util_safe_uint(items[2], size); if (ret) { COMMAND_ERROR("Invalid id offset for '%s', offset must be unsigned int", user_remap); break; } if (*size > MAX_ID_OFFSET || *size == 0) { COMMAND_ERROR("Invalid id offset for '%s', offset must be greater than 0 and less than %d", user_remap, MAX_ID_OFFSET); ret = -1; break; } break; default: COMMAND_ERROR("Invalid user remap specification '%s'. unsupported format", user_remap); ret = -1; break; } out: util_free_array(items); return ret; } char *util_str_token(char **input, const char *delimiter) { char *str = NULL; char *delimiter_found = NULL; char *tok = NULL; size_t tok_length = 0; if (input == NULL || delimiter == NULL) { return NULL; } str = *input; if (str == NULL) { return NULL; } delimiter_found = strstr(str, delimiter); if (delimiter_found != NULL) { tok_length = delimiter_found - str; } else { tok_length = strlen(str); } tok = strndup(str, tok_length); if (tok == NULL) { ERROR("strndup failed"); return NULL; } *input = delimiter_found != NULL ? delimiter_found + strlen(delimiter) : NULL; return tok; } bool pid_max_kernel_namespaced() { bool ret = false; FILE *fp = NULL; char *pline = NULL; size_t length = 0; fp = util_fopen("/proc/kallsyms", "r"); if (fp == NULL) { SYSERROR("Failed to open /proc/kallsyms"); return ret; } while (getline(&pline, &length, fp) != -1) { if (strstr(pline, "proc_dointvec_pidmax") != NULL) { ret = true; goto out; } } out: fclose(fp); free(pline); return ret; } bool check_sysctl_valid(const char *sysctl_key) { size_t i = 0; size_t full_keys_len = 0; size_t key_prefixes_len = 0; const char *sysctl_full_keys[] = { "kernel.msgmax", "kernel.msgmnb", "kernel.msgmni", "kernel.sem", "kernel.shmall", "kernel.shmmax", "kernel.shmmni", "kernel.shm_rmid_forced" }; const char *sysctl_key_prefixes[] = { "net.", "fs.mqueue." }; if (sysctl_key == NULL) { return false; } full_keys_len = sizeof(sysctl_full_keys) / sizeof(char *); key_prefixes_len = sizeof(sysctl_key_prefixes) / sizeof(char *); for (i = 0; i < full_keys_len; i++) { if (strcmp(sysctl_full_keys[i], sysctl_key) == 0) { return true; } } for (i = 0; i < key_prefixes_len; i++) { if (strncmp(sysctl_key_prefixes[i], sysctl_key, strlen(sysctl_key_prefixes[i])) == 0) { return true; } } return false; } void free_sensitive_string(char *str) { if (!util_valid_str(str)) { goto out; } (void)memset(str, 0, strlen(str)); out: free(str); } void memset_sensitive_string(char *str) { if (!util_valid_str(str)) { return; } (void)memset(str, 0, strlen(str)); } static char *get_mtpoint(const char *line) { int i; const char *tmp = NULL; char *pend = NULL; char *sret = NULL; size_t len; if (line == NULL) { goto err_out; } tmp = line; for (i = 0; i < 4; i++) { tmp = strchr(tmp, ' '); if (tmp == NULL) { goto err_out; } tmp++; } pend = strchr(tmp, ' '); if ((pend == NULL) || pend == tmp) { goto err_out; } /* stuck a \0 after the mountpoint */ len = (size_t)(pend - tmp); sret = util_common_calloc_s(len + 1); if (sret == NULL) { goto err_out; } (void)memcpy(sret, tmp, len); sret[len] = '\0'; err_out: return sret; } bool detect_mount(const char *path) { FILE *fp = NULL; char *line = NULL; char *mountpoint = NULL; size_t length = 0; bool bret = false; fp = util_fopen("/proc/self/mountinfo", "r"); if (fp == NULL) { ERROR("Failed opening /proc/self/mountinfo"); return false; } while (getline(&line, &length, fp) != -1) { mountpoint = get_mtpoint(line); if (mountpoint == NULL) { INFO("Error reading mountinfo: bad line '%s'", line); continue; } if (strcmp(mountpoint, path) == 0) { free(mountpoint); bret = true; goto out; } free(mountpoint); } out: fclose(fp); free(line); return bret; } bool util_deal_with_mount_info(mount_info_call_back_t cb, const char *pattern) { FILE *fp = NULL; char *line = NULL; char *mountpoint = NULL; size_t length = 0; bool bret = true; int nret = 0; fp = util_fopen("/proc/self/mountinfo", "r"); if (fp == NULL) { ERROR("Failed opening /proc/self/mountinfo"); return false; } while (getline(&line, &length, fp) != -1) { mountpoint = get_mtpoint(line); if (mountpoint == NULL) { INFO("Error reading mountinfo: bad line '%s'", line); continue; } nret = cb(mountpoint, pattern); free(mountpoint); if (nret != 0) { bret = false; goto out; } } out: fclose(fp); free(line); return bret; } static int set_echo_back(bool echo_back) { struct termios old, new; if (tcgetattr(STDIN_FILENO, &old)) { ERROR("get tc attribute failed: %s\n", strerror(errno)); return -1; } new = old; if (echo_back) { new.c_lflag |= ECHO | ICANON; } else { new.c_lflag &= ~(ECHO | ICANON); } if (tcsetattr(STDIN_FILENO, TCSANOW, &new)) { ERROR("set tc attribute failed: %s\n", strerror(errno)); return -1; } return 0; } int util_input_notty(char *buf, size_t maxlen) { size_t i = 0; int ret = 0; for (i = 0; i < maxlen; i++) { int c = getchar(); if (c == EOF || c == '\n') { break; } if (c < 0) { ret = -1; break; } buf[i] = (char) c; } return ret ? ret : (int)i; } int util_input_readall(char *buf, size_t maxlen) { size_t i = 0; int ret = 0; for (;;) { int c = getchar(); if (c == EOF) { break; } if (c < 0) { ret = -1; break; } // Skip chars larger than maxlen if (i + 1 >= maxlen) { continue; } buf[i] = (char) c; i++; } buf[i] = 0; // Strip last '\n' if (i > 0 && buf[i - 1] == '\n') { buf[i - 1] = 0; i--; } // Strip last '\r' if (i > 0 && buf[i - 1] == '\r') { buf[i - 1] = 0; i--; } return ret ? ret : (int)i; } static int util_input(char *buf, size_t maxlen, bool echo_back) { int ret = 0; if (set_echo_back(echo_back)) { return -1; } ret = util_input_notty(buf, maxlen); if (set_echo_back(true)) { return -1; } return ret; } // Get input from stdin, echo back if get any charactor. int util_input_echo(char *buf, size_t maxlen) { return util_input(buf, maxlen, true); } // Get input from stdin, no echo back. int util_input_noecho(char *buf, size_t maxlen) { return util_input(buf, maxlen, false); } void usleep_nointerupt(unsigned long usec) { #define SECOND_TO_USECOND_MUTIPLE 1000000 int ret = 0; struct timespec request = { 0 }; struct timespec remain = { 0 }; if (usec == 0) { return; } request.tv_sec = (time_t)(usec / SECOND_TO_USECOND_MUTIPLE); request.tv_nsec = (long)((usec % SECOND_TO_USECOND_MUTIPLE) * 1000); do { ret = nanosleep(&request, &remain); request = remain; } while (ret == -1 && errno == EINTR); } int util_generate_random_str(char *id, size_t len) { int fd = -1; int num = 0; size_t i; const int m = 256; len = len / 2; fd = open("/dev/urandom", O_RDONLY); if (fd == -1) { ERROR("Failed to open /dev/urandom"); return -1; } for (i = 0; i < len; i++) { int nret; if (read(fd, &num, sizeof(int)) < 0) { ERROR("Failed to read urandom value"); close(fd); return -1; } unsigned char rs = (unsigned char)(num % m); nret = snprintf((id + i * 2), ((len - i) * 2 + 1), "%02x", (unsigned int)rs); if (nret < 0 || (size_t)nret >= ((len - i) * 2 + 1)) { ERROR("Failed to snprintf random string"); close(fd); return -1; } } close(fd); id[i * 2] = '\0'; return 0; } void add_array_elem(char **array, size_t total, size_t *pos, const char *elem) { if (*pos + 1 >= total - 1) { return; } array[*pos] = util_strdup_s(elem); *pos += 1; } void add_array_kv(char **array, size_t total, size_t *pos, const char *k, const char *v) { if (k == NULL || v == NULL) { return; } add_array_elem(array, total, pos, k); add_array_elem(array, total, pos, v); } int util_validate_env(const char *env, char **dst) { int ret = 0; char *value = NULL; char **arr = util_string_split_multi(env, '='); if (arr == NULL) { ERROR("Failed to split env string"); return -1; } if (strlen(arr[0]) == 0) { ERROR("Invalid environment variable: %s", env); ret = -1; goto out; } if (util_array_len((const char **)arr) > 1) { *dst = util_strdup_s(env); goto out; } value = getenv(env); if (value == NULL) { *dst = NULL; goto out; } else { int sret; size_t len = strlen(env) + 1 + strlen(value) + 1; *dst = (char *)util_common_calloc_s(len); if (*dst == NULL) { ERROR("Out of memory"); ret = -1; goto out; } sret = snprintf(*dst, len, "%s=%s", env, value); if (sret < 0 || (size_t)sret >= len) { ERROR("Failed to compose env string"); ret = -1; goto out; } } out: util_free_array(arr); return ret; } int util_check_inherited_exclude_fds(bool closeall, int *fds_to_ignore, size_t len_fds) { struct dirent *pdirent = NULL; int fd, fddir; DIR *directory = NULL; size_t i = 0; restart: directory = opendir("/proc/self/fd"); if (directory == NULL) { WARN("Failed to open directory: %m."); return -1; } fddir = dirfd(directory); pdirent = readdir(directory); for (; pdirent != NULL; pdirent = readdir(directory)) { if (util_dir_skip_current(pdirent)) { continue; } if (util_safe_int(pdirent->d_name, &fd) < 0) { continue; } for (i = 0; i < len_fds; i++) { if (fds_to_ignore[i] == fd) { break; } } if (i < len_fds && fd == fds_to_ignore[i]) { continue; } if (util_is_std_fileno(fd) || fd == fddir) { continue; } if (closeall) { if (fd >= 0) { close(fd); fd = -1; } if (directory != NULL) { closedir(directory); directory = NULL; } goto restart; } } closedir(directory); return 0; } int get_cpu_num_cores(void) { int ncpus = (int)sysconf(_SC_NPROCESSORS_ONLN); if (ncpus < 1) { ERROR("Cannot determine number of CPUs: assuming 1\n"); ncpus = 1; } return ncpus; }