From 472aa08222783b4255b60a4dd7a10197711bf703 Mon Sep 17 00:00:00 2001
From: tanyifeng <tanyifeng1@huawei.com>
Date: Mon, 14 Jan 2019 20:12:06 +0800
Subject: [PATCH 024/131] 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 08e2fab6..f2928b7d 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 55d1e453..800573a8 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 00000000..e917dcb8
--- /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 00000000..e3a04cc5
--- /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
-- 
2.23.0

