OSDN Git Service

gas/opcodes: blackfin: move dsp mac func defines to common header
[pf3gnuchains/sourceware.git] / winsup / utils / path.cc
1 /* path.cc
2
3    Copyright 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009, 2010 Red Hat, Inc.
4
5 This file is part of Cygwin.
6
7 This software is a copyrighted work licensed under the terms of the
8 Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
9 details. */
10
11 /* The purpose of this file is to hide all the details about accessing
12    Cygwin's mount table, shortcuts, etc.  If the format or location of
13    the mount table, or the shortcut format changes, this is the file to
14    change to match it. */
15
16 #define str(a) #a
17 #define scat(a,b) str(a##b)
18 #include <windows.h>
19 #include <lmcons.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <malloc.h>
23 #include <wchar.h>
24 #include "path.h"
25 #include "cygwin/include/cygwin/version.h"
26 #include "cygwin/include/sys/mount.h"
27 #include "cygwin/include/mntent.h"
28 #include "testsuite.h"
29 #ifdef FSTAB_ONLY
30 #include <sys/cygwin.h>
31 #endif
32 #include "loadlib.h"
33
34 #ifndef FSTAB_ONLY
35 /* Used when treating / and \ as equivalent. */
36 #define isslash(ch) \
37   ({ \
38       char __c = (ch); \
39       ((__c) == '/' || (__c) == '\\'); \
40    })
41
42
43 static const GUID GUID_shortcut =
44   {0x00021401L, 0, 0, {0xc0, 0, 0, 0, 0, 0, 0, 0x46}};
45
46 enum {
47   WSH_FLAG_IDLIST = 0x01,       /* Contains an ITEMIDLIST. */
48   WSH_FLAG_FILE = 0x02,         /* Contains a file locator element. */
49   WSH_FLAG_DESC = 0x04,         /* Contains a description. */
50   WSH_FLAG_RELPATH = 0x08,      /* Contains a relative path. */
51   WSH_FLAG_WD = 0x10,           /* Contains a working dir. */
52   WSH_FLAG_CMDLINE = 0x20,      /* Contains command line args. */
53   WSH_FLAG_ICON = 0x40          /* Contains a custom icon. */
54 };
55
56 struct win_shortcut_hdr
57   {
58     DWORD size;         /* Header size in bytes.  Must contain 0x4c. */
59     GUID magic;         /* GUID of shortcut files. */
60     DWORD flags;        /* Content flags.  See above. */
61
62     /* The next fields from attr to icon_no are always set to 0 in Cygwin
63        and U/Win shortcuts. */
64     DWORD attr; /* Target file attributes. */
65     FILETIME ctime;     /* These filetime items are never touched by the */
66     FILETIME mtime;     /* system, apparently. Values don't matter. */
67     FILETIME atime;
68     DWORD filesize;     /* Target filesize. */
69     DWORD icon_no;      /* Icon number. */
70
71     DWORD run;          /* Values defined in winuser.h. Use SW_NORMAL. */
72     DWORD hotkey;       /* Hotkey value. Set to 0.  */
73     DWORD dummy[2];     /* Future extension probably. Always 0. */
74   };
75
76 static bool
77 cmp_shortcut_header (win_shortcut_hdr *file_header)
78 {
79   /* A Cygwin or U/Win shortcut only contains a description and a relpath.
80      Cygwin shortcuts also might contain an ITEMIDLIST. The run type is
81      always set to SW_NORMAL. */
82   return file_header->size == sizeof (win_shortcut_hdr)
83       && !memcmp (&file_header->magic, &GUID_shortcut, sizeof GUID_shortcut)
84       && (file_header->flags & ~WSH_FLAG_IDLIST)
85          == (WSH_FLAG_DESC | WSH_FLAG_RELPATH)
86       && file_header->run == SW_NORMAL;
87 }
88
89 int
90 get_word (HANDLE fh, int offset)
91 {
92   unsigned short rv;
93   unsigned r;
94
95   SetLastError(NO_ERROR);
96   if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
97       && GetLastError () != NO_ERROR)
98     return -1;
99
100   if (!ReadFile (fh, &rv, 2, (DWORD *) &r, 0))
101     return -1;
102
103   return rv;
104 }
105
106 /*
107  * Check the value of GetLastError() to find out whether there was an error.
108  */
109 int
110 get_dword (HANDLE fh, int offset)
111 {
112   int rv;
113   unsigned r;
114
115   SetLastError(NO_ERROR);
116   if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
117       && GetLastError () != NO_ERROR)
118     return -1;
119
120   if (!ReadFile (fh, &rv, 4, (DWORD *) &r, 0))
121     return -1;
122
123   return rv;
124 }
125
126 #define EXE_MAGIC ((int)*(unsigned short *)"MZ")
127 #define SHORTCUT_MAGIC ((int)*(unsigned short *)"L\0")
128 #define SYMLINK_COOKIE "!<symlink>"
129 #define SYMLINK_MAGIC ((int)*(unsigned short *)SYMLINK_COOKIE)
130
131 bool
132 is_exe (HANDLE fh)
133 {
134   int magic = get_word (fh, 0x0);
135   return magic == EXE_MAGIC;
136 }
137
138 bool
139 is_symlink (HANDLE fh)
140 {
141   int magic = get_word (fh, 0x0);
142   if (magic != SHORTCUT_MAGIC && magic != SYMLINK_MAGIC)
143     return false;
144   DWORD got;
145   BY_HANDLE_FILE_INFORMATION local;
146   if (!GetFileInformationByHandle (fh, &local))
147     return false;
148   if (magic == SHORTCUT_MAGIC)
149     {
150       DWORD size;
151       if (!local.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
152         return false; /* Not a Cygwin symlink. */
153       if ((size = GetFileSize (fh, NULL)) > 8192)
154         return false; /* Not a Cygwin symlink. */
155       char buf[size];
156       SetFilePointer (fh, 0, 0, FILE_BEGIN);
157       if (!ReadFile (fh, buf, size, &got, 0))
158         return false;
159       if (got != size || !cmp_shortcut_header ((win_shortcut_hdr *) buf))
160         return false; /* Not a Cygwin symlink. */
161       /* TODO: check for invalid path contents
162          (see symlink_info::check() in ../cygwin/path.cc) */
163     }
164   else /* magic == SYMLINK_MAGIC */
165     {
166       if (!local.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
167         return false; /* Not a Cygwin symlink. */
168       char buf[sizeof (SYMLINK_COOKIE) - 1];
169       SetFilePointer (fh, 0, 0, FILE_BEGIN);
170       if (!ReadFile (fh, buf, sizeof (buf), &got, 0))
171         return false;
172       if (got != sizeof (buf) ||
173           memcmp (buf, SYMLINK_COOKIE, sizeof (buf)) != 0)
174         return false; /* Not a Cygwin symlink. */
175     }
176   return true;
177 }
178
179 /* Assumes is_symlink(fh) is true */
180 bool
181 readlink (HANDLE fh, char *path, int maxlen)
182 {
183   DWORD rv;
184   char *buf, *cp;
185   unsigned short len;
186   win_shortcut_hdr *file_header;
187   BY_HANDLE_FILE_INFORMATION fi;
188
189   if (!GetFileInformationByHandle (fh, &fi)
190       || fi.nFileSizeHigh != 0
191       || fi.nFileSizeLow > 4 * 65536)
192     return false;
193
194   buf = (char *) alloca (fi.nFileSizeLow + 1);
195   file_header = (win_shortcut_hdr *) buf;
196
197   if (SetFilePointer (fh, 0L, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER
198       || !ReadFile (fh, buf, fi.nFileSizeLow, &rv, NULL)
199       || rv != fi.nFileSizeLow)
200     return false;
201   
202   if (fi.nFileSizeLow > sizeof (file_header)
203       && cmp_shortcut_header (file_header))
204     {
205       cp = buf + sizeof (win_shortcut_hdr);
206       if (file_header->flags & WSH_FLAG_IDLIST) /* Skip ITEMIDLIST */
207         cp += *(unsigned short *) cp + 2;
208       if (!(len = *(unsigned short *) cp))
209         return false;
210       cp += 2;
211       /* Has appended full path?  If so, use it instead of description. */
212       unsigned short relpath_len = *(unsigned short *) (cp + len);
213       if (cp + len + 2 + relpath_len < buf + fi.nFileSizeLow)
214         {
215           cp += len + 2 + relpath_len;
216           len = *(unsigned short *) cp;
217           cp += 2;
218         }
219       if (*(PWCHAR) cp == 0xfeff)       /* BOM */
220         {
221           len = wcstombs (NULL, (wchar_t *) (cp + 2), 0);
222           if (len == (size_t) -1 || len + 1 > maxlen)
223             return false;
224           wcstombs (path, (wchar_t *) (cp + 2), len + 1);
225         }
226       else if (len + 1 > maxlen)
227         return false;
228       else
229         memcpy (path, cp, len);
230       path[len] = '\0';
231       return true;
232     }
233   else if (strncmp (buf, SYMLINK_COOKIE, strlen (SYMLINK_COOKIE)) == 0
234            && buf[fi.nFileSizeLow - 1] == '\0')
235     {
236       cp = buf + strlen (SYMLINK_COOKIE);
237       if (*(PWCHAR) cp == 0xfeff)       /* BOM */
238         {
239           len = wcstombs (NULL, (wchar_t *) (cp + 2), 0);
240           if (len == (size_t) -1 || len + 1 > maxlen)
241             return false;
242           wcstombs (path, (wchar_t *) (cp + 2), len + 1);
243         }
244       else if (fi.nFileSizeLow - strlen (SYMLINK_COOKIE) > (unsigned) maxlen)
245         return false;
246       else
247         strcpy (path, cp);
248       return true;
249     }      
250   else
251     return false;
252 }
253 #endif /* !FSTAB_ONLY */
254
255 #ifndef TESTSUITE
256 mnt_t mount_table[255];
257 int max_mount_entry;
258 #else
259 #  define TESTSUITE_MOUNT_TABLE
260 #  include "testsuite.h"
261 #  undef TESTSUITE_MOUNT_TABLE
262 #endif
263
264 inline void
265 unconvert_slashes (char* name)
266 {
267   while ((name = strchr (name, '/')) != NULL)
268     *name++ = '\\';
269 }
270
271 /* These functions aren't called when defined(TESTSUITE) which results
272    in a compiler warning.  */
273 #ifndef TESTSUITE
274 inline char *
275 skip_ws (char *in)
276 {
277   while (*in == ' ' || *in == '\t')
278     ++in;
279   return in;
280 }
281
282 inline char *
283 find_ws (char *in)
284 {
285   while (*in && *in != ' ' && *in != '\t')
286     ++in;
287   return in;
288 }
289
290 inline char *
291 conv_fstab_spaces (char *field)
292 {
293   register char *sp = field;
294   while ((sp = strstr (sp, "\\040")) != NULL)
295     {
296       *sp++ = ' ';
297       memmove (sp, sp + 3, strlen (sp + 3) + 1);
298     }
299   return field;
300 }
301
302 static struct opt
303 {
304   const char *name;
305   unsigned val;
306   bool clear;
307 } oopts[] =
308 {
309   {"acl", MOUNT_NOACL, 1},
310   {"auto", 0, 0},
311   {"binary", MOUNT_BINARY, 0},
312   {"cygexec", MOUNT_CYGWIN_EXEC, 0},
313   {"dos", MOUNT_DOS, 0},
314   {"exec", MOUNT_EXEC, 0},
315   {"ihash", MOUNT_IHASH, 0},
316   {"noacl", MOUNT_NOACL, 0},
317   {"nosuid", 0, 0},
318   {"notexec", MOUNT_NOTEXEC, 0},
319   {"nouser", MOUNT_SYSTEM, 0},
320   {"override", MOUNT_OVERRIDE, 0},
321   {"posix=0", MOUNT_NOPOSIX, 0},
322   {"posix=1", MOUNT_NOPOSIX, 1},
323   {"text", MOUNT_BINARY, 1},
324   {"user", MOUNT_SYSTEM, 1}
325 };
326
327 static bool
328 read_flags (char *options, unsigned &flags)
329 {
330   while (*options)
331     {
332       char *p = strchr (options, ',');
333       if (p)
334         *p++ = '\0';
335       else
336         p = strchr (options, '\0');
337
338       for (opt *o = oopts;
339            o < (oopts + (sizeof (oopts) / sizeof (oopts[0])));
340            o++)
341         if (strcmp (options, o->name) == 0)
342           {
343             if (o->clear)
344               flags &= ~o->val;
345             else
346               flags |= o->val;
347             goto gotit;
348           }
349       return false;
350
351     gotit:
352       options = p;
353     }
354   return true;
355 }
356
357 bool
358 from_fstab_line (mnt_t *m, char *line, bool user)
359 {
360   char *native_path, *posix_path, *fs_type;
361
362   /* First field: Native path. */
363   char *c = skip_ws (line);
364   if (!*c || *c == '#')
365     return false;
366   char *cend = find_ws (c);
367   *cend = '\0';
368   native_path = conv_fstab_spaces (c);
369   /* Second field: POSIX path. */
370   c = skip_ws (cend + 1);
371   if (!*c)
372     return false;
373   cend = find_ws (c);
374   *cend = '\0';
375   posix_path = conv_fstab_spaces (c);
376   /* Third field: FS type. */
377   c = skip_ws (cend + 1);
378   if (!*c)
379     return false;
380   cend = find_ws (c);
381   *cend = '\0';
382   fs_type = c;
383   /* Forth field: Flags. */
384   c = skip_ws (cend + 1);
385   if (!*c)
386     return false;
387   cend = find_ws (c);
388   *cend = '\0';
389   unsigned mount_flags = MOUNT_SYSTEM;
390 #ifndef FSTAB_ONLY
391   if (!read_flags (c, mount_flags))
392 #else
393   if (cygwin_internal (CW_CVT_MNT_OPTS, &c, &mount_flags))
394 #endif
395     return false;
396   if (user)
397     mount_flags &= ~MOUNT_SYSTEM;
398   if (!strcmp (fs_type, "cygdrive"))
399     {
400       for (mnt_t *sm = mount_table; sm < m; ++sm)
401         if (sm->flags & MOUNT_CYGDRIVE)
402           {
403             if ((mount_flags & MOUNT_SYSTEM) || !(sm->flags & MOUNT_SYSTEM))
404               {
405                 if (sm->posix)
406                   free (sm->posix);
407                 sm->posix = strdup (posix_path);
408                 sm->flags = mount_flags | MOUNT_CYGDRIVE;
409               }
410             return false;
411           }
412       m->posix = strdup (posix_path);
413       m->native = strdup ("cygdrive prefix");
414       m->flags = mount_flags | MOUNT_CYGDRIVE;
415     }
416   else
417     {
418       for (mnt_t *sm = mount_table; sm < m; ++sm)
419         if (!strcmp (sm->posix, posix_path))
420           {
421             /* Don't allow overriding of a system mount with a user mount. */
422             if ((sm->flags & MOUNT_SYSTEM) && !(mount_flags & MOUNT_SYSTEM))
423               return false;
424             if ((sm->flags & MOUNT_SYSTEM) != (mount_flags & MOUNT_SYSTEM))
425               continue;
426             /* Changing immutable mount points require the override flag. */
427             if ((sm->flags & MOUNT_IMMUTABLE)
428                 && !(mount_flags & MOUNT_OVERRIDE))
429               return false;
430             if (mount_flags & MOUNT_OVERRIDE)
431               mount_flags |= MOUNT_IMMUTABLE;
432             if (sm->native)
433               free (sm->native);
434             sm->native = strdup (native_path);
435             sm->flags = mount_flags;
436             return false;
437           }
438       m->posix = strdup (posix_path);
439       unconvert_slashes (native_path);
440       m->native = strdup (native_path);
441       m->flags = mount_flags;
442     }
443   return true;
444 }
445
446 #ifndef FSTAB_ONLY
447
448 #define BUFSIZE 65536
449
450 static char *
451 get_user ()
452 {
453   static char user[UNLEN + 1];
454   char *userenv;
455
456   user[0] = '\0';
457   if ((userenv = getenv ("USER")) || (userenv = getenv ("USERNAME")))
458     strncat (user, userenv, UNLEN);
459   return user;
460 }
461
462 void
463 from_fstab (bool user, PWCHAR path, PWCHAR path_end)
464 {
465   mnt_t *m = mount_table + max_mount_entry;
466   char buf[BUFSIZE];
467
468   if (!user)
469     {
470       /* Create a default root dir from path. */
471       wcstombs (buf, path, BUFSIZE);
472       unconvert_slashes (buf);
473       char *native_path = buf;
474       if (!strncmp (native_path, "\\\\?\\", 4))
475         native_path += 4;
476       if (!strncmp (native_path, "UNC\\", 4))
477         *(native_path += 2) = '\\';
478       m->posix = strdup ("/");
479       m->native = strdup (native_path);
480       m->flags = MOUNT_SYSTEM | MOUNT_BINARY | MOUNT_IMMUTABLE
481                  | MOUNT_AUTOMATIC;
482       ++m;
483       /* Create default /usr/bin and /usr/lib entries. */
484       char *trail = strchr (native_path, '\0');
485       strcpy (trail, "\\bin");
486       m->posix = strdup ("/usr/bin");
487       m->native = strdup (native_path);
488       m->flags = MOUNT_SYSTEM | MOUNT_BINARY | MOUNT_AUTOMATIC;
489       ++m;
490       strcpy (trail, "\\lib");
491       m->posix = strdup ("/usr/lib");
492       m->native = strdup (native_path);
493       m->flags = MOUNT_SYSTEM | MOUNT_BINARY | MOUNT_AUTOMATIC;
494       ++m;
495       /* Create a default cygdrive entry.  Note that this is a user entry.
496          This allows to override it with mount, unless the sysadmin created
497          a cygdrive entry in /etc/fstab. */
498       m->posix = strdup (CYGWIN_INFO_CYGDRIVE_DEFAULT_PREFIX);
499       m->native = strdup ("cygdrive prefix");
500       m->flags = MOUNT_BINARY | MOUNT_CYGDRIVE;
501       ++m;
502       max_mount_entry = m - mount_table;
503     }
504
505   PWCHAR u = wcscpy (path_end, L"\\etc\\fstab") + 10;
506   if (user)
507     mbstowcs (wcscpy (u, L".d\\") + 3, get_user (), BUFSIZE - (u - path));
508   HANDLE h = CreateFileW (path, GENERIC_READ, FILE_SHARE_READ, NULL,
509                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
510   if (h == INVALID_HANDLE_VALUE)
511     return;
512   char *got = buf;
513   DWORD len = 0;
514   /* Using BUFSIZE-1 leaves space to append two \0. */
515   while (ReadFile (h, got, BUFSIZE - 1 - (got - buf),
516                    &len, NULL))
517     {
518       char *end;
519
520       /* Set end marker. */
521       got[len] = got[len + 1] = '\0';
522       /* Set len to the absolute len of bytes in buf. */
523       len += got - buf;
524       /* Reset got to start reading at the start of the buffer again. */
525       got = buf;
526       while (got < buf + len && (end = strchr (got, '\n')))
527         {
528           end[end[-1] == '\r' ? -1 : 0] = '\0';
529           if (from_fstab_line (m, got, user))
530             ++m;
531           got = end + 1;
532         }
533       if (len < BUFSIZE - 1)
534         break;
535       /* We have to read once more.  Move remaining bytes to the start of
536          the buffer and reposition got so that it points to the end of
537          the remaining bytes. */
538       len = buf + len - got;
539       memmove (buf, got, len);
540       got = buf + len;
541       buf[len] = buf[len + 1] = '\0';
542     }
543   if (got > buf && from_fstab_line (m, got, user))
544     ++m;
545   max_mount_entry = m - mount_table;
546   CloseHandle (h);
547 }
548 #endif /* !FSTAB_ONLY */
549 #endif /* !TESTSUITE */
550
551 #ifndef FSTAB_ONLY
552
553 static int
554 mnt_sort (const void *a, const void *b)
555 {
556   const mnt_t *ma = (const mnt_t *) a;
557   const mnt_t *mb = (const mnt_t *) b;
558   int ret;
559   
560   ret = (ma->flags & MOUNT_CYGDRIVE) - (mb->flags & MOUNT_CYGDRIVE);
561   if (ret)
562     return ret;
563   ret = (ma->flags & MOUNT_SYSTEM) - (mb->flags & MOUNT_SYSTEM);
564   if (ret)
565     return ret;
566   return strcmp (ma->posix, mb->posix);
567 }
568
569 extern "C" WCHAR cygwin_dll_path[];
570
571 static void
572 read_mounts ()
573 {
574 /* If TESTSUITE is defined, bypass this whole function as a harness
575    mount table will be provided.  */
576 #ifndef TESTSUITE
577   HKEY setup_key;
578   LONG ret;
579   DWORD len;
580   WCHAR path[32768];
581   PWCHAR path_end;
582   HMODULE h;
583
584   for (mnt_t *m1 = mount_table; m1->posix; m1++)
585     {
586       free (m1->posix);
587       if (m1->native)
588         free ((char *) m1->native);
589       m1->posix = NULL;
590     }
591   max_mount_entry = 0;
592
593   /* First fetch the cygwin1.dll path from the LoadLibrary call in load_cygwin.
594      This utilizes the DLL search order to find a matching cygwin1.dll and to
595      compute the installation path from that DLL's path. */
596   if (cygwin_dll_path[0])
597     wcscpy (path, cygwin_dll_path);
598   /* If we can't load cygwin1.dll, check where cygcheck is living itself and
599      try to fetch installation path from here.  Does cygwin1.dll exist in the
600      same path?  This should only kick in if the cygwin1.dll in the same path
601      has been made non-executable for the current user accidentally. */
602   else if (!GetModuleFileNameW (NULL, path, 32768))
603     return;
604   path_end = wcsrchr (path, L'\\');
605   if (path_end)
606     {
607       if (!cygwin_dll_path[0])
608         {
609           wcscpy (path_end, L"\\cygwin1.dll");
610           DWORD attr = GetFileAttributesW (path);
611           if (attr == (DWORD) -1
612               || (attr & (FILE_ATTRIBUTE_DIRECTORY
613                           | FILE_ATTRIBUTE_REPARSE_POINT)))
614             path_end = NULL;
615         }
616       if (path_end)
617         {
618           *path_end = L'\0';
619           path_end = wcsrchr (path, L'\\');
620         }
621     }
622   /* If we can't create a valid installation dir from that, try to fetch
623      the installation dir from the setup registry key. */
624   if (!path_end)
625     {
626       for (int i = 0; i < 2; ++i)
627         if ((ret = RegOpenKeyExW (i ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
628                                   L"Software\\Cygwin\\setup", 0,
629                                   KEY_READ, &setup_key)) == ERROR_SUCCESS)
630           {
631             len = 32768 * sizeof (WCHAR);
632             ret = RegQueryValueExW (setup_key, L"rootdir", NULL, NULL,
633                                     (PBYTE) path, &len);
634             RegCloseKey (setup_key);
635             if (ret == ERROR_SUCCESS)
636               break;
637           }
638       if (ret == ERROR_SUCCESS)
639         path_end = wcschr (path, L'\0');
640     }
641   /* If we can't fetch an installation dir, bail out. */
642   if (!path_end)
643     return;
644   *path_end = L'\0';
645
646   from_fstab (false, path, path_end);
647   from_fstab (true, path, path_end);
648   qsort (mount_table, max_mount_entry, sizeof (mnt_t), mnt_sort);
649 #endif /* !defined(TESTSUITE) */
650 }
651
652 /* Return non-zero if PATH1 is a prefix of PATH2.
653    Both are assumed to be of the same path style and / vs \ usage.
654    Neither may be "".
655    LEN1 = strlen (PATH1).  It's passed because often it's already known.
656
657    Examples:
658    /foo/ is a prefix of /foo  <-- may seem odd, but desired
659    /foo is a prefix of /foo/
660    / is a prefix of /foo/bar
661    / is not a prefix of foo/bar
662    foo/ is a prefix foo/bar
663    /foo is not a prefix of /foobar
664 */
665
666 static int
667 path_prefix_p (const char *path1, const char *path2, int len1)
668 {
669   /* Handle case where PATH1 has trailing '/' and when it doesn't.  */
670   if (len1 > 0 && isslash (path1[len1 - 1]))
671     len1--;
672
673   if (len1 == 0)
674     return isslash (path2[0]) && !isslash (path2[1]);
675
676   if (strncasecmp (path1, path2, len1) != 0)
677     return 0;
678
679   return isslash (path2[len1]) || path2[len1] == 0 || path1[len1 - 1] == ':';
680 }
681
682 static char *
683 vconcat (const char *s, va_list v)
684 {
685   int len;
686   char *rv, *arg;
687   va_list save_v = v;
688   int unc;
689
690   if (!s)
691     return 0;
692
693   len = strlen (s);
694
695   unc = isslash (*s) && isslash (s[1]);
696
697   while (1)
698     {
699       arg = va_arg (v, char *);
700       if (arg == 0)
701         break;
702       len += strlen (arg);
703     }
704   va_end (v);
705
706   rv = (char *) malloc (len + 1);
707   strcpy (rv, s);
708   v = save_v;
709   while (1)
710   {
711     arg = va_arg (v, char *);
712     if (arg == 0)
713       break;
714     strcat (rv, arg);
715   }
716   va_end (v);
717
718   char *d, *p;
719
720   /* concat is only used for urls and files, so we can safely
721      canonicalize the results */
722   for (p = d = rv; *p; p++)
723     {
724       *d++ = *p;
725       /* special case for URLs */
726       if (*p == ':' && p[1] == '/' && p[2] == '/' && p > rv + 1)
727         {
728           *d++ = *++p;
729           *d++ = *++p;
730         }
731       else if (isslash (*p))
732         {
733           if (p == rv && unc)
734             *d++ = *p++;
735           while (p[1] == '/')
736             p++;
737         }
738     }
739   *d = 0;
740
741   return rv;
742 }
743
744 static char *
745 concat (const char *s, ...)
746 {
747   va_list v;
748
749   va_start (v, s);
750
751   return vconcat (s, v);
752 }
753
754 /* This is a helper function for when vcygpath is passed what appears
755    to be a relative POSIX path.  We take a Win32 CWD (either as specified
756    in 'cwd' or as retrieved with GetCurrentDirectory() if 'cwd' is NULL)
757    and find the mount table entry with the longest match.  We replace the
758    matching portion with the corresponding POSIX prefix, and to that append
759    's' and anything in 'v'.  The returned result is a mostly-POSIX
760    absolute path -- 'mostly' because the portions of CWD that didn't
761    match the mount prefix will still have '\\' separators.  */
762 static char *
763 rel_vconcat (const char *cwd, const char *s, va_list v)
764 {
765   char pathbuf[MAX_PATH];
766   if (!cwd || *cwd == '\0')
767     {
768       if (!GetCurrentDirectory (MAX_PATH, pathbuf))
769         return NULL;
770       cwd = pathbuf;
771     }
772
773   int max_len = -1;
774   mnt_t *m, *match = NULL;
775
776   for (m = mount_table; m->posix; m++)
777     {
778       if (m->flags & MOUNT_CYGDRIVE)
779         continue;
780
781       int n = strlen (m->native);
782       if (n < max_len || !path_prefix_p (m->native, cwd, n))
783         continue;
784       max_len = n;
785       match = m;
786     }
787
788   char *temppath;
789   if (!match)
790     // No prefix matched - best effort to return meaningful value.
791     temppath = concat (cwd, "/", s, NULL);
792   else if (strcmp (match->posix, "/") != 0)
793     // Matched on non-root.  Copy matching prefix + remaining 'path'.
794     temppath = concat (match->posix, cwd + max_len, "/", s, NULL);
795   else if (cwd[max_len] == '\0')
796     // Matched on root and there's no remaining 'path'.
797     temppath = concat ("/", s, NULL);
798   else if (isslash (cwd[max_len]))
799     // Matched on root but remaining 'path' starts with a slash anyway.
800     temppath = concat (cwd + max_len, "/", s, NULL);
801   else
802     temppath = concat ("/", cwd + max_len, "/", s, NULL);
803
804   char *res = vconcat (temppath, v);
805   free (temppath);
806   return res;
807 }
808
809 /* Convert a POSIX path in 's' to an absolute Win32 path, and append
810    anything in 'v' to the end, returning the result.  If 's' is a
811    relative path then 'cwd' is used as the working directory to make
812    it absolute.  Pass NULL in 'cwd' to use GetCurrentDirectory.  */
813 static char *
814 vcygpath (const char *cwd, const char *s, va_list v)
815 {
816   int max_len = -1;
817   mnt_t *m, *match = NULL;
818
819   if (!max_mount_entry)
820     read_mounts ();
821   char *path;
822   if (s[0] == '.' && isslash (s[1]))
823     s += 2;
824
825   if (s[0] == '/' || s[1] == ':')       /* FIXME: too crude? */
826     path = vconcat (s, v);
827   else
828     path = rel_vconcat (cwd, s, v);
829
830   if (!path)
831     return NULL;
832
833   if (strncmp (path, "/./", 3) == 0)
834     memmove (path + 1, path + 3, strlen (path + 3) + 1);
835
836   for (m = mount_table; m->posix; m++)
837     {
838       if (m->flags & MOUNT_CYGDRIVE)
839         continue;
840
841       int n = strlen (m->posix);
842       if (n < max_len || !path_prefix_p (m->posix, path, n))
843         continue;
844       max_len = n;
845       match = m;
846     }
847
848   char *native;
849   if (match == NULL)
850     native = strdup (path);
851   else if (max_len == (int) strlen (path))
852     native = strdup (match->native);
853   else if (isslash (path[max_len]))
854     native = concat (match->native, path + max_len, NULL);
855   else
856     native = concat (match->native, "\\", path + max_len, NULL);
857   free (path);
858
859   unconvert_slashes (native);
860   for (char *s = strstr (native + 1, "\\.\\"); s && *s; s = strstr (s, "\\.\\"))
861     memmove (s + 1, s + 3, strlen (s + 3) + 1);
862   return native;
863 }
864
865 char *
866 cygpath_rel (const char *cwd, const char *s, ...)
867 {
868   va_list v;
869
870   va_start (v, s);
871
872   return vcygpath (cwd, s, v);
873 }
874
875 char *
876 cygpath (const char *s, ...)
877 {
878   va_list v;
879   
880   va_start (v, s);
881   
882   return vcygpath (NULL, s, v);
883 }
884
885 static mnt_t *m = NULL;
886
887 extern "C" FILE *
888 setmntent (const char *, const char *)
889 {
890   m = mount_table;
891   if (!max_mount_entry)
892     read_mounts ();
893   return NULL;
894 }
895
896 extern "C" struct mntent *
897 getmntent (FILE *)
898 {
899   static mntent mnt;
900   if (!m->posix)
901     return NULL;
902
903   mnt.mnt_fsname = (char *) m->native;
904   mnt.mnt_dir = (char *) m->posix;
905   if (!mnt.mnt_type)
906     mnt.mnt_type = (char *) malloc (16);
907   if (!mnt.mnt_opts)
908     mnt.mnt_opts = (char *) malloc (64);
909
910   strcpy (mnt.mnt_type, (char *) (m->flags & MOUNT_SYSTEM) ? "system" : "user");
911
912   if (!(m->flags & MOUNT_BINARY))
913     strcpy (mnt.mnt_opts, (char *) "text");
914   else
915     strcpy (mnt.mnt_opts, (char *) "binary");
916
917   if (m->flags & MOUNT_CYGWIN_EXEC)
918     strcat (mnt.mnt_opts, (char *) ",cygexec");
919   else if (m->flags & MOUNT_EXEC)
920     strcat (mnt.mnt_opts, (char *) ",exec");
921   else if (m->flags & MOUNT_NOTEXEC)
922     strcat (mnt.mnt_opts, (char *) ",notexec");
923
924   if (m->flags & MOUNT_NOACL)
925     strcat (mnt.mnt_opts, (char *) ",noacl");
926
927   if (m->flags & MOUNT_NOPOSIX)
928     strcat (mnt.mnt_opts, (char *) ",posix=0");
929
930   if (m->flags & (MOUNT_AUTOMATIC | MOUNT_CYGDRIVE))
931     strcat (mnt.mnt_opts, (char *) ",auto");
932
933   mnt.mnt_freq = 1;
934   mnt.mnt_passno = 1;
935   m++;
936   return &mnt;
937 }
938
939 #endif /* !FSTAB_ONLY */