superroot above the root directory @file{/}. On other filesystems,
@file{/../} is interpreted exactly the same as @file{/}.
+If a filename must be that of a directory, its expansion must be too.
+For example, if a filename ends in @samp{/} or @samp{/.} or @samp{/..}
+then its expansion ends in @samp{/} so that it cannot be
+misinterpreted as the name of a symbolic link:
+
+@example
+@group
+(expand-file-name "/a///b//.")
+ @result{} "/a/b/"
+@end group
+@end example
+
Expanding @file{.} or the empty string returns the default directory:
@example
@group
(expand-file-name "." "/usr/spool/")
- @result{} "/usr/spool"
+ @result{} "/usr/spool/"
(expand-file-name "" "/usr/spool/")
- @result{} "/usr/spool"
+ @result{} "/usr/spool/"
@end group
@end example
This function can be used by modes to add elements to the
'choice' customization type of a variable.
++++
+** 'expand-file-name' no longer omits a trailing slash if the omission
+changes the filename's meaning. E.g., (expand-file-name "/a/b/.") now
+returns "/a/b/" not "/a/b", which might be misinterpreted as the name
+of a symbolic link rather than of the directory it points to.
+
+++
** New function 'file-modes-number-to-symbolic' to convert a numeric
file mode specification into symbolic form.
#endif /* WINDOWSNT */
#endif /* DOS_NT */
- /* If nm is absolute, look for `/./' or `/../' or `//''sequences; if
+ /* If nm is absolute, look for "/./" or "/../" or "//" sequences; if
none are found, we can probably return right away. We will avoid
allocating a new string if name is already fully expanded. */
if (
if (newdir)
{
- if (nm[0] == 0 || IS_DIRECTORY_SEP (nm[0]))
+ if (IS_DIRECTORY_SEP (nm[0]))
{
#ifdef DOS_NT
/* If newdir is effectively "C:/", then the drive letter will have
{
*o++ = *p++;
}
- else if (p[1] == '.'
- && (IS_DIRECTORY_SEP (p[2])
- || p[2] == 0))
+ else if (p[1] == '.' && IS_DIRECTORY_SEP (p[2]))
{
- /* If "/." is the entire filename, keep the "/". Otherwise,
- just delete the whole "/.". */
- if (o == target && p[2] == '\0')
- *o++ = *p;
+ /* Replace "/./" with "/". */
+ p += 2;
+ }
+ else if (p[1] == '.' && !p[2])
+ {
+ /* At the end of the file name, replace "/." with "/".
+ The trailing "/" is for symlinks. */
+ *o++ = *p;
p += 2;
}
else if (p[1] == '.' && p[2] == '.'
#ifdef WINDOWSNT
char *prev_o = o;
#endif
- while (o != target && (--o, !IS_DIRECTORY_SEP (*o)))
- continue;
+ while (o != target)
+ {
+ o--;
+ if (IS_DIRECTORY_SEP (*o))
+ {
+ /* Keep "/" at the end of the name, for symlinks. */
+ o += p[3] == 0;
+
+ break;
+ }
+ }
#ifdef WINDOWSNT
/* Don't go below server level in UNC filenames. */
if (o == target + 1 && IS_DIRECTORY_SEP (*o)
&& IS_DIRECTORY_SEP (*target))
o = prev_o;
- else
#endif
- /* Keep initial / only if this is the whole name. */
- if (o == target && IS_ANY_SEP (*o) && p[3] == 0)
- ++o;
p += 3;
}
else if (IS_DIRECTORY_SEP (p[1])
(expand-file-name "/method:host:/path/../file") "/method:host:/file"))
(should
(string-equal
- (expand-file-name "/method:host:/path/.") "/method:host:/path"))
+ (expand-file-name "/method:host:/path/.") "/method:host:/path/"))
(should
(string-equal
(expand-file-name "/method:host:/path/..") "/method:host:/"))
(should
(string-equal
- (expand-file-name "." "/method:host:/path/") "/method:host:/path"))
+ (expand-file-name "." "/method:host:/path/") "/method:host:/path/"))
(should
(string-equal
- (expand-file-name "" "/method:host:/path/") "/method:host:/path"))
+ (expand-file-name "" "/method:host:/path/") "/method:host:/path/"))
;; Quoting local part.
(should
(string-equal
"/method:host:/:/~/path/file"))))
;; The following test is inspired by Bug#26911 and Bug#34834. They
-;; are rather bugs in `expand-file-name', and it fails for all Emacs
-;; versions. Test added for later, when they are fixed.
+;; were bugs in `expand-file-name'.
(ert-deftest tramp-test05-expand-file-name-relative ()
"Check `expand-file-name'."
- ;; Mark as failed until bug has been fixed.
- :expected-result :failed
(skip-unless (tramp--test-enabled))
;; These are the methods the test doesn't fail.
(should (equal (expand-file-name "~/bar") "x:/foo/bar")))
(setenv "HOME" old-home)))
+(ert-deftest fileio-tests--expand-file-name-trailing-slash ()
+ (dolist (fooslashalias '("foo/" "foo//" "foo/." "foo//." "foo///././."
+ "foo/a/.."))
+ (should (equal (expand-file-name fooslashalias "/") "/foo/"))
+ (should (equal (expand-file-name (concat "/" fooslashalias)) "/foo/")))
+ (should (equal (expand-file-name "." "/usr/spool/") "/usr/spool/"))
+ (should (equal (expand-file-name "" "/usr/spool/") "/usr/spool/"))
+ ;; Trailing "B/C/.." means B must be a directory.
+ (should (equal (expand-file-name "/a/b/c/..") "/a/b/"))
+ (should (equal (expand-file-name "/a/b/c/../") "/a/b/")))
+
(ert-deftest fileio-tests--insert-file-interrupt ()
(let ((text "-*- coding: binary -*-\n\xc3\xc3help")
f)