delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp-workers/2008/06/23/14:51:09

X-Authentication-Warning: delorie.com: mail set sender to djgpp-workers-bounces using -f
X-Recipient: djgpp-workers AT delorie DOT com
X-Authenticated: #27081556
X-Provags-ID: V01U2FsdGVkX19X41EFeU1Xh419u3LMWH4EQSC5QUxhynskE/dQgK
nVvLCI31Se+IWo
From: Juan Manuel Guerrero <juan DOT guerrero AT gmx DOT de>
To: djgpp-workers AT delorie DOT com
Subject: Two bug fixes for __get_current_directory
Date: Mon, 23 Jun 2008 17:59:02 +0200
User-Agent: KMail/1.9.5
MIME-Version: 1.0
Message-Id: <200806231759.03441.juan.guerrero@gmx.de>
X-Y-GMX-Trusted: 0
Reply-To: djgpp-workers AT delorie DOT com

Both bugs appears on WinNT/2K/XP when LFN support is enabled and the CWD is the
root dir.  If CWD is not the root directory you will never notice this.
This is the relevant part of the code from __get_current_directory():


  /* Root path under WinNT/2K/XP with lfn (may be silent failure).
     If the short name equivalent of the current path is greater than
     64 characters, Windows 2000 and XP do not return the correct long
     path name - they return the root directory instead without any
     failure code.  Since this can be disastrous in deep directories
     doing an rm -rf, we check for this bug and try and fix the path. */

  r.x.ax = 0x7160;
  r.x.cx = 0x8002;	/* Get Long Path Name, using subst drive basis */
  r.x.es = __tb_segment;
  r.x.di = __tb_offset + FILENAME_MAX;
  
  tmpbuf[0] = drive_number + 'A';
  tmpbuf[1] = ':';
  tmpbuf[2] = '.';
  tmpbuf[3] = 0;
  _put_path(tmpbuf);

  __dpmi_int(0x21, &r);

  if (!(r.x.flags & 1))
  {
    dosmemget(__tb + FILENAME_MAX, sizeof(tmpbuf), tmpbuf);

    /* Validate return form and drive matches what _fixpath expects. */
    if (tmpbuf[0] == (drive_number + 'A') && tmpbuf[1] == ':')
    {
      strcpy(out, tmpbuf+2);	/* Trim drive, just directory */
      return out + strlen(out);
    }
  } 

  /* Fixpath failed or returned inconsistent info.  Return relative path. */
  *out++ = '.';
  return out;


bug #1:
The first bug is that only upper case letters are checked to be valid drive
specifier letters.  If you start cmd.exe in the usual way and you inspect the
prompt you will notice that it is upper case.  In this case the bug will not
appear.  But if you start cmd.exe with a command like this:
  start /Dc:\ cmd
a new DOS box will be opened with a lower cased drive specifer letter in the
prompt.  In this case the bug will appear.  This can be verified by starting
find.exe in the root dir.  It is essential that the working dir is the root
directory.  Starting the program like this:

c:\>find

produces the following error message with no other output:

C:\djgpp\bin/find.exe: cannot get current directory: No such file or directory (ENOENT)

The function that fails with ENOENT is lstat() that has called stat_assist()
that has called _fixpath() and this one has called the broken __get_current_directory().
The only way to avoid the bug is to set LFN=n or to make the drive specifier
letter become upper case.  This can be easily achieved by changing into some directory
and calling some djgpp program and returning to the root dir again.  Now find.exe will
work as expected.

As can be seen in the code snippet, INT 0x21 Function 0x7160 (Win95 - LONG FILENAME,
"TRUENAME", CANONICALIZE PATH (description according latest RBIL)) is called.
After the call tmpbuf[] will contain the canonicalized path but there is no
guarantee at all that the canonicalized path will be upper case.  The reason
for the failure is quite simple, it is the following code line:

    if (tmpbuf[0] == (drive_number + 'A') && tmpbuf[1] == ':')

It checks only for upper case drive specifier letters.  Because it fails for
lower case drive specifier letters, the program flow returns according to the
following code lines:

  /* Fixpath failed or returned inconsistent info.  Return relative path. */
  *out++ = '.';
  return out;

the relative path to the calling context, making them fail too.


bug #2:
The reason for the second bug is the same code than the one for the first bug
and this bug has already been reported by me some time ago but I got never an
answer.  As told before tmpbuf contains the canonicalized path returned by
Function 0x7160.  In the case that it is a root path, the returned string may
look like this:
  tmpbuf = "c:\"
Please note the trailing backslash; the case of the drive specifier does not matter
in this case.  As can be seen in the following code snippet:

    /* Validate return form and drive matches what _fixpath expects. */
    if (tmpbuf[0] == (drive_number + 'A') && tmpbuf[1] == ':')
    {
      strcpy(out, tmpbuf+2);	/* Trim drive, just directory */
      return out + strlen(out);
    }

the drive specifier is removed and everything else of the path remaining in tmpbuf
is copied into out, and this string is returned to the calling function.  This
is wrong.  This function, __get_current_directory(), always returns the
canonicalized path string *without* a trailing slash and certainly *never* with
a trailing backslash.  The only situation where the function returns a path with
trailing slash and even worse with the wrong type of slash is in the above case
making the calling function fail producing a non cononicalized path string.
This means that this bug only appears when CWD is the root dir, LFN=y and the
OS is WinNT/2K/XP.  It should be noticed that the only function calling
__get_current_directory() is __canonicalize_path() and this function always
expects that the returned path has neither a trailing slash nor a backslash
because the trailing slash is always added by __canonicalize_path() itself to
build up the canonicalized path string.  For example, assuming that we are on
drive C: in the root dir and _fixpath() is called like this:
  _fixpath("foo\\bar", canonicalized_path);
the returned string will look like:
  canonicalized_path = "c:\\/foo/bar"
and this is wrong.  Of course the same apllies to function realpath() that calls
__canonicalize_path() too.



I have not been envolved in the coding of _fixpath and auxiliary functions, so
I do not know if I am missing something here.  I do not know if something in the
behaviour of the code lines I have described is intentional.  If the bugs are
really bugs then they have some implications.  Bug #1 concerns functions:
  access()
  chdir()
  opendir()
  lstat()
  xstat()
  _fixpath()
Bug #2 concerns functions:
  _fixpath()
  realpath()

Concerning bug #1, we are really very, very affortunate that the OS seems
to use upper case drive specifier letters by default so that the bug does
not appear very often.  Bug #2 causes some trouble to me when I try to port
something like splint and perl where I make havy use of realpath() to have all
paths in a canonical and POSIX similar form.


Here a small test program to demostrate both bugs, it can be compiled with
stock djdev204.zip; you do not need to check out CVS sources and rebuild libc.a.


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>


int main(void)
{
  struct stat s;
  char inpath[] = "foo\\bar";
  char outpath[FILENAME_MAX];
  int e, r;

  chdir("/");

  errno = 0;
  r = lstat(".", &s);
  e = errno;

  printf("Demonstration of bug #1:\n"
         "If drive specifier letter is lower case, r = -1 and e = 22 (ENOENT).\n"
         "If drive specifier letter is upper case, r =  0 and e = 0.\n"
         "Returned values by lstat() are:\nr = %d\ne = %d\n\n", r, e);

  printf("Demonstration of bug #2:\n"
         "Path to be canonicalized:     %s\n"
         "Returned canonicalized path:  %s\n"
         "Assuming drive specifier letter is \"c\"\n"
         "If drive specifier letter is lower case, canonicalized path = c:./foo/bar.\n"
         "If drive specifier letter is upper case, canonicalized path = c:\\/foo/bar.\n"
         "Both returned paths are wrong and not canonicalized in the sense as the docs explain.\n",
         inpath, realpath(inpath, outpath));

  return 0;
}



Thanks to Robert Riebisch to calling my attention about bug #1 and for helping
to debug it.  As usual, suggestions, objections, comments concerning the
proposed patch are welcome.


Regards,
Juan M. Guerrero




2008-06-23  Juan Manuell Guerrero  <juan DOT guerrero AT gmx DOT de>

	* src/libc/posix/sys/stat/fixpath.c: If path is root path remove
	the backslash following the drive specifier.  The trailing slash is
	always added by the calling function.
	Also always allow for lower case drive specifier letter in a path.




diff -aprNU3 djgpp.orig/src/libc/posix/sys/stat/fixpath.c djgpp/src/libc/posix/sys/stat/fixpath.c
--- djgpp.orig/src/libc/posix/sys/stat/fixpath.c	2002-09-25 21:45:20 +0000
+++ djgpp/src/libc/posix/sys/stat/fixpath.c	2008-06-23 17:35:46 +0000
@@ -1,3 +1,4 @@
+/* Copyright (C) 2008 DJ Delorie, see COPYING.DJ for details */
 /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */
 /* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */
 /* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */
@@ -17,6 +18,10 @@
 #include <sys/stat.h>
 #include <libc/dosio.h>
 
+#define IS_DRIVE_SPECIFIER(path)  (((path[0] == (drive_number + 'A')) || (path[0] == (drive_number + 'a'))) && (path[1] == ':')))
+#define IS_ABSOLUTE(path)         ((path[2] == '\\') && (path[3] == '\0'))
+
+
 static unsigned use_lfn;
 
 static char *__get_current_directory(char *out, int drive_number);
@@ -87,10 +92,22 @@ __get_current_directory(char *out, int d
     dosmemget(__tb + FILENAME_MAX, sizeof(tmpbuf), tmpbuf);
 
     /* Validate return form and drive matches what _fixpath expects. */
-    if (tmpbuf[0] == (drive_number + 'A') && tmpbuf[1] == ':')
+    if (IS_DRIVE_SPECIFIER(tmpbuf))
     {
-      strcpy(out, tmpbuf+2);	/* Trim drive, just directory */
-      return out + strlen(out);
+      if (IS_ABSOLUTE(tmpbuf))
+        /* Root path, don't insert "/", it'll be added later */
+        return out;
+      else
+      {
+        /*
+         *  I do not understand the reason for this code; it should
+         *  handle root paths but it certainly DNTRT.  Neitherless,
+         *  to avoid any backward incompatibilities I left it here.
+         *  JMG
+         */
+        strcpy(out, tmpbuf + 2);  /* Trim drive, just directory */
+        return out + strlen(out);
+      }
     }
   } 
 #ifdef TEST

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019