860 lines
21 KiB
C++
860 lines
21 KiB
C++
/* path.cc
|
|
|
|
Copyright 2001, 2002, 2003, 2005, 2006, 2007, 2008 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 <lmcons.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <malloc.h>
|
|
#include <wchar.h>
|
|
#include "path.h"
|
|
#include "cygwin/include/cygwin/version.h"
|
|
#include "cygwin/include/sys/mount.h"
|
|
#include "cygwin/include/mntent.h"
|
|
#include "testsuite.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)
|
|
{
|
|
DWORD rv;
|
|
char *buf, *cp;
|
|
unsigned short len;
|
|
win_shortcut_hdr *file_header;
|
|
BY_HANDLE_FILE_INFORMATION fi;
|
|
|
|
if (!GetFileInformationByHandle (fh, &fi)
|
|
|| fi.nFileSizeHigh != 0
|
|
|| fi.nFileSizeLow > 8192)
|
|
return false;
|
|
|
|
buf = (char *) alloca (fi.nFileSizeLow + 1);
|
|
file_header = (win_shortcut_hdr *) buf;
|
|
|
|
if (SetFilePointer (fh, 0L, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER
|
|
|| !ReadFile (fh, buf, fi.nFileSizeLow, &rv, NULL)
|
|
|| rv != fi.nFileSizeLow)
|
|
return false;
|
|
|
|
if (fi.nFileSizeLow > sizeof (file_header)
|
|
&& cmp_shortcut_header (file_header))
|
|
{
|
|
cp = buf + sizeof (win_shortcut_hdr);
|
|
if (file_header->flags & WSH_FLAG_IDLIST) /* Skip ITEMIDLIST */
|
|
cp += *(unsigned short *) cp + 2;
|
|
if (!(len = *(unsigned short *) cp))
|
|
return false;
|
|
cp += 2;
|
|
/* Has appended full path? If so, use it instead of description. */
|
|
unsigned short relpath_len = *(unsigned short *) (cp + len);
|
|
if (cp + len + 2 + relpath_len < buf + fi.nFileSizeLow)
|
|
{
|
|
cp += len + 2 + relpath_len;
|
|
len = *(unsigned short *) cp;
|
|
cp += 2;
|
|
}
|
|
if (len + 1 > maxlen)
|
|
return false;
|
|
memcpy (path, cp, len);
|
|
path[len] = '\0';
|
|
return true;
|
|
}
|
|
else if (strncmp (buf, SYMLINK_COOKIE, strlen (SYMLINK_COOKIE)) == 0
|
|
&& fi.nFileSizeLow - strlen (SYMLINK_COOKIE) <= (unsigned) maxlen
|
|
&& buf[fi.nFileSizeLow - 1] == '\0')
|
|
{
|
|
strcpy (path, &buf[strlen (SYMLINK_COOKIE)]);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
struct mnt_t
|
|
{
|
|
char *native;
|
|
char *posix;
|
|
unsigned flags;
|
|
};
|
|
|
|
#ifndef TESTSUITE
|
|
static mnt_t mount_table[255];
|
|
static int max_mount_entry;
|
|
#else
|
|
# define TESTSUITE_MOUNT_TABLE
|
|
# include "testsuite.h"
|
|
# undef TESTSUITE_MOUNT_TABLE
|
|
#endif
|
|
|
|
mnt_t *root_here = NULL;
|
|
|
|
inline void
|
|
unconvert_slashes (char* name)
|
|
{
|
|
while ((name = strchr (name, '/')) != NULL)
|
|
*name++ = '\\';
|
|
}
|
|
|
|
/* These functions aren't called when defined(TESTSUITE) which results
|
|
in a compiler warning. */
|
|
#ifndef TESTSUITE
|
|
inline char *
|
|
skip_ws (char *in)
|
|
{
|
|
while (*in == ' ' || *in == '\t')
|
|
++in;
|
|
return in;
|
|
}
|
|
|
|
inline char *
|
|
find_ws (char *in)
|
|
{
|
|
while (*in && *in != ' ' && *in != '\t')
|
|
++in;
|
|
return in;
|
|
}
|
|
|
|
inline char *
|
|
conv_fstab_spaces (char *field)
|
|
{
|
|
register char *sp = field;
|
|
while ((sp = strstr (sp, "\\040")) != NULL)
|
|
{
|
|
*sp++ = ' ';
|
|
memmove (sp, sp + 3, strlen (sp + 3) + 1);
|
|
}
|
|
return field;
|
|
}
|
|
|
|
static struct opt
|
|
{
|
|
const char *name;
|
|
unsigned val;
|
|
bool clear;
|
|
} oopts[] =
|
|
{
|
|
{"user", MOUNT_SYSTEM, 1},
|
|
{"nouser", MOUNT_SYSTEM, 0},
|
|
{"binary", MOUNT_BINARY, 0},
|
|
{"text", MOUNT_BINARY, 1},
|
|
{"exec", MOUNT_EXEC, 0},
|
|
{"notexec", MOUNT_NOTEXEC, 0},
|
|
{"cygexec", MOUNT_CYGWIN_EXEC, 0},
|
|
{"nosuid", 0, 0},
|
|
{"acl", MOUNT_NOACL, 1},
|
|
{"noacl", MOUNT_NOACL, 0},
|
|
{"posix=1", MOUNT_NOPOSIX, 1},
|
|
{"posix=0", MOUNT_NOPOSIX, 0}
|
|
};
|
|
|
|
static bool
|
|
read_flags (char *options, unsigned &flags)
|
|
{
|
|
while (*options)
|
|
{
|
|
char *p = strchr (options, ',');
|
|
if (p)
|
|
*p++ = '\0';
|
|
else
|
|
p = strchr (options, '\0');
|
|
|
|
for (opt *o = oopts;
|
|
o < (oopts + (sizeof (oopts) / sizeof (oopts[0])));
|
|
o++)
|
|
if (strcmp (options, o->name) == 0)
|
|
{
|
|
if (o->clear)
|
|
flags &= ~o->val;
|
|
else
|
|
flags |= o->val;
|
|
goto gotit;
|
|
}
|
|
return false;
|
|
|
|
gotit:
|
|
options = p;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
from_fstab_line (mnt_t *m, char *line, bool user)
|
|
{
|
|
char *native_path, *posix_path, *fs_type;
|
|
|
|
/* First field: Native path. */
|
|
char *c = skip_ws (line);
|
|
if (!*c || *c == '#')
|
|
return false;
|
|
char *cend = find_ws (c);
|
|
*cend = '\0';
|
|
native_path = conv_fstab_spaces (c);
|
|
/* Second field: POSIX path. */
|
|
c = skip_ws (cend + 1);
|
|
if (!*c)
|
|
return false;
|
|
cend = find_ws (c);
|
|
*cend = '\0';
|
|
posix_path = conv_fstab_spaces (c);
|
|
/* Third field: FS type. */
|
|
c = skip_ws (cend + 1);
|
|
if (!*c)
|
|
return false;
|
|
cend = find_ws (c);
|
|
*cend = '\0';
|
|
fs_type = c;
|
|
/* Forth field: Flags. */
|
|
c = skip_ws (cend + 1);
|
|
if (!*c)
|
|
return false;
|
|
cend = find_ws (c);
|
|
*cend = '\0';
|
|
unsigned mount_flags = MOUNT_SYSTEM;
|
|
if (!read_flags (c, mount_flags))
|
|
return false;
|
|
if (user)
|
|
mount_flags &= ~MOUNT_SYSTEM;
|
|
if (!strcmp (fs_type, "cygdrive"))
|
|
{
|
|
for (mnt_t *sm = mount_table; sm < m; ++sm)
|
|
if (sm->flags & MOUNT_CYGDRIVE)
|
|
{
|
|
if ((mount_flags & MOUNT_SYSTEM) || !(sm->flags & MOUNT_SYSTEM))
|
|
{
|
|
if (sm->posix)
|
|
free (sm->posix);
|
|
sm->posix = strdup (posix_path);
|
|
sm->flags = mount_flags | MOUNT_CYGDRIVE;
|
|
}
|
|
return false;
|
|
}
|
|
m->posix = strdup (posix_path);
|
|
m->native = strdup (".");
|
|
m->flags = mount_flags | MOUNT_CYGDRIVE;
|
|
}
|
|
else
|
|
{
|
|
for (mnt_t *sm = mount_table; sm < m; ++sm)
|
|
if (!strcasecmp (sm->posix, posix_path))
|
|
{
|
|
if ((mount_flags & MOUNT_SYSTEM) || !(sm->flags & MOUNT_SYSTEM))
|
|
{
|
|
if (sm->native)
|
|
free (sm->native);
|
|
sm->native = strdup (native_path);
|
|
sm->flags = mount_flags;
|
|
}
|
|
return false;
|
|
}
|
|
m->posix = strdup (posix_path);
|
|
unconvert_slashes (native_path);
|
|
m->native = strdup (native_path);
|
|
m->flags = mount_flags;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define BUFSIZE 65536
|
|
|
|
static char *
|
|
get_user ()
|
|
{
|
|
static char user[UNLEN + 1];
|
|
char *userenv;
|
|
|
|
user[0] = '\0';
|
|
if ((userenv = getenv ("USER")) || (userenv = getenv ("USERNAME")))
|
|
strncat (user, userenv, UNLEN);
|
|
return user;
|
|
}
|
|
|
|
void
|
|
from_fstab (bool user, PWCHAR path, PWCHAR path_end)
|
|
{
|
|
mnt_t *m = mount_table + max_mount_entry;
|
|
char buf[BUFSIZE];
|
|
|
|
if (!user)
|
|
{
|
|
/* Create a default root dir from path. */
|
|
WideCharToMultiByte (GetACP (), 0, path, -1, buf, BUFSIZE,
|
|
NULL, NULL);
|
|
unconvert_slashes (buf);
|
|
char *native_path = buf;
|
|
if (!strncmp (native_path, "\\\\?\\", 4))
|
|
native_path += 4;
|
|
if (!strncmp (native_path, "UNC\\", 4))
|
|
*(native_path += 2) = '\\';
|
|
m->posix = strdup ("/");
|
|
m->native = strdup (native_path);
|
|
m->flags = MOUNT_SYSTEM | MOUNT_BINARY;
|
|
++m;
|
|
/* Create a default cygdrive entry. Note that this is a user entry.
|
|
This allows to override it with mount, unless the sysadmin created
|
|
a cygdrive entry in /etc/fstab. */
|
|
m->posix = strdup (CYGWIN_INFO_CYGDRIVE_DEFAULT_PREFIX);
|
|
m->native = strdup (".");
|
|
m->flags = MOUNT_BINARY | MOUNT_CYGDRIVE;
|
|
++m;
|
|
max_mount_entry = m - mount_table;
|
|
}
|
|
|
|
PWCHAR u = wcscpy (path_end, L"\\etc\\fstab") + 10;
|
|
if (user)
|
|
MultiByteToWideChar (GetACP (), 0, get_user (), -1,
|
|
wcscpy (u, L".d\\") + 3, BUFSIZE - (u - path));
|
|
HANDLE h = CreateFileW (path, GENERIC_READ, FILE_SHARE_READ, NULL,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
return;
|
|
char *got = buf;
|
|
DWORD len = 0;
|
|
/* Using BUFSIZE-1 leaves space to append two \0. */
|
|
while (ReadFile (h, got, BUFSIZE - 1 - (got - buf),
|
|
&len, NULL))
|
|
{
|
|
char *end;
|
|
|
|
/* Set end marker. */
|
|
got[len] = got[len + 1] = '\0';
|
|
/* Set len to the absolute len of bytes in buf. */
|
|
len += got - buf;
|
|
/* Reset got to start reading at the start of the buffer again. */
|
|
got = buf;
|
|
while (got < buf + len && (end = strchr (got, '\n')))
|
|
{
|
|
end[end[-1] == '\r' ? -1 : 0] = '\0';
|
|
if (from_fstab_line (m, got, user))
|
|
++m;
|
|
got = end + 1;
|
|
}
|
|
if (len < BUFSIZE - 1)
|
|
break;
|
|
/* We have to read once more. Move remaining bytes to the start of
|
|
the buffer and reposition got so that it points to the end of
|
|
the remaining bytes. */
|
|
len = buf + len - got;
|
|
memmove (buf, got, len);
|
|
got = buf + len;
|
|
buf[len] = buf[len + 1] = '\0';
|
|
}
|
|
if (got > buf && from_fstab_line (m, got, user))
|
|
++m;
|
|
max_mount_entry = m - mount_table;
|
|
CloseHandle (h);
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
mnt_sort (const void *a, const void *b)
|
|
{
|
|
const mnt_t *ma = (const mnt_t *) a;
|
|
const mnt_t *mb = (const mnt_t *) b;
|
|
int ret;
|
|
|
|
ret = (ma->flags & MOUNT_CYGDRIVE) - (mb->flags & MOUNT_CYGDRIVE);
|
|
if (ret)
|
|
return ret;
|
|
ret = (ma->flags & MOUNT_SYSTEM) - (mb->flags & MOUNT_SYSTEM);
|
|
if (ret)
|
|
return ret;
|
|
return strcmp (ma->posix, mb->posix);
|
|
}
|
|
|
|
static void
|
|
read_mounts ()
|
|
{
|
|
/* If TESTSUITE is defined, bypass this whole function as a harness
|
|
mount table will be provided. */
|
|
#ifndef TESTSUITE
|
|
HKEY setup_key;
|
|
LONG ret;
|
|
DWORD len;
|
|
WCHAR path[32768];
|
|
PWCHAR path_end;
|
|
|
|
for (mnt_t *m1 = mount_table; m1->posix; m1++)
|
|
{
|
|
free (m1->posix);
|
|
if (m1->native)
|
|
free ((char *) m1->native);
|
|
m1->posix = NULL;
|
|
}
|
|
max_mount_entry = 0;
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
if ((ret = RegOpenKeyExW (i ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
|
|
L"Software\\Cygwin\\setup", 0,
|
|
KEY_READ, &setup_key)) == ERROR_SUCCESS)
|
|
{
|
|
len = 32768 * sizeof (WCHAR);
|
|
ret = RegQueryValueExW (setup_key, L"rootdir", NULL, NULL,
|
|
(PBYTE) path, &len);
|
|
RegCloseKey (setup_key);
|
|
if (ret == ERROR_SUCCESS)
|
|
break;
|
|
}
|
|
if (ret == ERROR_SUCCESS)
|
|
path_end = wcschr (path, L'\0');
|
|
else
|
|
{
|
|
if (!GetModuleFileNameW (NULL, path, 32768))
|
|
return;
|
|
path_end = wcsrchr (path, L'\\');
|
|
if (path_end)
|
|
{
|
|
*path_end = L'\0';
|
|
path_end = wcsrchr (path, L'\\');
|
|
}
|
|
}
|
|
if (!path_end)
|
|
return;
|
|
*path_end = L'\0';
|
|
|
|
from_fstab (false, path, path_end);
|
|
from_fstab (true, path, path_end);
|
|
qsort (mount_table, max_mount_entry, sizeof (mnt_t), mnt_sort);
|
|
#endif /* !defined(TESTSUITE) */
|
|
}
|
|
|
|
/* 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 (isslash (*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);
|
|
}
|
|
|
|
/* This is a helper function for when vcygpath is passed what appears
|
|
to be a relative POSIX path. We take a Win32 CWD (either as specified
|
|
in 'cwd' or as retrieved with GetCurrentDirectory() if 'cwd' is NULL)
|
|
and find the mount table entry with the longest match. We replace the
|
|
matching portion with the corresponding POSIX prefix, and to that append
|
|
's' and anything in 'v'. The returned result is a mostly-POSIX
|
|
absolute path -- 'mostly' because the portions of CWD that didn't
|
|
match the mount prefix will still have '\\' separators. */
|
|
static char *
|
|
rel_vconcat (const char *cwd, const char *s, va_list v)
|
|
{
|
|
char pathbuf[MAX_PATH];
|
|
if (!cwd || *cwd == '\0')
|
|
{
|
|
if (!GetCurrentDirectory (MAX_PATH, pathbuf))
|
|
return NULL;
|
|
cwd = pathbuf;
|
|
}
|
|
|
|
int max_len = -1;
|
|
mnt_t *m, *match = NULL;
|
|
|
|
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, cwd, n))
|
|
continue;
|
|
max_len = n;
|
|
match = m;
|
|
}
|
|
|
|
char *temppath;
|
|
if (!match)
|
|
// No prefix matched - best effort to return meaningful value.
|
|
temppath = concat (cwd, "/", s, NULL);
|
|
else if (strcmp (match->posix, "/") != 0)
|
|
// Matched on non-root. Copy matching prefix + remaining 'path'.
|
|
temppath = concat (match->posix, cwd + max_len, "/", s, NULL);
|
|
else if (cwd[max_len] == '\0')
|
|
// Matched on root and there's no remaining 'path'.
|
|
temppath = concat ("/", s, NULL);
|
|
else if (isslash (cwd[max_len]))
|
|
// Matched on root but remaining 'path' starts with a slash anyway.
|
|
temppath = concat (cwd + max_len, "/", s, NULL);
|
|
else
|
|
temppath = concat ("/", cwd + max_len, "/", s, NULL);
|
|
|
|
char *res = vconcat (temppath, v);
|
|
free (temppath);
|
|
return res;
|
|
}
|
|
|
|
/* Convert a POSIX path in 's' to an absolute Win32 path, and append
|
|
anything in 'v' to the end, returning the result. If 's' is a
|
|
relative path then 'cwd' is used as the working directory to make
|
|
it absolute. Pass NULL in 'cwd' to use GetCurrentDirectory. */
|
|
static char *
|
|
vcygpath (const char *cwd, const char *s, va_list v)
|
|
{
|
|
int max_len = -1;
|
|
mnt_t *m, *match = NULL;
|
|
|
|
if (!max_mount_entry)
|
|
read_mounts ();
|
|
char *path;
|
|
if (s[0] == '.' && isslash (s[1]))
|
|
s += 2;
|
|
|
|
if (s[0] == '/' || s[1] == ':') /* FIXME: too crude? */
|
|
path = vconcat (s, v);
|
|
else
|
|
path = rel_vconcat (cwd, 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 if (isslash (path[max_len]))
|
|
native = concat (match->native, path + max_len, NULL);
|
|
else
|
|
native = concat (match->native, "\\", path + max_len, NULL);
|
|
free (path);
|
|
|
|
unconvert_slashes (native);
|
|
for (char *s = strstr (native + 1, "\\.\\"); s && *s; s = strstr (s, "\\.\\"))
|
|
memmove (s + 1, s + 3, strlen (s + 3) + 1);
|
|
return native;
|
|
}
|
|
|
|
char *
|
|
cygpath_rel (const char *cwd, const char *s, ...)
|
|
{
|
|
va_list v;
|
|
|
|
va_start (v, s);
|
|
|
|
return vcygpath (cwd, s, v);
|
|
}
|
|
|
|
char *
|
|
cygpath (const char *s, ...)
|
|
{
|
|
va_list v;
|
|
|
|
va_start (v, s);
|
|
|
|
return vcygpath (NULL, s, v);
|
|
}
|
|
|
|
static mnt_t *m = NULL;
|
|
|
|
extern "C" FILE *
|
|
setmntent (const char *, const char *)
|
|
{
|
|
m = mount_table;
|
|
if (!max_mount_entry)
|
|
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->flags & MOUNT_SYSTEM)
|
|
strcpy (mnt.mnt_type, (char *) "system");
|
|
else
|
|
strcpy (mnt.mnt_type, (char *) "user");
|
|
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_CYGDRIVE)) /* cygdrive */
|
|
strcat (mnt.mnt_opts, (char *) ",cygdrive");
|
|
if ((m->flags & MOUNT_NOACL))
|
|
strcat (mnt.mnt_opts, (char *) ",noacl");
|
|
if ((m->flags & MOUNT_NOPOSIX))
|
|
strcat (mnt.mnt_opts, (char *) ",posix=0");
|
|
mnt.mnt_freq = 1;
|
|
mnt.mnt_passno = 1;
|
|
m++;
|
|
return &mnt;
|
|
}
|