delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp-workers/2003/01/16/10:19:42

Date: Thu, 16 Jan 2003 15:16:35 +0000
From: "Richard Dawe" <rich AT phekda DOT freeserve DOT co DOT uk>
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: <E18ZBjr-0000Mi-00@phekda.freeserve.co.uk>
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 <libc/stubs.h>
  #include <libc/symlink.h>
  #include <errno.h>
  #include <limits.h>
  #include <stdio.h>
- #include <unistd.h>
  #include <string.h>
  
  #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 <libc/stubs.h>
  #include <libc/symlink.h>
+ #include <libc/dosio.h>
+ #include <libc/unconst.h>
  #include <errno.h>
  #include <limits.h>
+ #include <stddef.h>
  #include <stdio.h>
  #include <string.h>
+ #include <sys/stat.h>
+ #include <assert.h>
+ #include <dpmi.h>
+ #include <go32.h>
  
  #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 <stdlib.h>
! 
! 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 <libc/symlink.h>
    #include <stdio.h>
  
    __solve_symlinks(fn, file_name);
    printf("File %s is really %s\n", fn, file_name);
  
- 
  @end example
- 
--- 34,43 ----
    #include <libc/symlink.h>
    #include <stdio.h>
  
+   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 <libc/symlink.h>
    #include <stdio.h>
  
    __solve_dir_symlinks(fn, file_name);
    printf("The real path to %s is %s\n", fn, file_name);
  
- 
  @end example
- 
--- 33,42 ----
    #include <libc/symlink.h>
    #include <stdio.h>
  
+   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 <libc/stubs.h>
+ #include <libc/unconst.h>
  #include <stdio.h>		/* For FILENAME_MAX */
  #include <stdlib.h>
  #include <errno.h>		/* 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 <stdlib.h>
+ 
+ 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

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019