newlib/winsup/utils/path.cc

860 lines
21 KiB
C++

/* path.cc
Copyright 2001, 2002, 2003, 2005, 2006, 2007, 2008 Red Hat, Inc.
This file is part of Cygwin.
This software is a copyrighted work licensed under the terms of the
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
details. */
/* The purpose of this file is to hide all the details about accessing
Cygwin's mount table, shortcuts, etc. If the format or location of
the mount table, or the shortcut format changes, this is the file to
change to match it. */
#define str(a) #a
#define scat(a,b) str(a##b)
#include <windows.h>
#include <lmcons.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <wchar.h>
#include "path.h"
#include "cygwin/include/cygwin/version.h"
#include "cygwin/include/sys/mount.h"
#include "cygwin/include/mntent.h"
#include "testsuite.h"
/* Used when treating / and \ as equivalent. */
#define isslash(ch) \
({ \
char __c = (ch); \
((__c) == '/' || (__c) == '\\'); \
})
static const GUID GUID_shortcut =
{0x00021401L, 0, 0, {0xc0, 0, 0, 0, 0, 0, 0, 0x46}};
enum {
WSH_FLAG_IDLIST = 0x01, /* Contains an ITEMIDLIST. */
WSH_FLAG_FILE = 0x02, /* Contains a file locator element. */
WSH_FLAG_DESC = 0x04, /* Contains a description. */
WSH_FLAG_RELPATH = 0x08, /* Contains a relative path. */
WSH_FLAG_WD = 0x10, /* Contains a working dir. */
WSH_FLAG_CMDLINE = 0x20, /* Contains command line args. */
WSH_FLAG_ICON = 0x40 /* Contains a custom icon. */
};
struct win_shortcut_hdr
{
DWORD size; /* Header size in bytes. Must contain 0x4c. */
GUID magic; /* GUID of shortcut files. */
DWORD flags; /* Content flags. See above. */
/* The next fields from attr to icon_no are always set to 0 in Cygwin
and U/Win shortcuts. */
DWORD attr; /* Target file attributes. */
FILETIME ctime; /* These filetime items are never touched by the */
FILETIME mtime; /* system, apparently. Values don't matter. */
FILETIME atime;
DWORD filesize; /* Target filesize. */
DWORD icon_no; /* Icon number. */
DWORD run; /* Values defined in winuser.h. Use SW_NORMAL. */
DWORD hotkey; /* Hotkey value. Set to 0. */
DWORD dummy[2]; /* Future extension probably. Always 0. */
};
static bool
cmp_shortcut_header (win_shortcut_hdr *file_header)
{
/* A Cygwin or U/Win shortcut only contains a description and a relpath.
Cygwin shortcuts also might contain an ITEMIDLIST. The run type is
always set to SW_NORMAL. */
return file_header->size == sizeof (win_shortcut_hdr)
&& !memcmp (&file_header->magic, &GUID_shortcut, sizeof GUID_shortcut)
&& (file_header->flags & ~WSH_FLAG_IDLIST)
== (WSH_FLAG_DESC | WSH_FLAG_RELPATH)
&& file_header->run == SW_NORMAL;
}
int
get_word (HANDLE fh, int offset)
{
unsigned short rv;
unsigned r;
SetLastError(NO_ERROR);
if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
&& GetLastError () != NO_ERROR)
return -1;
if (!ReadFile (fh, &rv, 2, (DWORD *) &r, 0))
return -1;
return rv;
}
/*
* Check the value of GetLastError() to find out whether there was an error.
*/
int
get_dword (HANDLE fh, int offset)
{
int rv;
unsigned r;
SetLastError(NO_ERROR);
if (SetFilePointer (fh, offset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER
&& GetLastError () != NO_ERROR)
return -1;
if (!ReadFile (fh, &rv, 4, (DWORD *) &r, 0))
return -1;
return rv;
}
#define EXE_MAGIC ((int)*(unsigned short *)"MZ")
#define SHORTCUT_MAGIC ((int)*(unsigned short *)"L\0")
#define SYMLINK_COOKIE "!<symlink>"
#define SYMLINK_MAGIC ((int)*(unsigned short *)SYMLINK_COOKIE)
bool
is_exe (HANDLE fh)
{
int magic = get_word (fh, 0x0);
return magic == EXE_MAGIC;
}
bool
is_symlink (HANDLE fh)
{
int magic = get_word (fh, 0x0);
if (magic != SHORTCUT_MAGIC && magic != SYMLINK_MAGIC)
return false;
DWORD got;
BY_HANDLE_FILE_INFORMATION local;
if (!GetFileInformationByHandle (fh, &local))
return false;
if (magic == SHORTCUT_MAGIC)
{
DWORD size;
if (!local.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
return false; /* Not a Cygwin symlink. */
if ((size = GetFileSize (fh, NULL)) > 8192)
return false; /* Not a Cygwin symlink. */
char buf[size];
SetFilePointer (fh, 0, 0, FILE_BEGIN);
if (!ReadFile (fh, buf, size, &got, 0))
return false;
if (got != size || !cmp_shortcut_header ((win_shortcut_hdr *) buf))
return false; /* Not a Cygwin symlink. */
/* TODO: check for invalid path contents
(see symlink_info::check() in ../cygwin/path.cc) */
}
else /* magic == SYMLINK_MAGIC */
{
if (!local.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
return false; /* Not a Cygwin symlink. */
char buf[sizeof (SYMLINK_COOKIE) - 1];
SetFilePointer (fh, 0, 0, FILE_BEGIN);
if (!ReadFile (fh, buf, sizeof (buf), &got, 0))
return false;
if (got != sizeof (buf) ||
memcmp (buf, SYMLINK_COOKIE, sizeof (buf)) != 0)
return false; /* Not a Cygwin symlink. */
}
return true;
}
/* Assumes is_symlink(fh) is true */
bool
readlink (HANDLE fh, char *path, int maxlen)
{
DWORD rv;
char *buf, *cp;
unsigned short len;
win_shortcut_hdr *file_header;
BY_HANDLE_FILE_INFORMATION fi;
if (!GetFileInformationByHandle (fh, &fi)
|| fi.nFileSizeHigh != 0
|| fi.nFileSizeLow > 8192)
return false;
buf = (char *) alloca (fi.nFileSizeLow + 1);
file_header = (win_shortcut_hdr *) buf;
if (SetFilePointer (fh, 0L, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER
|| !ReadFile (fh, buf, fi.nFileSizeLow, &rv, NULL)
|| rv != fi.nFileSizeLow)
return false;
if (fi.nFileSizeLow > sizeof (file_header)
&& cmp_shortcut_header (file_header))
{
cp = buf + sizeof (win_shortcut_hdr);
if (file_header->flags & WSH_FLAG_IDLIST) /* Skip ITEMIDLIST */
cp += *(unsigned short *) cp + 2;
if (!(len = *(unsigned short *) cp))
return false;
cp += 2;
/* Has appended full path? If so, use it instead of description. */
unsigned short relpath_len = *(unsigned short *) (cp + len);
if (cp + len + 2 + relpath_len < buf + fi.nFileSizeLow)
{
cp += len + 2 + relpath_len;
len = *(unsigned short *) cp;
cp += 2;
}
if (len + 1 > maxlen)
return false;
memcpy (path, cp, len);
path[len] = '\0';
return true;
}
else if (strncmp (buf, SYMLINK_COOKIE, strlen (SYMLINK_COOKIE)) == 0
&& fi.nFileSizeLow - strlen (SYMLINK_COOKIE) <= (unsigned) maxlen
&& buf[fi.nFileSizeLow - 1] == '\0')
{
strcpy (path, &buf[strlen (SYMLINK_COOKIE)]);
return true;
}
else
return false;
}
struct mnt_t
{
char *native;
char *posix;
unsigned flags;
};
#ifndef TESTSUITE
static mnt_t mount_table[255];
static int max_mount_entry;
#else
# define TESTSUITE_MOUNT_TABLE
# include "testsuite.h"
# undef TESTSUITE_MOUNT_TABLE
#endif
mnt_t *root_here = NULL;
inline void
unconvert_slashes (char* name)
{
while ((name = strchr (name, '/')) != NULL)
*name++ = '\\';
}
/* These functions aren't called when defined(TESTSUITE) which results
in a compiler warning. */
#ifndef TESTSUITE
inline char *
skip_ws (char *in)
{
while (*in == ' ' || *in == '\t')
++in;
return in;
}
inline char *
find_ws (char *in)
{
while (*in && *in != ' ' && *in != '\t')
++in;
return in;
}
inline char *
conv_fstab_spaces (char *field)
{
register char *sp = field;
while ((sp = strstr (sp, "\\040")) != NULL)
{
*sp++ = ' ';
memmove (sp, sp + 3, strlen (sp + 3) + 1);
}
return field;
}
static struct opt
{
const char *name;
unsigned val;
bool clear;
} oopts[] =
{
{"user", MOUNT_SYSTEM, 1},
{"nouser", MOUNT_SYSTEM, 0},
{"binary", MOUNT_BINARY, 0},
{"text", MOUNT_BINARY, 1},
{"exec", MOUNT_EXEC, 0},
{"notexec", MOUNT_NOTEXEC, 0},
{"cygexec", MOUNT_CYGWIN_EXEC, 0},
{"nosuid", 0, 0},
{"acl", MOUNT_NOACL, 1},
{"noacl", MOUNT_NOACL, 0},
{"posix=1", MOUNT_NOPOSIX, 1},
{"posix=0", MOUNT_NOPOSIX, 0}
};
static bool
read_flags (char *options, unsigned &flags)
{
while (*options)
{
char *p = strchr (options, ',');
if (p)
*p++ = '\0';
else
p = strchr (options, '\0');
for (opt *o = oopts;
o < (oopts + (sizeof (oopts) / sizeof (oopts[0])));
o++)
if (strcmp (options, o->name) == 0)
{
if (o->clear)
flags &= ~o->val;
else
flags |= o->val;
goto gotit;
}
return false;
gotit:
options = p;
}
return true;
}
static bool
from_fstab_line (mnt_t *m, char *line, bool user)
{
char *native_path, *posix_path, *fs_type;
/* First field: Native path. */
char *c = skip_ws (line);
if (!*c || *c == '#')
return false;
char *cend = find_ws (c);
*cend = '\0';
native_path = conv_fstab_spaces (c);
/* Second field: POSIX path. */
c = skip_ws (cend + 1);
if (!*c)
return false;
cend = find_ws (c);
*cend = '\0';
posix_path = conv_fstab_spaces (c);
/* Third field: FS type. */
c = skip_ws (cend + 1);
if (!*c)
return false;
cend = find_ws (c);
*cend = '\0';
fs_type = c;
/* Forth field: Flags. */
c = skip_ws (cend + 1);
if (!*c)
return false;
cend = find_ws (c);
*cend = '\0';
unsigned mount_flags = MOUNT_SYSTEM;
if (!read_flags (c, mount_flags))
return false;
if (user)
mount_flags &= ~MOUNT_SYSTEM;
if (!strcmp (fs_type, "cygdrive"))
{
for (mnt_t *sm = mount_table; sm < m; ++sm)
if (sm->flags & MOUNT_CYGDRIVE)
{
if ((mount_flags & MOUNT_SYSTEM) || !(sm->flags & MOUNT_SYSTEM))
{
if (sm->posix)
free (sm->posix);
sm->posix = strdup (posix_path);
sm->flags = mount_flags | MOUNT_CYGDRIVE;
}
return false;
}
m->posix = strdup (posix_path);
m->native = strdup (".");
m->flags = mount_flags | MOUNT_CYGDRIVE;
}
else
{
for (mnt_t *sm = mount_table; sm < m; ++sm)
if (!strcasecmp (sm->posix, posix_path))
{
if ((mount_flags & MOUNT_SYSTEM) || !(sm->flags & MOUNT_SYSTEM))
{
if (sm->native)
free (sm->native);
sm->native = strdup (native_path);
sm->flags = mount_flags;
}
return false;
}
m->posix = strdup (posix_path);
unconvert_slashes (native_path);
m->native = strdup (native_path);
m->flags = mount_flags;
}
return true;
}
#define BUFSIZE 65536
static char *
get_user ()
{
static char user[UNLEN + 1];
char *userenv;
user[0] = '\0';
if ((userenv = getenv ("USER")) || (userenv = getenv ("USERNAME")))
strncat (user, userenv, UNLEN);
return user;
}
void
from_fstab (bool user, PWCHAR path, PWCHAR path_end)
{
mnt_t *m = mount_table + max_mount_entry;
char buf[BUFSIZE];
if (!user)
{
/* Create a default root dir from path. */
WideCharToMultiByte (GetACP (), 0, path, -1, buf, BUFSIZE,
NULL, NULL);
unconvert_slashes (buf);
char *native_path = buf;
if (!strncmp (native_path, "\\\\?\\", 4))
native_path += 4;
if (!strncmp (native_path, "UNC\\", 4))
*(native_path += 2) = '\\';
m->posix = strdup ("/");
m->native = strdup (native_path);
m->flags = MOUNT_SYSTEM | MOUNT_BINARY;
++m;
/* Create a default cygdrive entry. Note that this is a user entry.
This allows to override it with mount, unless the sysadmin created
a cygdrive entry in /etc/fstab. */
m->posix = strdup (CYGWIN_INFO_CYGDRIVE_DEFAULT_PREFIX);
m->native = strdup (".");
m->flags = MOUNT_BINARY | MOUNT_CYGDRIVE;
++m;
max_mount_entry = m - mount_table;
}
PWCHAR u = wcscpy (path_end, L"\\etc\\fstab") + 10;
if (user)
MultiByteToWideChar (GetACP (), 0, get_user (), -1,
wcscpy (u, L".d\\") + 3, BUFSIZE - (u - path));
HANDLE h = CreateFileW (path, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE)
return;
char *got = buf;
DWORD len = 0;
/* Using BUFSIZE-1 leaves space to append two \0. */
while (ReadFile (h, got, BUFSIZE - 1 - (got - buf),
&len, NULL))
{
char *end;
/* Set end marker. */
got[len] = got[len + 1] = '\0';
/* Set len to the absolute len of bytes in buf. */
len += got - buf;
/* Reset got to start reading at the start of the buffer again. */
got = buf;
while (got < buf + len && (end = strchr (got, '\n')))
{
end[end[-1] == '\r' ? -1 : 0] = '\0';
if (from_fstab_line (m, got, user))
++m;
got = end + 1;
}
if (len < BUFSIZE - 1)
break;
/* We have to read once more. Move remaining bytes to the start of
the buffer and reposition got so that it points to the end of
the remaining bytes. */
len = buf + len - got;
memmove (buf, got, len);
got = buf + len;
buf[len] = buf[len + 1] = '\0';
}
if (got > buf && from_fstab_line (m, got, user))
++m;
max_mount_entry = m - mount_table;
CloseHandle (h);
}
#endif
static int
mnt_sort (const void *a, const void *b)
{
const mnt_t *ma = (const mnt_t *) a;
const mnt_t *mb = (const mnt_t *) b;
int ret;
ret = (ma->flags & MOUNT_CYGDRIVE) - (mb->flags & MOUNT_CYGDRIVE);
if (ret)
return ret;
ret = (ma->flags & MOUNT_SYSTEM) - (mb->flags & MOUNT_SYSTEM);
if (ret)
return ret;
return strcmp (ma->posix, mb->posix);
}
static void
read_mounts ()
{
/* If TESTSUITE is defined, bypass this whole function as a harness
mount table will be provided. */
#ifndef TESTSUITE
HKEY setup_key;
LONG ret;
DWORD len;
WCHAR path[32768];
PWCHAR path_end;
for (mnt_t *m1 = mount_table; m1->posix; m1++)
{
free (m1->posix);
if (m1->native)
free ((char *) m1->native);
m1->posix = NULL;
}
max_mount_entry = 0;
for (int i = 0; i < 2; ++i)
if ((ret = RegOpenKeyExW (i ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
L"Software\\Cygwin\\setup", 0,
KEY_READ, &setup_key)) == ERROR_SUCCESS)
{
len = 32768 * sizeof (WCHAR);
ret = RegQueryValueExW (setup_key, L"rootdir", NULL, NULL,
(PBYTE) path, &len);
RegCloseKey (setup_key);
if (ret == ERROR_SUCCESS)
break;
}
if (ret == ERROR_SUCCESS)
path_end = wcschr (path, L'\0');
else
{
if (!GetModuleFileNameW (NULL, path, 32768))
return;
path_end = wcsrchr (path, L'\\');
if (path_end)
{
*path_end = L'\0';
path_end = wcsrchr (path, L'\\');
}
}
if (!path_end)
return;
*path_end = L'\0';
from_fstab (false, path, path_end);
from_fstab (true, path, path_end);
qsort (mount_table, max_mount_entry, sizeof (mnt_t), mnt_sort);
#endif /* !defined(TESTSUITE) */
}
/* Return non-zero if PATH1 is a prefix of PATH2.
Both are assumed to be of the same path style and / vs \ usage.
Neither may be "".
LEN1 = strlen (PATH1). It's passed because often it's already known.
Examples:
/foo/ is a prefix of /foo <-- may seem odd, but desired
/foo is a prefix of /foo/
/ is a prefix of /foo/bar
/ is not a prefix of foo/bar
foo/ is a prefix foo/bar
/foo is not a prefix of /foobar
*/
static int
path_prefix_p (const char *path1, const char *path2, int len1)
{
/* Handle case where PATH1 has trailing '/' and when it doesn't. */
if (len1 > 0 && isslash (path1[len1 - 1]))
len1--;
if (len1 == 0)
return isslash (path2[0]) && !isslash (path2[1]);
if (strncasecmp (path1, path2, len1) != 0)
return 0;
return isslash (path2[len1]) || path2[len1] == 0 || path1[len1 - 1] == ':';
}
static char *
vconcat (const char *s, va_list v)
{
int len;
char *rv, *arg;
va_list save_v = v;
int unc;
if (!s)
return 0;
len = strlen (s);
unc = isslash (*s) && isslash (s[1]);
while (1)
{
arg = va_arg (v, char *);
if (arg == 0)
break;
len += strlen (arg);
}
va_end (v);
rv = (char *) malloc (len + 1);
strcpy (rv, s);
v = save_v;
while (1)
{
arg = va_arg (v, char *);
if (arg == 0)
break;
strcat (rv, arg);
}
va_end (v);
char *d, *p;
/* concat is only used for urls and files, so we can safely
canonicalize the results */
for (p = d = rv; *p; p++)
{
*d++ = *p;
/* special case for URLs */
if (*p == ':' && p[1] == '/' && p[2] == '/' && p > rv + 1)
{
*d++ = *++p;
*d++ = *++p;
}
else if (isslash (*p))
{
if (p == rv && unc)
*d++ = *p++;
while (p[1] == '/')
p++;
}
}
*d = 0;
return rv;
}
static char *
concat (const char *s, ...)
{
va_list v;
va_start (v, s);
return vconcat (s, v);
}
/* This is a helper function for when vcygpath is passed what appears
to be a relative POSIX path. We take a Win32 CWD (either as specified
in 'cwd' or as retrieved with GetCurrentDirectory() if 'cwd' is NULL)
and find the mount table entry with the longest match. We replace the
matching portion with the corresponding POSIX prefix, and to that append
's' and anything in 'v'. The returned result is a mostly-POSIX
absolute path -- 'mostly' because the portions of CWD that didn't
match the mount prefix will still have '\\' separators. */
static char *
rel_vconcat (const char *cwd, const char *s, va_list v)
{
char pathbuf[MAX_PATH];
if (!cwd || *cwd == '\0')
{
if (!GetCurrentDirectory (MAX_PATH, pathbuf))
return NULL;
cwd = pathbuf;
}
int max_len = -1;
mnt_t *m, *match = NULL;
for (m = mount_table; m->posix; m++)
{
if (m->flags & MOUNT_CYGDRIVE)
continue;
int n = strlen (m->native);
if (n < max_len || !path_prefix_p (m->native, cwd, n))
continue;
max_len = n;
match = m;
}
char *temppath;
if (!match)
// No prefix matched - best effort to return meaningful value.
temppath = concat (cwd, "/", s, NULL);
else if (strcmp (match->posix, "/") != 0)
// Matched on non-root. Copy matching prefix + remaining 'path'.
temppath = concat (match->posix, cwd + max_len, "/", s, NULL);
else if (cwd[max_len] == '\0')
// Matched on root and there's no remaining 'path'.
temppath = concat ("/", s, NULL);
else if (isslash (cwd[max_len]))
// Matched on root but remaining 'path' starts with a slash anyway.
temppath = concat (cwd + max_len, "/", s, NULL);
else
temppath = concat ("/", cwd + max_len, "/", s, NULL);
char *res = vconcat (temppath, v);
free (temppath);
return res;
}
/* Convert a POSIX path in 's' to an absolute Win32 path, and append
anything in 'v' to the end, returning the result. If 's' is a
relative path then 'cwd' is used as the working directory to make
it absolute. Pass NULL in 'cwd' to use GetCurrentDirectory. */
static char *
vcygpath (const char *cwd, const char *s, va_list v)
{
int max_len = -1;
mnt_t *m, *match = NULL;
if (!max_mount_entry)
read_mounts ();
char *path;
if (s[0] == '.' && isslash (s[1]))
s += 2;
if (s[0] == '/' || s[1] == ':') /* FIXME: too crude? */
path = vconcat (s, v);
else
path = rel_vconcat (cwd, s, v);
if (!path)
return NULL;
if (strncmp (path, "/./", 3) == 0)
memmove (path + 1, path + 3, strlen (path + 3) + 1);
for (m = mount_table; m->posix; m++)
{
if (m->flags & MOUNT_CYGDRIVE)
continue;
int n = strlen (m->posix);
if (n < max_len || !path_prefix_p (m->posix, path, n))
continue;
max_len = n;
match = m;
}
char *native;
if (match == NULL)
native = strdup (path);
else if (max_len == (int) strlen (path))
native = strdup (match->native);
else if (isslash (path[max_len]))
native = concat (match->native, path + max_len, NULL);
else
native = concat (match->native, "\\", path + max_len, NULL);
free (path);
unconvert_slashes (native);
for (char *s = strstr (native + 1, "\\.\\"); s && *s; s = strstr (s, "\\.\\"))
memmove (s + 1, s + 3, strlen (s + 3) + 1);
return native;
}
char *
cygpath_rel (const char *cwd, const char *s, ...)
{
va_list v;
va_start (v, s);
return vcygpath (cwd, s, v);
}
char *
cygpath (const char *s, ...)
{
va_list v;
va_start (v, s);
return vcygpath (NULL, s, v);
}
static mnt_t *m = NULL;
extern "C" FILE *
setmntent (const char *, const char *)
{
m = mount_table;
if (!max_mount_entry)
read_mounts ();
return NULL;
}
extern "C" struct mntent *
getmntent (FILE *)
{
static mntent mnt;
if (!m->posix)
return NULL;
mnt.mnt_fsname = (char *) m->native;
mnt.mnt_dir = (char *) m->posix;
if (!mnt.mnt_type)
mnt.mnt_type = (char *) malloc (1024);
if (!mnt.mnt_opts)
mnt.mnt_opts = (char *) malloc (1024);
if (m->flags & MOUNT_SYSTEM)
strcpy (mnt.mnt_type, (char *) "system");
else
strcpy (mnt.mnt_type, (char *) "user");
if (!(m->flags & MOUNT_BINARY))
strcpy (mnt.mnt_opts, (char *) "textmode");
else
strcpy (mnt.mnt_opts, (char *) "binmode");
if (m->flags & MOUNT_CYGWIN_EXEC)
strcat (mnt.mnt_opts, (char *) ",cygexec");
else if (m->flags & MOUNT_EXEC)
strcat (mnt.mnt_opts, (char *) ",exec");
else if (m->flags & MOUNT_NOTEXEC)
strcat (mnt.mnt_opts, (char *) ",noexec");
if ((m->flags & MOUNT_CYGDRIVE)) /* cygdrive */
strcat (mnt.mnt_opts, (char *) ",cygdrive");
if ((m->flags & MOUNT_NOACL))
strcat (mnt.mnt_opts, (char *) ",noacl");
if ((m->flags & MOUNT_NOPOSIX))
strcat (mnt.mnt_opts, (char *) ",posix=0");
mnt.mnt_freq = 1;
mnt.mnt_passno = 1;
m++;
return &mnt;
}