| /* -*- c-set-style: "K&R"; c-basic-offset: 8 -*- |
| * |
| * This file is part of PRoot. |
| * |
| * Copyright (C) 2014 STMicroelectronics |
| * |
| * 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 <sys/types.h> /* pid_t */ |
| #include <limits.h> /* PATH_MAX, */ |
| #include <sys/param.h> /* MAXSYMLINKS, */ |
| #include <errno.h> /* E*, */ |
| #include <sys/stat.h> /* lstat(2), S_ISREG(), */ |
| #include <unistd.h> /* access(2), lstat(2), */ |
| #include <string.h> /* string(3), */ |
| #include <assert.h> /* assert(3), */ |
| #include <stdio.h> /* sscanf(3), */ |
| |
| #include "path/canon.h" |
| #include "path/path.h" |
| #include "path/binding.h" |
| #include "path/glue.h" |
| #include "path/proc.h" |
| #include "extension/extension.h" |
| |
| /** |
| * Put an end-of-string ('\0') right before the last component of @path. |
| */ |
| static inline void pop_component(char *path) |
| { |
| int offset; |
| |
| /* Sanity checks. */ |
| assert(path != NULL); |
| |
| offset = strlen(path) - 1; |
| assert(offset >= 0); |
| |
| /* Don't pop over "/", it doesn't mean anything. */ |
| if (offset == 0) { |
| assert(path[0] == '/' && path[1] == '\0'); |
| return; |
| } |
| |
| /* Skip trailing path separators. */ |
| while (offset > 1 && path[offset] == '/') |
| offset--; |
| |
| /* Search for the previous path separator. */ |
| while (offset > 1 && path[offset] != '/') |
| offset--; |
| |
| /* Cut the end of the string before the last component. */ |
| path[offset] = '\0'; |
| assert(path[0] == '/'); |
| } |
| |
| /** |
| * Copy in @component the first path component pointed to by @cursor, |
| * this later is updated to point to the next component for a further |
| * call. This function returns: |
| * |
| * - -errno if an error occured. |
| * |
| * - FINAL_SLASH if it the last component of the path but we |
| * really expect a directory. |
| * |
| * - FINAL_NORMAL if it the last component of the path. |
| * |
| * - 0 otherwise. |
| */ |
| static inline Finality next_component(char component[NAME_MAX], const char **cursor) |
| { |
| const char *start; |
| ptrdiff_t length; |
| bool want_dir; |
| |
| /* Sanity checks. */ |
| assert(component != NULL); |
| assert(cursor != NULL); |
| |
| /* Skip leading path separators. */ |
| while (**cursor != '\0' && **cursor == '/') |
| (*cursor)++; |
| |
| /* Find the next component. */ |
| start = *cursor; |
| while (**cursor != '\0' && **cursor != '/') |
| (*cursor)++; |
| length = *cursor - start; |
| |
| if (length >= NAME_MAX) |
| return -ENAMETOOLONG; |
| |
| /* Extract the component. */ |
| strncpy(component, start, length); |
| component[length] = '\0'; |
| |
| /* Check if a [link to a] directory is expected. */ |
| want_dir = (**cursor == '/'); |
| |
| /* Skip trailing path separators. */ |
| while (**cursor != '\0' && **cursor == '/') |
| (*cursor)++; |
| |
| if (**cursor == '\0') |
| return (want_dir |
| ? FINAL_SLASH |
| : FINAL_NORMAL); |
| |
| return NOT_FINAL; |
| } |
| |
| /** |
| * Resolve bindings (if any) in @guest_path and copy the translated |
| * path into @host_path. Also, this function checks that a non-final |
| * component is either a directory (returned value is 0) or a symlink |
| * (returned value is 1), otherwise it returns -errno (-ENOENT or |
| * -ENOTDIR). |
| */ |
| static inline int substitute_binding_stat(Tracee *tracee, Finality finality, |
| const char guest_path[PATH_MAX], char host_path[PATH_MAX]) |
| { |
| struct stat statl; |
| int status; |
| |
| strcpy(host_path, guest_path); |
| status = substitute_binding(tracee, GUEST, host_path); |
| if (status < 0) |
| return status; |
| |
| /* Don't notify extensions during the initialization of a binding. */ |
| if (tracee->glue_type == 0) { |
| status = notify_extensions(tracee, HOST_PATH, (intptr_t)host_path, finality); |
| if (status < 0) |
| return status; |
| } |
| |
| statl.st_mode = 0; |
| status = lstat(host_path, &statl); |
| |
| /* Build the glue between the hostfs and the guestfs during |
| * the initialization of a binding. */ |
| if (status < 0 && tracee->glue_type != 0) { |
| statl.st_mode = build_glue(tracee, guest_path, host_path, finality); |
| if (statl.st_mode == 0) |
| status = -1; |
| } |
| |
| /* Return an error if a non-final component isn't a |
| * directory nor a symlink. The error is "No such |
| * file or directory" if this component doesn't exist, |
| * otherwise the error is "Not a directory". */ |
| if (!IS_FINAL(finality) && !S_ISDIR(statl.st_mode) && !S_ISLNK(statl.st_mode)) |
| return (status < 0 ? -ENOENT : -ENOTDIR); |
| |
| return (S_ISLNK(statl.st_mode) ? 1 : 0); |
| } |
| |
| /** |
| * Copy in @guest_path the canonicalization (see `man 3 realpath`) of |
| * @user_path regarding to @tracee->root. The path to canonicalize |
| * could be either absolute or relative to @guest_path. When the last |
| * component of @user_path is a link, it is dereferenced only if |
| * @deref_final is true -- it is useful for syscalls like lstat(2). |
| * The parameter @recursion_level should be set to 0 unless you know |
| * what you are doing. This function returns -errno if an error |
| * occured, otherwise it returns 0. |
| */ |
| int canonicalize(Tracee *tracee, const char *user_path, bool deref_final, |
| char guest_path[PATH_MAX], unsigned int recursion_level) |
| { |
| char scratch_path[PATH_MAX]; |
| Finality finality; |
| const char *cursor; |
| int status; |
| |
| /* Avoid infinite loop on circular links. */ |
| if (recursion_level > MAXSYMLINKS) |
| return -ELOOP; |
| |
| /* Sanity checks. */ |
| assert(user_path != NULL); |
| assert(guest_path != NULL); |
| assert(user_path != guest_path); |
| |
| if (strnlen(guest_path, PATH_MAX) >= PATH_MAX) |
| return -ENAMETOOLONG; |
| |
| if (user_path[0] != '/') { |
| /* Ensure 'guest_path' contains an absolute base of |
| * the relative `user_path`. */ |
| if (guest_path[0] != '/') |
| return -EINVAL; |
| } |
| else |
| strcpy(guest_path, "/"); |
| |
| /* Canonicalize recursely 'user_path' into 'guest_path'. */ |
| cursor = user_path; |
| finality = NOT_FINAL; |
| while (!IS_FINAL(finality)) { |
| Comparison comparison; |
| char component[NAME_MAX]; |
| char host_path[PATH_MAX]; |
| |
| finality = next_component(component, &cursor); |
| status = (int) finality; |
| if (status < 0) |
| return status; |
| |
| if (strcmp(component, ".") == 0) { |
| if (IS_FINAL(finality)) |
| finality = FINAL_DOT; |
| continue; |
| } |
| |
| if (strcmp(component, "..") == 0) { |
| pop_component(guest_path); |
| if (IS_FINAL(finality)) |
| finality = FINAL_SLASH; |
| continue; |
| } |
| |
| status = join_paths(2, scratch_path, guest_path, component); |
| if (status < 0) |
| return status; |
| |
| /* Resolve bindings and check that a non-final |
| * component exists and either is a directory or is a |
| * symlink. For this latter case, we check that the |
| * symlink points to a directory once it is |
| * canonicalized, at the end of this loop. */ |
| status = substitute_binding_stat(tracee, finality, scratch_path, host_path); |
| if (status < 0) |
| return status; |
| |
| /* Nothing special to do if it's not a link or if we |
| * explicitly ask to not dereference 'user_path', as |
| * required by syscalls like lstat(2). Obviously, this |
| * later condition does not apply to intermediate path |
| * components. Errors are explicitly ignored since |
| * they should be handled by the caller. */ |
| if (status <= 0 || (finality == FINAL_NORMAL && !deref_final)) { |
| strcpy(scratch_path, guest_path); |
| status = join_paths(2, guest_path, scratch_path, component); |
| if (status < 0) |
| return status; |
| continue; |
| } |
| |
| /* It's a link, so we have to dereference *and* |
| * canonicalize to ensure we are not going outside the |
| * new root. */ |
| comparison = compare_paths("/proc", guest_path); |
| switch (comparison) { |
| case PATHS_ARE_EQUAL: |
| case PATH1_IS_PREFIX: |
| /* Some links in "/proc" are generated |
| * dynamically by the kernel. PRoot has to |
| * emulate some of them. */ |
| status = readlink_proc(tracee, scratch_path, |
| guest_path, component, comparison); |
| switch (status) { |
| case CANONICALIZE: |
| /* The symlink is already dereferenced, |
| * now canonicalize it. */ |
| goto canon; |
| |
| case DONT_CANONICALIZE: |
| /* If and only very final, this symlink |
| * shouldn't be dereferenced nor canonicalized. */ |
| if (finality == FINAL_NORMAL) { |
| strcpy(guest_path, scratch_path); |
| return 0; |
| } |
| break; |
| |
| default: |
| if (status < 0) |
| return status; |
| } |
| |
| default: |
| break; |
| } |
| |
| status = readlink(host_path, scratch_path, sizeof(scratch_path)); |
| if (status < 0) |
| return status; |
| else if (status == sizeof(scratch_path)) |
| return -ENAMETOOLONG; |
| scratch_path[status] = '\0'; |
| |
| /* Remove the leading "root" part if needed, it's |
| * useful for "/proc/self/cwd/" for instance. */ |
| status = detranslate_path(tracee, scratch_path, host_path); |
| if (status < 0) |
| return status; |
| |
| canon: |
| /* Canonicalize recursively the referee in case it |
| * is/contains a link, moreover if it is not an |
| * absolute link then it is relative to |
| * 'guest_path'. */ |
| status = canonicalize(tracee, scratch_path, true, guest_path, recursion_level + 1); |
| if (status < 0) |
| return status; |
| |
| /* Check that a non-final canonicalized/dereferenced |
| * symlink exists and is a directory. */ |
| status = substitute_binding_stat(tracee, finality, guest_path, host_path); |
| if (status < 0) |
| return status; |
| |
| /* Here, 'guest_path' shouldn't be a symlink anymore, |
| * unless it is a named file descriptor. */ |
| assert(status != 1 || sscanf(guest_path, "/proc/%*d/fd/%d", &status) == 1); |
| } |
| |
| /* At the exit stage of the first level of recursion, |
| * `guest_path` is fully canonicalized but a terminating '/' |
| * or a terminating '.' may be required to keep the initial |
| * semantic of `user_path`. */ |
| if (recursion_level == 0) { |
| switch (finality) { |
| case FINAL_NORMAL: |
| break; |
| |
| case FINAL_SLASH: |
| strcpy(scratch_path, guest_path); |
| status = join_paths(2, guest_path, scratch_path, ""); |
| if (status < 0) |
| return status; |
| break; |
| |
| case FINAL_DOT: |
| strcpy(scratch_path, guest_path); |
| status = join_paths(2, guest_path, scratch_path, "."); |
| if (status < 0) |
| return status; |
| break; |
| |
| default: |
| assert(0); |
| } |
| } |
| |
| return 0; |
| } |