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>
35 /* Used when treating / and \ as equivalent. */
39 ((__c) == '/' || (__c) == '\\'); \
43 static const GUID GUID_shortcut =
44 {0x00021401L, 0, 0, {0xc0, 0, 0, 0, 0, 0, 0, 0x46}};
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. */
56 struct win_shortcut_hdr
58 DWORD size; /* Header size in bytes. Must contain 0x4c. */
59 GUID magic; /* GUID of shortcut files. */
60 DWORD flags; /* Content flags. See above. */
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. */
68 DWORD filesize; /* Target filesize. */
69 DWORD icon_no; /* Icon number. */
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. */
77 cmp_shortcut_header (win_shortcut_hdr *file_header)
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;
90 get_word (HANDLE fh, int offset)
95 SetLastError(NO_ERROR);
96 if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
97 && GetLastError () != NO_ERROR)
100 if (!ReadFile (fh, &rv, 2, (DWORD *) &r, 0))
107 * Check the value of GetLastError() to find out whether there was an error.
110 get_dword (HANDLE fh, int offset)
115 SetLastError(NO_ERROR);
116 if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
117 && GetLastError () != NO_ERROR)
120 if (!ReadFile (fh, &rv, 4, (DWORD *) &r, 0))
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)
134 int magic = get_word (fh, 0x0);
135 return magic == EXE_MAGIC;
139 is_symlink (HANDLE fh)
141 int magic = get_word (fh, 0x0);
142 if (magic != SHORTCUT_MAGIC && magic != SYMLINK_MAGIC)
145 BY_HANDLE_FILE_INFORMATION local;
146 if (!GetFileInformationByHandle (fh, &local))
148 if (magic == SHORTCUT_MAGIC)
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. */
156 SetFilePointer (fh, 0, 0, FILE_BEGIN);
157 if (!ReadFile (fh, buf, size, &got, 0))
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) */
164 else /* magic == SYMLINK_MAGIC */
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))
172 if (got != sizeof (buf) ||
173 memcmp (buf, SYMLINK_COOKIE, sizeof (buf)) != 0)
174 return false; /* Not a Cygwin symlink. */
179 /* Assumes is_symlink(fh) is true */
181 readlink (HANDLE fh, char *path, int maxlen)
186 win_shortcut_hdr *file_header;
187 BY_HANDLE_FILE_INFORMATION fi;
189 if (!GetFileInformationByHandle (fh, &fi)
190 || fi.nFileSizeHigh != 0
191 || fi.nFileSizeLow > 4 * 65536)
194 buf = (char *) alloca (fi.nFileSizeLow + 1);
195 file_header = (win_shortcut_hdr *) buf;
197 if (SetFilePointer (fh, 0L, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER
198 || !ReadFile (fh, buf, fi.nFileSizeLow, &rv, NULL)
199 || rv != fi.nFileSizeLow)
202 if (fi.nFileSizeLow > sizeof (file_header)
203 && cmp_shortcut_header (file_header))
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))
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)
215 cp += len + 2 + relpath_len;
216 len = *(unsigned short *) cp;
219 if (*(PWCHAR) cp == 0xfeff) /* BOM */
221 len = wcstombs (NULL, (wchar_t *) (cp + 2), 0);
222 if (len == (size_t) -1 || len + 1 > maxlen)
224 wcstombs (path, (wchar_t *) (cp + 2), len + 1);
226 else if (len + 1 > maxlen)
229 memcpy (path, cp, len);
233 else if (strncmp (buf, SYMLINK_COOKIE, strlen (SYMLINK_COOKIE)) == 0
234 && buf[fi.nFileSizeLow - 1] == '\0')
236 cp = buf + strlen (SYMLINK_COOKIE);
237 if (*(PWCHAR) cp == 0xfeff) /* BOM */
239 len = wcstombs (NULL, (wchar_t *) (cp + 2), 0);
240 if (len == (size_t) -1 || len + 1 > maxlen)
242 wcstombs (path, (wchar_t *) (cp + 2), len + 1);
244 else if (fi.nFileSizeLow - strlen (SYMLINK_COOKIE) > (unsigned) maxlen)
253 #endif /* !FSTAB_ONLY */
256 mnt_t mount_table[255];
259 # define TESTSUITE_MOUNT_TABLE
260 # include "testsuite.h"
261 # undef TESTSUITE_MOUNT_TABLE
265 unconvert_slashes (char* name)
267 while ((name = strchr (name, '/')) != NULL)
271 /* These functions aren't called when defined(TESTSUITE) which results
272 in a compiler warning. */
277 while (*in == ' ' || *in == '\t')
285 while (*in && *in != ' ' && *in != '\t')
291 conv_fstab_spaces (char *field)
293 register char *sp = field;
294 while ((sp = strstr (sp, "\\040")) != NULL)
297 memmove (sp, sp + 3, strlen (sp + 3) + 1);
309 {"acl", MOUNT_NOACL, 1},
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},
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}
328 read_flags (char *options, unsigned &flags)
332 char *p = strchr (options, ',');
336 p = strchr (options, '\0');
339 o < (oopts + (sizeof (oopts) / sizeof (oopts[0])));
341 if (strcmp (options, o->name) == 0)
358 from_fstab_line (mnt_t *m, char *line, bool user)
360 char *native_path, *posix_path, *fs_type;
362 /* First field: Native path. */
363 char *c = skip_ws (line);
364 if (!*c || *c == '#')
366 char *cend = find_ws (c);
368 native_path = conv_fstab_spaces (c);
369 /* Second field: POSIX path. */
370 c = skip_ws (cend + 1);
375 posix_path = conv_fstab_spaces (c);
376 /* Third field: FS type. */
377 c = skip_ws (cend + 1);
383 /* Forth field: Flags. */
384 c = skip_ws (cend + 1);
389 unsigned mount_flags = MOUNT_SYSTEM;
391 if (!read_flags (c, mount_flags))
393 if (cygwin_internal (CW_CVT_MNT_OPTS, &c, &mount_flags))
397 mount_flags &= ~MOUNT_SYSTEM;
398 if (!strcmp (fs_type, "cygdrive"))
400 for (mnt_t *sm = mount_table; sm < m; ++sm)
401 if (sm->flags & MOUNT_CYGDRIVE)
403 if ((mount_flags & MOUNT_SYSTEM) || !(sm->flags & MOUNT_SYSTEM))
407 sm->posix = strdup (posix_path);
408 sm->flags = mount_flags | MOUNT_CYGDRIVE;
412 m->posix = strdup (posix_path);
413 m->native = strdup ("cygdrive prefix");
414 m->flags = mount_flags | MOUNT_CYGDRIVE;
418 for (mnt_t *sm = mount_table; sm < m; ++sm)
419 if (!strcmp (sm->posix, posix_path))
421 /* Don't allow overriding of a system mount with a user mount. */
422 if ((sm->flags & MOUNT_SYSTEM) && !(mount_flags & MOUNT_SYSTEM))
424 if ((sm->flags & MOUNT_SYSTEM) != (mount_flags & MOUNT_SYSTEM))
426 /* Changing immutable mount points require the override flag. */
427 if ((sm->flags & MOUNT_IMMUTABLE)
428 && !(mount_flags & MOUNT_OVERRIDE))
430 if (mount_flags & MOUNT_OVERRIDE)
431 mount_flags |= MOUNT_IMMUTABLE;
434 sm->native = strdup (native_path);
435 sm->flags = mount_flags;
438 m->posix = strdup (posix_path);
439 unconvert_slashes (native_path);
440 m->native = strdup (native_path);
441 m->flags = mount_flags;
448 #define BUFSIZE 65536
453 static char user[UNLEN + 1];
457 if ((userenv = getenv ("USER")) || (userenv = getenv ("USERNAME")))
458 strncat (user, userenv, UNLEN);
463 from_fstab (bool user, PWCHAR path, PWCHAR path_end)
465 mnt_t *m = mount_table + max_mount_entry;
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))
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
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;
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;
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;
502 max_mount_entry = m - mount_table;
505 PWCHAR u = wcscpy (path_end, L"\\etc\\fstab") + 10;
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)
514 /* Using BUFSIZE-1 leaves space to append two \0. */
515 while (ReadFile (h, got, BUFSIZE - 1 - (got - buf),
520 /* Set end marker. */
521 got[len] = got[len + 1] = '\0';
522 /* Set len to the absolute len of bytes in buf. */
524 /* Reset got to start reading at the start of the buffer again. */
526 while (got < buf + len && (end = strchr (got, '\n')))
528 end[end[-1] == '\r' ? -1 : 0] = '\0';
529 if (from_fstab_line (m, got, user))
533 if (len < BUFSIZE - 1)
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);
541 buf[len] = buf[len + 1] = '\0';
543 if (got > buf && from_fstab_line (m, got, user))
545 max_mount_entry = m - mount_table;
548 #endif /* !FSTAB_ONLY */
549 #endif /* !TESTSUITE */
554 mnt_sort (const void *a, const void *b)
556 const mnt_t *ma = (const mnt_t *) a;
557 const mnt_t *mb = (const mnt_t *) b;
560 ret = (ma->flags & MOUNT_CYGDRIVE) - (mb->flags & MOUNT_CYGDRIVE);
563 ret = (ma->flags & MOUNT_SYSTEM) - (mb->flags & MOUNT_SYSTEM);
566 return strcmp (ma->posix, mb->posix);
569 extern "C" WCHAR cygwin_dll_path[];
574 /* If TESTSUITE is defined, bypass this whole function as a harness
575 mount table will be provided. */
584 for (mnt_t *m1 = mount_table; m1->posix; m1++)
588 free ((char *) m1->native);
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))
604 path_end = wcsrchr (path, L'\\');
607 if (!cygwin_dll_path[0])
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)))
619 path_end = wcsrchr (path, L'\\');
622 /* If we can't create a valid installation dir from that, try to fetch
623 the installation dir from the setup registry key. */
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)
631 len = 32768 * sizeof (WCHAR);
632 ret = RegQueryValueExW (setup_key, L"rootdir", NULL, NULL,
634 RegCloseKey (setup_key);
635 if (ret == ERROR_SUCCESS)
638 if (ret == ERROR_SUCCESS)
639 path_end = wcschr (path, L'\0');
641 /* If we can't fetch an installation dir, bail out. */
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) */
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.
655 LEN1 = strlen (PATH1). It's passed because often it's already known.
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
667 path_prefix_p (const char *path1, const char *path2, int len1)
669 /* Handle case where PATH1 has trailing '/' and when it doesn't. */
670 if (len1 > 0 && isslash (path1[len1 - 1]))
674 return isslash (path2[0]) && !isslash (path2[1]);
676 if (strncasecmp (path1, path2, len1) != 0)
679 return isslash (path2[len1]) || path2[len1] == 0 || path1[len1 - 1] == ':';
683 vconcat (const char *s, va_list v)
695 unc = isslash (*s) && isslash (s[1]);
699 arg = va_arg (v, char *);
706 rv = (char *) malloc (len + 1);
711 arg = va_arg (v, char *);
720 /* concat is only used for urls and files, so we can safely
721 canonicalize the results */
722 for (p = d = rv; *p; p++)
725 /* special case for URLs */
726 if (*p == ':' && p[1] == '/' && p[2] == '/' && p > rv + 1)
731 else if (isslash (*p))
745 concat (const char *s, ...)
751 return vconcat (s, v);
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. */
763 rel_vconcat (const char *cwd, const char *s, va_list v)
765 char pathbuf[MAX_PATH];
766 if (!cwd || *cwd == '\0')
768 if (!GetCurrentDirectory (MAX_PATH, pathbuf))
774 mnt_t *m, *match = NULL;
776 for (m = mount_table; m->posix; m++)
778 if (m->flags & MOUNT_CYGDRIVE)
781 int n = strlen (m->native);
782 if (n < max_len || !path_prefix_p (m->native, cwd, n))
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);
802 temppath = concat ("/", cwd + max_len, "/", s, NULL);
804 char *res = vconcat (temppath, v);
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. */
814 vcygpath (const char *cwd, const char *s, va_list v)
817 mnt_t *m, *match = NULL;
819 if (!max_mount_entry)
822 if (s[0] == '.' && isslash (s[1]))
825 if (s[0] == '/' || s[1] == ':') /* FIXME: too crude? */
826 path = vconcat (s, v);
828 path = rel_vconcat (cwd, s, v);
833 if (strncmp (path, "/./", 3) == 0)
834 memmove (path + 1, path + 3, strlen (path + 3) + 1);
836 for (m = mount_table; m->posix; m++)
838 if (m->flags & MOUNT_CYGDRIVE)
841 int n = strlen (m->posix);
842 if (n < max_len || !path_prefix_p (m->posix, path, n))
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);
856 native = concat (match->native, "\\", path + max_len, NULL);
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);
866 cygpath_rel (const char *cwd, const char *s, ...)
872 return vcygpath (cwd, s, v);
876 cygpath (const char *s, ...)
882 return vcygpath (NULL, s, v);
885 static mnt_t *m = NULL;
888 setmntent (const char *, const char *)
891 if (!max_mount_entry)
896 extern "C" struct mntent *
903 mnt.mnt_fsname = (char *) m->native;
904 mnt.mnt_dir = (char *) m->posix;
906 mnt.mnt_type = (char *) malloc (16);
908 mnt.mnt_opts = (char *) malloc (64);
910 strcpy (mnt.mnt_type, (char *) (m->flags & MOUNT_SYSTEM) ? "system" : "user");
912 if (!(m->flags & MOUNT_BINARY))
913 strcpy (mnt.mnt_opts, (char *) "text");
915 strcpy (mnt.mnt_opts, (char *) "binary");
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");
924 if (m->flags & MOUNT_NOACL)
925 strcat (mnt.mnt_opts, (char *) ",noacl");
927 if (m->flags & MOUNT_NOPOSIX)
928 strcat (mnt.mnt_opts, (char *) ",posix=0");
930 if (m->flags & (MOUNT_AUTOMATIC | MOUNT_CYGDRIVE))
931 strcat (mnt.mnt_opts, (char *) ",auto");
939 #endif /* !FSTAB_ONLY */