Mike Alexander <mta(a)arbortext.com> writes:
It's annoyed me for some time that the native Windows version of
XEmacs often ends up with two (or more) buffers visiting the same
file, even if find-file-compare-truenames or find-file-use-truenames
is set.
I totally agree. In fact I also started to fix this a while ago...
[...]
The attached patch changed Ffile_truename to use
win32_get_long_filename on Windows after changing it to do something
reasonable when given a path that contains a file or directory that
doesn't exist. This latter change might cause a problem with code
that used win32-long-file-name to test for the existence of a file,
but I can't find any code that uses it at all, let alone for that
purpose. I also changed xemacs.mak to remove realpath.c from the
native Windows build since it isn't needed anymore. With this patch I
haven't had any more problems with multiple buffers visiting the same
file if I set find-file-compare-truenames or find-file-use-truenames.
I chose the other alternative - making xrealpath work on windows. I
obviously think this is a better choice, mainly because it's mine :-)
My code have some real advantages, though. Mainly, it also works with
cygwin xemacs, which is broken in basically the same way as the native
built one. Besides that the current code doesn't care for case or
short filenames, it is totally broken when you feed it UNC paths.
My code deals with UNC paths by lower-casing the server and share
parts of the filename (i.e. //server/share/...), since case is not
significant here.
It is perhaps also an advantage that the code is shared between unix,
cygwin and native windows xemacsen. It should, for example, be quite
easy to add support for Win2k's reparse points (which basically work
like symlinks).
I also definitely think find-file-compare-truenames should be true by
default on windows.
Anyway, here it is. I've been using it for a while, on both native
xemacs and cygwin. However, I haven't tested the code on unix (the
changes there are mainly trivial, though; mostly *s == '/' ->
IS_DIRECTORY_SEP (*s), etc.).
/dan
--
Dan Ola Holmsand
IB
dan(a)eyebee.com
2000-08-31 Dan Holmsand <dan(a)eyebee.com>
* buffer.c: Make find-file-compare-truenames default to true on
windows.
* realpath.c (win32_abs_start):
(cygwin_readlink):
(win32_readlink): New functions.
(xrealpath): Return really real filenames on windows.
* fileio.c (Ffile_truename): Make file-truename work on windows.
Index: src/buffer.c
===================================================================
RCS file: /usr/CVSroot/XEmacs/xemacs/src/buffer.c,v
retrieving revision 1.36.2.38
diff -u -u -r1.36.2.38 buffer.c
--- buffer.c 2000/08/18 17:16:00 1.36.2.38
+++ buffer.c 2000/08/31 21:19:18
@@ -2227,7 +2227,11 @@
See also the variable find-file-use-truenames.
*/ );
+#if defined(CYGWIN) || defined(WIN32_NATIVE)
+ find_file_compare_truenames = 1;
+#else
find_file_compare_truenames = 0;
+#endif
DEFVAR_BOOL ("find-file-use-truenames", &find_file_use_truenames /*
If this is true, then a buffer's visited file-name will always be
Index: src/fileio.c
===================================================================
RCS file: /usr/CVSroot/XEmacs/xemacs/src/fileio.c,v
retrieving revision 1.51.2.26
diff -u -u -r1.51.2.26 fileio.c
--- fileio.c 2000/08/06 09:27:12 1.51.2.26
+++ fileio.c 2000/08/31 21:19:40
@@ -1316,14 +1316,30 @@
we just use our own version in realpath.c. */
for (;;)
{
- p = (Extbyte *) memchr (p + 1, '/', elen - (p + 1 - path));
- if (p)
- *p = 0;
+ Extbyte *pos;
+#ifdef WIN32_NATIVE
+ if (IS_DRIVE (p[0]) && IS_DEVICE_SEP (p[1])
+ && IS_DIRECTORY_SEP (p[2]))
+ /* don't test c: on windows */
+ p = p+2;
+ else if (IS_DIRECTORY_SEP (p[0]) && IS_DIRECTORY_SEP (p[1]))
+ /* start after // */
+ p = p+1;
+#endif
+ for (pos = p + 1; pos < path + elen; pos++)
+ if (IS_DIRECTORY_SEP (*pos))
+ {
+ *(p = pos) = 0;
+ break;
+ }
+ if (p != pos)
+ p = 0;
+
if (xrealpath ((char *) path, resolved_path))
{
if (p)
- *p = '/';
+ *p = DIRECTORY_SEP;
else
break;
@@ -1342,13 +1358,13 @@
{
int plen = elen - (p - path);
- if (rlen > 1 && resolved_path[rlen - 1] == '/')
+ if (rlen > 1 && IS_DIRECTORY_SEP (resolved_path[rlen - 1]))
rlen = rlen - 1;
if (plen + rlen + 1 > countof (resolved_path))
goto toolong;
- resolved_path[rlen] = '/';
+ resolved_path[rlen] = DIRECTORY_SEP;
memcpy (resolved_path + rlen + 1, p + 1, plen + 1 - 1);
}
break;
@@ -1361,12 +1377,12 @@
{
Lisp_Object resolved_name;
int rlen = strlen (resolved_path);
- if (elen > 0 && XSTRING_BYTE (expanded_name, elen - 1) == '/'
- && !(rlen > 0 && resolved_path[rlen - 1] == '/'))
+ if (elen > 0 && IS_DIRECTORY_SEP (XSTRING_BYTE (expanded_name, elen -
1))
+ && !(rlen > 0 && IS_DIRECTORY_SEP (resolved_path[rlen -
1])))
{
if (rlen + 1 > countof (resolved_path))
goto toolong;
- resolved_path[rlen++] = '/';
+ resolved_path[rlen++] = DIRECTORY_SEP;
resolved_path[rlen] = '\0';
}
TO_INTERNAL_FORMAT (DATA, (resolved_path, rlen),
Index: src/realpath.c
===================================================================
RCS file: /usr/CVSroot/XEmacs/xemacs/src/realpath.c,v
retrieving revision 1.6.2.7
diff -u -u -r1.6.2.7 realpath.c
--- realpath.c 2000/06/12 04:18:21 1.6.2.7
+++ realpath.c 2000/08/31 21:19:47
@@ -23,42 +23,145 @@
/* Synched up with: Not in FSF. */
#include <config.h>
+#include "lisp.h"
+#include "sysfile.h"
-#include <sys/types.h>
#include <stdio.h>
#include <string.h>
-#include <errno.h>
#include <limits.h>
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif
+/* First char after start of absolute filename. */
+#define ABS_START(name) (name + ABS_LENGTH (name))
-#if defined (HAVE_SYS_PARAM_H)
-#include <sys/param.h>
-#endif
+#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 sys_readlink(name, buf, size) win32_readlink (name, buf, size)
+#else
+# ifdef CYGWIN
+# define ABS_LENGTH(name) (IS_DIRECTORY_SEP (*name) ? \
+ (IS_DIRECTORY_SEP (name[1]) ? 2 : 1) : 0)
+# define sys_readlink(name, buf, size) cygwin_readlink (name, buf, size)
+# else
+# define ABS_LENGTH(name) (IS_DIRECTORY_SEP (*name) ? 1 : 0)
+# define sys_readlink(name, buf, size) readlink (name, buf, size)
+# 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. */
+ if (strpbrk (name, "*?|<>\"") || strlen (name) >= MAX_PATH)
+ {
+ errno = EIO;
+ 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)
+ {
+ /* 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_NATIVE
-#include <direct.h>
+#ifndef ELOOP
+#define ELOOP 10062 /* = WSAELOOP in winsock.h */
#endif
-
-#include <sys/stat.h> /* for S_IFLNK */
+/* 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
char * xrealpath (const char *path, char resolved_path []);
@@ -68,78 +171,63 @@
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);
path = copy_path;
max_path = copy_path + PATH_MAX - 2;
+
#ifdef WIN32_NATIVE
- /*
- ** 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) == ':')
+ /* Check for c:/... or //server/... */
+ if (abslen == 2 || abslen == 3)
{
- strncpy(new_path, path, 3);
- new_path += 3;
- path += 3;
+ strncpy (new_path, path, abslen);
+ new_path += abslen;
+ path += abslen;
}
-
- /*
- ** No drive letter, but a beginning slash? Prepend the drive
- ** letter...
- */
- else if (*path == '/')
+ /* No drive letter, but a beginning slash? Prepend drive letter. */
+ else if (abslen == 1)
{
getcwd (new_path, PATH_MAX - 1);
new_path += 3;
path++;
}
-
- /*
- ** Just a path name, prepend the current directory
- */
+ /* Just a path name, prepend the current directory */
else
{
getcwd (new_path, PATH_MAX - 1);
- new_path += strlen(new_path);
- if (new_path[-1] != '/')
- *new_path++ = '/';
+ new_path += strlen (new_path);
+ if (!IS_DIRECTORY_SEP (new_path[-1]))
+ *new_path++ = DIRECTORY_SEP;
}
-
#else
/* If it's a relative pathname use getcwd for starters. */
- if (*path != '/')
+ if (abslen == 0)
{
getcwd (new_path, PATH_MAX - 1);
- new_path += strlen(new_path);
- if (new_path[-1] != '/')
- *new_path++ = '/';
+ 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;
@@ -148,7 +236,7 @@
if (*path == '.')
{
/* Ignore ".". */
- if (path[1] == '\0' || path[1] == '/')
+ if (path[1] == '\0' || IS_DIRECTORY_SEP (path[1]))
{
path++;
continue;
@@ -156,23 +244,24 @@
/* Handle ".." */
if (path[1] == '.' &&
- (path[2] == '\0' || path[2] == '/'))
+ (path[2] == '\0' || IS_DIRECTORY_SEP (path[2])))
{
path += 2;
/* Ignore ".." at root. */
- if (new_path == resolved_path + 1)
+ if (new_path == ABS_START (resolved_path))
continue;
/* Handle ".." by backing up. */
- while ((--new_path)[-1] != '/')
- ;
+ --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)
{
@@ -182,10 +271,10 @@
*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 = sys_readlink (resolved_path, link_path, PATH_MAX - 1);
if (n < 0)
{
@@ -204,14 +293,14 @@
/* 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)
@@ -225,15 +314,21 @@
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';
+
+#ifdef WIN32_NATIVE
+ if (ABS_LENGTH (resolved_path) == 3)
+ /* Lowercase drive letter. */
+ *resolved_path = tolower (*resolved_path);
+#endif
return resolved_path;
}