nilfs-utils/sbin/mount/mount_libmount.c
Ryusuke Konishi dc7e116ddf treewide: embed git revision into binaries
Apply the NILFS_UTILS_GITID() macro to all major executables and
libraries to enable git revision tracking.

Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
2026-01-26 01:44:10 +09:00

597 lines
14 KiB
C

/*
* mount_libmount.c - NILFS mount helper program (libmount version)
*
* Copyright (C) 2007-2012 Nippon Telegraph and Telephone Corporation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*
* Written by Ryusuke Konishi <konishi.ryusuke@gmail.com>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#include <stdio.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif /* HAVE_STDLIB_H */
#if HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif /* HAVE_FCNTL_H */
#if HAVE_LIMITS_H
#include <limits.h> /* ULONG_MAX */
#endif /* HAVE_LIMITS_H */
#if HAVE_ERR_H
#include <err.h>
#endif /* HAVE_ERR_H */
#if HAVE_STRINGS_H
#include <strings.h>
#endif /* HAVE_STRINGS_H */
#if HAVE_STRING_H
#include <string.h>
#endif /* HAVE_STRING_H */
#if HAVE_SYS_IOCTL_H
#include <sys/ioctl.h> /* ioctl() */
#endif /* HAVE_SYS_IOCTL_H */
#if HAVE_SYS_MOUNT_H
#include <sys/mount.h> /* BLKROGET */
#endif /* HAVE_SYS_MOUNT_H */
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif /* HAVE_SYS_STAT_H */
#if HAVE_SYSLOG_H
#include <syslog.h>
#endif /* HAVE_SYSLOG_H */
#include <libmount.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include "compat.h" /* getprogname() */
#include "libmount_compat.h"
#include "mount.nilfs2.h"
#include "mount_attrs.h"
#include "cleaner_exec.h"
#include "nls.h"
#include "util.h" /* NILFS_UTILS_GITID() */
#ifdef _GNU_SOURCE
#include <getopt.h>
#endif /* _GNU_SOURCE */
/* mount options */
static int verbose;
static int devro;
static char *mount_fstype;
/* global variables */
static const char fstype[] = NILFS2_FS_NAME;
NILFS_UTILS_GITID();
/* mount info */
struct nilfs_mount_info {
struct libmnt_context *cxt;
unsigned long mflags;
int type;
int mounted;
struct nilfs_mount_attrs old_attrs;
struct nilfs_mount_attrs new_attrs;
};
enum {
NORMAL_MOUNT,
RW2RO_REMOUNT,
RW2RW_REMOUNT,
};
static void nilfs_mount_logger(int priority, const char *fmt, ...)
{
va_list args;
if ((verbose && priority > LOG_INFO) || priority >= LOG_INFO)
return;
va_start(args, fmt);
vwarnx(fmt, args);
va_end(args);
}
#if 0
static int nilfs_libmount_table_errcb(struct libmnt_table *tb,
const char *filename, int line)
{
if (filename)
warnx(_("%s: parse error: ignore entry at line %d."),
filename, line);
return 0;
}
#endif
/*
* Other routines
*/
static int device_is_readonly(const char *device, int *ro)
{
int fd, res = 0;
fd = open(device, O_RDONLY);
if (fd < 0)
return -errno;
if (ioctl(fd, BLKROGET, ro) < 0)
res = -errno;
close(fd);
return res;
}
static void show_version(void)
{
printf("%s (%s %s)\n", getprogname(), PACKAGE, PACKAGE_VERSION);
}
static void nilfs_mount_parse_options(int argc, char *argv[],
struct nilfs_mount_info *mi)
{
struct libmnt_context *cxt = mi->cxt;
struct libmnt_fs *fs;
int c, show_version_only = 0;
fs = mnt_context_get_fs(cxt);
if (!fs)
errx(MNT_EX_SYSERR, _("failed to get fs"));
while ((c = getopt(argc, argv, "fvnt:o:rwV")) != EOF) {
switch (c) {
case 'f':
mnt_context_enable_fake(cxt, 1);
break;
case 'v':
mnt_context_enable_verbose(cxt, 1);
verbose = 1;
break;
case 'n':
mnt_context_disable_mtab(cxt, 1);
break;
case 't':
mount_fstype = optarg;
break;
case 'o':
{
char *rest;
if (nilfs_mount_attrs_parse(&mi->new_attrs, optarg,
NULL, &rest, 0))
errx(MNT_EX_SYSERR,
_("failed to parse options"));
if (rest && mnt_context_append_options(cxt, rest))
errx(MNT_EX_SYSERR,
_("failed to append options"));
free(rest);
break;
}
case 'r':
if (mnt_context_append_options(cxt, "ro"))
errx(MNT_EX_SYSERR,
_("failed to append options"));
break;
case 'w':
if (mnt_context_append_options(cxt, "rw"))
errx(MNT_EX_SYSERR,
_("failed to append options"));
break;
case 'V':
show_version_only = 1;
break;
default:
break;
}
}
if (show_version_only) {
show_version();
exit(MNT_EX_SUCCESS);
}
}
/**
* nilfs_find_mount - find matching nilfs entry from mount table
* @cxt: mount context
* @mtab: pointer to tab
* @target: pointer to mountpoint path or %NULL
* @options: comma delimited list of options (and nooptions)
*
* This function searches in reverse order for an entry in the mount table
* given by @mtab that matches the device and file system type in @cxt.
* If @target and @options are not %NULL, the corresponding mount points and
* mount options are also added to the matching conditions.
*
* For details on the format of @options, see the specification of
* mnt_match_options().
*
* Return: If a matching entry is found, its FS description, or %NULL if not
* found.
*/
static struct libmnt_fs *nilfs_find_mount(struct libmnt_context *cxt,
struct libmnt_table *mtab,
const char *target,
const char *options)
{
struct libmnt_iter *iter = mnt_new_iter(MNT_ITER_BACKWARD);
const char *src = mnt_context_get_source(cxt);
const char *type = mnt_context_get_fstype(cxt);
struct libmnt_cache *cache = mnt_table_get_cache(mtab);
struct libmnt_fs *fs = NULL;
if (!iter)
errx(MNT_EX_SYSERR, _("libmount iterator allocation failed"));
while (mnt_table_next_fs(mtab, iter, &fs) == 0) {
if (mnt_fs_match_fstype(fs, type) &&
mnt_fs_match_source(fs, src, cache) &&
(!target || mnt_fs_match_target(fs, target, cache)) &&
(!options || mnt_fs_match_options(fs, options)))
break;
}
mnt_free_iter(iter);
return fs;
}
/**
* nilfs_find_rw_mount - find nilfs read/write mount entry from mount table
* @cxt: mount context
* @mtab: pointer to tab
*
* Return: If a matching entry is found, its FS description, or NULL if not
* found.
*/
static inline struct libmnt_fs *nilfs_find_rw_mount(struct libmnt_context *cxt,
struct libmnt_table *mtab)
{
return nilfs_find_mount(cxt, mtab, NULL, "rw");
}
static int nilfs_prepare_mount(struct nilfs_mount_info *mi)
{
struct libmnt_context *cxt = mi->cxt;
struct libmnt_fs *fs;
struct libmnt_table *mtab;
const char *attrs;
int res;
res = mnt_context_prepare_mount(cxt);
if (res < 0) {
warnx(_("preparation failed: %s"), strerror(-res));
goto failed;
}
/*
* mnt_context_prepare_mount() parses mtab (/etc/mtab or
* /proc/self/mountinfo + /run/mount/utabs or /proc/mounts)
*/
res = mnt_context_get_mflags(cxt, &mi->mflags);
if (res < 0) {
warnx(_("get mount flags failed: %s"), strerror(-res));
goto failed;
}
if (!(mi->mflags & MS_RDONLY) && !(mi->mflags & MS_BIND)) {
res = device_is_readonly(mnt_context_get_source(cxt),
&devro);
if (res < 0) {
warnx(_("device %s not accessible: %s"),
mnt_context_get_source(cxt), strerror(-res));
goto failed;
}
}
res = mnt_context_get_mtab(cxt, &mtab);
if (res < 0) {
warnx(_("libmount mount check failed: %s"), strerror(-res));
goto failed;
}
mi->mounted = mnt_table_is_fs_mounted(mtab, mnt_context_get_fs(cxt));
if (mi->mflags & MS_BIND)
return 0;
fs = nilfs_find_rw_mount(cxt, mtab);
if (fs == NULL)
return 0; /* no previous rw-mount */
switch (mi->mflags & (MS_RDONLY | MS_REMOUNT)) {
case 0: /* overlapping rw-mount */
warnx(_("the device already has a rw-mount on %s.\n"
"\t\tmultiple rw-mount is not allowed."),
mnt_fs_get_target(fs));
res = -EBUSY;
goto failed;
case MS_RDONLY: /* ro-mount (a rw-mount exists) */
break;
case MS_REMOUNT | MS_RDONLY: /* rw->ro remount */
case MS_REMOUNT: /* rw->rw remount */
mi->type = (mi->mflags & MS_RDONLY) ?
RW2RO_REMOUNT : RW2RW_REMOUNT;
attrs = mnt_fs_get_attributes(fs);
if (attrs) {
res = nilfs_mount_attrs_parse(&mi->old_attrs, attrs,
NULL, NULL, 1);
if (res < 0) {
warnx(_("libmount mount check failed: %s"),
strerror(-res));
goto failed;
}
}
if (!mnt_fs_match_target(fs, mnt_context_get_target(cxt),
mnt_table_get_cache(mtab))) {
warnx(_("different mount point (%s). remount failed."),
mnt_context_get_target(cxt));
res = -EINVAL;
goto failed;
}
if (mi->old_attrs.gcpid) {
res = nilfs_shutdown_cleanerd(
mnt_fs_get_source(fs), mi->old_attrs.gcpid);
if (res < 0) {
warnx(_("remount failed due to %s shutdown failure"),
NILFS_CLEANERD_NAME);
goto failed;
}
}
break;
}
res = 0;
failed:
return res;
}
static int nilfs_do_mount_one(struct nilfs_mount_info *mi)
{
struct libmnt_context *cxt = mi->cxt;
int res, ec;
res = mnt_context_do_mount(cxt);
if (!res)
goto out;
/*
* mnt_context_do_mount() returns:
* 0: success
* > 0: syscall error (positive errno)
* < 0: library error (negative error code)
*/
ec = res < 0 ? -res : res;
switch (ec) {
case ENODEV:
warnx(_("cannot find or load %s filesystem"), fstype);
break;
default:
warnx(_("Error while mounting %s on %s: %s"),
mnt_context_get_source(cxt),
mnt_context_get_target(cxt), strerror(ec));
break;
}
if (mi->type != RW2RO_REMOUNT && mi->type != RW2RW_REMOUNT)
goto out;
/* Cleaner daemon was stopped and it needs to run */
/* because filesystem is still mounted */
if (!mi->old_attrs.nogc) {
struct nilfs_mount_attrs mattrs = { .pp = mi->old_attrs.pp };
/* Restarting cleaner daemon */
if (nilfs_launch_cleanerd(mnt_context_get_source(cxt),
mnt_context_get_target(cxt),
mattrs.pp, &mattrs.gcpid) == 0) {
if (mnt_context_is_verbose(cxt))
printf(_("%s: restarted %s\n"),
getprogname(), NILFS_CLEANERD_NAME);
nilfs_mount_attrs_update(&mi->old_attrs, &mattrs, cxt);
mnt_context_finalize_mount(cxt);
} else {
warnx(_("failed to restart %s"), NILFS_CLEANERD_NAME);
}
} else {
printf(_("%s not restarted\n"), NILFS_CLEANERD_NAME);
}
out:
return res;
}
/**
* nilfs_mnt_context_complete_root - complete root of the mount
* @cxt: mount context
*
* This function tries to retrieve root from the mount entry information
* obtained by searching the mount table and sets it to @cxt.
*
* Return: 0 on success, or -errno on error.
*/
static int nilfs_mnt_context_complete_root(struct libmnt_context *cxt)
{
struct libmnt_table *mtab;
struct libmnt_fs *fs;
int res;
res = mnt_context_get_mtab(cxt, &mtab);
if (res < 0) {
warnx(_("failed to get mtab: %s"), strerror(-res));
goto failed;
}
fs = nilfs_find_mount(cxt, mtab, mnt_context_get_target(cxt), NULL);
if (fs) {
res = mnt_fs_set_root(mnt_context_get_fs(cxt),
mnt_fs_get_root(fs));
if (res < 0) {
warnx(_("failed to copy root of the mount: %s"),
strerror(-res));
goto failed;
}
}
res = 0;
failed:
return res;
}
static int nilfs_update_mount_state(struct nilfs_mount_info *mi)
{
struct libmnt_context *cxt = mi->cxt;
struct nilfs_mount_attrs *old_attrs;
int rungc, gc_ok;
if (!mnt_fs_get_root(mnt_context_get_fs(cxt)) &&
((mi->mflags & MS_REMOUNT) || mnt_context_is_fake(cxt))) {
/*
* Complement the mount root against remount or fake mount
* context to avoid problems due to incomplete utab entry
* with the mount root "ROOT" missing, whose attributes cannot
* be referenced by umount or remount, making cleanerd
* uncontrollable.
*/
nilfs_mnt_context_complete_root(cxt);
}
gc_ok = !(mi->mflags & MS_RDONLY) && !(mi->mflags & MS_BIND);
rungc = gc_ok && !mi->new_attrs.nogc;
old_attrs = (mi->mflags & MS_REMOUNT) ? &mi->old_attrs : NULL;
if (rungc) {
if (mi->new_attrs.pp == ULONG_MAX)
mi->new_attrs.pp = mi->old_attrs.pp;
if (nilfs_launch_cleanerd(mnt_context_get_source(cxt),
mnt_context_get_target(cxt),
mi->new_attrs.pp,
&mi->new_attrs.gcpid) < 0)
warnx(_("%s aborted"), NILFS_CLEANERD_NAME);
else if (mnt_context_is_verbose(cxt))
printf(_("%s: started %s\n"), getprogname(),
NILFS_CLEANERD_NAME);
}
nilfs_mount_attrs_update(old_attrs, &mi->new_attrs, cxt);
return mnt_context_finalize_mount(cxt);
}
static int nilfs_mount_one(struct nilfs_mount_info *mi)
{
int res, err = MNT_EX_FAIL;
res = nilfs_prepare_mount(mi);
if (res)
goto failed;
if (!mnt_context_is_fake(mi->cxt)) {
res = nilfs_do_mount_one(mi);
if (res)
goto failed;
}
res = nilfs_update_mount_state(mi);
if (!res)
err = 0;
failed:
return err;
}
int main(int argc, char *argv[])
{
struct nilfs_mount_info mi = {0};
char *device, *mntdir;
int res = 0;
nilfs_cleaner_logger = nilfs_mount_logger;
mi.type = NORMAL_MOUNT;
mnt_init_debug(0);
mi.cxt = mnt_new_context();
if (!mi.cxt)
errx(MNT_EX_SYSERR, _("libmount context allocation failed"));
#if 0
mnt_context_set_tables_errcb(mi.cxt, nilfs_libmount_table_errcb);
#endif
if (mnt_context_set_fstype(mi.cxt, fstype))
errx(MNT_EX_SYSERR,
_("libmount FS description allocation failed"));
mnt_context_disable_helpers(mi.cxt, 1);
nilfs_mount_attrs_init(&mi.old_attrs);
nilfs_mount_attrs_init(&mi.new_attrs);
nilfs_mount_parse_options(argc, argv, &mi);
umask(022);
if (optind >= argc || !argv[optind])
errx(MNT_EX_USAGE, _("No device specified"));
device = argv[optind++];
if (optind >= argc || !argv[optind])
errx(MNT_EX_USAGE, _("No mountpoint specified"));
mntdir = argv[optind++];
if (mnt_context_set_source(mi.cxt, device) ||
mnt_context_set_target(mi.cxt, mntdir))
errx(MNT_EX_SYSERR, _("Mount entry allocation failed"));
if (mount_fstype && strncmp(mount_fstype, fstype, strlen(fstype)))
errx(MNT_EX_USAGE, _("Unknown filesystem (%s)"),
mount_fstype);
if (getuid() != geteuid())
errx(MNT_EX_USAGE,
_("mount by non-root user is not supported yet"));
res = nilfs_mount_one(&mi);
mnt_free_context(mi.cxt);
return res;
}