newlib/winsup/utils/path.cc
Christopher Faylor f0136ac908 * cygcheck.cc (pathlike): New class.
(paths): Redefine as type pathlike.
(display_error): Declare a few different ways to allow more flexible usage
throughout.
(display_error_fmt): New function for those cases where C++ just isn't enough.
(add_path): Rewrite to allocate pathlike paths.  Always tack on a trailing
slash since that's what everyone who uses it wants.  NULL terminate the path
list.  Register "it's a system path" field.
(init_path): Call add_path with info regarding whether path is a system path or
not.
(pathlike::check_existence): Move into pathlike class.  Accept file and
extension arguments to build up path on the fly.  Modify other arguments.
(find_on_path): Constify return value and appropriate arguments.  Eliminate
short-circuit for fully-qualified paths.  Simplify loop which iterates over
path.
(already_did): Constify argument.
(track_down): Ditto.  Regularize some error messages.
(find_app_on_path): Ditto.
(cygcheck): Constify argument.  Use 20th century string handling functions.
(dump_sysinfo): Remove odd inclusion of '\\bin' "Just in case".  Accommodate
change of paths to pathlike.
* path.cc (isslash): Rename from SLASH_P and use throughout.
(rel_vconcat): Front-end to vconcat which prepends cwd to path before passing
along for conversion to native windows.
(cygpath): Remove "./" test.  Call rel_vconcat if filename appears to be
relative.
2007-06-04 01:57:16 +00:00

595 lines
14 KiB
C++

/* path.cc
Copyright 2001, 2002, 2003, 2005, 2006, 2007 Red Hat, Inc.
This file is part of Cygwin.
This software is a copyrighted work licensed under the terms of the
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
details. */
/* The purpose of this file is to hide all the details about accessing
Cygwin's mount table, shortcuts, etc. If the format or location of
the mount table, or the shortcut format changes, this is the file to
change to match it. */
#define str(a) #a
#define scat(a,b) str(a##b)
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "path.h"
#include "cygwin/include/cygwin/version.h"
#include "cygwin/include/sys/mount.h"
#include "cygwin/include/mntent.h"
/* Used when treating / and \ as equivalent. */
#define isslash(ch) \
({ \
char __c = (ch); \
((__c) == '/' || (__c) == '\\'); \
})
static const GUID GUID_shortcut
= { 0x00021401L, 0, 0, 0xc0, 0, 0, 0, 0, 0, 0, 0x46 };
enum {
WSH_FLAG_IDLIST = 0x01, /* Contains an ITEMIDLIST. */
WSH_FLAG_FILE = 0x02, /* Contains a file locator element. */
WSH_FLAG_DESC = 0x04, /* Contains a description. */
WSH_FLAG_RELPATH = 0x08, /* Contains a relative path. */
WSH_FLAG_WD = 0x10, /* Contains a working dir. */
WSH_FLAG_CMDLINE = 0x20, /* Contains command line args. */
WSH_FLAG_ICON = 0x40 /* Contains a custom icon. */
};
struct win_shortcut_hdr
{
DWORD size; /* Header size in bytes. Must contain 0x4c. */
GUID magic; /* GUID of shortcut files. */
DWORD flags; /* Content flags. See above. */
/* The next fields from attr to icon_no are always set to 0 in Cygwin
and U/Win shortcuts. */
DWORD attr; /* Target file attributes. */
FILETIME ctime; /* These filetime items are never touched by the */
FILETIME mtime; /* system, apparently. Values don't matter. */
FILETIME atime;
DWORD filesize; /* Target filesize. */
DWORD icon_no; /* Icon number. */
DWORD run; /* Values defined in winuser.h. Use SW_NORMAL. */
DWORD hotkey; /* Hotkey value. Set to 0. */
DWORD dummy[2]; /* Future extension probably. Always 0. */
};
static bool
cmp_shortcut_header (win_shortcut_hdr *file_header)
{
/* A Cygwin or U/Win shortcut only contains a description and a relpath.
Cygwin shortcuts also might contain an ITEMIDLIST. The run type is
always set to SW_NORMAL. */
return file_header->size == sizeof (win_shortcut_hdr)
&& !memcmp (&file_header->magic, &GUID_shortcut, sizeof GUID_shortcut)
&& (file_header->flags & ~WSH_FLAG_IDLIST)
== (WSH_FLAG_DESC | WSH_FLAG_RELPATH)
&& file_header->run == SW_NORMAL;
}
int
get_word (HANDLE fh, int offset)
{
unsigned short rv;
unsigned r;
SetLastError(NO_ERROR);
if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
&& GetLastError () != NO_ERROR)
return -1;
if (!ReadFile (fh, &rv, 2, (DWORD *) &r, 0))
return -1;
return rv;
}
/*
* Check the value of GetLastError() to find out whether there was an error.
*/
int
get_dword (HANDLE fh, int offset)
{
int rv;
unsigned r;
SetLastError(NO_ERROR);
if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
&& GetLastError () != NO_ERROR)
return -1;
if (!ReadFile (fh, &rv, 4, (DWORD *) &r, 0))
return -1;
return rv;
}
#define EXE_MAGIC ((int)*(unsigned short *)"MZ")
#define SHORTCUT_MAGIC ((int)*(unsigned short *)"L\0")
#define SYMLINK_COOKIE "!<symlink>"
#define SYMLINK_MAGIC ((int)*(unsigned short *)SYMLINK_COOKIE)
bool
is_exe (HANDLE fh)
{
int magic = get_word (fh, 0x0);
return magic == EXE_MAGIC;
}
bool
is_symlink (HANDLE fh)
{
int magic = get_word (fh, 0x0);
if (magic != SHORTCUT_MAGIC && magic != SYMLINK_MAGIC)
return false;
DWORD got;
BY_HANDLE_FILE_INFORMATION local;
if (!GetFileInformationByHandle (fh, &local))
return false;
if (magic == SHORTCUT_MAGIC)
{
DWORD size;
if (!local.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
return false; /* Not a Cygwin symlink. */
if ((size = GetFileSize (fh, NULL)) > 8192)
return false; /* Not a Cygwin symlink. */
char buf[size];
SetFilePointer (fh, 0, 0, FILE_BEGIN);
if (!ReadFile (fh, buf, size, &got, 0))
return false;
if (got != size || !cmp_shortcut_header ((win_shortcut_hdr *) buf))
return false; /* Not a Cygwin symlink. */
/* TODO: check for invalid path contents
(see symlink_info::check() in ../cygwin/path.cc) */
}
else /* magic == SYMLINK_MAGIC */
{
if (!local.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
return false; /* Not a Cygwin symlink. */
char buf[sizeof (SYMLINK_COOKIE) - 1];
SetFilePointer (fh, 0, 0, FILE_BEGIN);
if (!ReadFile (fh, buf, sizeof (buf), &got, 0))
return false;
if (got != sizeof (buf) ||
memcmp (buf, SYMLINK_COOKIE, sizeof (buf)) != 0)
return false; /* Not a Cygwin symlink. */
}
return true;
}
/* Assumes is_symlink(fh) is true */
bool
readlink (HANDLE fh, char *path, int maxlen)
{
int got;
int magic = get_word (fh, 0x0);
if (magic == SHORTCUT_MAGIC)
{
int offset = get_word (fh, 0x4c);
int slen = get_word (fh, 0x4c + offset + 2);
if (slen >= maxlen)
{
SetLastError (ERROR_FILENAME_EXCED_RANGE);
return false;
}
if (SetFilePointer (fh, 0x4c + offset + 4, 0, FILE_BEGIN) ==
INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR)
return false;
if (!ReadFile (fh, path, slen, (DWORD *) &got, 0))
return false;
else if (got < slen)
{
SetLastError (ERROR_READ_FAULT);
return false;
}
else
path[got] = '\0';
}
else if (magic == SYMLINK_MAGIC)
{
char cookie_buf[sizeof (SYMLINK_COOKIE) - 1];
if (SetFilePointer (fh, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
&& GetLastError () != NO_ERROR)
return false;
if (!ReadFile (fh, cookie_buf, sizeof (cookie_buf), (DWORD *) &got, 0))
return false;
else if (got == sizeof (cookie_buf)
&& memcmp (cookie_buf, SYMLINK_COOKIE, sizeof (cookie_buf)) == 0)
{
if (!ReadFile (fh, path, maxlen, (DWORD *) &got, 0))
return false;
else if (got >= maxlen)
{
SetLastError (ERROR_FILENAME_EXCED_RANGE);
path[0] = '\0';
return false;
}
else
path[got] = '\0';
}
}
else
return false;
return true;
}
static struct mnt
{
const char *native;
char *posix;
unsigned flags;
int issys;
} mount_table[255];
struct mnt *root_here = NULL;
static char *
find2 (HKEY rkey, unsigned *flags, char *what)
{
char *retval = 0;
DWORD retvallen = 0;
DWORD type;
HKEY key;
if (RegOpenKeyEx (rkey, what, 0, KEY_READ, &key) != ERROR_SUCCESS)
return 0;
if (RegQueryValueEx (key, "native", 0, &type, 0, &retvallen)
== ERROR_SUCCESS)
{
retval = (char *) malloc (MAX_PATH + 1);
if (RegQueryValueEx (key, "native", 0, &type, (BYTE *) retval, &retvallen)
!= ERROR_SUCCESS)
{
free (retval);
retval = 0;
}
}
retvallen = sizeof (flags);
RegQueryValueEx (key, "flags", 0, &type, (BYTE *)flags, &retvallen);
RegCloseKey (key);
return retval;
}
static LONG
get_cygdrive0 (HKEY key, const char *what, void *val, DWORD len)
{
LONG status = RegQueryValueEx (key, what, 0, 0, (BYTE *)val, &len);
return status;
}
static mnt *
get_cygdrive (HKEY key, mnt *m, int issystem)
{
if (get_cygdrive0 (key, CYGWIN_INFO_CYGDRIVE_FLAGS, &m->flags,
sizeof (m->flags)) != ERROR_SUCCESS) {
free (m->posix);
return m;
}
get_cygdrive0 (key, CYGWIN_INFO_CYGDRIVE_PREFIX, m->posix, MAX_PATH);
m->native = strdup (".");
m->issys = issystem;
return m + 1;
}
static void
read_mounts ()
{
DWORD posix_path_size;
int res;
struct mnt *m = mount_table;
DWORD disposition;
char buf[10000];
root_here = NULL;
for (mnt *m1 = mount_table; m1->posix; m1++)
{
free (m1->posix);
if (m1->native)
free ((char *) m1->native);
m1->posix = NULL;
}
/* Loop through subkeys */
/* FIXME: we would like to not check MAX_MOUNTS but the heap in the
shared area is currently statically allocated so we can't have an
arbitrarily large number of mounts. */
for (int issystem = 0; issystem <= 1; issystem++)
{
sprintf (buf, "Software\\%s\\%s\\%s",
CYGWIN_INFO_CYGNUS_REGISTRY_NAME,
CYGWIN_INFO_CYGWIN_REGISTRY_NAME,
CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME);
HKEY key = issystem ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
if (RegCreateKeyEx (key, buf, 0, (LPTSTR) "Cygwin", 0, KEY_READ,
0, &key, &disposition) != ERROR_SUCCESS)
break;
for (int i = 0; ;i++, m++)
{
m->posix = (char *) malloc (MAX_PATH + 1);
posix_path_size = MAX_PATH;
/* FIXME: if maximum posix_path_size is 256, we're going to
run into problems if we ever try to store a mount point that's
over 256 but is under MAX_PATH. */
res = RegEnumKeyEx (key, i, m->posix, &posix_path_size, NULL,
NULL, NULL, NULL);
if (res == ERROR_NO_MORE_ITEMS)
{
m = get_cygdrive (key, m, issystem);
m->posix = NULL;
break;
}
if (!*m->posix)
goto no_go;
else if (res != ERROR_SUCCESS)
break;
else
{
m->native = find2 (key, &m->flags, m->posix);
m->issys = issystem;
if (!m->native)
goto no_go;
}
continue;
no_go:
free (m->posix);
m->posix = NULL;
m--;
}
RegCloseKey (key);
}
}
/* Return non-zero if PATH1 is a prefix of PATH2.
Both are assumed to be of the same path style and / vs \ usage.
Neither may be "".
LEN1 = strlen (PATH1). It's passed because often it's already known.
Examples:
/foo/ is a prefix of /foo <-- may seem odd, but desired
/foo is a prefix of /foo/
/ is a prefix of /foo/bar
/ is not a prefix of foo/bar
foo/ is a prefix foo/bar
/foo is not a prefix of /foobar
*/
static int
path_prefix_p (const char *path1, const char *path2, int len1)
{
/* Handle case where PATH1 has trailing '/' and when it doesn't. */
if (len1 > 0 && isslash (path1[len1 - 1]))
len1--;
if (len1 == 0)
return isslash (path2[0]) && !isslash (path2[1]);
if (strncasecmp (path1, path2, len1) != 0)
return 0;
return isslash (path2[len1]) || path2[len1] == 0 || path1[len1 - 1] == ':';
}
static char *
vconcat (const char *s, va_list v)
{
int len;
char *rv, *arg;
va_list save_v = v;
int unc;
if (!s)
return 0;
len = strlen (s);
unc = isslash (*s) && isslash (s[1]);
while (1)
{
arg = va_arg (v, char *);
if (arg == 0)
break;
len += strlen (arg);
}
va_end (v);
rv = (char *) malloc (len + 1);
strcpy (rv, s);
v = save_v;
while (1)
{
arg = va_arg (v, char *);
if (arg == 0)
break;
strcat (rv, arg);
}
va_end (v);
char *d, *p;
/* concat is only used for urls and files, so we can safely
canonicalize the results */
for (p = d = rv; *p; p++)
{
*d++ = *p;
/* special case for URLs */
if (*p == ':' && p[1] == '/' && p[2] == '/' && p > rv + 1)
{
*d++ = *++p;
*d++ = *++p;
}
else if (*p == '/' || *p == '\\')
{
if (p == rv && unc)
*d++ = *p++;
while (p[1] == '/')
p++;
}
}
*d = 0;
return rv;
}
static char *
concat (const char *s, ...)
{
va_list v;
va_start (v, s);
return vconcat (s, v);
}
static char *
rel_vconcat (const char *s, va_list v)
{
char path[MAX_PATH + 1];
if (!GetCurrentDirectory (MAX_PATH, path))
return NULL;
int max_len = -1;
struct mnt *m, *match = NULL;
if (s[0] == '.' && isslash (s[1]))
s += 2;
for (m = mount_table; m->posix ; m++)
{
if (m->flags & MOUNT_CYGDRIVE)
continue;
int n = strlen (m->native);
if (n < max_len || !path_prefix_p (m->native, path, n))
continue;
max_len = n;
match = m;
}
if (match)
strcpy (path, match->posix);
if (!isslash (strchr (path, '\0')[-1]))
strcat (path, "/");
char *temppath = concat (path, s, NULL);
char *res = vconcat (temppath, v);
free (temppath);
return res;
}
char *
cygpath (const char *s, ...)
{
va_list v;
int max_len = -1;
struct mnt *m, *match = NULL;
if (!mount_table[0].posix)
read_mounts ();
va_start (v, s);
char *path;
if (s[0] == '/' || s[1] == ':') /* FIXME: too crude? */
path = vconcat (s, v);
else
path = rel_vconcat (s, v);
if (!path)
return NULL;
if (strncmp (path, "/./", 3) == 0)
memmove (path + 1, path + 3, strlen (path + 3) + 1);
for (m = mount_table; m->posix ; m++)
{
if (m->flags & MOUNT_CYGDRIVE)
continue;
int n = strlen (m->posix);
if (n < max_len || !path_prefix_p (m->posix, path, n))
continue;
max_len = n;
match = m;
}
char *native;
if (match == NULL)
native = strdup (path);
else if (max_len == (int) strlen (path))
native = strdup (match->native);
else
native = concat (match->native, "\\", path + max_len, NULL);
free (path);
return native;
}
static mnt *m = NULL;
extern "C" FILE *
setmntent (const char *, const char *)
{
m = mount_table;
if (!m->posix)
read_mounts ();
return NULL;
}
extern "C" struct mntent *
getmntent (FILE *)
{
static mntent mnt;
if (!m->posix)
return NULL;
mnt.mnt_fsname = (char *) m->native;
mnt.mnt_dir = (char *) m->posix;
if (!mnt.mnt_type)
mnt.mnt_type = (char *) malloc (1024);
if (!mnt.mnt_opts)
mnt.mnt_opts = (char *) malloc (1024);
if (!m->issys)
strcpy (mnt.mnt_type, (char *) "user");
else
strcpy (mnt.mnt_type, (char *) "system");
if (!(m->flags & MOUNT_BINARY))
strcpy (mnt.mnt_opts, (char *) "textmode");
else
strcpy (mnt.mnt_opts, (char *) "binmode");
if (m->flags & MOUNT_CYGWIN_EXEC)
strcat (mnt.mnt_opts, (char *) ",cygexec");
else if (m->flags & MOUNT_EXEC)
strcat (mnt.mnt_opts, (char *) ",exec");
else if (m->flags & MOUNT_NOTEXEC)
strcat (mnt.mnt_opts, (char *) ",noexec");
if (m->flags & MOUNT_ENC)
strcat (mnt.mnt_opts, ",managed");
if ((m->flags & MOUNT_CYGDRIVE)) /* cygdrive */
strcat (mnt.mnt_opts, (char *) ",cygdrive");
mnt.mnt_freq = 1;
mnt.mnt_passno = 1;
m++;
return &mnt;
}