lxc/0024-isulad-support-symlink-in-mount-entry-and-not-permit.patch
LiFeng c1c967d9bc lxc: make lxc-libs package
Signed-off-by: LiFeng <lifeng68@huawei.com>
2020-02-14 06:13:22 -05:00

822 lines
22 KiB
Diff

From c008838abc34819705f1b2ad51b24b5d7bbd629c Mon Sep 17 00:00:00 2001
From: tanyifeng <tanyifeng1@huawei.com>
Date: Mon, 14 Jan 2019 20:12:06 +0800
Subject: [PATCH 024/139] isulad: support symlink in mount entry, and not
permit mount to /proc
Signed-off-by: LiFeng <lifeng68@huawei.com>
---
src/lxc/Makefile.am | 2 +
src/lxc/conf.c | 108 ++++++++++-
src/lxc/path.c | 546 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/lxc/path.h | 70 +++++++
4 files changed, 721 insertions(+), 5 deletions(-)
create mode 100644 src/lxc/path.c
create mode 100644 src/lxc/path.h
diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am
index 08e2fab..f2928b7 100644
--- a/src/lxc/Makefile.am
+++ b/src/lxc/Makefile.am
@@ -12,6 +12,7 @@ noinst_HEADERS = attach.h \
confile_utils.h \
criu.h \
error.h \
+ path.h \
file_utils.h \
../include/netns_ifaddrs.h \
initutils.h \
@@ -95,6 +96,7 @@ liblxc_la_SOURCES = af_unix.c af_unix.h \
commands_utils.c commands_utils.h \
conf.c conf.h \
confile.c confile.h \
+ path.c path.h \
confile_utils.c confile_utils.h \
criu.c criu.h \
error.c error.h \
diff --git a/src/lxc/conf.c b/src/lxc/conf.c
index 55d1e45..800573a 100644
--- a/src/lxc/conf.c
+++ b/src/lxc/conf.c
@@ -77,6 +77,7 @@
#include "storage/overlay.h"
#include "syscall_wrappers.h"
#include "terminal.h"
+#include "path.h"
#include "utils.h"
#ifdef MAJOR_IN_MKDEV
@@ -2309,6 +2310,79 @@ static void cull_mntent_opt(struct mntent *mntent)
}
}
+/* isulad: checkMountDestination checks to ensure that the mount destination is not over the top of /proc.
+ * dest is required to be an abs path and have any symlinks resolved before calling this function. */
+static int check_mount_destination(const char *rootfs, const char *dest)
+{
+ const char *invalid_destinations[] = {
+ "/proc",
+ NULL
+ };
+ // White list, it should be sub directories of invalid destinations
+ const char *valid_destinations[] = {
+ // These entries can be bind mounted by files emulated by fuse,
+ // so commands like top, free displays stats in container.
+ "/proc/cpuinfo",
+ "/proc/diskstats",
+ "/proc/meminfo",
+ "/proc/stat",
+ "/proc/swaps",
+ "/proc/uptime",
+ "/proc/net/dev",
+ NULL
+ };
+ const char **valid, **invalid;
+
+ for(valid = valid_destinations; *valid != NULL; valid++) {
+ char *fullpath, *relpath;
+ const char *parts[3] = {
+ rootfs,
+ *valid,
+ NULL
+ };
+ fullpath = lxc_string_join("/", parts, false);
+ if (!fullpath) {
+ ERROR("Out of memory");
+ return -1;
+ }
+ relpath = path_relative(fullpath, dest);
+ free(fullpath);
+ if (!relpath)
+ return -1;
+ if (!strcmp(relpath, ".")) {
+ free(relpath);
+ return 0;
+ }
+ free(relpath);
+ }
+
+ for(invalid = invalid_destinations; *invalid != NULL; invalid++) {
+ char *fullpath, *relpath;
+ const char *parts[3] = {
+ rootfs,
+ *invalid,
+ NULL
+ };
+ fullpath = lxc_string_join("/", parts, false);
+ if (!fullpath) {
+ ERROR("Out of memory");
+ return -1;
+ }
+ relpath = path_relative(fullpath, dest);
+ free(fullpath);
+ if (!relpath)
+ return -1;
+ if (!strcmp(relpath, ".") || strncmp(relpath, "..", 2)) {
+ ERROR("%s cannot be mounted because it is located inside %s", dest, *invalid);
+ free(relpath);
+ return -1;
+ }
+ free(relpath);
+ }
+
+ return 0;
+}
+
static int mount_entry_create_dir_file(const struct mntent *mntent,
const char *path,
const struct lxc_rootfs *rootfs,
@@ -2370,7 +2444,8 @@ static inline int mount_entry_on_generic(struct mntent *mntent,
unsigned long mntflags, pflags;
char *mntdata;
bool dev, optional, relative;
- char *rootfs_path = NULL;
+ char *rootfs_path = NULL, *rpath = NULL;
+ const char *dest = path;
optional = hasmntopt(mntent, "optional") != NULL;
dev = hasmntopt(mntent, "dev") != NULL;
@@ -2379,9 +2454,29 @@ static inline int mount_entry_on_generic(struct mntent *mntent,
if (rootfs && rootfs->path)
rootfs_path = rootfs->mount;
- ret = mount_entry_create_dir_file(mntent, path, rootfs, lxc_name,
- lxc_path);
+ // isulad: ensure that the destination of the bind mount is resolved of symlinks at mount time because
+ // any previous mounts can invalidate the next mount's destination.
+ // this can happen when a user specifies mounts within other mounts to cause breakouts or other
+ // evil stuff to try to escape the container's rootfs.
+ if (rootfs_path) {
+ rpath = follow_symlink_in_scope(path, rootfs_path);
+ if (!rpath) {
+ ERROR("Failed to get real path for '%s'", path);
+ return -1;
+ }
+ dest = rpath;
+
+ ret = check_mount_destination(rootfs_path, dest);
+ if (ret) {
+ ERROR("Mount destination is invalid: '%s'", dest);
+ free(rpath);
+ return -1;
+ }
+ }
+
+ ret = mount_entry_create_dir_file(mntent, dest, rootfs, lxc_name, lxc_path);
if (ret < 0) {
+ free(rpath);
if (optional)
return 0;
@@ -2390,13 +2485,16 @@ static inline int mount_entry_on_generic(struct mntent *mntent,
cull_mntent_opt(mntent);
ret = parse_mntopts(mntent->mnt_opts, &mntflags, &pflags, &mntdata);
- if (ret < 0)
+ if (ret < 0) {
+ free(rpath);
return -1;
+ }
- ret = mount_entry(mntent->mnt_fsname, path, mntent->mnt_type, mntflags,
+ ret = mount_entry(mntent->mnt_fsname, dest, mntent->mnt_type, mntflags,
pflags, mntdata, optional, dev, relative, rootfs_path);
free(mntdata);
+ free(rpath);
return ret;
}
diff --git a/src/lxc/path.c b/src/lxc/path.c
new file mode 100644
index 0000000..e917dcb
--- /dev/null
+++ b/src/lxc/path.c
@@ -0,0 +1,546 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <libgen.h>
+
+#include "path.h"
+#include "log.h"
+
+lxc_log_define(lxc_path_ui, lxc);
+
+#define ISSLASH(C) ((C) == '/')
+#define IS_ABSOLUTE_FILE_NAME(F) (ISSLASH ((F)[0]))
+#define IS_RELATIVE_FILE_NAME(F) (! IS_ABSOLUTE_FILE_NAME (F))
+
+bool specify_current_dir(const char *path)
+{
+ char *basec = NULL, *bname = NULL;
+ bool res = false;
+
+ basec = strdup(path);
+ if (!basec) {
+ ERROR("Out of memory");
+ return false;
+ }
+
+ bname = basename(basec);
+ res = !strcmp(bname, ".");
+ free(basec);
+ return res;
+}
+
+bool has_traling_path_separator(const char *path)
+{
+ return path && strlen(path) && (path[strlen(path) - 1] == '/');
+}
+
+// PreserveTrailingDotOrSeparator returns the given cleaned path
+// and appends a trailing `/.` or `/` if its corresponding original
+// path ends with a trailing `/.` or `/`. If the cleaned
+// path already ends in a `.` path segment, then another is not added. If the
+// clean path already ends in a path separator, then another is not added.
+char *preserve_trailing_dot_or_separator(const char *cleanedpath,
+ const char *originalpath)
+{
+ char *respath = NULL;
+ size_t len;
+
+ len = strlen(cleanedpath) + 3;
+ respath = malloc(len);
+ if (!respath) {
+ ERROR("Out of memory");
+ return NULL;
+ }
+ memset(respath, 0x00, len);
+ strcat(respath, cleanedpath);
+
+ if (!specify_current_dir(cleanedpath) && specify_current_dir(originalpath)) {
+ if (!has_traling_path_separator(respath))
+ strcat(respath, "/");
+ strcat(respath, ".");
+ }
+
+ if (!has_traling_path_separator(respath) &&
+ has_traling_path_separator(originalpath))
+ strcat(respath, "/");
+
+ return respath;
+}
+
+
+// Split splits path immediately following the final Separator,
+// separating it into a directory and file name component.
+// If there is no Separator in path, Split returns an empty dir
+// and file set to path.
+// The returned values have the property that path = dir+file.
+bool filepath_split(const char *path, char **dir, char **base)
+{
+ ssize_t i;
+ size_t len;
+
+ len = strlen(path);
+ i = len - 1;
+ while (i >= 0 && path[i] != '/')
+ i--;
+
+ *dir = malloc(i + 2);
+ if (!*dir) {
+ ERROR("Out of memory");
+ return false;
+ }
+ memcpy(*dir, path, i + 1);
+ *(*dir + i + 1) = '\0';
+
+ *base = strdup(path + i + 1);
+ if (!*base) {
+ ERROR("Out of memory");
+ free(*dir);
+ *dir = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * cleanpath is similar to realpath of glibc, but not expands symbolic links,
+ * and not check the existence of components of the path.
+ */
+char *cleanpath(const char *path, char *resolved)
+{
+ char *rpath, *dest;
+ const char *start, *end, *rpath_limit;
+
+ if (path == NULL || path[0] == '\0')
+ return NULL;
+
+ if (resolved == NULL) {
+ rpath = malloc(PATH_MAX);
+ if (rpath == NULL) {
+ ERROR("Out of memory");
+ return NULL;
+ }
+ } else {
+ rpath = resolved;
+ }
+ rpath_limit = rpath + PATH_MAX;
+
+ if (!IS_ABSOLUTE_FILE_NAME(path)) {
+ if (!getcwd(rpath, PATH_MAX)) {
+ ERROR("Failed to getcwd");
+ rpath[0] = '\0';
+ goto error;
+ }
+ dest = strchr(rpath, '\0');
+ start = path;
+ } else {
+ dest = rpath;
+ *dest++ = '/';
+ start = path;
+ }
+
+ for (end = start; *start; start = end) {
+ /* Skip sequence of multiple path-separators. */
+ while (ISSLASH(*start))
+ ++start;
+
+ /* Find end of path component. */
+ for (end = start; *end && !ISSLASH(*end); ++end)
+ /* Nothing. */;
+
+ if (end - start == 0) {
+ break;
+ } else if (end - start == 1 && start[0] == '.') {
+ /* nothing */;
+ } else if (end - start == 2 && start[0] == '.' && start[1] == '.') {
+ /* Back up to previous component, ignore if at root already. */
+ if (dest > rpath + 1)
+ for (--dest; dest > rpath && !ISSLASH(dest[-1]); --dest)
+ continue;
+ } else {
+ size_t new_size;
+
+ if (!ISSLASH(dest[-1]))
+ *dest++ = '/';
+
+ if (dest + (end - start) >= rpath_limit) {
+ long long dest_offset = dest - rpath;
+ char *new_rpath;
+
+ if (resolved) {
+ printf("Path is to long");
+ if (dest > rpath + 1)
+ dest--;
+ *dest = '\0';
+ goto error;
+ }
+
+ new_size = rpath_limit - rpath;
+ if (end - start + 1 > PATH_MAX)
+ new_size += end - start + 1;
+ else
+ new_size += PATH_MAX;
+ new_rpath = (char *) realloc(rpath, new_size);
+ if (new_rpath == NULL) {
+ ERROR("Out of memory");
+ goto error;
+ }
+ rpath = new_rpath;
+ rpath_limit = rpath + new_size;
+
+ dest = rpath + dest_offset;
+ }
+
+ memcpy(dest, start, end - start);
+ dest += end - start;
+ *dest = '\0';
+ }
+ }
+ if (dest > rpath + 1 && ISSLASH(dest[-1]))
+ --dest;
+ *dest = '\0';
+
+ return rpath;
+
+error:
+ if (resolved == NULL)
+ free(rpath);
+ return NULL;
+}
+
+// evalSymlinksInScope will evaluate symlinks in `path` within a scope `root` and return
+// a result guaranteed to be contained within the scope `root`, at the time of the call.
+// Symlinks in `root` are not evaluated and left as-is.
+// Errors encountered while attempting to evaluate symlinks in path will be returned.
+// Non-existing paths are valid and do not constitute an error.
+// `path` has to contain `root` as a prefix, or else an error will be returned.
+// Trying to break out from `root` does not constitute an error.
+//
+// Example:
+// If /foo/bar -> /outside,
+// FollowSymlinkInScope("/foo/bar", "/foo") == "/foo/outside" instead of "/oustide"
+char *eval_symlinks_in_scope(const char *fullpath, const char *rootpath)
+{
+ char resroot[PATH_MAX] = {0}, *root = NULL;
+ char *rpath, *dest, *prefix, *extra_buf = NULL;
+ const char *start, *end, *rpath_limit;
+ int num_links = 0;
+ size_t prefix_len;
+
+ if (!fullpath || !rootpath)
+ return NULL;
+
+ root = cleanpath(rootpath, resroot);
+ if (!root) {
+ ERROR("Failed to get cleaned path");
+ return NULL;
+ }
+
+ if (!strcmp(fullpath, root))
+ return strdup(fullpath);
+
+ if (!strstr(fullpath, root)) {
+ ERROR("Path '%s' is not in '%s'", fullpath, root);
+ return NULL;
+ }
+
+ rpath = malloc(PATH_MAX);
+ if (rpath == NULL) {
+ ERROR("Out of memory");
+ goto error;
+ return NULL;
+ }
+ rpath_limit = rpath + PATH_MAX;
+
+ prefix = root;
+ prefix_len = strlen(prefix);
+ if (!strcmp(prefix, "/"))
+ prefix_len = 0;
+
+ dest = rpath;
+ if (prefix_len) {
+ memcpy(rpath, prefix, prefix_len);
+ dest += prefix_len;
+ }
+ *dest++ = '/';
+ start = fullpath + prefix_len;
+
+ for (end = start; *start; start = end) {
+ struct stat st;
+ int n;
+
+ /* Skip sequence of multiple path-separators. */
+ while (ISSLASH(*start))
+ ++start;
+
+ /* Find end of path component. */
+ for (end = start; *end && !ISSLASH(*end); ++end)
+ /* Nothing. */;
+
+ if (end - start == 0) {
+ break;
+ } else if (end - start == 1 && start[0] == '.') {
+ /* nothing */;
+ } else if (end - start == 2 && start[0] == '.' && start[1] == '.') {
+ /* Back up to previous component, ignore if at root already. */
+ if (dest > rpath + prefix_len + 1)
+ for (--dest; dest > rpath && !ISSLASH(dest[-1]); --dest)
+ continue;
+ } else {
+ size_t new_size;
+
+ if (!ISSLASH(dest[-1]))
+ *dest++ = '/';
+
+ if (dest + (end - start) >= rpath_limit) {
+ long long dest_offset = dest - rpath;
+ char *new_rpath;
+
+ new_size = rpath_limit - rpath;
+ if (end - start + 1 > PATH_MAX)
+ new_size += end - start + 1;
+ else
+ new_size += PATH_MAX;
+ new_rpath = (char *) realloc(rpath, new_size);
+ if (new_rpath == NULL) {
+ ERROR("Out of memory");
+ goto error;
+ }
+ rpath = new_rpath;
+ rpath_limit = rpath + new_size;
+
+ dest = rpath + dest_offset;
+ }
+
+ memcpy(dest, start, end - start);
+ dest += end - start;
+ *dest = '\0';
+
+ if (lstat(rpath, &st) < 0) {
+ // if rpath does not exist, accept it
+ continue;
+ }
+
+ if (S_ISLNK(st.st_mode)) {
+ char *buf;
+ size_t len;
+
+ if (++num_links > MAXSYMLINKS) {
+ ERROR("Too many links in '%s'", fullpath);
+ goto error;
+ }
+
+ buf = malloc(PATH_MAX);
+ if (!buf) {
+ ERROR("Out of memory");
+ goto error;
+ }
+
+ n = readlink(rpath, buf, PATH_MAX - 1);
+ if (n < 0) {
+ free(buf);
+ goto error;
+ }
+ buf[n] = '\0';
+
+ if (!extra_buf) {
+ extra_buf = malloc(PATH_MAX);
+ if (!extra_buf) {
+ ERROR("Out of memory");
+ free(buf);
+ goto error;
+ }
+ }
+
+ len = strlen(end);
+ if ((long int)(n + len) >= PATH_MAX) {
+ free(buf);
+ ERROR("Path is too long");
+ goto error;
+ }
+
+ /* Careful here, end may be a pointer into extra_buf... */
+ memmove(&extra_buf[n], end, len + 1);
+ fullpath = end = memcpy(extra_buf, buf, n);
+
+ if (IS_ABSOLUTE_FILE_NAME(buf)) {
+ if (prefix_len)
+ memcpy(rpath, prefix, prefix_len);
+ dest = rpath + prefix_len;
+ *dest++ = '/'; /* It's an absolute symlink */
+ } else {
+ /* Back up to previous component, ignore if at root
+ already: */
+ if (dest > rpath + prefix_len + 1)
+ for (--dest; dest > rpath && !ISSLASH(dest[-1]); --dest)
+ continue;
+ }
+ }
+ }
+ }
+ if (dest > rpath + prefix_len + 1 && ISSLASH(dest[-1]))
+ --dest;
+ *dest = '\0';
+
+ if (extra_buf)
+ free(extra_buf);
+
+ return rpath;
+
+error:
+ if (extra_buf)
+ free(extra_buf);
+ free(rpath);
+ return NULL;
+}
+
+// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an
+// absolute path. This function handles paths in a platform-agnostic manner.
+char *follow_symlink_in_scope(const char *fullpath, const char *rootpath)
+{
+ char resfull[PATH_MAX] = {0}, *full = NULL;
+ char resroot[PATH_MAX] = {0}, *root = NULL;
+
+ full = cleanpath(fullpath, resfull);
+ if (!full) {
+ ERROR("Failed to get cleaned path");
+ return NULL;
+ }
+
+ root = cleanpath(rootpath, resroot);
+ if (!root) {
+ ERROR("Failed to get cleaned path");
+ return NULL;
+ }
+
+ return eval_symlinks_in_scope(full, root);
+}
+
+// GetResourcePath evaluates `path` in the scope of the container's rootpath, with proper path
+// sanitisation. Symlinks are all scoped to the rootpath of the container, as
+// though the container's rootpath was `/`.
+//
+// The BaseFS of a container is the host-facing path which is bind-mounted as
+// `/` inside the container. This method is essentially used to access a
+// particular path inside the container as though you were a process in that
+// container.
+int get_resource_path(const char *rootpath, const char *path,
+ char **scopepath)
+{
+ char resolved[PATH_MAX] = {0}, *cleanedpath = NULL;
+ char *fullpath = NULL;
+ size_t len;
+
+ if (!rootpath || !path || !scopepath)
+ return -1;
+
+ *scopepath = NULL;
+
+ cleanedpath = cleanpath(path, resolved);
+ if (!cleanedpath) {
+ ERROR("Failed to get cleaned path");
+ return -1;
+ }
+
+ len = strlen(rootpath) + strlen(cleanedpath) + 1;
+ fullpath = malloc(len);
+ if (!fullpath) {
+ ERROR("Out of memory");
+ return -1;
+ }
+ snprintf(fullpath, len, "%s%s", rootpath, cleanedpath);
+
+ *scopepath = follow_symlink_in_scope(fullpath, rootpath);
+
+ free(fullpath);
+ return 0;
+}
+
+// Rel returns a relative path that is lexically equivalent to targpath when
+// joined to basepath with an intervening separator. That is,
+// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
+// On success, the returned path will always be relative to basepath,
+// even if basepath and targpath share no elements.
+// An error is returned if targpath can't be made relative to basepath or if
+// knowing the current working directory would be necessary to compute it.
+// Rel calls Clean on the result.
+char *path_relative(const char *basepath, const char *targpath)
+{
+ char resbase[PATH_MAX] = {0}, *base = NULL;
+ char restarg[PATH_MAX] = {0}, *targ = NULL;
+ size_t bl = 0, tl = 0, b0 = 0, bi = 0, t0 = 0, ti = 0;
+
+ base = cleanpath(basepath, resbase);
+ if (!base) {
+ ERROR("Failed to get cleaned path");
+ return NULL;
+ }
+
+ targ = cleanpath(targpath, restarg);
+ if (!targ) {
+ ERROR("Failed to get cleaned path");
+ return NULL;
+ }
+
+ if (strcmp(base, targ) == 0)
+ return strdup(".");
+
+ bl = strlen(base);
+ tl = strlen(targ);
+ while(true) {
+ while(bi < bl && !ISSLASH(base[bi]))
+ bi++;
+ while(ti < tl && !ISSLASH(targ[ti]))
+ ti++;
+ //not the same string
+ if (((bi - b0) != (ti - t0)) || strncmp(base + b0, targ + t0, bi - b0))
+ break;
+ if (bi < bl)
+ bi++;
+ if (ti < tl)
+ ti++;
+ b0 = bi;
+ t0 = ti;
+ }
+
+ if (b0 != bl) {
+ // Base elements left. Must go up before going down.
+ int seps = 0, i;
+ size_t ncopyed = 0, seps_size;
+ char *buf;
+
+ for (bi = b0; bi < bl; bi++) {
+ if (ISSLASH(base[bi]))
+ seps++;
+ }
+ //strlen(..) + strlen(/..) + '\0'
+ seps_size = 2 + seps * 3 + 1;
+ if (t0 != tl)
+ seps_size += 1 + tl - t0;
+
+ buf = calloc(seps_size, 1);
+ if (!buf) {
+ ERROR("Out of memory");
+ return NULL;
+ }
+ buf[ncopyed++] = '.';
+ buf[ncopyed++] = '.';
+ for (i = 0; i < seps; i++) {
+ buf[ncopyed++] = '/';
+ buf[ncopyed++] = '.';
+ buf[ncopyed++] = '.';
+ }
+ if (t0 != tl) {
+ buf[ncopyed++] = '/';
+ memcpy(buf + ncopyed, targ + t0, tl - t0 + 1);
+ }
+ return buf;
+ }
+
+ return strdup(targ + t0);
+}
\ No newline at end of file
diff --git a/src/lxc/path.h b/src/lxc/path.h
new file mode 100644
index 0000000..e3a04cc
--- /dev/null
+++ b/src/lxc/path.h
@@ -0,0 +1,70 @@
+#ifndef __LCRD_PATH_H_
+#define __LCRD_PATH_H_
+
+#include <stdbool.h>
+
+bool specify_current_dir(const char *path);
+
+bool has_traling_path_separator(const char *path);
+
+// PreserveTrailingDotOrSeparator returns the given cleaned path
+// and appends a trailing `/.` or `/` if its corresponding original
+// path ends with a trailing `/.` or `/`. If the cleaned
+// path already ends in a `.` path segment, then another is not added. If the
+// clean path already ends in a path separator, then another is not added.
+char *preserve_trailing_dot_or_separator(const char *cleanedpath,
+ const char *originalpath);
+
+
+// Split splits path immediately following the final Separator,
+// separating it into a directory and file name component.
+// If there is no Separator in path, Split returns an empty dir
+// and file set to path.
+// The returned values have the property that path = dir+file.
+bool filepath_split(const char *path, char **dir, char **base);
+
+/*
+ * cleanpath is similar to realpath of glibc, but not expands symbolic links,
+ * and not check the existence of components of the path.
+ */
+char *cleanpath(const char *path, char *resolved);
+
+// evalSymlinksInScope will evaluate symlinks in `path` within a scope `root` and return
+// a result guaranteed to be contained within the scope `root`, at the time of the call.
+// Symlinks in `root` are not evaluated and left as-is.
+// Errors encountered while attempting to evaluate symlinks in path will be returned.
+// Non-existing paths are valid and do not constitute an error.
+// `path` has to contain `root` as a prefix, or else an error will be returned.
+// Trying to break out from `root` does not constitute an error.
+//
+// Example:
+// If /foo/bar -> /outside,
+// FollowSymlinkInScope("/foo/bar", "/foo") == "/foo/outside" instead of "/oustide"
+char *eval_symlinks_in_scope(const char *fullpath, const char *rootpath);
+
+// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an
+// absolute path. This function handles paths in a platform-agnostic manner.
+char *follow_symlink_in_scope(const char *fullpath, const char *rootpath);
+
+// GetResourcePath evaluates `path` in the scope of the container's rootpath, with proper path
+// sanitisation. Symlinks are all scoped to the rootpath of the container, as
+// though the container's rootpath was `/`.
+//
+// The BaseFS of a container is the host-facing path which is bind-mounted as
+// `/` inside the container. This method is essentially used to access a
+// particular path inside the container as though you were a process in that
+// container.
+int get_resource_path(const char *rootpath, const char *path,
+ char **scopepath);
+
+// Rel returns a relative path that is lexically equivalent to targpath when
+// joined to basepath with an intervening separator. That is,
+// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
+// On success, the returned path will always be relative to basepath,
+// even if basepath and targpath share no elements.
+// An error is returned if targpath can't be made relative to basepath or if
+// knowing the current working directory would be necessary to compute it.
+// Rel calls Clean on the result.
+char *path_relative(const char *basepath, const char *targpath);
+
+#endif
--
1.8.3.1