OSDN Git Service

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