libc/winsup/utils/mount.cc

555 lines
13 KiB
C++

/* mount.cc
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. */
#include <stdio.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <mntent.h>
#include <windows.h>
#include <sys/cygwin.h>
#include <cygwin/version.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <dirent.h>
#include "path.h"
#include <errno.h>
#define NT_MAX_PATH 32768
#define EXEC_FLAGS (MOUNT_EXEC | MOUNT_NOTEXEC | MOUNT_CYGWIN_EXEC)
static void mount_entries (void);
static void show_mounts (void);
static void show_cygdrive_info (void);
static void change_cygdrive_prefix (const char *new_prefix, int flags);
static int mount_already_exists (const char *posix_path, int flags);
// static short create_missing_dirs = FALSE;
static bool force = false;
static const char *progname;
static void
error (const char *path)
{
fprintf (stderr, "%s: %s: %s\n", progname, path,
(errno == EMFILE) ? "Too many mount entries" : strerror (errno));
exit (1);
}
/* FIXME: do_mount should also print a warning message if the dev arg
is a non-existent Win32 path. */
static void
do_mount (const char *dev, const char *where, int flags)
{
struct stat statbuf;
int statres;
statres = stat (where, &statbuf);
#if 0
if (statres == -1)
{
/* FIXME: this'll fail if mount dir is missing any parent dirs */
if (create_missing_dirs == TRUE)
{
if (mkdir (where, 0755) == -1)
fprintf (stderr, "Warning: unable to create %s!\n", where);
else
statres = 0; /* Pretend stat succeeded if we could mkdir. */
}
}
#endif
if (statres == -1)
{
if (!force)
fprintf (stderr, "%s: warning - %s does not exist.\n", progname, where);
}
else if (!(statbuf.st_mode & S_IFDIR))
{
if (!force)
fprintf (stderr, "%s: warning: %s is not a directory.\n",
progname, where);
}
if (!force && !(flags & (EXEC_FLAGS | MOUNT_BIND)) && strlen (dev))
{
char devtmp[1 + 2 * strlen (dev)];
strcpy (devtmp, dev);
char c = strchr (devtmp, '\0')[-1];
if (c == '/' || c == '\\')
strcat (devtmp, ".");
/* Use a curious property of Windows which allows the use of \.. even
on non-directory paths. */
for (const char *p = dev; (p = strpbrk (p, "/\\")); p++)
strcat (devtmp, "\\..");
strcat (devtmp, "\\");
if (GetDriveType (devtmp) == DRIVE_REMOTE)
{
fprintf (stderr,
"%s: defaulting to 'notexec' mount option for speed since native path\n"
"%*creferences a remote share. Use '-f' option to override.\n",
progname, (int) strlen(progname) + 2, ' ');
flags |= MOUNT_NOTEXEC;
}
}
if (mount (dev, where, flags))
error (where);
}
static void
from_fstab (bool user)
{
char path[PATH_MAX];
char buf[65536];
mnt_t *m = mount_table + max_mount_entry;
strcpy (path, "/etc/fstab");
if (user)
{
strcat (path, ".d/");
strcat (path, getlogin ());
}
FILE *fh = fopen (path, "rt");
if (!fh)
return;
while (fgets (buf, 65536, fh))
{
char *c = strrchr (buf, '\n');
if (c)
*c = '\0';
if (from_fstab_line (m, buf, user))
++m;
}
max_mount_entry = m - mount_table;
fclose (fh);
}
static void
do_mount_from_fstab (const char *where)
{
force = true;
/* Read fstab entries. */
from_fstab (false);
from_fstab (true);
/* Loop through fstab entries and see if it matches `where'. If `where'
is NULL, all entries match. */
bool exists = false;
for (mnt_t *m = mount_table; m - mount_table < max_mount_entry; ++m)
if (!where || !strcmp (where, m->posix))
{
if (m->flags & MOUNT_CYGDRIVE)
{
/* Get the cygdrive info */
char user[MAX_PATH];
char system[MAX_PATH];
char user_flags[MAX_PATH];
char system_flags[MAX_PATH];
exists = true;
cygwin_internal (CW_GET_CYGDRIVE_INFO, user, system, user_flags,
system_flags);
if ((*user && strcmp (user, m->posix) != 0)
|| (*system && strcmp (system, m->posix) != 0))
if (mount (NULL, m->posix, m->flags))
error (m->posix);
}
else
{
exists = true;
/* Compare with existing mount table. If the entry doesn't exist,
mount it. */
FILE *mt = setmntent ("/-not-used-", "r");
struct mntent *p;
while ((p = getmntent (mt)) != NULL)
if (!strcmp (m->posix, p->mnt_dir))
break;
if (!p)
do_mount (m->native, m->posix, m->flags);
endmntent (mt);
if (where)
break;
}
}
if (!exists && where)
fprintf (stderr,
"%s: can't find %s in /etc/fstab or in /etc/fstab.d/$USER\n",
progname, where);
}
static struct option longopts[] =
{
{"all", no_argument, NULL, 'a'},
{"change-cygdrive-prefix", no_argument, NULL, 'c'},
{"force", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h' },
{"mount-entries", no_argument, NULL, 'm'},
{"options", required_argument, NULL, 'o'},
{"show-cygdrive-prefix", no_argument, NULL, 'p'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}
};
static char opts[] = "acfhmpVo:";
static void
usage (FILE *where = stderr)
{
char *options;
fprintf (where, "Usage: %1$s [OPTION] [<win32path> <posixpath>]\n\
%1$s -a\n\
%1$s <posixpath>\n\
\n\
Display information about mounted filesystems, or mount a filesystem\n\
\n\
-a, --all mount all filesystems mentioned in fstab\n\
-c, --change-cygdrive-prefix change the cygdrive path prefix to <posixpath>\n\
-f, --force force mount, don't warn about missing mount\n\
point directories\n\
-h, --help output usage information and exit\n\
-m, --mount-entries write fstab entries to replicate mount points\n\
and cygdrive prefixes\n\
-o, --options X[,X...] specify mount options\n\
-p, --show-cygdrive-prefix show user and/or system cygdrive path prefix\n\
-V, --version output version information and exit\n\n",
progname);
if (!cygwin_internal (CW_LST_MNT_OPTS, &options))
fprintf (where, "Valid options are: %s\n\n", options);
exit (where == stderr ? 1 : 0);
}
static void
print_version ()
{
printf ("mount (cygwin) %d.%d.%d\n"
"Mount filesystem utility\n"
"Copyright (C) 1996 - %s Cygwin Authors\n"
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
CYGWIN_VERSION_DLL_MAJOR / 1000,
CYGWIN_VERSION_DLL_MAJOR % 1000,
CYGWIN_VERSION_DLL_MINOR,
strrchr (__DATE__, ' ') + 1);
}
static char *
concat3 (char *a, const char *b, const char *c)
{
size_t totlen = strlen (a) + strlen (b) + strlen (c) + 1;
a = (char *) realloc (a, totlen);
return strcat (strcat (a, b), c);
}
int
main (int argc, char **argv)
{
int i;
int flags = MOUNT_BINARY;
char *options = strdup ("");
enum do_what
{
nada,
saw_change_cygdrive_prefix,
saw_show_cygdrive_prefix,
saw_mount_commands,
saw_mount_all,
} do_what = nada;
progname = program_invocation_short_name;
if (argc == 1)
{
show_mounts ();
exit (0);
}
while ((i = getopt_long (argc, argv, opts, longopts, NULL)) != EOF)
switch (i)
{
case 'a':
if (do_what == nada)
do_what = saw_mount_all;
else
usage ();
break;
case 'c':
if (do_what == nada)
do_what = saw_change_cygdrive_prefix;
else
usage ();
break;
case 'f':
force = true;
break;
case 'h':
usage (stdout);
break;
case 'm':
if (do_what == nada)
do_what = saw_mount_commands;
else
usage ();
break;
case 'o':
if (do_what == saw_mount_all)
usage ();
else if (*options)
options = concat3 (options, ",", optarg);
else
options = strdup (optarg);
break;
case 'p':
if (do_what == nada)
do_what = saw_show_cygdrive_prefix;
else
usage ();
break;
case 'V':
print_version ();
return 0;
break;
default:
fprintf (stderr, "Try `%s --help' for more information.\n", progname);
return 1;
}
if (cygwin_internal (CW_CVT_MNT_OPTS, &options, &flags))
{
fprintf (stderr, "%s: invalid option - '%s'\n", progname, options);
exit (1);
}
if (flags & MOUNT_NOTEXEC && flags & (MOUNT_EXEC | MOUNT_CYGWIN_EXEC))
{
fprintf (stderr, "%s: invalid combination of executable options\n",
progname);
exit (1);
}
cygwin_internal (CW_SET_DOS_FILE_WARNING, false);
argc--;
switch (do_what)
{
case saw_change_cygdrive_prefix:
if (optind != argc)
usage ();
change_cygdrive_prefix (argv[optind], flags);
break;
case saw_show_cygdrive_prefix:
if (optind <= argc)
usage ();
show_cygdrive_info ();
break;
case saw_mount_commands:
if (optind <= argc)
usage ();
mount_entries ();
break;
case saw_mount_all:
if (optind <= argc)
usage ();
do_mount_from_fstab (NULL);
break;
default:
if (optind == argc)
do_mount_from_fstab (argv[optind]);
else if (optind != (argc - 1))
{
fprintf (stderr, "%s: too many arguments\n", progname);
usage ();
}
else if (force || !mount_already_exists (argv[optind + 1], flags))
do_mount (argv[optind], argv[optind + 1], flags);
else
{
errno = EBUSY;
error (argv[optind + 1]);
}
}
/* NOTREACHED */
return 0;
}
static char *
convert_spaces (char *tgt, const char *src)
{
char *tp, *spacep;
const char *sp;
tp = tgt;
for (sp = src; (spacep = strchr (sp, ' ')); sp = spacep + 1)
{
tp = stpncpy (tp, sp, spacep - sp);
tp = stpcpy (tp, "\\040");
}
stpcpy (tp, sp);
return tgt;
}
static void
mount_entries (void)
{
FILE *m = setmntent ("/-not-used-", "r");
struct mntent *p;
const char *format_mnt = "%s %s %s %s 0 0\n";
const char *format_cyg = "none %s cygdrive %s 0 0\n";
// write fstab entries for normal mount points
while ((p = getmntent (m)) != NULL)
// Only list non-cygdrives and non-automounts
if (!strstr (p->mnt_opts, ",noumount") && !strstr (p->mnt_opts, ",auto"))
{
char fsname[NT_MAX_PATH], dirname[NT_MAX_PATH];
/* Drop the "bind" option since it can't be reverted. */
char *c = strstr (p->mnt_opts, ",bind");
if (c)
memmove (c, c + 5, strlen (c + 5) + 1);
printf (format_mnt, convert_spaces (fsname, p->mnt_fsname),
convert_spaces (dirname, p->mnt_dir),
p->mnt_type, p->mnt_opts);
}
endmntent (m);
// write fstab entry for cygdrive prefix
m = setmntent ("/-not-used-", "r");
while ((p = getmntent (m)) != NULL)
{
char *noumount;
if ((noumount = strstr (p->mnt_opts, ",noumount")))
{
char dirname[NT_MAX_PATH];
char opts[strlen (p->mnt_opts) + 1];
convert_spaces (dirname, p->mnt_dir);
// remove trailing slash
char *ls = strrchr (dirname, '/');
if (ls)
{
// last slash == leading slash? cygdrive prefix == "/"
if (ls == dirname)
++ls;
*ls = '\0';
}
*stpncpy (opts, p->mnt_opts, noumount - p->mnt_opts) = '\0';
printf (format_cyg, dirname, opts);
break;
}
}
endmntent (m);
exit(0);
}
static void
show_mounts (void)
{
FILE *m = setmntent ("/-not-used-", "r");
struct mntent *p;
const char *format = "%s on %s type %s (%s)\n";
// printf (format, "Device", "Directory", "Type", "Flags");
while ((p = getmntent (m)) != NULL)
printf (format, p->mnt_fsname, p->mnt_dir, p->mnt_type, p->mnt_opts);
endmntent (m);
}
/* Return 1 if mountpoint from the same registry area is already in
mount table. Otherwise return 0. */
static int
mount_already_exists (const char *posix_path, int flags)
{
int found_matching = 0;
FILE *m = setmntent ("/-not-used-", "r");
struct mntent *p;
while ((p = getmntent (m)) != NULL)
{
/* if the paths match, and they're both the same type of mount. */
if (strcmp (p->mnt_dir, posix_path) == 0)
{
if (p->mnt_type[0] == 'u')
{
if (!(flags & MOUNT_SYSTEM)) /* both current_user */
found_matching = 1;
else
fprintf (stderr,
"%s: warning: system mount point of '%s' "
"will always be masked by user mount.\n",
progname, posix_path);
break;
}
else if (p->mnt_type[0] == 's')
{
if (flags & MOUNT_SYSTEM) /* both system */
found_matching = 1;
else
fprintf (stderr,
"%s: warning: user mount point of '%s' "
"masks system mount.\n", progname, posix_path);
break;
}
else
{
fprintf (stderr, "%s: warning: couldn't determine mount type.\n",
progname);
break;
}
}
}
endmntent (m);
return found_matching;
}
/* change_cygdrive_prefix: Change the cygdrive prefix */
static void
change_cygdrive_prefix (const char *new_prefix, int flags)
{
flags |= MOUNT_CYGDRIVE;
if (mount (NULL, new_prefix, flags))
error (new_prefix);
exit (0);
}
/* show_cygdrive_info: Show the user and/or cygdrive info, i.e., prefix and
flags.*/
static void
show_cygdrive_info ()
{
/* Get the cygdrive info */
char user[MAX_PATH];
char system[MAX_PATH];
char user_flags[MAX_PATH];
char system_flags[MAX_PATH];
cygwin_internal (CW_GET_CYGDRIVE_INFO, user, system, user_flags,
system_flags);
/* Display the user and system cygdrive path prefix, if necessary
(ie, not empty) */
const char *format = "%-18s %-11s %s\n";
printf (format, "Prefix", "Type", "Flags");
if (strlen (user) > 0)
printf (format, user, "user", user_flags);
if (strlen (system) > 0)
printf (format, system, "nouser", system_flags);
exit (0);
}