/****************************************************************************** * 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 container path functions ******************************************************************************/ #include #include #include #include #include #include #include #include #include "log.h" #include "path.h" #include "utils.h" #define ISSLASH(C) ((C) == '/') #define IS_ABSOLUTE_FILE_NAME(F) (ISSLASH ((F)[0])) #define IS_RELATIVE_FILE_NAME(F) (!IS_ABSOLUTE_FILE_NAME (F)) static bool do_clean_path_continue(const char *endpos, const char *stpos, const char *respath, char **dst) { if (endpos - stpos == 1 && stpos[0] == '.') { return true; } else if (endpos - stpos == 2 && stpos[0] == '.' && stpos[1] == '.') { char *dest = *dst; if (dest <= respath + 1) { return true; } for (--dest; dest > respath && !ISSLASH(dest[-1]); --dest) { continue; } *dst = dest; return true; } return false; } static int do_clean_path(const char *respath, const char *limit_respath, const char *stpos, char **dst) { char *dest = *dst; const char *endpos = NULL; for (endpos = stpos; *stpos; stpos = endpos) { while (ISSLASH(*stpos)) { ++stpos; } for (endpos = stpos; *endpos && !ISSLASH(*endpos); ++endpos) { } if (endpos - stpos == 0) { break; } else if (do_clean_path_continue(endpos, stpos, respath, &dest)) { continue; } if (!ISSLASH(dest[-1])) { *dest++ = '/'; } if (dest + (endpos - stpos) >= limit_respath) { ERROR("Path is too long"); if (dest > respath + 1) { dest--; } *dest = '\0'; return -1; } (void)memcpy(dest, stpos, (size_t)(endpos - stpos)); dest += endpos - stpos; *dest = '\0'; } *dst = dest; return 0; } char *cleanpath(const char *path, char *realpath, size_t realpath_len) { char *respath = NULL; char *dest = NULL; const char *stpos = NULL; const char *limit_respath = NULL; if (path == NULL || path[0] == '\0' || \ realpath == NULL || (realpath_len < PATH_MAX)) { return NULL; } respath = realpath; (void)memset(respath, 0, realpath_len); limit_respath = respath + PATH_MAX; if (!IS_ABSOLUTE_FILE_NAME(path)) { if (!getcwd(respath, PATH_MAX)) { ERROR("Failed to getcwd"); respath[0] = '\0'; goto error; } dest = strchr(respath, '\0'); if (dest == NULL) { ERROR("Failed to get the end of respath"); goto error; } if (strlen(path) >= (PATH_MAX - 1) - strlen(respath)) { ERROR("%s path too long", path); goto error; } (void)strcat(respath, path); stpos = path; } else { dest = respath; *dest++ = '/'; stpos = path; } if (do_clean_path(respath, limit_respath, stpos, &dest)) { goto error; } if (dest > respath + 1 && ISSLASH(dest[-1])) { --dest; } *dest = '\0'; return respath; error: return NULL; } static int do_path_realloc(const char *start, const char *end, char **rpath, char **dest, const char **rpath_limit) { int nret = 0; size_t new_size; size_t gap = 0; long long dest_offset = *dest - *rpath; char *new_rpath = NULL; if (*dest + (end - start) < *rpath_limit) { return 0; } gap = (size_t)(end - start) + 1; new_size = (size_t)(*rpath_limit - *rpath); if (new_size > SIZE_MAX - gap) { ERROR("Out of range!"); return -1; } if (gap > PATH_MAX) { new_size += gap; } else { new_size += PATH_MAX; } nret = mem_realloc((void **)(&new_rpath), new_size, *rpath, PATH_MAX); if (nret) { ERROR("Failed to realloc memory for files limit variables"); return -1; } *rpath = new_rpath; *rpath_limit = *rpath + new_size; *dest = *rpath + dest_offset; return 0; } static int do_get_symlinks_copy_buf(const char *buf, const char *prefix, size_t prefix_len, char **rpath, char **dest) { if (IS_ABSOLUTE_FILE_NAME(buf)) { if (prefix_len) { (void)memcpy(*rpath, prefix, prefix_len); } *dest = *rpath + prefix_len; *(*dest)++ = '/'; } else { if (*dest > *rpath + prefix_len + 1) { for (--(*dest); *dest > *rpath && !ISSLASH((*dest)[-1]); --(*dest)) { continue; } } } return 0; } static int do_get_symlinks(const char **fullpath, const char *prefix, size_t prefix_len, char **rpath, char **dest, const char **end, int *num_links, char **extra_buf) { int ret = -1; size_t len; ssize_t n; char *buf = NULL; if (++(*num_links) > MAXSYMLINKS) { ERROR("Too many links in '%s'", *fullpath); goto out; } buf = util_common_calloc_s(PATH_MAX); if (buf == NULL) { ERROR("Out of memory"); goto out; } n = readlink(*rpath, buf, PATH_MAX - 1); if (n < 0) { goto out; } buf[n] = '\0'; if (*extra_buf == NULL) { *extra_buf = util_common_calloc_s(PATH_MAX); if (*extra_buf == NULL) { ERROR("Out of memory"); goto out; } } len = strlen(*end); if (len >= PATH_MAX - (size_t)n) { ERROR("Path is too long"); goto out; } (void)memmove(&(*extra_buf)[n], *end, len + 1); (void)memcpy(*extra_buf, buf, (size_t)n); *fullpath = *end = *extra_buf; if (do_get_symlinks_copy_buf(buf, prefix, prefix_len, rpath, dest) != 0) { goto out; } ret = 0; out: free(buf); return ret; } static bool do_eval_symlinks_in_scope_is_symlink(const char *path) { struct stat st; if (lstat(path, &st) < 0) { return true; } if (!S_ISLNK(st.st_mode)) { return true; } return false; } static void do_eval_symlinks_skip_slash(const char **start, const char **end) { while (ISSLASH(**start)) { ++(*start); } for (*end = *start; **end && !ISSLASH(**end); ++(*end)) { } } static inline void skip_dest_trailing_slash(char **dest, char **rpath, size_t prefix_len) { if (*dest > *rpath + prefix_len + 1) { for (--(*dest); *dest > *rpath && !ISSLASH((*dest)[-1]); --(*dest)) { continue; } } } static inline bool is_current_char(const char c) { return c == '.'; } static inline bool is_specify_current(const char *end, const char *start) { return (end - start == 1) && is_current_char(start[0]); } static inline bool is_specify_parent(const char *end, const char *start) { return (end - start == 2) && is_current_char(start[0]) && is_current_char(start[1]); } static int do_eval_symlinks_in_scope(const char *fullpath, const char *prefix, size_t prefix_len, char **rpath, char **dest, const char *rpath_limit) { int nret = 0; int num_links = 0; const char *start = NULL; const char *end = NULL; char *extra_buf = NULL; start = fullpath + prefix_len; for (end = start; *start; start = end) { do_eval_symlinks_skip_slash(&start, &end); if (end - start == 0) { break; } else if (is_specify_current(end, start)) { continue; } else if (is_specify_parent(end, start)) { skip_dest_trailing_slash(dest, rpath, prefix_len); } else { if (!ISSLASH((*dest)[-1])) { *(*dest)++ = '/'; } nret = do_path_realloc(start, end, rpath, dest, &rpath_limit); if (nret != 0) { nret = -1; goto out; } (void)memcpy(*dest, start, (size_t)(end - start)); *dest += end - start; **dest = '\0'; if (do_eval_symlinks_in_scope_is_symlink(*rpath)) { continue; } nret = do_get_symlinks(&fullpath, prefix, prefix_len, rpath, dest, &end, &num_links, &extra_buf); if (nret != 0) { nret = -1; goto out; } } } out: free(extra_buf); return nret; } static char *eval_symlinks_in_scope(const char *fullpath, const char *rootpath) { char *root = NULL; char *rpath = NULL; char *dest = NULL; char *prefix = NULL; const char *rpath_limit = NULL; size_t prefix_len; char resroot[PATH_MAX] = { 0 }; if (fullpath == NULL || rootpath == NULL) { return NULL; } root = cleanpath(rootpath, resroot, sizeof(resroot)); if (root == NULL) { ERROR("Failed to get cleaned path"); return NULL; } if (strcmp(fullpath, root) == 0) { return util_strdup_s(fullpath); } if (strstr(fullpath, root) == NULL) { ERROR("Path '%s' is not in '%s'", fullpath, root); return NULL; } rpath = util_common_calloc_s(PATH_MAX); if (rpath == NULL) { ERROR("Out of memory"); goto out; } rpath_limit = rpath + PATH_MAX; prefix = root; prefix_len = (size_t)strlen(prefix); if (strcmp(prefix, "/") == 0) { prefix_len = 0; } dest = rpath; if (prefix_len) { (void)memcpy(rpath, prefix, prefix_len); dest += prefix_len; } *dest++ = '/'; if (do_eval_symlinks_in_scope(fullpath, prefix, prefix_len, &rpath, &dest, rpath_limit)) { goto out; } if (dest > rpath + prefix_len + 1 && ISSLASH(dest[-1])) { --dest; } *dest = '\0'; return rpath; out: free(rpath); return NULL; } char *follow_symlink_in_scope(const char *fullpath, const char *rootpath) { char resfull[PATH_MAX] = { 0 }; char *full = NULL; char resroot[PATH_MAX] = { 0 }; char *root = NULL; full = cleanpath(fullpath, resfull, sizeof(resfull)); if (full == NULL) { ERROR("Failed to get cleaned path"); return NULL; } root = cleanpath(rootpath, resroot, sizeof(resroot)); if (root == NULL) { ERROR("Failed to get cleaned path"); return NULL; } return eval_symlinks_in_scope(full, root); } bool specify_current_dir(const char *path) { char *dup = NULL; char *base = NULL; bool res = false; if (path == NULL) { return false; } if (path[0] == '\0') { return true; } dup = util_strdup_s(path); base = basename(dup); res = (base != NULL) && (strcmp(base, ".") == 0); free(dup); return res; } bool has_trailing_path_separator(const char *path) { return path != NULL && strlen(path) > 0 && (path[strlen(path) - 1] == '/'); } static void set_char_to_separator(char *p) { *p = '/'; } char *preserve_trailing_dot_or_separator(const char *cleanedpath, const char *originalpath) { int nret; char respath[PATH_MAX + 3] = { 0 }; if (cleanedpath == NULL || originalpath == NULL) { return NULL; } if (cleanedpath[0] == '\0' || originalpath[0] == '\0') { return NULL; } nret = snprintf(respath, PATH_MAX, "%s", cleanedpath); if (nret < 0 || (size_t)nret >= PATH_MAX) { ERROR("Failed to print string"); return NULL; } if (!specify_current_dir(cleanedpath) && specify_current_dir(originalpath)) { if (!has_trailing_path_separator(respath)) { set_char_to_separator(&respath[strlen(respath)]); } respath[strlen(respath)] = '.'; } if (!has_trailing_path_separator(respath) && has_trailing_path_separator(originalpath)) { set_char_to_separator(&respath[strlen(respath)]); } return util_strdup_s(respath); } int filepath_split(const char *path, char **dir, char **base) { ssize_t i; char *dup = NULL; if (path == NULL) { return -1; } dup = util_strdup_s(path); i = (ssize_t)strlen(dup) - 1; while (i >= 0 && dup[i] != '/') { i--; } if (i != -1) { char cache = dup[i + 1]; dup[i + 1] = '\0'; if (dir != NULL) { *dir = util_strdup_s(dup); } dup[i + 1] = cache; } else { if (dir != NULL) { *dir = util_strdup_s("."); } } if (base != NULL) { *base = util_strdup_s(dup + i + 1); } free(dup); return 0; } int split_dir_and_base_name(const char *path, char **dir, char **base) { char *dupdir = NULL; char *dupbase = NULL; if (path == NULL) { return -1; } dupdir = util_strdup_s(path); dupbase = util_strdup_s(path); if (dir != NULL) { *dir = util_strdup_s(dirname(dupdir)); } if (base != NULL) { *base = util_strdup_s(basename(dupbase)); } free(dupdir); free(dupbase); return 0; } char *get_resource_path(const char *rootpath, const char *path) { int nret; char tmppath[PATH_MAX] = { 0 }; char fullpath[PATH_MAX] = { 0 }; nret = snprintf(tmppath, sizeof(tmppath), "/%s/%s", rootpath, path); if (nret < 0 || (size_t)nret >= sizeof(tmppath)) { return NULL; } if (cleanpath(tmppath, fullpath, sizeof(fullpath)) == NULL) { return NULL; } return follow_symlink_in_scope(fullpath, rootpath); } int resolve_path(const char *rootpath, const char *path, char **resolvedpath, char **abspath) { int ret = -1; int nret; size_t len; char *dirpath = NULL; char *basepath = NULL; char *resolved_dir_path = NULL; char tmppath[PATH_MAX] = { 0 }; char cleanedpath[PATH_MAX] = { 0 }; *resolvedpath = NULL; *abspath = NULL; nret = snprintf(tmppath, sizeof(tmppath), "/%s", path); if (nret < 0 || (size_t)nret >= sizeof(tmppath)) { ERROR("Failed to print string"); return -1; } if (cleanpath(tmppath, cleanedpath, sizeof(cleanedpath)) == NULL) { ERROR("Failed to get cleaned path: %s", tmppath); return -1; } *abspath = preserve_trailing_dot_or_separator(cleanedpath, tmppath); if (*abspath == NULL) { ERROR("Failed to preserve path"); goto cleanup; } nret = filepath_split(*abspath, &dirpath, &basepath); if (nret < 0) { ERROR("Failed to split path"); goto cleanup; } resolved_dir_path = get_resource_path(rootpath, dirpath); if (resolved_dir_path == NULL) { ERROR("Failed to get resource path"); goto cleanup; } len = strlen(resolved_dir_path) + strlen("/") + strlen(basepath) + (size_t)1; *resolvedpath = util_common_calloc_s(len); if (*resolvedpath == NULL) { ERROR("Out of memory"); goto cleanup; } nret = snprintf(*resolvedpath, len, "%s/%s", resolved_dir_path, basepath); if (nret < 0 || (size_t)nret >= len) { ERROR("Failed to print string"); goto cleanup; } ret = 0; cleanup: free(dirpath); free(basepath); free(resolved_dir_path); if (ret != 0) { free(*abspath); *abspath = NULL; free(*resolvedpath); *resolvedpath = NULL; } return ret; } int split_path_dir_entry(const char *path, char **dir, char **base) { #define EXTRA_LEN 3 char cleaned[PATH_MAX + EXTRA_LEN] = { 0 }; char *dup = NULL; if (cleanpath(path, cleaned, PATH_MAX) == NULL) { ERROR("Failed to clean path"); return -1; } if (specify_current_dir(path)) { set_char_to_separator(&cleaned[strlen(cleaned)]); cleaned[strlen(cleaned)] = '.'; } dup = util_strdup_s(cleaned); if (dir != NULL) { *dir = util_strdup_s(dirname(dup)); } if (base != NULL) { *base = util_strdup_s(basename(cleaned)); } free(dup); return 0; } static char *find_realpath(const char *path) { int ret; int i; int max_symlink_iter = 10; char *iter_path = NULL; struct stat st; iter_path = util_strdup_s(path); ret = lstat(iter_path, &st); for (i = 0; i <= max_symlink_iter && ret == 0 && S_ISLNK(st.st_mode); i++) { char target[PATH_MAX] = { 0 }; char *parent = NULL; ret = (int)readlink(iter_path, target, PATH_MAX - 1); if (ret < 0) { ERROR("Failed to read link of %s: %s", iter_path, strerror(errno)); goto out; } // is not absolutely path if (target[0] != '\0' && target[0] != '/') { if (split_path_dir_entry(iter_path, &parent, NULL) < 0) { goto out; } free(iter_path); iter_path = util_path_join(parent, target); if (iter_path == NULL) { ERROR("Failed to join path"); free(parent); goto out; } } else { free(iter_path); iter_path = util_strdup_s(target); } ret = lstat(iter_path, &st); free(parent); } if (i > max_symlink_iter) { ERROR("Too many symlinks in: %s", path); goto out; } return iter_path; out: free(iter_path); return NULL; } int realpath_in_scope(const char *rootfs, const char *path, char **real_path) { int ret = 0; char full_path[PATH_MAX] = { 0 }; char cleaned[PATH_MAX] = { 0 }; char *tmp = NULL; int nret = snprintf(full_path, sizeof(full_path), "%s%s", rootfs, path); if (nret < 0 || (size_t)nret >= sizeof(full_path)) { ERROR("sprintf error: %s", strerror(errno)); ret = -1; goto out; } if (cleanpath(full_path, cleaned, PATH_MAX) == NULL) { ERROR("Failed to clean path: %s", full_path); ret = -1; goto out; } tmp = find_realpath(cleaned); if (tmp == NULL) { ret = -1; goto out; } if (strncmp(tmp, rootfs, strlen(rootfs)) != 0) { free(tmp); ret = -1; goto out; } *real_path = tmp; out: return ret; }