update.
[chise/xemacs-chise.git.1] / src / realpath.c
index 78ccc25..442c3ff 100644 (file)
@@ -22,133 +22,264 @@ Boston, MA 02111-1307, USA.  */
 
 /* Synched up with: Not in FSF. */
 
-#ifdef HAVE_CONFIG_H
 #include <config.h>
-#endif
+#include "lisp.h"
+#include <errno.h>
 
-#include <sys/types.h>
-#if defined(HAVE_UNISTD_H) || defined(STDC_HEADERS)
+#ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
-#include <stdio.h>
-#include <string.h>
-#ifdef _POSIX_VERSION
-#include <limits.h>                    /* for PATH_MAX */
-#else
-#include <sys/param.h>                 /* for MAXPATHLEN */
-#endif
-#include <errno.h>
-#ifndef STDC_HEADERS
-extern int errno;
+
+#if defined (HAVE_SYS_PARAM_H) && !defined (WIN32_NATIVE)
+#include <sys/param.h>
 #endif
 
-#ifdef WINDOWSNT
+#ifdef WIN32_NATIVE
 #include <direct.h>
 #endif
 
 #include <sys/stat.h>                  /* for S_IFLNK */
 
-#ifndef PATH_MAX
-#ifdef _POSIX_VERSION
-#define PATH_MAX _POSIX_PATH_MAX
-#else
-#ifdef MAXPATHLEN
-#define PATH_MAX MAXPATHLEN
+#if defined(WIN32_NATIVE) || defined(CYGWIN)
+#define WIN32_FILENAMES
+#endif
+
+/* First char after start of absolute filename. */
+#define ABS_START(name) (name + ABS_LENGTH (name))
+
+#if defined (WIN32_NATIVE)
+/* Length of start of absolute filename. */
+# define ABS_LENGTH(name) (win32_abs_start (name))
+static int win32_abs_start (const char * name);
+/* System dependent version of readlink. */
+# define system_readlink win32_readlink
 #else
-#define PATH_MAX 1024
+# ifdef CYGWIN
+#  ifdef WIN32_FILENAMES
+#   define ABS_LENGTH(name) (win32_abs_start (name))
+static int win32_abs_start (const char * name);
+#  else
+#   define ABS_LENGTH(name) (IS_DIRECTORY_SEP (*name) ? \
+                             (IS_DIRECTORY_SEP (name[1]) ? 2 : 1) : 0)
+#  endif
+#  define system_readlink cygwin_readlink
+# else
+#  define ABS_LENGTH(name) (IS_DIRECTORY_SEP (*name) ? 1 : 0)
+#  define system_readlink readlink
+# endif /* CYGWIN */
+#endif /* WIN32_NATIVE */
+
+#if defined (WIN32_NATIVE) || defined (CYGWIN)
+#include "syswindows.h"
+/* Emulate readlink on win32 - finds real name (i.e. correct case) of
+   a file. UNC servers and shares are lower-cased. Directories must be
+   given without trailing '/'. One day, this could read Win2K's
+   reparse points. */
+static int
+win32_readlink (const char * name, char * buf, int size)
+{
+  WIN32_FIND_DATA find_data;
+  HANDLE dir_handle = NULL;
+  int len = 0;
+  int err = 0;
+  const char* lastname;
+  int count = 0;
+  const char* tmp;
+  char* res = NULL;
+  
+  assert (*name);
+  
+  /* Sort of check we have a valid filename. */
+  /* #### can we have escaped shell operators in a Windows filename? */
+  if (strpbrk (name, "|<>\"") || strlen (name) >= MAX_PATH)
+    {
+      errno = EIO;
+      return -1;
+    }
+  /* #### can we have escaped wildcards in a Windows filename? */
+  else if (strpbrk (name, "*?"))
+    {
+      errno = EINVAL;          /* this valid path can't be a symlink */
+      return -1;
+    }
+  
+  /* Find start of filename */
+  lastname = name + strlen (name);
+  while (lastname > name && !IS_DIRECTORY_SEP (lastname[-1]))
+    --lastname;
+
+  /* Count slashes in unc path */
+  if (ABS_LENGTH (name) == 2)
+    for (tmp = name; *tmp; tmp++)
+      if (IS_DIRECTORY_SEP (*tmp))
+       count++;
+
+  if (count >= 2 && count < 4)
+    {
+      /* UNC server or share name: just copy lowercased name. */
+      res = find_data.cFileName;
+      for (tmp = lastname; *tmp; tmp++)
+       *res++ = tolower (*tmp);
+      *res = '\0';
+    }
+  else
+    dir_handle = FindFirstFile (name, &find_data);
+
+  if (res || dir_handle != INVALID_HANDLE_VALUE)
+    {
+      if ((len = strlen (find_data.cFileName)) < size)
+       {
+         if (strcmp (lastname, find_data.cFileName) == 0)
+           /* Signal that the name is already OK. */
+           err = EINVAL;
+         else
+           memcpy (buf, find_data.cFileName, len + 1);
+       }
+      else
+       err = ENAMETOOLONG;
+      if (!res) FindClose (dir_handle);
+    }
+  else
+    err = ENOENT;
+
+  errno = err;
+  return err ? -1 : len;
+}
+#endif /* WIN32_NATIVE || CYGWIN */
+
+#ifdef CYGWIN
+/* Call readlink and try to find out the correct case for the file. */
+static int
+cygwin_readlink (const char * name, char * buf, int size)
+{
+  int n = readlink (name, buf, size);
+  if (n < 0 && errno == EINVAL)
+    {
+      /* The file may exist, but isn't a symlink. Try to find the
+         right name. */
+      char* tmp = alloca (cygwin_posix_to_win32_path_list_buf_size (name));
+      cygwin_posix_to_win32_path_list (name, tmp);
+      n = win32_readlink (tmp, buf, size);
+    }
+  return n;
+}
+#endif /* CYGWIN */
+
+#ifdef WIN32_FILENAMES
+#ifndef ELOOP
+#define ELOOP 10062 /* = WSAELOOP in winsock.h */
 #endif
+/* Length of start of absolute filename. */
+static int 
+win32_abs_start (const char * name)
+{
+  if (isalpha (*name) && IS_DEVICE_SEP (name[1])
+      && IS_DIRECTORY_SEP (name[2]))
+    return 3;
+  else if (IS_DIRECTORY_SEP (*name))
+    return IS_DIRECTORY_SEP (name[1]) ? 2 : 1;
+  else 
+    return 0;
+}
+#endif /* WIN32_NATIVE */
+
+#if !defined (HAVE_GETCWD) && defined (HAVE_GETWD)
+#undef getcwd
+#define getcwd(buffer, len) getwd (buffer)
 #endif
+
+#ifndef PATH_MAX
+# if defined (_POSIX_PATH_MAX)
+#  define PATH_MAX _POSIX_PATH_MAX
+# elif defined (MAXPATHLEN)
+#  define PATH_MAX MAXPATHLEN
+# else
+#  define PATH_MAX 1024
+# endif
 #endif
 
 #define MAX_READLINKS 32
 
-#ifdef __STDC__
-char *xrealpath(const char *path, char resolved_path [])
-#else
-char *xrealpath(path, resolved_path)
-const char *path;
-char resolved_path [];
-#endif
+char * xrealpath (const char *path, char resolved_path []);
+char *
+xrealpath (const char *path, char resolved_path [])
 {
   char copy_path[PATH_MAX];
   char *new_path = resolved_path;
   char *max_path;
-#ifdef S_IFLNK
+#if defined (S_IFLNK) || defined (WIN32_NATIVE)
   int readlinks = 0;
   char link_path[PATH_MAX];
   int n;
+  int abslen = ABS_LENGTH (path);
 #endif
 
   /* Make a copy of the source path since we may need to modify it. */
-  strcpy(copy_path, path);
+  strcpy (copy_path, path);
   path = copy_path;
   max_path = copy_path + PATH_MAX - 2;
-#ifdef WINDOWSNT
-  /*
-  ** In NT we have two different cases:  (1) the path name begins
-  ** with a drive letter, e.g., "C:"; and (2) the path name begins
-  ** with just a slash, which roots to the current drive. In the
-  ** first case we are going to leave things alone, in the second
-  ** case we will prepend the drive letter to the given path.
-  ** Note: So far in testing, I'm only seeing case #1, even though
-  ** I've tried to get the other cases to happen.
-  ** August Hill, 31 Aug 1997.
-  **
-  ** Check for a driver letter...C:/...
-  */
-  if (*(path + 1) == ':')
+
+  if (0)
+    ;
+#ifdef WIN32_FILENAMES
+  /* Check for c:/... or //server/... */
+  else if (abslen == 3 || abslen == 2)
     {
-      strncpy(new_path, path, 3);
-      new_path += 3;
-      path += 3;
+      /* Make sure drive letter is lowercased. */
+      if (abslen == 3) {
+       *new_path = tolower (*path);
+       new_path++;
+       path++;
+       abslen--;
+      }
+      /* Coerce directory chars. */
+      while (abslen-- > 0) {
+       if (IS_DIRECTORY_SEP (*path))
+         *new_path++ = DIRECTORY_SEP;
+       else
+         *new_path++ = *path;
+       path++;
+      }
     }
-
-  /*
-  ** No drive letter, but a beginning slash? Prepend the drive
-  ** letter...
-  */
-  else if (*path == '/')
+#endif
+#ifdef WIN32_NATIVE
+  /* No drive letter, but a beginning slash? Prepend drive letter. */
+  else if (abslen == 1)
     {
-      getcwd(new_path, PATH_MAX - 1);
+      getcwd (new_path, PATH_MAX - 1);
       new_path += 3;
       path++;
     }
-
-  /*
-  ** Just a path name, prepend the current directory
-  */
-  else
+  /* Just a path name, prepend the current directory */
+  else if (1)
     {
-      getcwd(new_path, PATH_MAX - 1);
-      new_path += strlen(new_path);
-      if (new_path[-1] != '/')
-       *new_path++ = '/';
+      getcwd (new_path, PATH_MAX - 1);
+      new_path += strlen (new_path);
+      if (!IS_DIRECTORY_SEP (new_path[-1]))
+       *new_path++ = DIRECTORY_SEP;
     }
-
 #else
-  /* If it's a relative pathname use getwd for starters. */
-  if (*path != '/')
+  /* If it's a relative pathname use getcwd for starters. */
+  else if (abslen == 0)
     {
-#ifdef HAVE_GETCWD
-      getcwd(new_path, PATH_MAX - 1);
-#else
-      getwd(new_path);
-#endif
-      new_path += strlen(new_path);
-      if (new_path[-1] != '/')
-       *new_path++ = '/';
+      getcwd (new_path, PATH_MAX - 1);
+      new_path += strlen (new_path);
+      if (!IS_DIRECTORY_SEP (new_path[-1]))
+       *new_path++ = DIRECTORY_SEP;
     }
   else
     {
-      *new_path++ = '/';
-      path++;
+      /* Copy first directory sep. May have two on cygwin. */
+      strncpy (new_path, path, abslen);
+      new_path += abslen;
+      path += abslen;
     }
 #endif
   /* Expand each slash-separated pathname component. */
   while (*path != '\0')
     {
       /* Ignore stray "/". */
-      if (*path == '/')
+      if (IS_DIRECTORY_SEP (*path))
        {
          path++;
          continue;
@@ -157,32 +288,32 @@ char resolved_path [];
       if (*path == '.')
        {
          /* Ignore ".". */
-         if (path[1] == '\0' || path[1] == '/')
+         if (path[1] == '\0' || IS_DIRECTORY_SEP (path[1]))
            {
              path++;
              continue;
            }
 
-         if (path[1] == '.')
+         /* Handle ".." */
+         if (path[1] == '.' &&
+             (path[2] == '\0' || IS_DIRECTORY_SEP (path[2])))
            {
-             if (path[2] == '\0' || path[2] == '/')
-               {
-                 path += 2;
-
-                 /* Ignore ".." at root. */
-                 if (new_path == resolved_path + 1)
-                   continue;
-
-                 /* Handle ".." by backing up. */
-                 while ((--new_path)[-1] != '/')
-                   ;
-                 continue;
-               }
+             path += 2;
+
+             /* Ignore ".." at root. */
+             if (new_path == ABS_START (resolved_path))
+               continue;
+
+             /* Handle ".." by backing up. */
+             --new_path;
+             while (!IS_DIRECTORY_SEP (new_path[-1]))
+               --new_path;
+             continue;
            }
        }
 
       /* Safely copy the next pathname component. */
-      while (*path != '\0' && *path != '/')
+      while (*path != '\0' && !IS_DIRECTORY_SEP (*path))
        {
          if (path > max_path)
            {
@@ -192,15 +323,19 @@ char resolved_path [];
          *new_path++ = *path++;
        }
 
-#ifdef S_IFLNK
+#if defined (S_IFLNK) || defined (WIN32_NATIVE)
       /* See if latest pathname component is a symlink. */
       *new_path = '\0';
-      n = readlink(resolved_path, link_path, PATH_MAX - 1);
+      n = system_readlink (resolved_path, link_path, PATH_MAX - 1);
 
       if (n < 0)
        {
          /* EINVAL means the file exists but isn't a symlink. */
-         if (errno != EINVAL)
+#ifdef CYGWIN
+         if (errno != EINVAL && errno != ENOENT)
+#else
+         if (errno != EINVAL) 
+#endif
            return NULL;
        }
       else
@@ -214,14 +349,14 @@ char resolved_path [];
 
          /* Note: readlink doesn't add the null byte. */
          link_path[n] = '\0';
-
-         if (*link_path == '/')
+         
+         if (ABS_LENGTH (link_path) > 0)
            /* Start over for an absolute symlink. */
-           new_path = resolved_path;
+           new_path = resolved_path + ABS_LENGTH (link_path) - 1;
          else
            /* Otherwise back up over this component. */
-           while (*(--new_path) != '/')
-             ;
+           for (--new_path; !IS_DIRECTORY_SEP (*new_path); --new_path)
+             assert (new_path > resolved_path);
 
          /* Safe sex check. */
          if (strlen(path) + n >= PATH_MAX)
@@ -235,15 +370,16 @@ char resolved_path [];
          strcpy(copy_path, link_path);
          path = copy_path;
        }
-#endif /* S_IFLNK */
-      *new_path++ = '/';
+#endif /* S_IFLNK || WIN32_NATIVE */
+      *new_path++ = DIRECTORY_SEP;
     }
 
   /* Delete trailing slash but don't whomp a lone slash. */
-  if (new_path != resolved_path + 1 && new_path[-1] == '/')
+  if (new_path != ABS_START (resolved_path) && IS_DIRECTORY_SEP (new_path[-1]))
     new_path--;
 
   /* Make sure it's null terminated. */
   *new_path = '\0';
+
   return resolved_path;
 }