/* * SPDX-License-Identifier: GPL-2.0-or-later * * Copyright (C) 2011 Red Hat, Inc. All rights reserved. * Written by Karel Zak * * 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. * * mountpoint(1) - see if a directory is a mountpoint * * This is libmount based reimplementation of the mountpoint(1) * from sysvinit project. */ #include #include #include #include #include #include #include #include #include #include #include "nls.h" #include "xalloc.h" #include "c.h" #include "closestream.h" #include "pathnames.h" #include "mount-api-utils.h" #define MOUNTPOINT_EXIT_NOMNT 32 struct mountpoint_control { char *path; char *mnt_target; dev_t dev; struct stat st; struct libmnt_cache *cache; bool dev_devno, fs_devno, nofollow, quiet, show; }; #ifdef HAVE_STATMOUNT_API /* * dir_to_device_statmount - check if path is a mountpoint using statmount() * @ctl: mountpoint control structure * * Returns: <0 on error, 0 if mountpoint, 1 if not a mountpoint */ static int dir_to_device_statmount(struct mountpoint_control *ctl) { struct libmnt_fs *fs = NULL; const char *mnt_target; char *cn = NULL; uint64_t id = 0; int rc; cn = mnt_resolve_path(ctl->path, ctl->cache); rc = mnt_id_from_path(cn ? cn : ctl->path, &id, NULL); if (rc) goto done; fs = mnt_new_fs(); if (!fs) { rc = -ENOMEM; goto done; } mnt_fs_set_uniq_id(fs, id); rc = mnt_fs_fetch_statmount(fs, STATMOUNT_MNT_POINT | STATMOUNT_SB_BASIC); if (rc) goto done; mnt_target = mnt_fs_get_target(fs); if (!mnt_target) { rc = -EINVAL; goto done; } if (ctl->show) ctl->mnt_target = xstrdup(mnt_target); if (strcmp(mnt_target, cn ? cn : ctl->path) != 0) rc = 1; /* not a mountpoint */ else { ctl->dev = mnt_fs_get_devno(fs); rc = 0; /* is a mountpoint */ } done: mnt_unref_fs(fs); return rc; } #endif /* STATMOUNT_MNT_POINT */ /* * dir_to_device - check if path is a mountpoint * @ctl: mountpoint control structure * * Returns: <0 on error, 0 if mountpoint, 1 if not a mountpoint */ static int dir_to_device(struct mountpoint_control *ctl) { struct libmnt_table *tb; struct libmnt_fs *fs; int rc; #ifdef HAVE_STATMOUNT_API rc = dir_to_device_statmount(ctl); if (rc >= 0) return rc; #endif /* * Fallback for older kernels without statmount() support */ if (ctl->show) errx(EXIT_FAILURE, _("--show is not supported on this system")); tb = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO); if (!tb) { /* * Fallback. Traditional way to detect mountpoints. This way * is independent on /proc, but not able to detect bind mounts. */ struct stat pst; char buf[PATH_MAX]; const char *cn; int len; cn = mnt_resolve_path(ctl->path, ctl->cache); /* canonicalize */ len = snprintf(buf, sizeof(buf), "%s/..", cn ? cn : ctl->path); if (len < 0 || (size_t) len >= sizeof(buf)) return -EINVAL; rc = stat(buf, &pst); if (rc) return -errno; if (ctl->st.st_dev != pst.st_dev || ctl->st.st_ino == pst.st_ino) { ctl->dev = ctl->st.st_dev; return 0; /* is a mountpoint */ } return 1; /* not a mountpoint */ } /* to canonicalize all necessary paths */ mnt_table_set_cache(tb, ctl->cache); fs = mnt_table_find_target(tb, ctl->path, MNT_ITER_BACKWARD); if (fs && mnt_fs_get_target(fs)) { ctl->dev = mnt_fs_get_devno(fs); rc = 0; /* is a mountpoint */ } else rc = 1; /* not a mountpoint */ mnt_unref_table(tb); return rc; } static int print_devno(const struct mountpoint_control *ctl) { if (!S_ISBLK(ctl->st.st_mode)) { if (!ctl->quiet) warnx(_("%s: not a block device"), ctl->path); return -1; } printf("%u:%u\n", major(ctl->st.st_rdev), minor(ctl->st.st_rdev)); return 0; } static void __attribute__((__noreturn__)) usage(void) { FILE *out = stdout; fputs(USAGE_HEADER, out); fprintf(out, _(" %1$s [-qd] /path/to/directory\n" " %1$s -x /dev/device\n"), program_invocation_short_name); fputs(USAGE_SEPARATOR, out); fputs(_("Check whether a directory or file is a mountpoint.\n"), out); fputs(USAGE_OPTIONS, out); fputs(_(" -q, --quiet quiet mode - don't print anything\n" " --nofollow do not follow symlink\n" " -d, --fs-devno print maj:min device number of the filesystem\n" " -x, --devno print maj:min device number of the block device\n" " --show print mountpoint for given path\n"), out); fputs(USAGE_SEPARATOR, out); fprintf(out, USAGE_HELP_OPTIONS(20)); fprintf(out, USAGE_MAN_TAIL("mountpoint(1)")); exit(EXIT_SUCCESS); } int main(int argc, char **argv) { int c, rc; struct mountpoint_control ctl = { NULL }; enum { OPT_NOFOLLOW = CHAR_MAX + 1, OPT_SHOW }; static const struct option longopts[] = { { "quiet", no_argument, NULL, 'q' }, { "nofollow", no_argument, NULL, OPT_NOFOLLOW }, { "fs-devno", no_argument, NULL, 'd' }, { "devno", no_argument, NULL, 'x' }, { "show", no_argument, NULL, OPT_SHOW }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { NULL, 0, NULL, 0 } }; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); close_stdout_atexit(); mnt_init_debug(0); while ((c = getopt_long(argc, argv, "qdxhV", longopts, NULL)) != -1) { switch(c) { case 'q': ctl.quiet = 1; break; case OPT_NOFOLLOW: ctl.nofollow = 1; break; case 'd': ctl.fs_devno = 1; break; case 'x': ctl.dev_devno = 1; break; case OPT_SHOW: ctl.show = 1; break; case 'h': usage(); case 'V': print_version(EXIT_SUCCESS); default: errtryhelp(EXIT_FAILURE); } } if (optind + 1 != argc) { warnx(_("bad usage")); errtryhelp(EXIT_FAILURE); } if (ctl.nofollow && ctl.dev_devno) errx(EXIT_FAILURE, _("%s and %s are mutually exclusive"), "--devno", "--nofollow"); ctl.path = argv[optind]; c = ctl.nofollow ? lstat(ctl.path, &ctl.st) : stat(ctl.path, &ctl.st); if (c) { if (!ctl.quiet) err(EXIT_FAILURE, "%s", ctl.path); return EXIT_FAILURE; } if (ctl.dev_devno) return print_devno(&ctl) ? MOUNTPOINT_EXIT_NOMNT : EXIT_SUCCESS; if (ctl.nofollow && S_ISLNK(ctl.st.st_mode)) { if (!ctl.quiet) printf(_("%s is not a mountpoint\n"), ctl.path); return MOUNTPOINT_EXIT_NOMNT; } ctl.cache = mnt_new_cache(); if (!ctl.cache) err(EXIT_FAILURE, _("failed to initialize libmount cache")); rc = dir_to_device(&ctl); if (rc < 0) { if (!ctl.quiet) { errno = -rc; warn("%s", ctl.path); } rc = EXIT_FAILURE; goto done; } if (ctl.show) { printf("%s\n", ctl.mnt_target); rc = EXIT_SUCCESS; goto done; } if (rc == 1) { if (!ctl.quiet) printf(_("%s is not a mountpoint\n"), ctl.path); rc = MOUNTPOINT_EXIT_NOMNT; goto done; } if (ctl.fs_devno) printf("%u:%u\n", major(ctl.dev), minor(ctl.dev)); else if (!ctl.quiet) printf(_("%s is a mountpoint\n"), ctl.path); rc = EXIT_SUCCESS; done: free(ctl.mnt_target); mnt_unref_cache(ctl.cache); return rc; }