+ if ((anchored = *f == '/') != 0)
+ *t++ = *f++;
+ else if (flags & CFN_KEEP_DOT_DIRS && *f == '.' && f[1] == '/') {
+ *t++ = *f++;
+ *t++ = *f++;
+ }
+ while (*f) {
+ /* discard extra slashes */
+ if (*f == '/') {
+ f++;
+ continue;
+ }
+ if (*f == '.') {
+ /* discard interior "." dirs */
+ if (f[1] == '/' && !(flags & CFN_KEEP_DOT_DIRS)) {
+ f += 2;
+ continue;
+ }
+ if (f[1] == '\0' && flags & CFN_DROP_TRAILING_DOT_DIR)
+ break;
+ /* collapse ".." dirs */
+ if (flags & CFN_COLLAPSE_DOT_DOT_DIRS
+ && f[1] == '.' && (f[2] == '/' || !f[2])) {
+ char *s = t - 1;
+ if (s == name && anchored) {
+ f += 2;
+ continue;
+ }
+ while (s > limit && *--s != '/') {}
+ if (s != t - 1 && (s < name || *s == '/')) {
+ t = s + 1;
+ f += 2;
+ continue;
+ }
+ limit = t + 2;
+ }
+ }
+ while (*f && (*t++ = *f++) != '/') {}
+ }
+
+ if (t > name+anchored && t[-1] == '/' && !(flags & CFN_KEEP_TRAILING_SLASH))
+ t--;
+ if (t == name)
+ *t++ = '.';
+ *t = '\0';
+
+ return t - name;
+}
+
+/* Make path appear as if a chroot had occurred. This handles a leading
+ * "/" (either removing it or expanding it) and any leading or embedded
+ * ".." components that attempt to escape past the module's top dir.
+ *
+ * If dest is NULL, a buffer is allocated to hold the result. It is legal
+ * to call with the dest and the path (p) pointing to the same buffer, but
+ * rootdir will be ignored to avoid expansion of the string.
+ *
+ * The rootdir string contains a value to use in place of a leading slash.
+ * Specify NULL to get the default of "module_dir".
+ *
+ * The depth var is a count of how many '..'s to allow at the start of the
+ * path.
+ *
+ * We also clean the path in a manner similar to clean_fname() but with a
+ * few differences:
+ *
+ * Turns multiple adjacent slashes into a single slash, gets rid of "." dir
+ * elements (INCLUDING a trailing dot dir), PRESERVES a trailing slash, and
+ * ALWAYS collapses ".." elements (except for those at the start of the
+ * string up to "depth" deep). If the resulting name would be empty,
+ * change it into a ".". */
+char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth,
+ int flags)
+{
+ char *start, *sanp;
+ int rlen = 0, drop_dot_dirs = !relative_paths || !(flags & SP_KEEP_DOT_DIRS);
+
+ if (dest != p) {
+ int plen = strlen(p);
+ if (*p == '/') {
+ if (!rootdir)
+ rootdir = module_dir;
+ rlen = strlen(rootdir);
+ depth = 0;
+ p++;
+ }
+ if (dest) {
+ if (rlen + plen + 1 >= MAXPATHLEN)
+ return NULL;
+ } else if (!(dest = new_array(char, rlen + plen + 1)))
+ out_of_memory("sanitize_path");
+ if (rlen) {
+ memcpy(dest, rootdir, rlen);
+ if (rlen > 1)
+ dest[rlen++] = '/';
+ }
+ }
+
+ if (drop_dot_dirs) {
+ while (*p == '.' && p[1] == '/')
+ p += 2;
+ }
+
+ start = sanp = dest + rlen;
+ /* This loop iterates once per filename component in p, pointing at
+ * the start of the name (past any prior slash) for each iteration. */
+ while (*p) {
+ /* discard leading or extra slashes */
+ if (*p == '/') {
+ p++;
+ continue;
+ }
+ if (drop_dot_dirs) {
+ if (*p == '.' && (p[1] == '/' || p[1] == '\0')) {
+ /* skip "." component */
+ p++;
+ continue;
+ }
+ }
+ if (*p == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) {
+ /* ".." component followed by slash or end */
+ if (depth <= 0 || sanp != start) {
+ p += 2;
+ if (sanp != start) {
+ /* back up sanp one level */
+ --sanp; /* now pointing at slash */
+ while (sanp > start && sanp[-1] != '/')
+ sanp--;
+ }
+ continue;
+ }
+ /* allow depth levels of .. at the beginning */
+ depth--;
+ /* move the virtual beginning to leave the .. alone */
+ start = sanp + 3;
+ }
+ /* copy one component through next slash */
+ while (*p && (*sanp++ = *p++) != '/') {}
+ }
+ if (sanp == dest) {
+ /* ended up with nothing, so put in "." component */
+ *sanp++ = '.';
+ }
+ *sanp = '\0';
+
+ return dest;
+}
+
+/* Like chdir(), but it keeps track of the current directory (in the
+ * global "curr_dir"), and ensures that the path size doesn't overflow.
+ * Also cleans the path using the clean_fname() function. */
+int change_dir(const char *dir, int set_path_only)
+{
+ static int initialised;
+ unsigned int len;
+
+ if (!initialised) {
+ initialised = 1;
+ getcwd(curr_dir, sizeof curr_dir - 1);
+ curr_dir_len = strlen(curr_dir);
+ }
+
+ if (!dir) /* this call was probably just to initialize */
+ return 0;
+
+ len = strlen(dir);
+ if (len == 1 && *dir == '.')
+ return 1;
+
+ if (*dir == '/') {
+ if (len >= sizeof curr_dir) {
+ errno = ENAMETOOLONG;
+ return 0;
+ }
+ if (!set_path_only && chdir(dir))
+ return 0;
+ memcpy(curr_dir, dir, len + 1);
+ } else {
+ if (curr_dir_len + 1 + len >= sizeof curr_dir) {
+ errno = ENAMETOOLONG;
+ return 0;
+ }
+ curr_dir[curr_dir_len] = '/';
+ memcpy(curr_dir + curr_dir_len + 1, dir, len + 1);
+
+ if (!set_path_only && chdir(curr_dir)) {
+ curr_dir[curr_dir_len] = '\0';
+ return 0;
+ }
+ }
+
+ curr_dir_len = clean_fname(curr_dir, CFN_COLLAPSE_DOT_DOT_DIRS);
+ if (sanitize_paths) {
+ if (module_dirlen > curr_dir_len)
+ module_dirlen = curr_dir_len;
+ curr_dir_depth = count_dir_elements(curr_dir + module_dirlen);
+ }
+
+ if (DEBUG_GTE(CHDIR, 1) && !set_path_only)
+ rprintf(FINFO, "[%s] change_dir(%s)\n", who_am_i(), curr_dir);
+
+ return 1;
+}
+
+/**
+ * Return a quoted string with the full pathname of the indicated filename.
+ * The string " (in MODNAME)" may also be appended. The returned pointer
+ * remains valid until the next time full_fname() is called.
+ **/
+char *full_fname(const char *fn)
+{
+ static char *result = NULL;
+ char *m1, *m2, *m3;
+ char *p1, *p2;
+
+ if (result)
+ free(result);
+
+ if (*fn == '/')
+ p1 = p2 = "";
+ else {
+ p1 = curr_dir + module_dirlen;
+ for (p2 = p1; *p2 == '/'; p2++) {}
+ if (*p2)
+ p2 = "/";
+ }
+ if (module_id >= 0) {
+ m1 = " (in ";
+ m2 = lp_name(module_id);
+ m3 = ")";
+ } else
+ m1 = m2 = m3 = "";
+
+ if (asprintf(&result, "\"%s%s%s\"%s%s%s", p1, p2, fn, m1, m2, m3) <= 0)
+ out_of_memory("full_fname");
+
+ return result;
+}
+
+static char partial_fname[MAXPATHLEN];
+
+char *partial_dir_fname(const char *fname)