From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: "Drew Adams" Newsgroups: gmane.emacs.bugs Subject: bug#7617: 24.0.50; `expand-file-name': removal of slashes Date: Sat, 11 Dec 2010 13:53:38 -0800 Message-ID: <7F477A16FFA145E5AB532A96F55D5CDC@us.oracle.com> NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_NextPart_000_003C_01CB993A.D0BA3C10" X-Trace: dough.gmane.org 1292106172 17932 80.91.229.12 (11 Dec 2010 22:22:52 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Sat, 11 Dec 2010 22:22:52 +0000 (UTC) To: 7617@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Sat Dec 11 23:22:47 2010 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([199.232.76.165]) by lo.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1PRXpo-0004mH-MZ for geb-bug-gnu-emacs@m.gmane.org; Sat, 11 Dec 2010 23:22:43 +0100 Original-Received: from localhost ([127.0.0.1]:39896 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1PRXpn-0003y1-BJ for geb-bug-gnu-emacs@m.gmane.org; Sat, 11 Dec 2010 17:22:31 -0500 Original-Received: from [140.186.70.92] (port=53565 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1PRXpi-0003w8-Ln for bug-gnu-emacs@gnu.org; Sat, 11 Dec 2010 17:22:27 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1PRXpd-0002aX-P6 for bug-gnu-emacs@gnu.org; Sat, 11 Dec 2010 17:22:26 -0500 Original-Received: from debbugs.gnu.org ([140.186.70.43]:34463) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1PRXpd-0002aS-NH for bug-gnu-emacs@gnu.org; Sat, 11 Dec 2010 17:22:21 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.69) (envelope-from ) id 1PRXJP-0002uf-Dw; Sat, 11 Dec 2010 16:49:03 -0500 X-Loop: help-debbugs@gnu.org Resent-From: "Drew Adams" Original-Sender: debbugs-submit-bounces@debbugs.gnu.org Resent-To: owner@debbugs.gnu.org Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sat, 11 Dec 2010 21:49:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 7617 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: X-Debbugs-Original-To: Original-Received: via spool by submit@debbugs.gnu.org id=B.129210409011119 (code B ref -1); Sat, 11 Dec 2010 21:49:02 +0000 Original-Received: (at submit) by debbugs.gnu.org; 11 Dec 2010 21:48:10 +0000 Original-Received: from localhost ([127.0.0.1] helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.69) (envelope-from ) id 1PRXIX-0002tI-OI for submit@debbugs.gnu.org; Sat, 11 Dec 2010 16:48:10 -0500 Original-Received: from eggs.gnu.org ([140.186.70.92]) by debbugs.gnu.org with esmtp (Exim 4.69) (envelope-from ) id 1PRXIV-0002t3-Li for submit@debbugs.gnu.org; Sat, 11 Dec 2010 16:48:08 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1PRXOP-0006L0-Dp for submit@debbugs.gnu.org; Sat, 11 Dec 2010 16:54:14 -0500 Original-Received: from lists.gnu.org ([199.232.76.165]:38593) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1PRXOP-0006Kp-4a for submit@debbugs.gnu.org; Sat, 11 Dec 2010 16:54:13 -0500 Original-Received: from [140.186.70.92] (port=47902 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1PRXON-0003z3-Lc for bug-gnu-emacs@gnu.org; Sat, 11 Dec 2010 16:54:12 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1PRXOM-0006Jw-BY for bug-gnu-emacs@gnu.org; Sat, 11 Dec 2010 16:54:11 -0500 Original-Received: from rcsinet10.oracle.com ([148.87.113.121]:46851) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1PRXOL-0006Il-VG for bug-gnu-emacs@gnu.org; Sat, 11 Dec 2010 16:54:10 -0500 Original-Received: from rcsinet15.oracle.com (rcsinet15.oracle.com [148.87.113.117]) by rcsinet10.oracle.com (Switch-3.4.2/Switch-3.4.2) with ESMTP id oBBLs5Yr012919 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Sat, 11 Dec 2010 21:54:06 GMT Original-Received: from acsmt355.oracle.com (acsmt355.oracle.com [141.146.40.155]) by rcsinet15.oracle.com (Switch-3.4.2/Switch-3.4.1) with ESMTP id oBBLrx8w004678 for ; Sat, 11 Dec 2010 21:54:02 GMT Original-Received: from abhmt007.oracle.com by acsmt354.oracle.com with ESMTP id 866376331292104419; Sat, 11 Dec 2010 13:53:39 -0800 Original-Received: from dradamslap1 (/10.159.220.201) by default (Oracle Beehive Gateway v4.0) with ESMTP ; Sat, 11 Dec 2010 13:53:38 -0800 X-Mailer: Microsoft Office Outlook 11 Thread-Index: AcuZfd3SOme47opfReix3lhmOPcFJw== X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.5994 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 2) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.11 Precedence: list Resent-Date: Sat, 11 Dec 2010 16:49:03 -0500 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.bugs:42430 Archived-At: This is a multi-part message in MIME format. ------=_NextPart_000_003C_01CB993A.D0BA3C10 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit emacs -Q (expand-file-name "share/" "~/today//usr/") -> c:/today/usr/share/. (On my Windows laptop, `~' is just `c:/'.) IOW, multiple consecutive slashes are collapsed to one. This is not the behavior I want. This behavior started in Emacs 21; in Emacs 20 no such removal of slashes occurs. I would consider this loss of slashes a bug. But I see that comments in the current C code indicate that it is intentional (but not _why_). I'm not great at reading C code, but I see these comments in the Emacs 24 code: /* We want to replace multiple `/' in a row with a single slash. */ else if (p > nm && IS_DIRECTORY_SEP (p[0]) && IS_DIRECTORY_SEP (p[1])) And later on: /* Now canonicalize by removing `//', `/.' and `/foo/..' if they appear. */ ... /* Collapse multiple `/' in a row. */ In the Emacs 20 C code the equivalent comment says only this: /* Now canonicalize by removing /. and /foo/.. if they appear. */ I don't have C source for Emacs 21, but the behavior of 21 indicates that that is when the change was made, presumably intentionally. I searched the change logs but found no mention of this change or why it was made. Given that the change was presumably intentional (but why?), you might not be disposed to consider this a bug. I imagine you might argue that the second arg to `expand-file-name' in this case is not a valid directory name, so all bets are off wrt the behavior - or something like that. Or perhaps you will argue that `expand*' is supposed to give you a canonicalized file name, and a name such as "c:/today//usr/share/" cannot be said to be canonicalized. IOW, I imagine there can be arguments that defend the current (Emacs 21+) behavior. But shouldn't `expand-file-name' do the right thing if the second arg is in fact `file-directory-p'? For a user on GNU/Linux with $HOME = /home/toto": (file-directory-p "~/today//usr") -> nil, but (file-directory-p "~//usr/") --> t, and we have the same problem: (expand-file-name "foo" "~//usr/") -> "/home/toto/usr/foo" Surely the behavior here is buggy, no? The result should be "/home/toto//usr/foo", I would think. It seems like any special treatment of double slashes should only be an interactive treatment (e.g. ignore or remove everything up to the second consecutive slash). _Why should_ `expand-file-name' collapse multiple consecutive slashes into a single slash? I would start with that questiom. What is the rationale for that Emacs 21 change wrt Emacs 20. In case it helps, I've attached the Emacs 20 code for `expand-file-name'. Finally, I need the Emacs 20 behavior for this for some of my code. If you decide not to change the behavior for this in Emacs, can you suggest a good way (in Lisp) to get the behavior I want - i.e., the Emacs 20 behavior? I can try to work around this by substituting something for multiple consecutive slashes, so that `expand*' won't collapse them, and then substituting back again to get the slashes, but that is a truly gross and fragile hack. I'm open to better suggestions. Thanks. Oh, and another thing. This behavior of `expand-file-name' is not documented. If the decision is to keep this behavior, then the doc should let users know that the function does this. See the doc string - it describes everything the function does (including `.' removal etc.) - except this. In GNU Emacs 24.0.50.1 (i386-mingw-nt5.1.2600) of 2010-12-06 on 3249CTO Windowing system distributor `Microsoft Corp.', version 5.1.2600 configured using `configure --with-gcc (4.4) --no-opt --cflags -Ic:/imagesupport/include' ------=_NextPart_000_003C_01CB993A.D0BA3C10 Content-Type: application/octet-stream; name="emacs-20-dot-7-expand-file-name.c" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="emacs-20-dot-7-expand-file-name.c" DEFUN ("expand-file-name", Fexpand_file_name, Sexpand_file_name, 1, 2, 0,=0A= "Convert filename NAME to absolute, and canonicalize it.\n\=0A= Second arg DEFAULT-DIRECTORY is directory to start with if NAME is = relative\n\=0A= (does not start with slash); if DEFAULT-DIRECTORY is nil or missing,\n\=0A= the current buffer's value of default-directory is used.\n\=0A= File name components that are `.' are removed, and \n\=0A= so are file name components followed by `..', along with the `..' = itself;\n\=0A= note that these simplifications are done without checking the = resulting\n\=0A= file names in the file system.\n\=0A= An initial `~/' expands to your home directory.\n\=0A= An initial `~USER/' expands to USER's home directory.\n\=0A= See also the function `substitute-in-file-name'.")=0A= (name, default_directory)=0A= Lisp_Object name, default_directory;=0A= {=0A= unsigned char *nm;=0A= =0A= register unsigned char *newdir, *p, *o;=0A= int tlen;=0A= unsigned char *target;=0A= struct passwd *pw;=0A= #ifdef VMS=0A= unsigned char * colon =3D 0;=0A= unsigned char * close =3D 0;=0A= unsigned char * slash =3D 0;=0A= unsigned char * brack =3D 0;=0A= int lbrack =3D 0, rbrack =3D 0;=0A= int dots =3D 0;=0A= #endif /* VMS */=0A= #ifdef DOS_NT=0A= int drive =3D 0;=0A= int collapse_newdir =3D 1;=0A= int is_escaped =3D 0;=0A= #endif /* DOS_NT */=0A= int length;=0A= Lisp_Object handler;=0A= =0A= CHECK_STRING (name, 0);=0A= =0A= /* If the file name has special constructs in it,=0A= call the corresponding file handler. */=0A= handler =3D Ffind_file_name_handler (name, Qexpand_file_name);=0A= if (!NILP (handler))=0A= return call3 (handler, Qexpand_file_name, name, default_directory);=0A= =0A= /* Use the buffer's default-directory if DEFAULT_DIRECTORY is omitted. = */=0A= if (NILP (default_directory))=0A= default_directory =3D current_buffer->directory;=0A= if (! STRINGP (default_directory))=0A= default_directory =3D build_string ("/");=0A= =0A= if (!NILP (default_directory))=0A= {=0A= handler =3D Ffind_file_name_handler (default_directory, = Qexpand_file_name);=0A= if (!NILP (handler))=0A= return call3 (handler, Qexpand_file_name, name, default_directory);=0A= }=0A= =0A= o =3D XSTRING (default_directory)->data;=0A= =0A= /* Make sure DEFAULT_DIRECTORY is properly expanded.=0A= It would be better to do this down below where we actually use=0A= default_directory. Unfortunately, calling Fexpand_file_name = recursively=0A= could invoke GC, and the strings might be relocated. This would=0A= be annoying because we have pointers into strings lying around=0A= that would need adjusting, and people would add new pointers to=0A= the code and forget to adjust them, resulting in intermittent bugs.=0A= Putting this call here avoids all that crud.=0A= =0A= The EQ test avoids infinite recursion. */=0A= if (! NILP (default_directory) && !EQ (default_directory, name)=0A= /* Save time in some common cases - as long as default_directory=0A= is not relative, it can be canonicalized with name below (if it=0A= is needed at all) without requiring it to be expanded now. */=0A= #ifdef DOS_NT=0A= /* Detect MSDOS file names with drive specifiers. */=0A= && ! (IS_DRIVE (o[0]) && IS_DEVICE_SEP (o[1]) && IS_DIRECTORY_SEP = (o[2]))=0A= #ifdef WINDOWSNT=0A= /* Detect Windows file names in UNC format. */=0A= && ! (IS_DIRECTORY_SEP (o[0]) && IS_DIRECTORY_SEP (o[1]))=0A= #endif=0A= #else /* not DOS_NT */=0A= /* Detect Unix absolute file names (/... alone is not absolute on=0A= DOS or Windows). */=0A= && ! (IS_DIRECTORY_SEP (o[0]))=0A= #endif /* not DOS_NT */=0A= )=0A= {=0A= struct gcpro gcpro1;=0A= =0A= GCPRO1 (name);=0A= default_directory =3D Fexpand_file_name (default_directory, Qnil);=0A= UNGCPRO;=0A= }=0A= =0A= #ifdef VMS=0A= /* Filenames on VMS are always upper case. */=0A= name =3D Fupcase (name);=0A= #endif=0A= #ifdef FILE_SYSTEM_CASE=0A= name =3D FILE_SYSTEM_CASE (name);=0A= #endif=0A= =0A= nm =3D XSTRING (name)->data;=0A= =0A= #ifdef DOS_NT=0A= /* We will force directory separators to be either all \ or /, so make=0A= a local copy to modify, even if there ends up being no change. */=0A= nm =3D strcpy (alloca (strlen (nm) + 1), nm);=0A= =0A= /* Note if special escape prefix is present, but remove for now. */=0A= if (nm[0] =3D=3D '/' && nm[1] =3D=3D ':')=0A= {=0A= is_escaped =3D 1;=0A= nm +=3D 2;=0A= }=0A= =0A= /* Find and remove drive specifier if present; this makes nm absolute=0A= even if the rest of the name appears to be relative. Only look for=0A= drive specifier at the beginning. */=0A= if (IS_DRIVE (nm[0]) && IS_DEVICE_SEP (nm[1]))=0A= {=0A= drive =3D nm[0];=0A= nm +=3D 2;=0A= }=0A= =0A= #ifdef WINDOWSNT=0A= /* If we see "c://somedir", we want to strip the first slash after the=0A= colon when stripping the drive letter. Otherwise, this expands to=0A= "//somedir". */=0A= if (drive && IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1]))=0A= nm++;=0A= #endif /* WINDOWSNT */=0A= #endif /* DOS_NT */=0A= =0A= #ifdef WINDOWSNT=0A= /* Discard any previous drive specifier if nm is now in UNC format. */=0A= if (IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1]))=0A= {=0A= drive =3D 0;=0A= }=0A= #endif=0A= =0A= /* If nm is absolute, look for /./ or /../ sequences; if none are=0A= found, we can probably return right away. We will avoid allocating=0A= a new string if name is already fully expanded. */=0A= if (=0A= IS_DIRECTORY_SEP (nm[0])=0A= #ifdef MSDOS=0A= && drive && !is_escaped=0A= #endif=0A= #ifdef WINDOWSNT=0A= && (drive || IS_DIRECTORY_SEP (nm[1])) && !is_escaped=0A= #endif=0A= #ifdef VMS=0A= || index (nm, ':')=0A= #endif /* VMS */=0A= )=0A= {=0A= /* If it turns out that the filename we want to return is just a=0A= suffix of FILENAME, we don't need to go through and edit=0A= things; we just need to construct a new string using data=0A= starting at the middle of FILENAME. If we set lose to a=0A= non-zero value, that means we've discovered that we can't do=0A= that cool trick. */=0A= int lose =3D 0;=0A= =0A= p =3D nm;=0A= while (*p)=0A= {=0A= /* Since we know the name is absolute, we can assume that each=0A= element starts with a "/". */=0A= =0A= /* "." and ".." are hairy. */=0A= if (IS_DIRECTORY_SEP (p[0])=0A= && p[1] =3D=3D '.'=0A= && (IS_DIRECTORY_SEP (p[2])=0A= || p[2] =3D=3D 0=0A= || (p[2] =3D=3D '.' && (IS_DIRECTORY_SEP (p[3])=0A= || p[3] =3D=3D 0))))=0A= lose =3D 1;=0A= #ifdef VMS=0A= if (p[0] =3D=3D '\\')=0A= lose =3D 1;=0A= if (p[0] =3D=3D '/') {=0A= /* if dev:[dir]/, move nm to / */=0A= if (!slash && p > nm && (brack || colon)) {=0A= nm =3D (brack ? brack + 1 : colon + 1);=0A= lbrack =3D rbrack =3D 0;=0A= brack =3D 0;=0A= colon =3D 0;=0A= }=0A= slash =3D p;=0A= }=0A= if (p[0] =3D=3D '-')=0A= #ifndef VMS4_4=0A= /* VMS pre V4.4,convert '-'s in filenames. */=0A= if (lbrack =3D=3D rbrack)=0A= {=0A= if (dots < 2) /* this is to allow negative version numbers */=0A= p[0] =3D '_';=0A= }=0A= else=0A= #endif /* VMS4_4 */=0A= if (lbrack > rbrack &&=0A= ((p[-1] =3D=3D '.' || p[-1] =3D=3D '[' || p[-1] =3D=3D '<') &&=0A= (p[1] =3D=3D '.' || p[1] =3D=3D ']' || p[1] =3D=3D '>')))=0A= lose =3D 1;=0A= #ifndef VMS4_4=0A= else=0A= p[0] =3D '_';=0A= #endif /* VMS4_4 */=0A= /* count open brackets, reset close bracket pointer */=0A= if (p[0] =3D=3D '[' || p[0] =3D=3D '<')=0A= lbrack++, brack =3D 0;=0A= /* count close brackets, set close bracket pointer */=0A= if (p[0] =3D=3D ']' || p[0] =3D=3D '>')=0A= rbrack++, brack =3D p;=0A= /* detect ][ or >< */=0A= if ((p[0] =3D=3D ']' || p[0] =3D=3D '>') && (p[1] =3D=3D '[' || p[1] = =3D=3D '<'))=0A= lose =3D 1;=0A= if ((p[0] =3D=3D ':' || p[0] =3D=3D ']' || p[0] =3D=3D '>') && p[1] = =3D=3D '~')=0A= nm =3D p + 1, lose =3D 1;=0A= if (p[0] =3D=3D ':' && (colon || slash))=0A= /* if dev1:[dir]dev2:, move nm to dev2: */=0A= if (brack)=0A= {=0A= nm =3D brack + 1;=0A= brack =3D 0;=0A= }=0A= /* if /name/dev:, move nm to dev: */=0A= else if (slash)=0A= nm =3D slash + 1;=0A= /* if node::dev:, move colon following dev */=0A= else if (colon && colon[-1] =3D=3D ':')=0A= colon =3D p;=0A= /* if dev1:dev2:, move nm to dev2: */=0A= else if (colon && colon[-1] !=3D ':')=0A= {=0A= nm =3D colon + 1;=0A= colon =3D 0;=0A= }=0A= if (p[0] =3D=3D ':' && !colon)=0A= {=0A= if (p[1] =3D=3D ':')=0A= p++;=0A= colon =3D p;=0A= }=0A= if (lbrack =3D=3D rbrack)=0A= if (p[0] =3D=3D ';')=0A= dots =3D 2;=0A= else if (p[0] =3D=3D '.')=0A= dots++;=0A= #endif /* VMS */=0A= p++;=0A= }=0A= if (!lose)=0A= {=0A= #ifdef VMS=0A= if (index (nm, '/'))=0A= return build_string (sys_translate_unix (nm));=0A= #endif /* VMS */=0A= #ifdef DOS_NT=0A= /* Make sure directories are all separated with / or \ as=0A= desired, but avoid allocation of a new string when not=0A= required. */=0A= CORRECT_DIR_SEPS (nm);=0A= #ifdef WINDOWSNT=0A= if (IS_DIRECTORY_SEP (nm[1]))=0A= {=0A= if (strcmp (nm, XSTRING (name)->data) !=3D 0)=0A= name =3D build_string (nm);=0A= }=0A= else=0A= #endif=0A= /* drive must be set, so this is okay */=0A= if (strcmp (nm - 2, XSTRING (name)->data) !=3D 0)=0A= {=0A= name =3D make_string (nm - 2, p - nm + 2);=0A= XSTRING (name)->data[0] =3D DRIVE_LETTER (drive);=0A= XSTRING (name)->data[1] =3D ':';=0A= }=0A= return name;=0A= #else /* not DOS_NT */=0A= if (nm =3D=3D XSTRING (name)->data)=0A= return name;=0A= return build_string (nm);=0A= #endif /* not DOS_NT */=0A= }=0A= }=0A= =0A= /* At this point, nm might or might not be an absolute file name. We=0A= need to expand ~ or ~user if present, otherwise prefix nm with=0A= default_directory if nm is not absolute, and finally collapse /./=0A= and /foo/../ sequences.=0A= =0A= We set newdir to be the appropriate prefix if one is needed:=0A= - the relevant user directory if nm starts with ~ or ~user=0A= - the specified drive's working dir (DOS/NT only) if nm does not=0A= start with /=0A= - the value of default_directory.=0A= =0A= Note that these prefixes are not guaranteed to be absolute (except=0A= for the working dir of a drive). Therefore, to ensure we always=0A= return an absolute name, if the final prefix is not absolute we=0A= append it to the current working directory. */=0A= =0A= newdir =3D 0;=0A= =0A= if (nm[0] =3D=3D '~') /* prefix ~ */=0A= {=0A= if (IS_DIRECTORY_SEP (nm[1])=0A= #ifdef VMS=0A= || nm[1] =3D=3D ':'=0A= #endif /* VMS */=0A= || nm[1] =3D=3D 0) /* ~ by itself */=0A= {=0A= if (!(newdir =3D (unsigned char *) egetenv ("HOME")))=0A= newdir =3D (unsigned char *) "";=0A= nm++;=0A= #ifdef DOS_NT=0A= collapse_newdir =3D 0;=0A= #endif=0A= #ifdef VMS=0A= nm++; /* Don't leave the slash in nm. */=0A= #endif /* VMS */=0A= }=0A= else /* ~user/filename */=0A= {=0A= for (p =3D nm; *p && (!IS_DIRECTORY_SEP (*p)=0A= #ifdef VMS=0A= && *p !=3D ':'=0A= #endif /* VMS */=0A= ); p++);=0A= o =3D (unsigned char *) alloca (p - nm + 1);=0A= bcopy ((char *) nm, o, p - nm);=0A= o [p - nm] =3D 0;=0A= =0A= pw =3D (struct passwd *) getpwnam (o + 1);=0A= if (pw)=0A= {=0A= newdir =3D (unsigned char *) pw -> pw_dir;=0A= #ifdef VMS=0A= nm =3D p + 1; /* skip the terminator */=0A= #else=0A= nm =3D p;=0A= #ifdef DOS_NT=0A= collapse_newdir =3D 0;=0A= #endif=0A= #endif /* VMS */=0A= }=0A= =0A= /* If we don't find a user of that name, leave the name=0A= unchanged; don't move nm forward to p. */=0A= }=0A= }=0A= =0A= #ifdef DOS_NT=0A= /* On DOS and Windows, nm is absolute if a drive name was specified;=0A= use the drive's current directory as the prefix if needed. */=0A= if (!newdir && drive)=0A= {=0A= /* Get default directory if needed to make nm absolute. */=0A= if (!IS_DIRECTORY_SEP (nm[0]))=0A= {=0A= newdir =3D alloca (MAXPATHLEN + 1);=0A= if (!getdefdir (toupper (drive) - 'A' + 1, newdir))=0A= newdir =3D NULL;=0A= }=0A= if (!newdir)=0A= {=0A= /* Either nm starts with /, or drive isn't mounted. */=0A= newdir =3D alloca (4);=0A= newdir[0] =3D DRIVE_LETTER (drive);=0A= newdir[1] =3D ':';=0A= newdir[2] =3D '/';=0A= newdir[3] =3D 0;=0A= }=0A= }=0A= #endif /* DOS_NT */=0A= =0A= /* Finally, if no prefix has been specified and nm is not absolute,=0A= then it must be expanded relative to default_directory. */=0A= =0A= if (1=0A= #ifndef DOS_NT=0A= /* /... alone is not absolute on DOS and Windows. */=0A= && !IS_DIRECTORY_SEP (nm[0])=0A= #endif=0A= #ifdef WINDOWSNT=0A= && !(IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1]))=0A= #endif=0A= #ifdef VMS=0A= && !index (nm, ':')=0A= #endif=0A= && !newdir)=0A= {=0A= newdir =3D XSTRING (default_directory)->data;=0A= #ifdef DOS_NT=0A= /* Note if special escape prefix is present, but remove for now. = */=0A= if (newdir[0] =3D=3D '/' && newdir[1] =3D=3D ':')=0A= {=0A= is_escaped =3D 1;=0A= newdir +=3D 2;=0A= }=0A= #endif=0A= }=0A= =0A= #ifdef DOS_NT=0A= if (newdir)=0A= {=0A= /* First ensure newdir is an absolute name. */=0A= if (=0A= /* Detect MSDOS file names with drive specifiers. */=0A= ! (IS_DRIVE (newdir[0])=0A= && IS_DEVICE_SEP (newdir[1]) && IS_DIRECTORY_SEP (newdir[2]))=0A= #ifdef WINDOWSNT=0A= /* Detect Windows file names in UNC format. */=0A= && ! (IS_DIRECTORY_SEP (newdir[0]) && IS_DIRECTORY_SEP (newdir[1]))=0A= #endif=0A= )=0A= {=0A= /* Effectively, let newdir be (expand-file-name newdir cwd).=0A= Because of the admonition against calling expand-file-name=0A= when we have pointers into lisp strings, we accomplish this=0A= indirectly by prepending newdir to nm if necessary, and using=0A= cwd (or the wd of newdir's drive) as the new newdir. */=0A= =0A= if (IS_DRIVE (newdir[0]) && newdir[1] =3D=3D ':')=0A= {=0A= drive =3D newdir[0];=0A= newdir +=3D 2;=0A= }=0A= if (!IS_DIRECTORY_SEP (nm[0]))=0A= {=0A= char * tmp =3D alloca (strlen (newdir) + strlen (nm) + 2);=0A= file_name_as_directory (tmp, newdir);=0A= strcat (tmp, nm);=0A= nm =3D tmp;=0A= }=0A= newdir =3D alloca (MAXPATHLEN + 1);=0A= if (drive)=0A= {=0A= if (!getdefdir (toupper (drive) - 'A' + 1, newdir))=0A= newdir =3D "/";=0A= }=0A= else=0A= getwd (newdir);=0A= }=0A= =0A= /* Strip off drive name from prefix, if present. */=0A= if (IS_DRIVE (newdir[0]) && newdir[1] =3D=3D ':')=0A= {=0A= drive =3D newdir[0];=0A= newdir +=3D 2;=0A= }=0A= =0A= /* Keep only a prefix from newdir if nm starts with slash=0A= (//server/share for UNC, nothing otherwise). */=0A= if (IS_DIRECTORY_SEP (nm[0]) && collapse_newdir)=0A= {=0A= #ifdef WINDOWSNT=0A= if (IS_DIRECTORY_SEP (newdir[0]) && IS_DIRECTORY_SEP (newdir[1]))=0A= {=0A= newdir =3D strcpy (alloca (strlen (newdir) + 1), newdir);=0A= p =3D newdir + 2;=0A= while (*p && !IS_DIRECTORY_SEP (*p)) p++;=0A= p++;=0A= while (*p && !IS_DIRECTORY_SEP (*p)) p++;=0A= *p =3D 0;=0A= }=0A= else=0A= #endif=0A= newdir =3D "";=0A= }=0A= }=0A= #endif /* DOS_NT */=0A= =0A= if (newdir)=0A= {=0A= /* Get rid of any slash at the end of newdir, unless newdir is=0A= just / or // (an incomplete UNC name). */=0A= length =3D strlen (newdir);=0A= if (length > 1 && IS_DIRECTORY_SEP (newdir[length - 1])=0A= #ifdef WINDOWSNT=0A= && !(length =3D=3D 2 && IS_DIRECTORY_SEP (newdir[0]))=0A= #endif=0A= )=0A= {=0A= unsigned char *temp =3D (unsigned char *) alloca (length);=0A= bcopy (newdir, temp, length - 1);=0A= temp[length - 1] =3D 0;=0A= newdir =3D temp;=0A= }=0A= tlen =3D length + 1;=0A= }=0A= else=0A= tlen =3D 0;=0A= =0A= /* Now concatenate the directory and name to new space in the stack = frame */=0A= tlen +=3D strlen (nm) + 1;=0A= #ifdef DOS_NT=0A= /* Reserve space for drive specifier and escape prefix, since either=0A= or both may need to be inserted. (The Microsoft x86 compiler=0A= produces incorrect code if the following two lines are combined.) = */=0A= target =3D (unsigned char *) alloca (tlen + 4);=0A= target +=3D 4;=0A= #else /* not DOS_NT */=0A= target =3D (unsigned char *) alloca (tlen);=0A= #endif /* not DOS_NT */=0A= *target =3D 0;=0A= =0A= if (newdir)=0A= {=0A= #ifndef VMS=0A= if (nm[0] =3D=3D 0 || IS_DIRECTORY_SEP (nm[0]))=0A= {=0A= #ifdef DOS_NT=0A= /* If newdir is effectively "C:/", then the drive letter will have=0A= been stripped and newdir will be "/". Concatenating with an=0A= absolute directory in nm produces "//", which will then be=0A= incorrectly treated as a network share. Ignore newdir in=0A= this case (keeping the drive letter). */=0A= if (!(drive && nm[0] && IS_DIRECTORY_SEP (newdir[0]) =0A= && newdir[1] =3D=3D '\0'))=0A= #endif=0A= strcpy (target, newdir);=0A= }=0A= else=0A= #endif=0A= file_name_as_directory (target, newdir);=0A= }=0A= =0A= strcat (target, nm);=0A= #ifdef VMS=0A= if (index (target, '/'))=0A= strcpy (target, sys_translate_unix (target));=0A= #endif /* VMS */=0A= =0A= /* ASSERT (IS_DIRECTORY_SEP (target[0])) if not VMS */=0A= =0A= /* Now canonicalize by removing /. and /foo/.. if they appear. */=0A= =0A= p =3D target;=0A= o =3D target;=0A= =0A= while (*p)=0A= {=0A= #ifdef VMS=0A= if (*p !=3D ']' && *p !=3D '>' && *p !=3D '-')=0A= {=0A= if (*p =3D=3D '\\')=0A= p++;=0A= *o++ =3D *p++;=0A= }=0A= else if ((p[0] =3D=3D ']' || p[0] =3D=3D '>') && p[0] =3D=3D p[1] = + 2)=0A= /* brackets are offset from each other by 2 */=0A= {=0A= p +=3D 2;=0A= if (*p !=3D '.' && *p !=3D '-' && o[-1] !=3D '.')=0A= /* convert [foo][bar] to [bar] */=0A= while (o[-1] !=3D '[' && o[-1] !=3D '<')=0A= o--;=0A= else if (*p =3D=3D '-' && *o !=3D '.')=0A= *--p =3D '.';=0A= }=0A= else if (p[0] =3D=3D '-' && o[-1] =3D=3D '.' &&=0A= (p[1] =3D=3D '.' || p[1] =3D=3D ']' || p[1] =3D=3D '>'))=0A= /* flush .foo.- ; leave - if stopped by '[' or '<' */=0A= {=0A= do=0A= o--;=0A= while (o[-1] !=3D '.' && o[-1] !=3D '[' && o[-1] !=3D '<');=0A= if (p[1] =3D=3D '.') /* foo.-.bar =3D=3D> bar. */=0A= p +=3D 2;=0A= else if (o[-1] =3D=3D '.') /* '.foo.-]' =3D=3D> ']' */=0A= p++, o--;=0A= /* else [foo.-] =3D=3D> [-] */=0A= }=0A= else=0A= {=0A= #ifndef VMS4_4=0A= if (*p =3D=3D '-' &&=0A= o[-1] !=3D '[' && o[-1] !=3D '<' && o[-1] !=3D '.' &&=0A= p[1] !=3D ']' && p[1] !=3D '>' && p[1] !=3D '.')=0A= *p =3D '_';=0A= #endif /* VMS4_4 */=0A= *o++ =3D *p++;=0A= }=0A= #else /* not VMS */=0A= if (!IS_DIRECTORY_SEP (*p))=0A= {=0A= *o++ =3D *p++;=0A= }=0A= else if (IS_DIRECTORY_SEP (p[0])=0A= && p[1] =3D=3D '.'=0A= && (IS_DIRECTORY_SEP (p[2])=0A= || p[2] =3D=3D 0))=0A= {=0A= /* If "/." is the entire filename, keep the "/". Otherwise,=0A= just delete the whole "/.". */=0A= if (o =3D=3D target && p[2] =3D=3D '\0')=0A= *o++ =3D *p;=0A= p +=3D 2;=0A= }=0A= else if (IS_DIRECTORY_SEP (p[0]) && p[1] =3D=3D '.' && p[2] =3D=3D = '.'=0A= /* `/../' is the "superroot" on certain file systems. */=0A= && o !=3D target=0A= && (IS_DIRECTORY_SEP (p[3]) || p[3] =3D=3D 0))=0A= {=0A= while (o !=3D target && (--o) && !IS_DIRECTORY_SEP (*o))=0A= ;=0A= /* Keep initial / only if this is the whole name. */=0A= if (o =3D=3D target && IS_ANY_SEP (*o) && p[3] =3D=3D 0)=0A= ++o;=0A= p +=3D 3;=0A= }=0A= else=0A= {=0A= *o++ =3D *p++;=0A= }=0A= #endif /* not VMS */=0A= }=0A= =0A= #ifdef DOS_NT=0A= /* At last, set drive name. */=0A= #ifdef WINDOWSNT=0A= /* Except for network file name. */=0A= if (!(IS_DIRECTORY_SEP (target[0]) && IS_DIRECTORY_SEP (target[1])))=0A= #endif /* WINDOWSNT */=0A= {=0A= if (!drive) abort ();=0A= target -=3D 2;=0A= target[0] =3D DRIVE_LETTER (drive);=0A= target[1] =3D ':';=0A= }=0A= /* Reinsert the escape prefix if required. */=0A= if (is_escaped)=0A= {=0A= target -=3D 2;=0A= target[0] =3D '/';=0A= target[1] =3D ':';=0A= }=0A= CORRECT_DIR_SEPS (target);=0A= #endif /* DOS_NT */=0A= =0A= return make_string (target, o - target);=0A= } ------=_NextPart_000_003C_01CB993A.D0BA3C10--