Function: expand-file-name
expand-file-name is a function defined in fileio.c.
Signature
(expand-file-name NAME &optional DEFAULT-DIRECTORY)
Documentation
Convert filename NAME to absolute, and canonicalize it.
Second arg DEFAULT-DIRECTORY is directory to start with if NAME is relative
(does not start with slash or tilde); both the directory name and
a directory's file name are accepted. If DEFAULT-DIRECTORY is nil or
missing, the current buffer's value of default-directory is used.
NAME should be a string that is a valid file name for the underlying
filesystem.
File name components that are . are removed, and so are file name
components followed by .., along with the .. itself; note that
these simplifications are done without checking the resulting file
names in the file system.
Multiple consecutive slashes are collapsed into a single slash, except at the beginning of the file name when they are significant (e.g., UNC file names on MS-Windows.)
An initial "~" in NAME expands to your home directory.
An initial "~USER" in NAME expands to USER's home directory. If USER doesn't exist, "~USER" is not expanded.
To do other file name substitutions, see substitute-in-file-name.
For technical reasons, this function can return correct but
non-intuitive results for the root directory; for instance,
(expand-file-name ".." "/") returns "/..". For this reason, use
(directory-file-name (file-name-directory dirname)) to traverse a
filesystem tree, not (expand-file-name ".." dirname). Note: make
sure DIRNAME in this example doesn't end in a slash, unless it's
the root directory.
Other relevant functions are documented in the file-name group.
Probably introduced at or before Emacs version 1.6.
Shortdoc
;; file-name
(expand-file-name "foo" "/tmp/")
=> "/tmp/foo"
(expand-file-name "foo" "/tmp///")
=> "/tmp/foo"
(expand-file-name "foo" "/tmp/foo/.././")
=> "/tmp/foo"
(expand-file-name "~" "/tmp/")
=> "/root"
Source Code
// Defined in /usr/src/emacs/src/fileio.c
// Skipping highlighting due to helpful-max-highlight.
{
/* These point to SDATA and need to be careful with string-relocation
during GC (via DECODE_FILE). */
char *nm;
char *nmlim;
const char *newdir;
const char *newdirlim;
/* This should only point to alloca'd data. */
char *target;
ptrdiff_t tlen;
#ifdef DOS_NT
int drive = 0;
bool collapse_newdir = true;
bool is_escaped = 0;
#endif /* DOS_NT */
ptrdiff_t length, nbytes;
Lisp_Object handler, result, handled_name;
bool multibyte;
Lisp_Object hdir;
USE_SAFE_ALLOCA;
CHECK_STRING (name);
CHECK_STRING_NULL_BYTES (name);
/* If the file name has special constructs in it,
call the corresponding file name handler. */
handler = Ffind_file_name_handler (name, Qexpand_file_name);
if (!NILP (handler))
{
handled_name = calln (handler, Qexpand_file_name,
name, default_directory);
if (STRINGP (handled_name))
return handled_name;
error ("Invalid handler in `file-name-handler-alist'");
}
/* As a last resort, we may have to use the root as
default_directory below. */
Lisp_Object root;
#ifdef DOS_NT
/* "/" is not considered a root directory on DOS_NT, so using it
as default_directory causes an infinite recursion in, e.g.,
the following:
(let (default-directory)
(expand-file-name "a"))
To avoid this, we use the root of the current drive. */
root = build_string (emacs_root_dir ());
#else
root = build_string ("/");
#endif
/* Use the buffer's default-directory if DEFAULT_DIRECTORY is omitted. */
if (NILP (default_directory))
{
Lisp_Object dir = BVAR (current_buffer, directory);
/* The buffer's default-directory should be absolute or should
start with `~'. If it isn't absolute, we replace it by its
expansion relative to a known absolute name ABSDIR, which is
the invocation-directory if the latter is absolute, or the
root otherwise.
In case default-directory starts with `~' or `~user', where
USER is a valid user name, this correctly expands it (and
ABSDIR plays no role). If USER is not a valid user name, the
leading `~' loses its special meaning and is retained as part
of the expanded name. */
if (STRINGP (dir))
{
if (file_name_absolute_no_tilde_p (dir))
{
CHECK_STRING_NULL_BYTES (dir);
default_directory = dir;
}
else
{
Lisp_Object absdir
= STRINGP (Vinvocation_directory)
&& file_name_absolute_no_tilde_p (Vinvocation_directory)
? Vinvocation_directory : root;
default_directory = Fexpand_file_name (dir, absdir);
}
}
}
if (! STRINGP (default_directory))
default_directory = root;
handler = Ffind_file_name_handler (default_directory, Qexpand_file_name);
if (!NILP (handler))
{
handled_name = calln (handler, Qexpand_file_name,
name, default_directory);
if (STRINGP (handled_name))
return handled_name;
error ("Invalid handler in `file-name-handler-alist'");
}
{
char *o = SSDATA (default_directory);
/* Make sure DEFAULT_DIRECTORY is properly expanded.
It would be better to do this down below where we actually use
default_directory. Unfortunately, calling Fexpand_file_name recursively
could invoke GC, and the strings might be relocated. This would
be annoying because we have pointers into strings lying around
that would need adjusting, and people would add new pointers to
the code and forget to adjust them, resulting in intermittent bugs.
Putting this call here avoids all that crud.
The EQ test avoids infinite recursion. */
if (! NILP (default_directory) && !EQ (default_directory, name)
/* Save time in some common cases - as long as default_directory
is not relative, it can be canonicalized with name below (if it
is needed at all) without requiring it to be expanded now. */
#ifdef DOS_NT
/* Detect MSDOS file names with drive specifiers. */
&& ! (IS_DRIVE (o[0]) && IS_DEVICE_SEP (o[1])
&& IS_DIRECTORY_SEP (o[2]))
/* Detect escaped file names without drive spec after "/:".
These should not be recursively expanded, to avoid
including the default directory twice in the expanded
result. */
&& ! (o[0] == '/' && o[1] == ':')
#ifdef WINDOWSNT
/* Detect Windows file names in UNC format. */
&& ! (IS_DIRECTORY_SEP (o[0]) && IS_DIRECTORY_SEP (o[1]))
#endif
#else /* not DOS_NT */
/* Detect Unix absolute file names (/... alone is not absolute on
DOS or Windows). */
&& ! (IS_DIRECTORY_SEP (o[0]))
#endif /* not DOS_NT */
)
{
default_directory = Fexpand_file_name (default_directory, Qnil);
/* The above expansion might have produced a remote file name,
so give the handlers one last chance to DTRT. This can
happen when both NAME and DEFAULT-DIRECTORY arguments are
relative file names, and the buffer's default-directory is
remote. */
handler = Ffind_file_name_handler (default_directory,
Qexpand_file_name);
if (!NILP (handler))
{
handled_name = calln (handler, Qexpand_file_name,
name, default_directory);
if (STRINGP (handled_name))
return handled_name;
error ("Invalid handler in `file-name-handler-alist'");
}
}
}
multibyte = STRING_MULTIBYTE (name);
bool defdir_multibyte = STRING_MULTIBYTE (default_directory);
if (multibyte != defdir_multibyte)
{
/* We want to make both NAME and DEFAULT_DIRECTORY have the same
multibyteness. Strategy:
. If either NAME or DEFAULT_DIRECTORY is pure-ASCII, they
can be converted to the multibyteness of the other one
while keeping the same byte sequence.
. If both are non-ASCII, the only safe conversion is to
convert the multibyte one to be unibyte, because the
reverse conversion potentially adds bytes while raw bytes
are converted to their multibyte forms, which we will be
unable to account for, since the information about the
original multibyteness is lost. If those additional bytes
later leak to system APIs because they are not encoded or
because they are converted to unibyte strings by keeping
the data, file APIs will fail.
Note: One could argue that if we see a multibyte string, it
is evidence that file-name decoding was already set up, and
we could convert unibyte strings to multibyte using
DECODE_FILE. However, this is risky, because the likes of
string_to_multibyte are able of creating multibyte strings
without any decoding. */
if (multibyte)
{
bool name_ascii_p = SCHARS (name) == SBYTES (name);
unsigned char *p = SDATA (default_directory);
if (!name_ascii_p)
while (*p && ASCII_CHAR_P (*p))
p++;
if (name_ascii_p || *p != '\0')
{
/* DEFAULT_DIRECTORY is unibyte and possibly non-ASCII.
Make a unibyte string out of NAME, and arrange for
the result of this function to be a unibyte string.
This is needed during bootstrapping and dumping, when
Emacs cannot decode file names, because the locale
environment is not set up. */
name = make_unibyte_string (SSDATA (name), SBYTES (name));
multibyte = 0;
}
else
{
/* NAME is non-ASCII and multibyte, and
DEFAULT_DIRECTORY is unibyte and pure-ASCII: make a
multibyte string out of DEFAULT_DIRECTORY's data. */
default_directory =
make_multibyte_string (SSDATA (default_directory),
SCHARS (default_directory),
SCHARS (default_directory));
}
}
else
{
unsigned char *p = SDATA (name);
while (*p && ASCII_CHAR_P (*p))
p++;
if (*p == '\0')
{
/* DEFAULT_DIRECTORY is multibyte and NAME is unibyte
and pure-ASCII. Make a multibyte string out of
NAME's data. */
name = make_multibyte_string (SSDATA (name),
SCHARS (name), SCHARS (name));
multibyte = 1;
}
else
default_directory = make_unibyte_string (SSDATA (default_directory),
SBYTES (default_directory));
}
}
#ifdef WINDOWSNT
if (!NILP (Vw32_downcase_file_names))
default_directory = Fdowncase (default_directory);
#endif
/* Make a local copy of NAME to protect it from GC in DECODE_FILE below. */
SAFE_ALLOCA_STRING (nm, name);
nmlim = nm + SBYTES (name);
#ifdef DOS_NT
/* Note if special escape prefix is present, but remove for now. */
if (nm[0] == '/' && nm[1] == ':')
{
is_escaped = 1;
nm += 2;
}
/* Find and remove drive specifier if present; this makes nm absolute
even if the rest of the name appears to be relative. Only look for
drive specifier at the beginning. */
if (IS_DRIVE (nm[0]) && IS_DEVICE_SEP (nm[1]))
{
drive = (unsigned char) nm[0];
nm += 2;
}
#ifdef WINDOWSNT
/* If we see "c://somedir", we want to strip the first slash after the
colon when stripping the drive letter. Otherwise, this expands to
"//somedir". */
if (drive && IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1]))
nm++;
/* Discard any previous drive specifier if nm is now in UNC format. */
if (IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1])
&& !IS_DIRECTORY_SEP (nm[2]))
drive = 0;
#endif /* WINDOWSNT */
#endif /* DOS_NT */
/* 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 (
IS_DIRECTORY_SEP (nm[0])
#ifdef MSDOS
&& drive && !is_escaped
#endif
#ifdef WINDOWSNT
&& (drive || IS_DIRECTORY_SEP (nm[1])) && !is_escaped
#endif
)
{
/* If it turns out that the filename we want to return is just a
suffix of FILENAME, we don't need to go through and edit
things; we just need to construct a new string using data
starting at the middle of FILENAME. If we set LOSE, that
means we've discovered that we can't do that cool trick. */
bool lose = 0;
char *p = nm;
while (*p)
{
/* Since we know the name is absolute, we can assume that each
element starts with a "/". */
/* "." and ".." are hairy. */
if (IS_DIRECTORY_SEP (p[0])
&& p[1] == '.'
&& (IS_DIRECTORY_SEP (p[2])
|| p[2] == 0
|| (p[2] == '.' && (IS_DIRECTORY_SEP (p[3])
|| p[3] == 0))))
lose = 1;
/* Replace multiple slashes with a single one, except
leave leading "//" alone. */
else if (IS_DIRECTORY_SEP (p[0])
&& IS_DIRECTORY_SEP (p[1])
&& (p != nm || IS_DIRECTORY_SEP (p[2])))
lose = 1;
p++;
}
if (!lose)
{
#ifdef DOS_NT
/* Make sure directories are all separated with /, but
avoid allocation of a new string when not required. */
dostounix_filename (nm);
#ifdef WINDOWSNT
if (IS_DIRECTORY_SEP (nm[1]))
{
if (strcmp (nm, SSDATA (name)) != 0)
name = make_specified_string (nm, -1, nmlim - nm, multibyte);
}
else
#endif
/* Drive must be set, so this is okay. */
if (strcmp (nm - 2, SSDATA (name)) != 0)
{
name = make_specified_string (nm, -1, p - nm, multibyte);
char temp[] = { DRIVE_LETTER (drive), ':', 0 };
AUTO_STRING_WITH_LEN (drive_prefix, temp, 2);
name = concat2 (drive_prefix, name);
}
#ifdef WINDOWSNT
if (!NILP (Vw32_downcase_file_names))
name = Fdowncase (name);
#endif
#else /* not DOS_NT */
if (strcmp (nm, SSDATA (name)) != 0)
name = make_specified_string (nm, -1, nmlim - nm, multibyte);
#endif /* not DOS_NT */
SAFE_FREE ();
return name;
}
}
/* At this point, nm might or might not be an absolute file name. We
need to expand ~ or ~user if present, otherwise prefix nm with
default_directory if nm is not absolute, and finally collapse /./
and /foo/../ sequences.
We set newdir to be the appropriate prefix if one is needed:
- the relevant user directory if nm starts with ~ or ~user
- the specified drive's working dir (DOS/NT only) if nm does not
start with /
- the value of default_directory.
Note that these prefixes are not guaranteed to be absolute (except
for the working dir of a drive). Therefore, to ensure we always
return an absolute name, if the final prefix is not absolute we
append it to the current working directory. */
newdir = newdirlim = 0;
if (nm[0] == '~' /* prefix ~ */
#ifdef DOS_NT
&& !is_escaped /* don't expand ~ in escaped file names */
#endif
)
{
if (IS_DIRECTORY_SEP (nm[1])
|| nm[1] == 0) /* ~ by itself */
{
Lisp_Object tem;
newdir = get_homedir ();
nm++;
tem = build_string (newdir);
newdirlim = newdir + SBYTES (tem);
/* get_homedir may return a unibyte string, which will bite us
if we expect the directory to be multibyte. */
if (multibyte && !STRING_MULTIBYTE (tem))
{
hdir = DECODE_FILE (tem);
newdir = SSDATA (hdir);
newdirlim = newdir + SBYTES (hdir);
}
else if (!multibyte && STRING_MULTIBYTE (tem))
multibyte = 1;
#ifdef DOS_NT
collapse_newdir = false;
#endif
}
else /* ~user/filename */
{
char *nmhome = user_homedir (nm + 1);
if (nmhome)
{
ptrdiff_t nmhomelen = strlen (nmhome);
newdir = nmhome;
newdirlim = newdir + nmhomelen;
if (multibyte)
{
AUTO_STRING_WITH_LEN (lisp_nmhome, nmhome, nmhomelen);
hdir = DECODE_FILE (lisp_nmhome);
newdir = SSDATA (hdir);
newdirlim = newdir + SBYTES (hdir);
}
while (*++nm && !IS_DIRECTORY_SEP (*nm))
continue;
#ifdef DOS_NT
collapse_newdir = false;
#endif
}
/* If we don't find a user of that name, leave the name
unchanged. */
}
}
#ifdef DOS_NT
/* On DOS and Windows, nm is absolute if a drive name was specified;
use the drive's current directory as the prefix if needed. */
if (!newdir && drive)
{
/* Get default directory if needed to make nm absolute. */
char *adir = NULL;
if (!IS_DIRECTORY_SEP (nm[0]))
{
adir = SAFE_ALLOCA (MAXPATHLEN + 1);
if (!getdefdir (c_toupper (drive) - 'A' + 1, adir))
adir = NULL;
else if (multibyte)
{
Lisp_Object tem = build_string (adir);
tem = DECODE_FILE (tem);
newdirlim = adir + SBYTES (tem);
memcpy (adir, SSDATA (tem), SBYTES (tem) + 1);
}
else
newdirlim = adir + strlen (adir);
}
if (!adir)
{
/* Either nm starts with /, or drive isn't mounted. */
adir = SAFE_ALLOCA (4);
adir[0] = DRIVE_LETTER (drive);
adir[1] = ':';
adir[2] = '/';
adir[3] = 0;
newdirlim = adir + 3;
}
newdir = adir;
}
#endif /* DOS_NT */
/* Finally, if no prefix has been specified and nm is not absolute,
then it must be expanded relative to default_directory. */
if (1
#ifndef DOS_NT
/* /... alone is not absolute on DOS and Windows. */
&& !IS_DIRECTORY_SEP (nm[0])
#endif
#ifdef WINDOWSNT
&& !(IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1])
&& !IS_DIRECTORY_SEP (nm[2]))
#endif
&& !newdir)
{
newdir = SSDATA (default_directory);
newdirlim = newdir + SBYTES (default_directory);
#ifdef DOS_NT
/* Note if special escape prefix is present, but remove for now. */
if (newdir[0] == '/' && newdir[1] == ':')
{
is_escaped = 1;
newdir += 2;
}
#endif
}
#ifdef DOS_NT
if (newdir)
{
/* First ensure newdir is an absolute name. */
if (
/* Detect MSDOS file names with drive specifiers. */
! (IS_DRIVE (newdir[0])
&& IS_DEVICE_SEP (newdir[1]) && IS_DIRECTORY_SEP (newdir[2]))
#ifdef WINDOWSNT
/* Detect Windows file names in UNC format. */
&& ! (IS_DIRECTORY_SEP (newdir[0]) && IS_DIRECTORY_SEP (newdir[1])
&& !IS_DIRECTORY_SEP (newdir[2]))
#endif
)
{
/* Effectively, let newdir be (expand-file-name newdir cwd).
Because of the admonition against calling expand-file-name
when we have pointers into lisp strings, we accomplish this
indirectly by prepending newdir to nm if necessary, and using
cwd (or the wd of newdir's drive) as the new newdir. */
char *adir;
#ifdef WINDOWSNT
const int adir_size = MAX_UTF8_PATH;
#else
const int adir_size = MAXPATHLEN + 1;
#endif
if (IS_DRIVE (newdir[0]) && IS_DEVICE_SEP (newdir[1]))
{
drive = (unsigned char) newdir[0];
newdir += 2;
}
if (!IS_DIRECTORY_SEP (nm[0]))
{
ptrdiff_t nmlen = nmlim - nm;
ptrdiff_t newdirlen = newdirlim - newdir;
char *tmp = SAFE_ALLOCA (newdirlen + file_name_as_directory_slop
+ nmlen + 1);
ptrdiff_t dlen = file_name_as_directory (tmp, newdir, newdirlen,
multibyte);
memcpy (tmp + dlen, nm, nmlen + 1);
nm = tmp;
nmlim = nm + dlen + nmlen;
}
adir = SAFE_ALLOCA (adir_size);
if (drive)
{
if (!getdefdir (c_toupper (drive) - 'A' + 1, adir))
strcpy (adir, "/");
}
else
getcwd (adir, adir_size);
if (multibyte)
{
Lisp_Object tem = build_string (adir);
tem = DECODE_FILE (tem);
newdirlim = adir + SBYTES (tem);
memcpy (adir, SSDATA (tem), SBYTES (tem) + 1);
}
else
newdirlim = adir + strlen (adir);
newdir = adir;
}
/* Strip off drive name from prefix, if present. */
if (IS_DRIVE (newdir[0]) && IS_DEVICE_SEP (newdir[1]))
{
drive = newdir[0];
newdir += 2;
}
/* Keep only a prefix from newdir if nm starts with slash
(//server/share for UNC, nothing otherwise). */
if (IS_DIRECTORY_SEP (nm[0]) && collapse_newdir)
{
#ifdef WINDOWSNT
if (IS_DIRECTORY_SEP (newdir[0]) && IS_DIRECTORY_SEP (newdir[1])
&& !IS_DIRECTORY_SEP (newdir[2]))
{
char *adir = strcpy (SAFE_ALLOCA (newdirlim - newdir + 1), newdir);
char *p = adir + 2;
while (*p && !IS_DIRECTORY_SEP (*p)) p++;
p++;
while (*p && !IS_DIRECTORY_SEP (*p)) p++;
*p = 0;
newdir = adir;
newdirlim = newdir + strlen (adir);
}
else
#endif
newdir = newdirlim = "";
}
}
#endif /* DOS_NT */
/* Ignore any slash at the end of newdir, unless newdir is
just "/" or "//". */
length = newdirlim - newdir;
while (length > 1 && IS_DIRECTORY_SEP (newdir[length - 1])
&& ! (length == 2 && IS_DIRECTORY_SEP (newdir[0])))
length--;
/* Now concatenate the directory and name to new space in the stack frame. */
tlen = length + file_name_as_directory_slop + (nmlim - nm) + 1;
eassert (tlen >= file_name_as_directory_slop + 1);
#ifdef DOS_NT
/* Reserve space for drive specifier and escape prefix, since either
or both may need to be inserted. (The Microsoft x86 compiler
produces incorrect code if the following two lines are combined.) */
target = SAFE_ALLOCA (tlen + 4);
target += 4;
#else /* not DOS_NT */
target = SAFE_ALLOCA (tlen);
#endif /* not DOS_NT */
*target = 0;
nbytes = 0;
if (newdir)
{
if (nm[0] == 0 || IS_DIRECTORY_SEP (nm[0]))
{
#ifdef DOS_NT
/* If newdir is effectively "C:/", then the drive letter will have
been stripped and newdir will be "/". Concatenating with an
absolute directory in nm produces "//", which will then be
incorrectly treated as a network share. Ignore newdir in
this case (keeping the drive letter). */
if (!(drive && nm[0] && IS_DIRECTORY_SEP (newdir[0])
&& newdir[1] == '\0'))
#endif
{
memcpy (target, newdir, length);
target[length] = 0;
nbytes = length;
}
}
else
nbytes = file_name_as_directory (target, newdir, length, multibyte);
}
memcpy (target + nbytes, nm, nmlim - nm + 1);
/* Now canonicalize by removing `//', `/.' and `/foo/..' if they
appear. */
{
char *p = target;
char *o = target;
while (*p)
{
if (!IS_DIRECTORY_SEP (*p))
{
*o++ = *p++;
}
else if (p[1] == '.'
&& (IS_DIRECTORY_SEP (p[2])
|| p[2] == 0))
{
/* If "/." is the entire filename, keep the "/". Otherwise,
just delete the whole "/.". */
if (o == target && p[2] == '\0')
*o++ = *p;
p += 2;
}
else if (p[1] == '.' && p[2] == '.'
/* `/../' is the "superroot" on certain file systems.
Turned off on DOS_NT systems because they have no
"superroot" and because this causes us to produce
file names like "d:/../foo" which fail file-related
functions of the underlying OS. (To reproduce, try a
long series of "../../" in default_directory, longer
than the number of levels from the root.) */
#ifndef DOS_NT
&& o != target
#endif
&& (IS_DIRECTORY_SEP (p[3]) || p[3] == 0))
{
#ifdef WINDOWSNT
char *prev_o = o;
#endif
while (o != target && (--o, !IS_DIRECTORY_SEP (*o)))
continue;
#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])
&& (p != target || IS_DIRECTORY_SEP (p[2])))
/* Collapse multiple "/", except leave leading "//" alone. */
p++;
else
{
*o++ = *p++;
}
}
#ifdef DOS_NT
/* At last, set drive name. */
#ifdef WINDOWSNT
/* Except for network file name. */
if (!(IS_DIRECTORY_SEP (target[0]) && IS_DIRECTORY_SEP (target[1])))
#endif /* WINDOWSNT */
{
if (!drive) emacs_abort ();
target -= 2;
target[0] = DRIVE_LETTER (drive);
target[1] = ':';
}
/* Reinsert the escape prefix if required. */
if (is_escaped)
{
target -= 2;
target[0] = '/';
target[1] = ':';
}
result = make_specified_string (target, -1, o - target, multibyte);
dostounix_filename (SSDATA (result));
#ifdef WINDOWSNT
if (!NILP (Vw32_downcase_file_names))
result = Fdowncase (result);
#endif
#else /* !DOS_NT */
result = make_specified_string (target, -1, o - target, multibyte);
#endif /* !DOS_NT */
}
/* Again look to see if the file name has special constructs in it
and perhaps call the corresponding file name handler. This is needed
for filenames such as "/foo/../user@host:/bar/../baz". Expanding
the ".." component gives us "/user@host:/bar/../baz" which needs
to be expanded again. */
handler = Ffind_file_name_handler (result, Qexpand_file_name);
if (!NILP (handler))
{
handled_name = calln (handler, Qexpand_file_name,
result, default_directory);
if (! STRINGP (handled_name))
error ("Invalid handler in `file-name-handler-alist'");
result = handled_name;
}
SAFE_FREE ();
return result;
}