3 Copyright 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009, 2010 Red Hat, Inc.
5 This file is part of Cygwin.
7 This software is a copyrighted work licensed under the terms of the
8 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
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. */
17 #define scat(a,b) str(a##b)
25 #include "cygwin/include/cygwin/version.h"
26 #include "cygwin/include/sys/mount.h"
27 #include "cygwin/include/mntent.h"
28 #include "testsuite.h"
30 #include <sys/cygwin.h>
34 /* Used when treating / and \ as equivalent. */
38 ((__c) == '/' || (__c) == '\\'); \
42 static const GUID GUID_shortcut =
43 {0x00021401L, 0, 0, {0xc0, 0, 0, 0, 0, 0, 0, 0x46}};
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. */
55 struct win_shortcut_hdr
57 DWORD size; /* Header size in bytes. Must contain 0x4c. */
58 GUID magic; /* GUID of shortcut files. */
59 DWORD flags; /* Content flags. See above. */
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. */
67 DWORD filesize; /* Target filesize. */
68 DWORD icon_no; /* Icon number. */
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. */
76 cmp_shortcut_header (win_shortcut_hdr *file_header)
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;
89 get_word (HANDLE fh, int offset)
94 SetLastError(NO_ERROR);
95 if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
96 && GetLastError () != NO_ERROR)
99 if (!ReadFile (fh, &rv, 2, (DWORD *) &r, 0))
106 * Check the value of GetLastError() to find out whether there was an error.
109 get_dword (HANDLE fh, int offset)
114 SetLastError(NO_ERROR);
115 if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
116 && GetLastError () != NO_ERROR)
119 if (!ReadFile (fh, &rv, 4, (DWORD *) &r, 0))
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)
133 int magic = get_word (fh, 0x0);
134 return magic == EXE_MAGIC;
138 is_symlink (HANDLE fh)
140 int magic = get_word (fh, 0x0);
141 if (magic != SHORTCUT_MAGIC && magic != SYMLINK_MAGIC)
144 BY_HANDLE_FILE_INFORMATION local;
145 if (!GetFileInformationByHandle (fh, &local))
147 if (magic == SHORTCUT_MAGIC)
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. */
155 SetFilePointer (fh, 0, 0, FILE_BEGIN);
156 if (!ReadFile (fh, buf, size, &got, 0))
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) */
163 else /* magic == SYMLINK_MAGIC */
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))
171 if (got != sizeof (buf) ||
172 memcmp (buf, SYMLINK_COOKIE, sizeof (buf)) != 0)
173 return false; /* Not a Cygwin symlink. */
178 /* Assumes is_symlink(fh) is true */
180 readlink (HANDLE fh, char *path, int maxlen)
185 win_shortcut_hdr *file_header;
186 BY_HANDLE_FILE_INFORMATION fi;
188 if (!GetFileInformationByHandle (fh, &fi)
189 || fi.nFileSizeHigh != 0
190 || fi.nFileSizeLow > 4 * 65536)
193 buf = (char *) alloca (fi.nFileSizeLow + 1);
194 file_header = (win_shortcut_hdr *) buf;
196 if (SetFilePointer (fh, 0L, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER
197 || !ReadFile (fh, buf, fi.nFileSizeLow, &rv, NULL)
198 || rv != fi.nFileSizeLow)
201 if (fi.nFileSizeLow > sizeof (file_header)
202 && cmp_shortcut_header (file_header))
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))
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)
214 cp += len + 2 + relpath_len;
215 len = *(unsigned short *) cp;
218 if (*(PWCHAR) cp == 0xfeff) /* BOM */
220 len = wcstombs (NULL, (wchar_t *) (cp + 2), 0);
221 if (len == (size_t) -1 || len + 1 > maxlen)
223 wcstombs (path, (wchar_t *) (cp + 2), len + 1);
225 else if (len + 1 > maxlen)
228 memcpy (path, cp, len);
232 else if (strncmp (buf, SYMLINK_COOKIE, strlen (SYMLINK_COOKIE)) == 0
233 && buf[fi.nFileSizeLow - 1] == '\0')
235 cp = buf + strlen (SYMLINK_COOKIE);
236 if (*(PWCHAR) cp == 0xfeff) /* BOM */
238 len = wcstombs (NULL, (wchar_t *) (cp + 2), 0);
239 if (len == (size_t) -1 || len + 1 > maxlen)
241 wcstombs (path, (wchar_t *) (cp + 2), len + 1);
243 else if (fi.nFileSizeLow - strlen (SYMLINK_COOKIE) > (unsigned) maxlen)
252 #endif /* !FSTAB_ONLY */
255 mnt_t mount_table[255];
258 # define TESTSUITE_MOUNT_TABLE
259 # include "testsuite.h"
260 # undef TESTSUITE_MOUNT_TABLE
264 unconvert_slashes (char* name)
266 while ((name = strchr (name, '/')) != NULL)
270 /* These functions aren't called when defined(TESTSUITE) which results
271 in a compiler warning. */
276 while (*in == ' ' || *in == '\t')
284 while (*in && *in != ' ' && *in != '\t')
290 conv_fstab_spaces (char *field)
292 register char *sp = field;
293 while ((sp = strstr (sp, "\\040")) != NULL)
296 memmove (sp, sp + 3, strlen (sp + 3) + 1);
308 {"acl", MOUNT_NOACL, 1},
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},
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}
327 read_flags (char *options, unsigned &flags)
331 char *p = strchr (options, ',');
335 p = strchr (options, '\0');
338 o < (oopts + (sizeof (oopts) / sizeof (oopts[0])));
340 if (strcmp (options, o->name) == 0)
357 from_fstab_line (mnt_t *m, char *line, bool user)
359 char *native_path, *posix_path, *fs_type;
361 /* First field: Native path. */
362 char *c = skip_ws (line);
363 if (!*c || *c == '#')
365 char *cend = find_ws (c);
367 native_path = conv_fstab_spaces (c);
368 /* Second field: POSIX path. */
369 c = skip_ws (cend + 1);
374 posix_path = conv_fstab_spaces (c);
375 /* Third field: FS type. */
376 c = skip_ws (cend + 1);
382 /* Forth field: Flags. */
383 c = skip_ws (cend + 1);
388 unsigned mount_flags = MOUNT_SYSTEM;
390 if (!read_flags (c, mount_flags))
392 if (cygwin_internal (CW_CVT_MNT_OPTS, &c, &mount_flags))
396 mount_flags &= ~MOUNT_SYSTEM;
397 if (!strcmp (fs_type, "cygdrive"))
399 for (mnt_t *sm = mount_table; sm < m; ++sm)
400 if (sm->flags & MOUNT_CYGDRIVE)
402 if ((mount_flags & MOUNT_SYSTEM) || !(sm->flags & MOUNT_SYSTEM))
406 sm->posix = strdup (posix_path);
407 sm->flags = mount_flags | MOUNT_CYGDRIVE;
411 m->posix = strdup (posix_path);
412 m->native = strdup ("cygdrive prefix");
413 m->flags = mount_flags | MOUNT_CYGDRIVE;
417 for (mnt_t *sm = mount_table; sm < m; ++sm)
418 if (!strcmp (sm->posix, posix_path))
420 /* Don't allow overriding of a system mount with a user mount. */
421 if ((sm->flags & MOUNT_SYSTEM) && !(mount_flags & MOUNT_SYSTEM))
423 if ((sm->flags & MOUNT_SYSTEM) != (mount_flags & MOUNT_SYSTEM))
425 /* Changing immutable mount points require the override flag. */
426 if ((sm->flags & MOUNT_IMMUTABLE)
427 && !(mount_flags & MOUNT_OVERRIDE))
429 if (mount_flags & MOUNT_OVERRIDE)
430 mount_flags |= MOUNT_IMMUTABLE;
433 sm->native = strdup (native_path);
434 sm->flags = mount_flags;
437 m->posix = strdup (posix_path);
438 unconvert_slashes (native_path);
439 m->native = strdup (native_path);
440 m->flags = mount_flags;
447 #define BUFSIZE 65536
452 static char user[UNLEN + 1];
456 if ((userenv = getenv ("USER")) || (userenv = getenv ("USERNAME")))
457 strncat (user, userenv, UNLEN);
462 from_fstab (bool user, PWCHAR path, PWCHAR path_end)
464 mnt_t *m = mount_table + max_mount_entry;
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))
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
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;
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;
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;
501 max_mount_entry = m - mount_table;
504 PWCHAR u = wcscpy (path_end, L"\\etc\\fstab") + 10;
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)
513 /* Using BUFSIZE-1 leaves space to append two \0. */
514 while (ReadFile (h, got, BUFSIZE - 1 - (got - buf),
519 /* Set end marker. */
520 got[len] = got[len + 1] = '\0';
521 /* Set len to the absolute len of bytes in buf. */
523 /* Reset got to start reading at the start of the buffer again. */
525 while (got < buf + len && (end = strchr (got, '\n')))
527 end[end[-1] == '\r' ? -1 : 0] = '\0';
528 if (from_fstab_line (m, got, user))
532 if (len < BUFSIZE - 1)
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);
540 buf[len] = buf[len + 1] = '\0';
542 if (got > buf && from_fstab_line (m, got, user))
544 max_mount_entry = m - mount_table;
547 #endif /* !FSTAB_ONLY */
548 #endif /* !TESTSUITE */
553 mnt_sort (const void *a, const void *b)
555 const mnt_t *ma = (const mnt_t *) a;
556 const mnt_t *mb = (const mnt_t *) b;
559 ret = (ma->flags & MOUNT_CYGDRIVE) - (mb->flags & MOUNT_CYGDRIVE);
562 ret = (ma->flags & MOUNT_SYSTEM) - (mb->flags & MOUNT_SYSTEM);
565 return strcmp (ma->posix, mb->posix);
568 extern "C" WCHAR cygwin_dll_path[];
573 /* If TESTSUITE is defined, bypass this whole function as a harness
574 mount table will be provided. */
583 for (mnt_t *m1 = mount_table; m1->posix; m1++)
587 free ((char *) m1->native);
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))
603 path_end = wcsrchr (path, L'\\');
606 if (!cygwin_dll_path[0])
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)))
618 path_end = wcsrchr (path, L'\\');
621 /* If we can't create a valid installation dir from that, try to fetch
622 the installation dir from the setup registry key. */
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)
630 len = 32768 * sizeof (WCHAR);
631 ret = RegQueryValueExW (setup_key, L"rootdir", NULL, NULL,
633 RegCloseKey (setup_key);
634 if (ret == ERROR_SUCCESS)
637 if (ret == ERROR_SUCCESS)
638 path_end = wcschr (path, L'\0');
640 /* If we can't fetch an installation dir, bail out. */
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) */
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.
654 LEN1 = strlen (PATH1). It's passed because often it's already known.
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
666 path_prefix_p (const char *path1, const char *path2, int len1)
668 /* Handle case where PATH1 has trailing '/' and when it doesn't. */
669 if (len1 > 0 && isslash (path1[len1 - 1]))
673 return isslash (path2[0]) && !isslash (path2[1]);
675 if (strncasecmp (path1, path2, len1) != 0)
678 return isslash (path2[len1]) || path2[len1] == 0 || path1[len1 - 1] == ':';
682 vconcat (const char *s, va_list v)
694 unc = isslash (*s) && isslash (s[1]);
698 arg = va_arg (v, char *);
705 rv = (char *) malloc (len + 1);
710 arg = va_arg (v, char *);
719 /* concat is only used for urls and files, so we can safely
720 canonicalize the results */
721 for (p = d = rv; *p; p++)
724 /* special case for URLs */
725 if (*p == ':' && p[1] == '/' && p[2] == '/' && p > rv + 1)
730 else if (isslash (*p))
744 concat (const char *s, ...)
750 return vconcat (s, v);
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. */
762 rel_vconcat (const char *cwd, const char *s, va_list v)
764 char pathbuf[MAX_PATH];
765 if (!cwd || *cwd == '\0')
767 if (!GetCurrentDirectory (MAX_PATH, pathbuf))
773 mnt_t *m, *match = NULL;
775 for (m = mount_table; m->posix; m++)
777 if (m->flags & MOUNT_CYGDRIVE)
780 int n = strlen (m->native);
781 if (n < max_len || !path_prefix_p (m->native, cwd, n))
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);
801 temppath = concat ("/", cwd + max_len, "/", s, NULL);
803 char *res = vconcat (temppath, v);
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. */
813 vcygpath (const char *cwd, const char *s, va_list v)
816 mnt_t *m, *match = NULL;
818 if (!max_mount_entry)
821 if (s[0] == '.' && isslash (s[1]))
824 if (s[0] == '/' || s[1] == ':') /* FIXME: too crude? */
825 path = vconcat (s, v);
827 path = rel_vconcat (cwd, s, v);
832 if (strncmp (path, "/./", 3) == 0)
833 memmove (path + 1, path + 3, strlen (path + 3) + 1);
835 for (m = mount_table; m->posix; m++)
837 if (m->flags & MOUNT_CYGDRIVE)
840 int n = strlen (m->posix);
841 if (n < max_len || !path_prefix_p (m->posix, path, n))
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);
855 native = concat (match->native, "\\", path + max_len, NULL);
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);
865 cygpath_rel (const char *cwd, const char *s, ...)
871 return vcygpath (cwd, s, v);
875 cygpath (const char *s, ...)
881 return vcygpath (NULL, s, v);
884 static mnt_t *m = NULL;
887 setmntent (const char *, const char *)
890 if (!max_mount_entry)
895 extern "C" struct mntent *
902 mnt.mnt_fsname = (char *) m->native;
903 mnt.mnt_dir = (char *) m->posix;
905 mnt.mnt_type = (char *) malloc (16);
907 mnt.mnt_opts = (char *) malloc (64);
909 strcpy (mnt.mnt_type, (char *) (m->flags & MOUNT_SYSTEM) ? "system" : "user");
911 if (!(m->flags & MOUNT_BINARY))
912 strcpy (mnt.mnt_opts, (char *) "text");
914 strcpy (mnt.mnt_opts, (char *) "binary");
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");
923 if (m->flags & MOUNT_NOACL)
924 strcat (mnt.mnt_opts, (char *) ",noacl");
926 if (m->flags & MOUNT_NOPOSIX)
927 strcat (mnt.mnt_opts, (char *) ",posix=0");
929 if (m->flags & (MOUNT_AUTOMATIC | MOUNT_CYGDRIVE))
930 strcat (mnt.mnt_opts, (char *) ",auto");
938 #endif /* !FSTAB_ONLY */