3 Copyright 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009, 2010,
6 This file is part of Cygwin.
8 This software is a copyrighted work licensed under the terms of the
9 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
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. */
18 #define scat(a,b) str(a##b)
26 #include "cygwin/include/cygwin/version.h"
27 #include "cygwin/include/sys/mount.h"
28 #include "cygwin/include/mntent.h"
29 #include "testsuite.h"
31 #include <sys/cygwin.h>
36 /* Used when treating / and \ as equivalent. */
40 ((__c) == '/' || (__c) == '\\'); \
44 static const GUID GUID_shortcut =
45 {0x00021401L, 0, 0, {0xc0, 0, 0, 0, 0, 0, 0, 0x46}};
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. */
57 struct win_shortcut_hdr
59 DWORD size; /* Header size in bytes. Must contain 0x4c. */
60 GUID magic; /* GUID of shortcut files. */
61 DWORD flags; /* Content flags. See above. */
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. */
69 DWORD filesize; /* Target filesize. */
70 DWORD icon_no; /* Icon number. */
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. */
78 cmp_shortcut_header (win_shortcut_hdr *file_header)
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;
91 get_word (HANDLE fh, int offset)
96 SetLastError(NO_ERROR);
97 if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
98 && GetLastError () != NO_ERROR)
101 if (!ReadFile (fh, &rv, 2, (DWORD *) &r, 0))
108 * Check the value of GetLastError() to find out whether there was an error.
111 get_dword (HANDLE fh, int offset)
116 SetLastError(NO_ERROR);
117 if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
118 && GetLastError () != NO_ERROR)
121 if (!ReadFile (fh, &rv, 4, (DWORD *) &r, 0))
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)
135 int magic = get_word (fh, 0x0);
136 return magic == EXE_MAGIC;
140 is_symlink (HANDLE fh)
142 int magic = get_word (fh, 0x0);
143 if (magic != SHORTCUT_MAGIC && magic != SYMLINK_MAGIC)
146 BY_HANDLE_FILE_INFORMATION local;
147 if (!GetFileInformationByHandle (fh, &local))
149 if (magic == SHORTCUT_MAGIC)
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. */
157 SetFilePointer (fh, 0, 0, FILE_BEGIN);
158 if (!ReadFile (fh, buf, size, &got, 0))
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) */
165 else /* magic == SYMLINK_MAGIC */
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))
173 if (got != sizeof (buf) ||
174 memcmp (buf, SYMLINK_COOKIE, sizeof (buf)) != 0)
175 return false; /* Not a Cygwin symlink. */
180 /* Assumes is_symlink(fh) is true */
182 readlink (HANDLE fh, char *path, int maxlen)
187 win_shortcut_hdr *file_header;
188 BY_HANDLE_FILE_INFORMATION fi;
190 if (!GetFileInformationByHandle (fh, &fi)
191 || fi.nFileSizeHigh != 0
192 || fi.nFileSizeLow > 4 * 65536)
195 buf = (char *) alloca (fi.nFileSizeLow + 1);
196 file_header = (win_shortcut_hdr *) buf;
198 if (SetFilePointer (fh, 0L, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER
199 || !ReadFile (fh, buf, fi.nFileSizeLow, &rv, NULL)
200 || rv != fi.nFileSizeLow)
203 if (fi.nFileSizeLow > sizeof (file_header)
204 && cmp_shortcut_header (file_header))
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))
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)
216 cp += len + 2 + relpath_len;
217 len = *(unsigned short *) cp;
220 if (*(PWCHAR) cp == 0xfeff) /* BOM */
222 len = wcstombs (NULL, (wchar_t *) (cp + 2), 0);
223 if (len == (size_t) -1 || len + 1 > maxlen)
225 wcstombs (path, (wchar_t *) (cp + 2), len + 1);
227 else if (len + 1 > maxlen)
230 memcpy (path, cp, len);
234 else if (strncmp (buf, SYMLINK_COOKIE, strlen (SYMLINK_COOKIE)) == 0
235 && buf[fi.nFileSizeLow - 1] == '\0')
237 cp = buf + strlen (SYMLINK_COOKIE);
238 if (*(PWCHAR) cp == 0xfeff) /* BOM */
240 len = wcstombs (NULL, (wchar_t *) (cp + 2), 0);
241 if (len == (size_t) -1 || len + 1 > maxlen)
243 wcstombs (path, (wchar_t *) (cp + 2), len + 1);
245 else if (fi.nFileSizeLow - strlen (SYMLINK_COOKIE) > (unsigned) maxlen)
254 #endif /* !FSTAB_ONLY */
257 mnt_t mount_table[255];
260 # define TESTSUITE_MOUNT_TABLE
261 # include "testsuite.h"
262 # undef TESTSUITE_MOUNT_TABLE
266 unconvert_slashes (char* name)
268 while ((name = strchr (name, '/')) != NULL)
272 /* These functions aren't called when defined(TESTSUITE) which results
273 in a compiler warning. */
278 while (*in == ' ' || *in == '\t')
286 while (*in && *in != ' ' && *in != '\t')
292 conv_fstab_spaces (char *field)
294 register char *sp = field;
295 while ((sp = strstr (sp, "\\040")) != NULL)
298 memmove (sp, sp + 3, strlen (sp + 3) + 1);
311 {"acl", MOUNT_NOACL, 1},
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},
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}
330 read_flags (char *options, unsigned &flags)
334 char *p = strchr (options, ',');
338 p = strchr (options, '\0');
341 o < (oopts + (sizeof (oopts) / sizeof (oopts[0])));
343 if (strcmp (options, o->name) == 0)
361 from_fstab_line (mnt_t *m, char *line, bool user)
363 char *native_path, *posix_path, *fs_type;
365 /* First field: Native path. */
366 char *c = skip_ws (line);
367 if (!*c || *c == '#')
369 char *cend = find_ws (c);
371 native_path = conv_fstab_spaces (c);
372 /* Second field: POSIX path. */
373 c = skip_ws (cend + 1);
378 posix_path = conv_fstab_spaces (c);
379 /* Third field: FS type. */
380 c = skip_ws (cend + 1);
386 /* Forth field: Flags. */
387 c = skip_ws (cend + 1);
392 unsigned mount_flags = MOUNT_SYSTEM;
394 if (!read_flags (c, mount_flags))
396 if (cygwin_internal (CW_CVT_MNT_OPTS, &c, &mount_flags))
400 mount_flags &= ~MOUNT_SYSTEM;
401 if (!strcmp (fs_type, "cygdrive"))
403 for (mnt_t *sm = mount_table; sm < m; ++sm)
404 if (sm->flags & MOUNT_CYGDRIVE)
406 if ((mount_flags & MOUNT_SYSTEM) || !(sm->flags & MOUNT_SYSTEM))
410 sm->posix = strdup (posix_path);
411 sm->flags = mount_flags | MOUNT_CYGDRIVE;
415 m->posix = strdup (posix_path);
416 m->native = strdup ("cygdrive prefix");
417 m->flags = mount_flags | MOUNT_CYGDRIVE;
421 for (mnt_t *sm = mount_table; sm < m; ++sm)
422 if (!strcmp (sm->posix, posix_path))
424 /* Don't allow overriding of a system mount with a user mount. */
425 if ((sm->flags & MOUNT_SYSTEM) && !(mount_flags & MOUNT_SYSTEM))
427 if ((sm->flags & MOUNT_SYSTEM) != (mount_flags & MOUNT_SYSTEM))
429 /* Changing immutable mount points require the override flag. */
430 if ((sm->flags & MOUNT_IMMUTABLE)
431 && !(mount_flags & MOUNT_OVERRIDE))
433 if (mount_flags & MOUNT_OVERRIDE)
434 mount_flags |= MOUNT_IMMUTABLE;
437 sm->native = strdup (native_path);
438 sm->flags = mount_flags;
441 m->posix = strdup (posix_path);
442 unconvert_slashes (native_path);
443 m->native = strdup (native_path);
444 m->flags = mount_flags;
451 #define BUFSIZE 65536
456 static char user[UNLEN + 1];
460 if ((userenv = getenv ("USER")) || (userenv = getenv ("USERNAME")))
461 strncat (user, userenv, UNLEN);
466 from_fstab (bool user, PWCHAR path, PWCHAR path_end)
468 mnt_t *m = mount_table + max_mount_entry;
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))
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
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;
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;
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;
505 max_mount_entry = m - mount_table;
508 PWCHAR u = wcscpy (path_end, L"\\etc\\fstab") + 10;
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)
517 /* Using BUFSIZE-1 leaves space to append two \0. */
518 while (ReadFile (h, got, BUFSIZE - 1 - (got - buf),
523 /* Set end marker. */
524 got[len] = got[len + 1] = '\0';
525 /* Set len to the absolute len of bytes in buf. */
527 /* Reset got to start reading at the start of the buffer again. */
529 while (got < buf + len && (end = strchr (got, '\n')))
531 end[end[-1] == '\r' ? -1 : 0] = '\0';
532 if (from_fstab_line (m, got, user))
536 if (len < BUFSIZE - 1)
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);
544 buf[len] = buf[len + 1] = '\0';
546 if (got > buf && from_fstab_line (m, got, user))
548 max_mount_entry = m - mount_table;
551 #endif /* !FSTAB_ONLY */
552 #endif /* !TESTSUITE */
557 mnt_sort (const void *a, const void *b)
559 const mnt_t *ma = (const mnt_t *) a;
560 const mnt_t *mb = (const mnt_t *) b;
563 ret = (ma->flags & MOUNT_CYGDRIVE) - (mb->flags & MOUNT_CYGDRIVE);
566 ret = (ma->flags & MOUNT_SYSTEM) - (mb->flags & MOUNT_SYSTEM);
569 return strcmp (ma->posix, mb->posix);
572 extern "C" WCHAR cygwin_dll_path[];
577 /* If TESTSUITE is defined, bypass this whole function as a harness
578 mount table will be provided. */
586 for (mnt_t *m1 = mount_table; m1->posix; m1++)
590 free ((char *) m1->native);
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))
606 path_end = wcsrchr (path, L'\\');
609 if (!cygwin_dll_path[0])
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)))
621 path_end = wcsrchr (path, L'\\');
624 /* If we can't create a valid installation dir from that, try to fetch
625 the installation dir from the setup registry key. */
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)
633 len = 32768 * sizeof (WCHAR);
634 ret = RegQueryValueExW (setup_key, L"rootdir", NULL, NULL,
636 RegCloseKey (setup_key);
637 if (ret == ERROR_SUCCESS)
640 if (ret == ERROR_SUCCESS)
641 path_end = wcschr (path, L'\0');
643 /* If we can't fetch an installation dir, bail out. */
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) */
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.
657 LEN1 = strlen (PATH1). It's passed because often it's already known.
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
669 path_prefix_p (const char *path1, const char *path2, int len1)
671 /* Handle case where PATH1 has trailing '/' and when it doesn't. */
672 if (len1 > 0 && isslash (path1[len1 - 1]))
676 return isslash (path2[0]) && !isslash (path2[1]);
678 if (strncasecmp (path1, path2, len1) != 0)
681 return isslash (path2[len1]) || path2[len1] == 0 || path1[len1 - 1] == ':';
685 vconcat (const char *s, va_list v)
697 unc = isslash (*s) && isslash (s[1]);
701 arg = va_arg (v, char *);
708 rv = (char *) malloc (len + 1);
713 arg = va_arg (v, char *);
722 /* concat is only used for urls and files, so we can safely
723 canonicalize the results */
724 for (p = d = rv; *p; p++)
727 /* special case for URLs */
728 if (*p == ':' && p[1] == '/' && p[2] == '/' && p > rv + 1)
733 else if (isslash (*p))
747 concat (const char *s, ...)
753 return vconcat (s, v);
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. */
765 rel_vconcat (const char *cwd, const char *s, va_list v)
767 char pathbuf[MAX_PATH];
768 if (!cwd || *cwd == '\0')
770 if (!GetCurrentDirectory (MAX_PATH, pathbuf))
776 mnt_t *m, *match = NULL;
778 for (m = mount_table; m->posix; m++)
780 if (m->flags & MOUNT_CYGDRIVE)
783 int n = strlen (m->native);
784 if (n < max_len || !path_prefix_p (m->native, cwd, n))
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);
804 temppath = concat ("/", cwd + max_len, "/", s, NULL);
806 char *res = vconcat (temppath, v);
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. */
816 vcygpath (const char *cwd, const char *s, va_list v)
819 mnt_t *m, *match = NULL;
821 if (!max_mount_entry)
824 if (s[0] == '.' && isslash (s[1]))
827 if (s[0] == '/' || s[1] == ':') /* FIXME: too crude? */
828 path = vconcat (s, v);
830 path = rel_vconcat (cwd, s, v);
835 if (strncmp (path, "/./", 3) == 0)
836 memmove (path + 1, path + 3, strlen (path + 3) + 1);
838 for (m = mount_table; m->posix; m++)
840 if (m->flags & MOUNT_CYGDRIVE)
843 int n = strlen (m->posix);
844 if (n < max_len || !path_prefix_p (m->posix, path, n))
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);
858 native = concat (match->native, "\\", path + max_len, NULL);
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);
868 cygpath_rel (const char *cwd, const char *s, ...)
874 return vcygpath (cwd, s, v);
878 cygpath (const char *s, ...)
884 return vcygpath (NULL, s, v);
887 static mnt_t *m = NULL;
890 setmntent (const char *, const char *)
893 if (!max_mount_entry)
898 extern "C" struct mntent *
905 mnt.mnt_fsname = (char *) m->native;
906 mnt.mnt_dir = (char *) m->posix;
908 mnt.mnt_type = (char *) malloc (16);
910 mnt.mnt_opts = (char *) malloc (64);
912 strcpy (mnt.mnt_type, (char *) (m->flags & MOUNT_SYSTEM) ? "system" : "user");
914 if (!(m->flags & MOUNT_BINARY))
915 strcpy (mnt.mnt_opts, (char *) "text");
917 strcpy (mnt.mnt_opts, (char *) "binary");
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");
926 if (m->flags & MOUNT_NOACL)
927 strcat (mnt.mnt_opts, (char *) ",noacl");
929 if (m->flags & MOUNT_NOPOSIX)
930 strcat (mnt.mnt_opts, (char *) ",posix=0");
932 if (m->flags & (MOUNT_AUTOMATIC | MOUNT_CYGDRIVE))
933 strcat (mnt.mnt_opts, (char *) ",auto");
941 #endif /* !FSTAB_ONLY */