make-w32
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Bug in builtin function abspath


From: Alessandro Vesely
Subject: Re: Bug in builtin function abspath
Date: Sun, 04 Jun 2006 13:50:08 +0200
User-agent: Thunderbird 1.5.0.4 (Windows/20060516)

Eli Zaretskii wrote:
Date: Thu, 01 Jun 2006 16:02:22 +0200
From: Alessandro Vesely <address@hidden>

_fullpath is a wrapper around GetFullPathName: it possibly allocates
the buffer and maps errors to errno. Since _fullpath is not in OS/2
either, I'd use GetFullPathName directly.

I'd like to avoid too many Windows-specific APIs as much as possible.
There's no need to use _fullpath or GetFullPathName, as of now; the
patch I suggested works on Windows without any direct Win32 calls.
The questions I asked about _getdcwd was about OS/2, not Windows, and
they are only relevant if we decide that "d:./foo" is not the right
value abspath should return for d:foo arguments.

I agree avoiding w32 API in function.c is good.

I don't think "d:./foo" is the value abspath should return,
because it is not an absolute path.

[...]
Sorry, I don't understand what problem are you trying to solve here.
Please explain.

I beg your pardon for presenting rough ideas. I'm not sure what abspath
should do on win32. And what about realpath?

As I'm not sure, I cannot attach a patch here. In order to explain
what I mean, I moved abspath into a standalone program and then I
freely altered it bearing upon the mumblings in my Thursday post.
I attach that. It runs like so:

C:\ale\c>wpath .
. ==> c:/ale/c

C:\ale\c>wpath -D "d:existing dir with spaces\existing file with spaces" "d:existing 
dir with spaces/existingfilewithoutspaces"
d:existing dir with spaces\existing file with spaces ==> 
d:/tools/others/EXISTI~1/Existing File With Spaces
d:existing dir with spaces/existingfilewithoutspaces ==> 
d:/tools/others/EXISTI~1/ExistingFileWithoutSpaces

C:\ale\c>wpath -S "d:existing dir with spaces\existing file with spaces" "d:existing 
dir withspaces/existingfilewithoutspaces"
d:existing dir with spaces\existing file with spaces ==> 
d:/tools/others/EXISTI~1/EXISTI~1
d:existing dir with spaces/existingfilewithoutspaces ==> 
d:/tools/others/EXISTI~1/ExistingFileWithoutSpaces

C:\ale\c>wpath -s "d:existing dir with spaces\existing file with spaces" "d:existing 
dir with spaces/existingfilewithoutspaces"
d:existing dir with spaces\existing file with spaces ==> 
d:/tools/others/EXISTI~1/EXISTI~1
d:existing dir with spaces/existingfilewithoutspaces ==> 
d:/tools/others/EXISTI~1/EXISTI~2

and also

C:\ale\c>wpath -\ wpath.c
wpath.c ==> c:\ale\c\wpath.c

C:\ale\c>wpath -j \mingw\include\w32api\w32api\w32api
\mingw\include\w32api\w32api\w32api ==> c:/uX/MinGW/include

C:\ale\c>wpath -j "drive d"
drive d ==> d:/

(where "drive d" was linked to d:\ using mountvol, and
mingw and w32api were linked using linkd from w2k resource kit)

Would any of that stuff be useful for Windows users?
Would it make sense to add Windows-specific functions to make?
In case both answers are "no" I apologize for this long post.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <Winioctl.h>
//#include <NewAPIs.h>

enum path_option_flags
{
        pof_junction = 1,             /* option j */
        pof_short = 2,                /* option s */
        pof_short_only_dir = 4,       /* option S (implies s) */
        pof_short_only_space = 8,     /* option D (implies s) */
        pof_backslash = 16            /* option \ */
};

static char *windows_syserr(DWORD err)
/* result must be freed with LocalFree() */
{
   LPVOID lpMsgBuf = NULL;
   FormatMessage(
       FORMAT_MESSAGE_ALLOCATE_BUFFER |
       FORMAT_MESSAGE_FROM_SYSTEM |
       FORMAT_MESSAGE_IGNORE_INSERTS,
       NULL,
       err,
       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
       (LPTSTR) &lpMsgBuf,
       0,
       NULL
   );
   return lpMsgBuf;
}

#define GET_PATH_MAX 1024
#define HAVE_DOS_PATHS
#define IS_PATHSEP(c) ((c) == '/' || (c) == '\\')

#ifdef HAVE_DOS_PATHS
// was: #define IS_ABSOLUTE(n) (n[0] && n[1] == ':')
#define IS_ABSOLUTE_OR_HAS_DRIVE(n) (n[0] && n[1] == ':')
#define ROOT_LEN 3
#else
#define IS_ABSOLUTE_OR_HAS_DRIVE(n) (n[0] == '/')
#define ROOT_LEN 1
#endif

static char *starting_directory;

static void print_error(char const*what)
{
        DWORD err = GetLastError();
        char *errst = windows_syserr(err);
        fprintf(stderr, "%s: %s [%d]\n",
                what, errst? errst: "?", err);
        if (errst)
                LocalFree(errst);
}

static int
check_volume(char *apath, char *pathname)
{
        char vol[GET_PATH_MAX];
        char *volname;
        size_t len, rtc;
        DWORD ldrives;
        
        len = strlen(pathname);
        if (pathname[len-1] != '\\')
                ++len;

        if ((volname = (char*)malloc(len + 1)) == NULL)
                return 0;
        
        strcpy(volname, pathname);
        volname[len-1] = 0;
        rtc = QueryDosDevice(volname, vol, sizeof vol);
        free(volname);
        
        if (rtc == 0)
        {
                print_error("cannot get drive");
                return 0;
        }
        if ((ldrives = GetLogicalDrives()) == 0)
        {
                print_error("cannot get logical drives");
                return 0;
        }
        if ((volname = strdup(vol)) == NULL)
                return 0;
        
        rtc = 'a';
        while (ldrives)
        {
                if (ldrives & 1)
                {
                        char root[8];
                        sprintf(root, "%c:", rtc);
                        if (QueryDosDevice(root, vol, sizeof vol) == 0)
                                print_error(root);
                        else if (strcmp(vol, volname) == 0)
                                break;
                }
                ldrives >>= 1;
                ++rtc;
        }
        
        if (ldrives)
                sprintf(apath, "%c:/", rtc);
        else
                rtc = 0;
        free(volname);
        return rtc;
}

/*
*  REPARSE_DATA_BUFFER has been moved to ntifs.h?, see
*  
http://msdn.microsoft.com/library/en-us/IFSK_r/hh/IFSK_r/fileinformationstructures_4f1b658e-1833-421f-a726-448b20b1c595.xml.asp
 
*/
#if !defined REPARSE_DATA_BUFFER_HEADER_SIZE
typedef struct _REPARSE_DATA_BUFFER {
  ULONG  ReparseTag;
  USHORT  ReparseDataLength;
  USHORT  Reserved;
  union {
    struct {
      USHORT  SubstituteNameOffset;
      USHORT  SubstituteNameLength;
      USHORT  PrintNameOffset;
      USHORT  PrintNameLength;
      WCHAR  PathBuffer[1];
      } SymbolicLinkReparseBuffer;
    struct {
      USHORT  SubstituteNameOffset;
      USHORT  SubstituteNameLength;
      USHORT  PrintNameOffset;
      USHORT  PrintNameLength;
      WCHAR  PathBuffer[1];
      } MountPointReparseBuffer;
    struct {
      UCHAR  DataBuffer[1];
    } GenericReparseBuffer;
  };
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
#endif
static int
check_junction(char *apath)
{
/*
*  http://msdn.microsoft.com/library/en-us/fileio/fs/reparse_points.asp
*
*  Reparse points and extended attributes are mutually exclusive.
*  The NTFS file system cannot create a reparse point when the
*  file contains extended attributes, and it cannot create extended
*  attributes on a file that contains a reparse point.
*  
*  Reparse point data, including the tag and optional GUID,
*  cannot exceed 16 kilobytes. Setting a reparse point fails
*  if the amount of data to be placed in the reparse point
*  exceeds this limit.
*/
        union reparse
        {
                REPARSE_GUID_DATA_BUFFER rgdb;
                REPARSE_DATA_BUFFER rdb;
                char data[GET_PATH_MAX + sizeof(REPARSE_DATA_BUFFER)];
        } buf;
        DWORD size;
        int rtc;
        HANDLE h = CreateFile(
                apath,                                 //  LPCTSTR lpFileName,
                STANDARD_RIGHTS_READ,//|FILE_READ_EA,  //  DWORD 
dwDesiredAccess,
                FILE_SHARE_READ,                       //  DWORD dwShareMode,
                NULL,           //  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                OPEN_EXISTING,                         //  DWORD 
dwCreationDisposition,
                FILE_ATTRIBUTE_NORMAL                  //  DWORD 
dwFlagsAndAttributes,
                | FILE_FLAG_BACKUP_SEMANTICS
                | FILE_FLAG_OPEN_REPARSE_POINT,
                NULL);                                 //  HANDLE hTemplateFile
        if (h == INVALID_HANDLE_VALUE)
        {
                print_error("no reparse: cannot open");
                return 0;
        }

        rtc = DeviceIoControl(
                        h,                                  //  HANDLE hDevice,
                        FSCTL_GET_REPARSE_POINT,            //  DWORD 
dwIoControlCode,
                        NULL,                               //  LPVOID 
lpInBuffer,
                        0,                                  //  DWORD 
nInBufferSize,
                        &buf,                               //  LPVOID 
lpOutBuffer,
                        sizeof buf,                         //  DWORD 
nOutBufferSize,
                        &size,                              //  LPDWORD 
lpBytesReturned,
                        NULL);                              //  LPOVERLAPPED 
lpOverlapped
        if (!rtc)
                print_error("cannot devio");

        CloseHandle(h);
        
        if (rtc &&
                size >= sizeof buf.rdb &&
                buf.rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
        {
                char pathname[GET_PATH_MAX];

// #if !defined(UNICODE)
                unsigned int nsize = WideCharToMultiByte(
                        CP_ACP,            // UINT CodePage: ANSI
                        0,                 // DWORD dwFlags
                        
(LPCWSTR)((char*)buf.rdb.MountPointReparseBuffer.PathBuffer
                                + 
buf.rdb.MountPointReparseBuffer.SubstituteNameOffset), // LPCWSTR in
                        buf.rdb.MountPointReparseBuffer.SubstituteNameLength /
                                sizeof(WCHAR),  // int cchWideChar,
                        pathname,          // LPSTR lpMultiByteStr, 
                        sizeof(pathname),  // int cbMultiByte,
                        NULL,              // LPCSTR lpDefaultChar,
                        NULL);             // LPBOOL lpUsedDefaultChar
                if (nsize == 0)
                {
                        print_error("cannot convert reparse");
                        rtc = 0;
                }
                else
                {
                        char *p = pathname;
                        if (nsize >= sizeof pathname)
                                nsize = sizeof pathname;
                        pathname[nsize] = 0;
                        if (strncmp(p, "\\??\\", 4) == 0 ||
                                strncmp(p, "\\\\?\\", 4) == 0)
                                        p += 4;
                        if (buf.rdb.MountPointReparseBuffer.PrintNameLength == 
0)
                                return check_volume(apath, p);

                        strcpy(apath, p);
                }
        }
        else
        {
                rtc = 0;
        }
        
        return rtc;
}

static int more_file_found(HANDLE h) /* helper for check_real_path below */
{
        WIN32_FIND_DATA buf;
        int rtc = FindNextFile(h, &buf);
        if (rtc == 0 && GetLastError() != ERROR_NO_MORE_FILES)
                rtc = 1;
        FindClose(h);
        return rtc;
}

static int
check_real_path(char *apath, char *component_start, int *check_real)
{
        WIN32_FIND_DATA buf;
        char *copy_name = buf.cFileName;
        HANDLE h = FindFirstFile(apath, &buf);
        int rtc = *check_real;
        if (h == INVALID_HANDLE_VALUE || more_file_found(h))
        {
                *check_real = 0;
                return 0;
        }
                
        if ((rtc & pof_junction) != 0 &&
                (buf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 &&
                buf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT)
                        return check_junction(apath);
        
        if ((rtc & pof_short) != 0 && buf.cAlternateFileName[0] != 0 &&
                ((rtc & pof_short_only_dir) == 0 ||
                        (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) 
&&
                ((rtc & pof_short_only_space) == 0 ||
                        strchr(component_start, ' ') != NULL))
                                copy_name = buf.cAlternateFileName;
        
        rtc = strlen(copy_name) != strlen(component_start);
        strcpy(component_start, copy_name);
        return rtc;
}

static char *
abspath (const char *name, char *apath, int check_real)
{
  char *dest;
  const char *start, *end, *apath_limit;
  unsigned long root_len = ROOT_LEN;

  if (name[0] == '\0' || apath == NULL)
    return NULL;

  apath_limit = apath + GET_PATH_MAX;
  
  /* It is unlikely we would make it until here but just to make sure. */
  if (!starting_directory)
    return NULL;

  if (!IS_ABSOLUTE_OR_HAS_DRIVE(name))
    {

      strcpy (apath, starting_directory);

#ifdef HAVE_DOS_PATHS
      if (IS_PATHSEP(name[0]))
        {
          /* We have /foo, an absolute file name except for the drive
             letter.  Assume the missing drive letter is the current
             drive, which we can get if we remove from starting_directory
             everything past the root directory.  */
          apath[root_len] = '\0';
        }
#endif

      dest = strchr (apath, '\0');
    }
  else
    {
      strncpy (apath, name, root_len);
      apath[root_len] = '\0';
      dest = apath + root_len;
      /* Get past the root in name, since we already copied it.  */
      name += root_len;
#ifdef HAVE_DOS_PATHS
      if (!IS_PATHSEP(apath[2]))
        {
          /* Convert d:foo into current-or-starting-directory/foo */
          int ddrive = apath[0] - starting_directory[0];
          if (ddrive == 0 || ddrive == 'A' -'a' || ddrive == 'a' - 'A')
            strcpy(apath, starting_directory);
          else
          {
            char current[8];
            strcpy(current, apath);
            current[2] = '.';
            if (GetFullPathName(current, GET_PATH_MAX, apath, &dest /* not used 
*/) == 0)
              return NULL;
          }
          dest = strchr(apath, '\0');
          /* addition above skipped one character too many.  */
          name--;
        }
#endif
    }

  for (start = end = name; *start != '\0'; start = end)
    {
      unsigned long len;

      /* Skip sequence of multiple path-separators.  */
      while (IS_PATHSEP(*start))
        ++start;

      /* Find end of path component.  */
      for (end = start; *end != '\0' && !IS_PATHSEP(*end); ++end)
        ;

      len = end - start;

      if (len == 0)
        break;
      else if (len == 1 && start[0] == '.')
        /* nothing */;
      else if (len == 2 && start[0] == '.' && start[1] == '.')
        {
          /* Back up to previous component, ignore if at root already.  */
          if (dest > apath + root_len)
            for (--dest; !IS_PATHSEP(dest[-1]); --dest);
        }
      else
        {
          char *component_start;
          if (!IS_PATHSEP(dest[-1]))
            *dest++ = '/';

          if (dest + len >= apath_limit)
            return NULL;

          component_start = dest;
          dest = memcpy (dest, start, len);
          dest += len;
          *dest = '\0';
          if (check_real && check_real_path(apath, component_start, 
&check_real))
            dest = strchr(apath, '\0');
        }
    }

  /* Unless it is root strip trailing separator.  */
  if (dest > apath + root_len && IS_PATHSEP(dest[-1]))
    --dest;

  *dest = '\0';

  return apath;
}

static void slashed(char *r, char to)
{
        char *s = r, from = to == '/'? '\\' : '/';
        if (r[0] == 0) return;
        if (r[1] == ':' && r[0] >= 'A' && r[0] <= 'Z')
                r[0] += 'c' - 'C';
        while ((s = strchr(s + 1, from)) != 0)
                *s = to;
}

int main(int argc, char *argv[])
{
        char apath[GET_PATH_MAX], *nu;
        int i = 1, check_real = 0;
        
        if (GetFullPathName(".", sizeof apath, apath, &nu))
                starting_directory = strdup(apath);
        
        if (1 < argc && argv[1][0] == '-' && argv[1][1] != 0)
        {
                char *opt = &argv[1][0], ch;
                while ((ch = *++opt) != 0)
                        switch (ch)
                        {
                                case 'j': check_real |= pof_junction; break;
                                case 's': check_real |= pof_short; break;
                                case 'S': check_real |= pof_short_only_space | 
pof_short; break;
                                case 'D': check_real |= pof_short_only_dir | 
pof_short; break;
                                case '\\': check_real |= pof_backslash; break;
                                default:
                                        fprintf(stderr, "%c ignored (valid: 
jsSD\\)\n", ch);
                                        break;
                        }
                i = 2;
        }
        
        for (; i < argc; ++i)
        {
                char *r = abspath(argv[i], apath, check_real);
                if (r)
                        slashed(r, (check_real & pof_backslash)? '\\' : '/');
                else r = "-- FAILED!! --";
                printf("%s ==> %s\n", argv[i], r);
        }
        return 0;
}

reply via email to

[Prev in Thread] Current Thread [Next in Thread]