Date: Thu, 16 Jan 2003 15:16:35 +0000 From: "Richard Dawe" Sender: rich AT phekda DOT freeserve DOT co DOT uk To: djgpp-workers AT delorie DOT com X-Mailer: Emacs 21.0.98 (via feedmail 8.3.emacs20_6 I) and Blat ver 1.8.6 Subject: Rewritten __solve_symlinks to fix bugs [PATCH] Message-Id: Reply-To: djgpp-workers AT delorie DOT com Hello. I mentioned a while ago that __solve_symlinks() doesn't work with relative symlinks that have too many ".."s, i.e.: when they have more ".."s than required to reach the root directory. (It's possible that it also does not work with symlinks to a file in a directory accessed via a symlink to the directory. E.g.: mkdir foo touch foo/bar ln -s foo baz cat baz/foo I've been through a few iterations of patching __solve_symlinks, so perhaps it works. I'm pretty sure that it fails, though.) On Unix "/.." is equivalent to "/". This is not true on DOS. We have to emulate this behaviour using _fixpath(), which removes excess ".."s. I tried to fix the current implementation, to cope with ".."s, but I hit some problems: * How do you know how many directory levels are present? If you have a path like /dev/env/DJDIR, then you need to expand it, to find out how many directory levels are present. * How do you collapse ".."s. You could call _fixpath(), but you have to be careful - that may over-optimise and remove too many ".."s. You can only call _fixpath() on what you have resolved so far. Anyway, with these questions in mind, I tried to fix the current __solve_symlinks implementation. I found the current code a bit difficult to work with, so in the end I rewrote it. Below is the patch. Reasons why you might not like the patch: * It's a complete rewrite. * It doesn't cope with UNCs (\\machine\path). * It's probably slower, since it calls _put_path a lot. * It probably tidies up filenames more than necessary. * It needs to check for buffer overflows more. * It uses goto. ;) (This makes the code more readable IMNSHO.) Reasons to like the patch: * It handles all the symlink cases I've thrown at it. I spent a few hours debugging weird cases, like: c:../../../foo, /dev/env/DJDIR/../../../../foo. * fileutils still works. I didn't see any regressions in the test suite. Things you should ignore in the patch: * __partially_canonicalize_path - This is cruft from a previous iteration of the patch, which I haven't clean up yet. Below is a test script. It should all work, when you've rebuild fileutils and textutils with the patch below. ---Start test-sym.sh--- #!/bin/bash CAT=${CAT-cat} LN=${LN-ln} MKDIR=${MKDIR-mkdir} if [ "a$1" = "a-x" ]; then set -x shift fi # Chain of symlinks $LN -fnsv b a $LN -fnsv c b $LN -fnsv d c echo foo > d $CAT d # Relative symlink $MKDIR -p t-dir $LN -fnsv ../d t-dir/d $CAT t-dir/d # Too many dots here=$(pwd) dotty=$(echo $here | sed -e 's:/:/\.\./\.\./\.\./\.\./\.\./:') $LN -fnsv ${dotty}/d e $CAT e # If this directory is under /dev/env/DJDIR, try putting that in a symlink. drive=$(echo $here | sed -e 's!^\(.:\).*!\1!') wibble=$(echo $here | sed -e "s!^$DJDIR!/dev/env/DJDIR!") if [ "a$wibble" != "a" ]; then $LN -fnsv ${wibble}/d f $CAT f if [ "a$drive" != "a" ]; then # Chuck in an extra slash, to spice things up. $LN -fnsv ${drive}/${wibble}/d g $CAT g fi fi ---End test-sym.sh--- I'd appreciate any comments on the patch. If anyone's feeling brave, please test it too. This doesn't affect just symlinks - it affects all files. Bye, Rich =] Index: src/libc/compat/unistd/xsymlink.c =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/compat/unistd/xsymlink.c,v retrieving revision 1.6 diff -p -c -3 -r1.6 xsymlink.c *** src/libc/compat/unistd/xsymlink.c 23 Dec 2002 11:44:49 -0000 1.6 --- src/libc/compat/unistd/xsymlink.c 16 Jan 2003 09:53:12 -0000 *************** *** 1,18 **** /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ ! /* Written by Laurynas Biveinis */ /* Internal source file specifying DJGPP symlink prefix and internal */ /* function which fully resolves given symlink. (Function readlink() */ /* resolves only last filename component and one symlink level.) */ #include #include #include #include #include - #include #include #include "xsymlink.h" --- 1,30 ---- + /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ ! /* Written by Laurynas Biveinis. Rewritten by Richard Dawe. */ /* Internal source file specifying DJGPP symlink prefix and internal */ /* function which fully resolves given symlink. (Function readlink() */ /* resolves only last filename component and one symlink level.) */ + /* TODO: Audit the code for places where the FILENAME_MAX buffers + * could overflow. */ + + /* TODO: Should this code preserve UNC paths? Probably. */ + #include #include + #include + #include #include #include + #include #include #include + #include + #include + #include + #include #include "xsymlink.h" *************** *** 21,180 **** */ #define MAX_SYMLINK 8 ! static void advance(char ** s, char ** e); ! int __solve_symlinks(const char * __symlink_path, char * __real_path) { ! int bytes_copied; ! char * start; ! char * end; ! int old_errno; ! char fn_buf[FILENAME_MAX + 1]; ! char resolved[FILENAME_MAX + 1]; ! int link_level = 0; ! int found_absolute_path; ! int done_something; ! char * ptr; ! int non_trivial; ! ! if (!__symlink_path || !__real_path) ! { ! errno = EINVAL; ! return 0; ! } ! ! if (strlen(__symlink_path) > FILENAME_MAX) ! { ! errno = ENAMETOOLONG; ! return 0; ! } ! ! strcpy(__real_path, __symlink_path); ! start = __real_path; ! end = strpbrk(__real_path, "/\\"); ! if (!end) ! end = __real_path + strlen(__real_path); ! while (start && *start) ! { ! /* Extract path component we will be resolving */ ! strcpy(resolved, __real_path); ! if (*end) ! resolved[end - __real_path] = '\0'; ! old_errno = errno; ! found_absolute_path = 0; ! done_something = 0; ! non_trivial = 0; ! /* Resolve that component. Repeat until we encounter non-symlink, ! or not trivial symlink (in form 'dir/file'). */ ! do ! { ! bytes_copied = __internal_readlink(resolved, 0, fn_buf, FILENAME_MAX); ! if (bytes_copied != -1) ! { ! done_something = 1; ! link_level++; ! fn_buf[bytes_copied] = '\0'; ! strcpy(resolved, fn_buf); ! /* FIXME: does absolute path check below work with chroot()? */ ! if (((bytes_copied > 2) && (resolved[1] == ':')) || ! ((bytes_copied > 0) && ((resolved[0] == '/') || ! (resolved[0] == '\\')))) ! { ! found_absolute_path = 1; ! } ! /* If we found dir/file, do not iterate */ ! else ! { ! if ((resolved[0] == '.') && ! ((resolved[1] == '/') || (resolved[1] == '\\'))) ! ptr = resolved + 2; ! else ! ptr = resolved; ! while (ptr < resolved + strlen(resolved)) /* Skip last char */ ! if ((*ptr == '/') || (*ptr == '\\')) ! { ! bytes_copied = -1; ! non_trivial = 1; ! break; ! } ! else ! ptr++; ! } ! } ! } while ((bytes_copied != -1) && (link_level <= MAX_SYMLINK)); ! if (link_level > MAX_SYMLINK) ! { ! errno = ELOOP; ! return 0; ! } ! else ! errno = old_errno; ! if (done_something) ! { ! /* If it wasn't the last path component resolved, save the ! * unresolved tail for future ! */ ! if (*end) ! strcpy(fn_buf, end); ! else ! fn_buf[0] = '\0'; ! if (found_absolute_path) ! { ! /* Discard already resolved part, because symlink's target */ ! /* is absolute path */ ! strcpy(__real_path, resolved); ! } ! else ! { ! /* Add resolved symlink component to already resolved part */ ! memcpy(start, resolved, strlen(resolved) + 1); ! } ! /* Append saved tail for further processing */ ! if (fn_buf[0]) ! strcat(__real_path, fn_buf); } ! if (done_something) ! { ! if (found_absolute_path) ! { ! /* Restart processing. God knows what's in the new absolute path */ ! start = __real_path; ! end = strpbrk(__real_path, "/\\"); ! } ! else if (non_trivial) ! { ! /* If we got component like 'dir/file', start resolving from */ ! /* dir. */ ! end = strpbrk(start + 1, "/\\"); ! } ! else ! { ! /* Resolve next component */ ! advance(&start, &end); ! } } else ! { ! /* Resolve next component */ ! advance(&start, &end); } ! if (!end) ! end = __real_path + strlen(__real_path); ! } ! return 1; ! } ! ! /* Advance to the next portion of the path. Cope with multiple slashes. */ ! static void advance(char ** s, char ** e) ! { ! *s = strpbrk(*s + 1, "/\\"); ! if (*s) ! { ! while ((**s == '/') || (**s == '\\')) ! (*s)++; ! } ! ! while ((**e == '/') || (**e == '\\')) ! (*e)++; ! *e = strpbrk(*e, "/\\"); } --- 33,661 ---- */ #define MAX_SYMLINK 8 ! static inline int ! is_slash (int ch) ! { ! return((ch == '\\') || (ch == '/')); ! } ! static inline int ! has_drive (const char *path) { ! return(path[0] && path[1] == ':'); ! } ! ! static inline int ! is_absolute (const char *path) ! { ! return(is_slash(path[0]) || (has_drive(path) && is_slash(path[2]))); ! } ! ! static inline int ! has_trailing_slash (char *path) ! { ! return(is_slash(path[strlen(path) - 1])); ! } ! ! static inline void ! add_trailing_slash (char *path) ! { ! if (!has_trailing_slash(path)) ! strcat(path, "/"); ! } ! ! static inline void ! eat_trailing_slash (char *path) ! { ! size_t endpos = strlen(path) - 1; ! char *p = strchr(path, '/'); ! ! /* The slash in / or c:/ is not considered to be a trailing slash. */ ! if (is_slash(path[endpos]) && (&path[endpos] != p)) ! path[endpos] = '\0'; ! } ! ! /* ! * put_path_wrapper: _put_path on 'in'; result into 'out'. 'in' and 'out' ! * can point to the same buffer. ! */ ! ! static int ! put_path_wrapper (const char *in, char *out) ! { ! int ret = _put_path(in); ! ! dosmemget(__tb, FILENAME_MAX, out); ! return(ret); ! } ! ! /* ! * tidy_up_path: Collapse consecutive slashes to one. Convert slashes ! * to forward slashes. Eliminate occurrences of '.'. ! */ ! ! static void ! tidy_up_path (const char *in, char *out) ! { ! int in_pos = 0; ! int out_pos = 0; ! ! /* Copy the drive & root over, if present. We do this here, to avoid ! * it getting munged by the "/./" collapsing code. */ ! if (has_drive(in)) { ! out[out_pos] = in[in_pos]; ! out[out_pos + 1] = in[in_pos + 1]; ! in_pos += 2; ! out_pos += 2; ! } ! ! if (is_absolute(in)) { ! out[out_pos] = in[in_pos]; ! in_pos++; ! out_pos++; ! } ! ! /* Eat any './' at the start. */ ! while (1) { ! if (strstr(&in[in_pos], "./") == &in[in_pos]) ! in_pos += 2; ! else ! break; ! } ! ! for (; in[in_pos] != '\0'; in_pos++) { ! while (is_slash(in[in_pos])) { ! /* Eat consecutive slashes. */ ! if (is_slash(in[in_pos + 1])) { ! in_pos++; ! continue; } ! ! /* Collapse "/./" to "/". */ ! if ((in[in_pos + 1] == '.') && (is_slash(in[in_pos + 2]))) { ! in_pos += 2; ! continue; } + + break; + } + + /* Convert backward-slashes to forward-slashes. */ + if (is_slash(in[in_pos])) + out[out_pos] = '/'; + else + out[out_pos] = in[in_pos]; + + out_pos++; + } + + out[out_pos] = '\0'; + eat_trailing_slash(out); + + /* Eat any '/.' at the end. */ + while (1) { + size_t endpos = strlen(out); + + /* Leave '.' if that's all that's there. */ + if (endpos < 3) + break; + + endpos--; + if ((is_slash(out[endpos - 1]) && (out[endpos] == '.'))) + out[endpos - 1] = '\0'; + else + break; + } + } + + static int + get_current_drive (void) + { + __dpmi_regs r; + + r.h.ah = 0x19; + __dpmi_int(0x21, &r); + return(r.h.al); + } + + static char * + strrpbrk (const char *str, const char *set) + { + char *rightmost = NULL; + char *p; + + for (; *set; set++) { + p = strrchr(str, *set); + if (p > rightmost) + rightmost = unconst(p, char *); + } + + return(rightmost); + } + + /* + * collapse_dirs_and_dotdots: Simplify paths by eliminating directories + * according to the number of leading '..'s. + * + * Count how many '../' prefixes there are in suffix - n. Eat n trailing + * directories from prefix and n leading '..' from suffix. Don't go below + * the root directory. + * + * ASSUMPTIONS: + * + * . prefix is an absolute path - with or without drive letter. + */ + static void + collapse_dirs_and_dotdots (char *prefix, char *suffix) + { + char *p, *q; + int prefix_start = 0; /* Don't eat path parts beyond this point. */ + int no_slash_after = 0; /* No slash after the dot-dots? */ + int n = 0; + int i; + + assert(prefix && suffix); + + p = strstr(suffix, ".."); + if (p != suffix) + return; + + do { + /* Move on to next '..', if any. */ + p += 2; + if (*p) { + if (is_slash(*p)) + break; else ! p++; ! } ! ! n++; ! ! q = strstr(p, ".."); ! if ((p != NULL) && (p != q)) ! break; ! } while (p != NULL); ! ! if (!n) ! return; ! ! /* Eat path parts in prefix. */ ! eat_trailing_slash(prefix); ! ! if (prefix[0] && (prefix[1] == ':')) ! prefix_start = 2; ! else ! prefix_start = 0; ! ! for (i = 0; i < n; i++) { ! p = strrpbrk(prefix, "\\/"); ! if (p == NULL) ! continue; ! ! if (p == &prefix[prefix_start]) { ! /* We've reached the root directory. Eat everything after it. */ ! if (p[1]) ! p[1] = '\0'; ! } else { ! *p = '\0'; ! } ! } ! ! /* Eat dot-dots in suffix. If there's slash at the end ! * of the matched prefixes, it means there's nothing after the dot-dots ! * and we should eat all of suffix. */ ! eat_trailing_slash(suffix); ! ! no_slash_after = (suffix[(3 * n) - 1] == '\0'); ! ! if (no_slash_after) ! suffix[0] = '\0'; ! else ! memmove(suffix, suffix + (3 * n), strlen(suffix + (3 * n)) + 1); ! } ! ! /* ! * get_cwd_and_collapse: For any path 'in', make it absolute ! * with a drive specifier. Eliminate any leading '..'s from 'in' ! * by removing the preceeding directories in the absolute path. ! * 'in' and 'out' can be the same buffer. ! */ ! ! static void ! get_cwd_and_collapse (const char *in, char *out) ! { ! char prefix[FILENAME_MAX], suffix[FILENAME_MAX]; ! int drive; ! ! tidy_up_path(in, suffix); ! ! if (has_drive(suffix) && !is_absolute(suffix)) { ! /* c:foo */ ! /* Relative path with drive => get the drive, ! * so we can get the working directory. */ ! if ((suffix[0] >= 'a') && (suffix[0] <= 'z')) ! drive = suffix[0] - 'a'; ! else ! drive = suffix[0] - 'A'; ! ! memcpy(prefix, suffix, 2); ! prefix[2] = '\0'; ! ! __get_current_directory(prefix + 2, drive); ! ! /* Eat drive, now it's in prefix. */ ! memmove(suffix, suffix + 2, strlen(suffix + 2) + 1); ! } else if (has_drive(suffix) && is_absolute(suffix)) { ! /* c:/foo */ ! /* Split the path on the slash after the drive specifier. ! * Then we can collapse any leading '..'s after the drive specifier. */ ! strncpy(prefix, suffix, 3); ! prefix[3] = '\0'; ! memmove(suffix, suffix + 3, strlen(suffix + 3) + 1); ! } else if (!has_drive(suffix) && is_absolute(suffix)) { ! /* /foo */ ! /* Get the drive into prefix, add a slash, shift suffix to allow ! * collapse of any leading '..'s after the slash. */ ! drive = get_current_drive(); ! ! prefix[0] = drive + (drive < 26 ? 'a' : 'A'); ! prefix[1] = ':'; ! prefix[2] = '/'; ! prefix[3] = '\0'; ! ! memmove(suffix, suffix + 1, strlen(suffix + 1) + 1); ! } else { ! /* foo */ ! /* Get the current working directory as the prefix. This is enough ! * allow collapsing of any leading '..'s. */ ! drive = get_current_drive(); ! ! prefix[0] = drive + (drive < 26 ? 'a' : 'A'); ! prefix[1] = ':'; ! prefix[2] = '\0'; ! ! __get_current_directory(prefix + 2, drive); ! } ! ! /* Eliminate any leading '..'s. */ ! collapse_dirs_and_dotdots(prefix, suffix); ! ! /* Reconstruct the path. */ ! strcpy(out, prefix); ! if (strlen(suffix)) { ! if (!is_slash(out[strlen(out) - 1])) ! strcat(out, "/"); ! strcat(out, suffix); ! } ! } ! ! /* ! * __solve_symlinks: Given a path, resolve all the symlinks in it, ! * to give the real path. ! * ! * ALGORITHM: ! * ! * Maintain a string containing what has been resolved so far. Work from ! * left to right. For each part, perform a symlink lookup ! * on the concatentation of resolved-so-far and the part. ! * ! * If the lookup fails, because the part is not a symlink, add it ! * to resolved-so-far. Carry on resolving. ! * ! * If the lookup succeeds, then there are couple of cases: ! * ! * . For absolute symlinks, discard what has been resolved so far. ! * Restart the resolution process with the concatenation ! * of the lookup result and whatever is remaining from the original path. ! * ! * . For relative symlinks, restart the resolution process ! * with the concatenation of resolved-so-far, the lookup result ! * and whatever is remaining from the original path. ! * ! * We need to restart the resolution process, because the target ! * of the symlink may be a symlink. Where we have to restart depends ! * on the symlink type: absolute => completely restart; ! * relative => at the first part of the lookup result. ! * ! * COMPLICATIONS: ! * ! * . There are special paths such as /dev/env/DJDIR. ! * ! * . "/.." is equivalent to "/" on Unix, but not on DOS. ! * So we have to emulate this by collapsing "..". This can only be done ! * between the end of the resolved path and the start ! * of the lookup result, otherwise we may collapse parts of the path ! * that are actually symlinks. (This means we cannot use _fixpath().) ! */ ! ! int ! __solve_symlinks (const char * __symlink_path, char * __real_path) ! { ! int old_errno = errno; ! ! /* Initially this is __symlink_path after a little tidying up. ! * Later this may be partially-resolved, when we restart resolution. */ ! char my_symlink[FILENAME_MAX] = ""; ! ! /* What we've resolved of __symlink_path so far. */ ! char resolved[FILENAME_MAX] = ""; ! ! /* The current part of my_symlink that we're trying to resolve. */ ! char part[FILENAME_MAX] = ""; ! ! /* Buffer and results for/from __internal_readlink calls. */ ! char readlink_buf[FILENAME_MAX]; ! int readlink_res; ! ! /* Temporary buffer. */ ! char buf[FILENAME_MAX]; ! ! char *start, *end; ! ! int link_level = 0; ! ! if (!__symlink_path || !__real_path) { ! errno = EINVAL; ! return(0); ! } ! ! if (strlen(__symlink_path) >= FILENAME_MAX) { ! errno = ENAMETOOLONG; ! return(0); ! } ! ! /* Collapse any '..'s and fix up special names like /dev/env/DJDIR. */ ! get_cwd_and_collapse(__symlink_path, my_symlink); ! put_path_wrapper(my_symlink, buf); ! tidy_up_path(buf, my_symlink); ! ! /* Iterate over each part of the path. */ ! ! /* ASSUMPTION: my_symlink is now a drive-qualified absolute path. ! * Skip over the drive and its root directory. */ ! start = my_symlink + 3; ! memcpy(resolved, my_symlink, 3); ! resolved[3] = '\0'; ! end = strpbrk(start, "\\/"); ! ! do { ! ptrdiff_t diff = 0; ! ! /* Cope if this is the last part of the path. */ ! if (end == NULL) ! end = start + strlen(start); ! ! diff = end - start; ! memcpy(part, start, diff); ! part[diff] = '\0'; ! ! /* If this part is a '..', then we can eliminate it. */ ! if (strcmp(part, "..") == 0) { ! collapse_dirs_and_dotdots(resolved, part); ! goto next_part; ! } ! ! /* ! * Resolve this part. Consider the different types of path that can ! * result, if this is a symlink: ! * ! * (1) Absolute with or without a DOS drive: We discard everything ! * that has been resolved previously and start with the result. ! * ! * (2) Relative: We add the result to the previously resolved path. ! * ! * (3) Relative with a DOS drive: This is treated the same way as (1). ! * It's just slightly less absolute. ! * ! * Since any part of the result from the lookup can be a symlink, ! * we have to resume the matching there. ! */ ! ! strcpy(buf, resolved); ! add_trailing_slash(buf); ! strcat(buf, part); ! ! readlink_res ! = __internal_readlink(buf, 0, readlink_buf, sizeof(readlink_buf)); ! ! if (readlink_res >= 0) { ! /* Symlink */ ! link_level++; ! ! if (link_level > MAX_SYMLINK) { ! errno = ELOOP; ! return(0); ! } ! ! readlink_buf[readlink_res] = '\0'; ! } else { ! /* Probably not a symlink - a regular file or directory? */ ! /* Just update resolved and soldier on. */ ! strcpy(resolved, buf); ! goto next_part; ! } ! ! tidy_up_path(readlink_buf, buf); ! ! if (is_absolute(buf) || has_drive(buf)) { ! /* Absolute => just start with this path. */ ! get_cwd_and_collapse(buf, resolved); ! } else { ! /* Relative => prefix with resolved path up to now. */ ! collapse_dirs_and_dotdots(resolved, buf); ! if (buf[0]) { ! add_trailing_slash(resolved); ! strcat(resolved, buf); } ! } ! ! /* Append unresolved parts, if any. */ ! if (end && *end) ! strcat(resolved, end); ! ! /* Fix up any special paths like /dev/env/DJDIR that may have appeared. */ ! put_path_wrapper(resolved, resolved); ! tidy_up_path(resolved, my_symlink); ! ! /* Restart the resolution. */ ! /* ASSUMPTION: my_symlink is now a drive-qualified absolute path. ! * Point end at the root directory, so next_part will go to the first ! * non-root part. */ ! memcpy(resolved, my_symlink, 3); ! resolved[3] = '\0'; ! ! start = NULL; ! end = my_symlink + 2; ! ! /* Find the next part of the path, if any. */ ! next_part: ! start = end; ! if (start && *start) { ! start++; ! end = strpbrk(start, "\\/"); ! } else { ! start = end = NULL; ! } ! } while (start != NULL); ! ! strcpy(__real_path, resolved); ! errno = old_errno; ! ! return(1); ! } ! ! #ifdef TEST ! ! #include ! ! int ! main (int argc, char *argv[]) ! { ! typedef struct { ! char *prefix; ! char *suffix; ! } collapse_dirs_and_dotdots_t; ! ! const collapse_dirs_and_dotdots_t cddd_tests[] = { ! { "c:/", ".." }, ! { "c:/", "../.." }, ! { "c:/", "../../../" }, ! { "/", ".." }, ! { "/", "../.." }, ! { "/", "../../../" }, ! { "c:/djgpp", ".." }, ! { "c:/djgpp", "../.." }, ! { "c:/djgpp", "../../../" }, ! { "/djgpp", ".." }, ! { "/djgpp", "../.." }, ! { "/djgpp", "../../../" }, ! { "c:/really/really/really/really/long/path", ".." }, ! { "c:/really/really/really/really/long/path", "../.." }, ! { "c:/really/really/really/really/long/path", "../../../.." }, ! { "c:/really/really/really/really/long/path", "../../../../.." }, ! { "c:/really/really/really/really/long/path", "../../../../../.." }, ! { "c:/really/really/really/really/long/path", "../../../../../../.." }, ! { "c:/really/really/really/really/long/path", "../../../../../../../.." }, ! { "c:/really/really/really/really/long/path", ! "../../../../../../../../.." }, ! { "c:/really/really/really/really/long/path", ! "../../../../../../../../../" }, ! { "c:/djgpp/share/doodah", "../foo" }, ! { "c:/djgpp/share/doodah/", "../../foo" }, ! { "c:/djgpp/share/doodah/", "../../../foo" }, ! { "c:/djgpp/share/doodah", "../foo" }, ! { "c:/djgpp/share/doodah/", "../../foo" }, ! { "c:/djgpp/share/doodah/", "../../../foo" }, ! { NULL, NULL } ! }; ! ! const char *solve_tests[] = { ! /* These aren't symlinks. */ ! "c:../../../../../../../..", ! "c:../..", ! "/dev/env/DJDIR", ! "/dev/env/DJDIR/djgpp.env", ! "/dev/env/DJDIR/../../..", ! NULL ! }; ! ! const char *gcac_tests[] = { ! NULL ! }; ! ! char buf[FILENAME_MAX], buf2[FILENAME_MAX];; ! int i; ! ! puts("collapse_dirs_and_dotdots tests:"); ! ! for (i = 0; cddd_tests[i].prefix != NULL; i++) { ! strcpy(buf, cddd_tests[i].prefix); ! strcpy(buf2, cddd_tests[i].suffix); ! ! printf("[%s, %s] -> ", buf, buf2); ! collapse_dirs_and_dotdots(buf, buf2); ! printf("[%s, %s]\n", buf, buf2); ! } ! ! puts("\n__solve_symlinks tests:"); ! ! for (i = 0; solve_tests[i] != NULL; i++) { ! memset(buf, 0, sizeof(buf)); ! __solve_symlinks(solve_tests[i], buf); ! printf("%s -> %s\n", solve_tests[i], buf); ! } ! ! puts("\nget_cwd_and_collapse tests:"); ! ! for (i = 0; cddd_tests[i].prefix != NULL; i++) { ! strcpy(buf, cddd_tests[i].suffix); ! memset(buf2, 0, sizeof(buf2)); ! get_cwd_and_collapse(buf, buf2); ! printf("%s -> %s\n", buf, buf2); ! } ! ! for (i = 0; solve_tests[i] != NULL; i++) { ! strcpy(buf, solve_tests[i]); ! memset(buf2, 0, sizeof(buf2)); ! get_cwd_and_collapse(buf, buf2); ! printf("%s -> %s\n", buf, buf2); ! } ! ! for (i = 0; gcac_tests[i] != NULL; i++) { ! strcpy(buf, gcac_tests[i]); ! memset(buf2, 0, sizeof(buf2)); ! get_cwd_and_collapse(buf, buf2); ! printf("%s -> %s\n", buf, buf2); ! } ! ! puts("User requested tests:"); ! for (i = 1; i < argc; i++) { ! memset(buf, 0, sizeof(buf)); ! __solve_symlinks(argv[i], buf); ! printf("%s -> %s\n", argv[i], buf); ! } ! ! return EXIT_SUCCESS; } + #endif Index: src/libc/compat/unistd/xsymlink.txh =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/compat/unistd/xsymlink.txh,v retrieving revision 1.1 diff -p -c -3 -r1.1 xsymlink.txh *** src/libc/compat/unistd/xsymlink.txh 14 Aug 2000 08:51:30 -0000 1.1 --- src/libc/compat/unistd/xsymlink.txh 16 Jan 2003 09:53:12 -0000 *************** int __solve_symlinks(const char *symlink *** 8,19 **** --- 8,23 ---- @end example @subheading Description + This function fully resolves given symlink in @var{symlink_path}---all path components and all symlink levels are resolved. The returned path in @var{real_path} is guaranteed to be symlink-clean and understandable by DOS. If @var{symlink_path} does not contain symlinks at all, it is simply copied to @var{real_path}. + @var{real_path} should be of size @code{FILENAME_MAX}, to contain + the maximum-length path. + @subheading Return Value Zero in case of error (and @code{errno} set to the appropriate *************** error code), non-zero in case of success *** 30,38 **** #include #include __solve_symlinks(fn, file_name); printf("File %s is really %s\n", fn, file_name); - @end example - --- 34,43 ---- #include #include + const char fn[] = "c:/somedir/somelink"; + char file_name[FILENAME_MAX]; + __solve_symlinks(fn, file_name); printf("File %s is really %s\n", fn, file_name); @end example Index: src/libc/compat/unistd/sdirlink.txh =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/compat/unistd/sdirlink.txh,v retrieving revision 1.1 diff -p -c -3 -r1.1 sdirlink.txh *** src/libc/compat/unistd/sdirlink.txh 25 Aug 2000 11:35:37 -0000 1.1 --- src/libc/compat/unistd/sdirlink.txh 16 Jan 2003 09:53:23 -0000 *************** int __solve_dir_symlinks(const char *sym *** 8,18 **** --- 8,22 ---- @end example @subheading Description + This function resolves given symlink in @var{symlink_path}---all path components @strong{except} the last one and all symlink levels are resolved. If @var{symlink_path} does not contain symlinks at all, it is simply copied to @var{real_path}. + @var{real_path} should be of size @code{FILENAME_MAX}, to contain + the maximum-length path. + @subheading Return Value Zero in case of error (and @code{errno} set to the appropriate *************** error code), non-zero in case of success *** 29,37 **** #include #include __solve_dir_symlinks(fn, file_name); printf("The real path to %s is %s\n", fn, file_name); - @end example - --- 33,42 ---- #include #include + char fn[] = "c:/somelink/someotherlink/somefile"; + char file_name[FILENAME_MAX]; + __solve_dir_symlinks(fn, file_name); printf("The real path to %s is %s\n", fn, file_name); @end example Index: include/sys/stat.h =================================================================== RCS file: /cvs/djgpp/djgpp/include/sys/stat.h,v retrieving revision 1.7 diff -p -c -3 -r1.7 stat.h *** include/sys/stat.h 17 Oct 2002 23:00:24 -0000 1.7 --- include/sys/stat.h 16 Jan 2003 09:53:24 -0000 *************** mode_t umask(mode_t _cmask); *** 84,93 **** --- 84,95 ---- void _fixpath(const char *, char *); char * __canonicalize_path(const char *, char *, size_t); + char * __get_current_directory(char *out, int drive_number); unsigned short _get_magic(const char *, int); int _is_executable(const char *, int, const char *); int lstat(const char * _path, struct stat * _buf); int mknod(const char *_path, mode_t _mode, dev_t _dev); + char * __partially_canonicalize_path(const char *, char *, size_t); char * _truename(const char *, char *); /* Bit-mapped variable _djstat_flags describes what expensive Index: src/libc/posix/sys/stat/fixpath.c =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/posix/sys/stat/fixpath.c,v retrieving revision 1.8 diff -p -c -3 -r1.8 fixpath.c *** src/libc/posix/sys/stat/fixpath.c 25 Sep 2002 21:45:20 -0000 1.8 --- src/libc/posix/sys/stat/fixpath.c 16 Jan 2003 09:53:44 -0000 *************** *** 1,3 **** --- 1,4 ---- + /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */ *************** *** 5,10 **** --- 6,12 ---- /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ #include + #include #include /* For FILENAME_MAX */ #include #include /* For errno */ *************** *** 19,27 **** static unsigned use_lfn; ! static char *__get_current_directory(char *out, int drive_number); ! ! static char * __get_current_directory(char *out, int drive_number) { __dpmi_regs r; --- 21,27 ---- static unsigned use_lfn; ! char * __get_current_directory(char *out, int drive_number) { __dpmi_regs r; *************** is_term(int c) *** 130,140 **** */ char * ! __canonicalize_path(const char *in, char *out, size_t path_max) { int drive_number; ! char in1[FILENAME_MAX]; ! char *ip; char *op = out; int preserve_case = _preserve_fncase(); char *name_start; --- 130,139 ---- */ char * ! __partially_canonicalize_path(const char *in, char *out, size_t path_max) { int drive_number; ! char *ip = unconst(in, char *); char *op = out; int preserve_case = _preserve_fncase(); char *name_start; *************** __canonicalize_path(const char *in, char *** 148,158 **** op_limit = op + path_max; - /* Perform the same magic conversions that _put_path does. */ - _put_path(in); - dosmemget(__tb, sizeof(in1), in1); - ip = in1; - /* Add drive specification to output string */ if (((*ip >= 'a' && *ip <= 'z') || (*ip >= 'A' && *ip <= 'Z')) --- 147,152 ---- *************** __canonicalize_path(const char *in, char *** 318,323 **** --- 312,329 ---- } return out; + } + + char * + __canonicalize_path(const char *in, char *out, size_t path_max) + { + char in1[FILENAME_MAX]; + + /* Perform the same magic conversions that _put_path does. */ + _put_path(in); + dosmemget(__tb, sizeof(in1), in1); + + return __partially_canonicalize_path(in1, out, path_max); } void Index: src/libc/compat/unistd/sdirlink.c =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/compat/unistd/sdirlink.c,v retrieving revision 1.5 diff -p -c -3 -r1.5 sdirlink.c *** src/libc/compat/unistd/sdirlink.c 17 Oct 2002 23:00:25 -0000 1.5 --- src/libc/compat/unistd/sdirlink.c 16 Jan 2003 09:53:44 -0000 *************** int __solve_dir_symlinks(const char * __ *** 72,74 **** --- 72,93 ---- strcat(__real_path, last_part); return 1; } + + #ifdef TEST + + #include + + int main(int argc, char *argv[]) + { + char buf[FILENAME_MAX]; + int i; + + puts("User requested tests:"); + for (i = 1; i < argc; i++) { + __solve_dir_symlinks(argv[i], buf); + printf("%s -> %s\n", argv[i], buf); + } + + return EXIT_SUCCESS; + } + #endif