OSDN Git Service

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