1  /*
       2   * mountpoint(1) - see if a directory is a mountpoint
       3   *
       4   * This is libmount based reimplementation of the mountpoint(1)
       5   * from sysvinit project.
       6   *
       7   *
       8   * Copyright (C) 2011 Red Hat, Inc. All rights reserved.
       9   * Written by Karel Zak <kzak@redhat.com>
      10   *
      11   * This program is free software; you can redistribute it and/or modify
      12   * it under the terms of the GNU General Public License as published by
      13   * the Free Software Foundation; either version 2 of the License, or
      14   * (at your option) any later version.
      15   *
      16   * This program is distributed in the hope that it would be useful,
      17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      19   * GNU General Public License for more details.
      20   *
      21   * You should have received a copy of the GNU General Public License along
      22   * with this program; if not, write to the Free Software Foundation, Inc.,
      23   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      24   */
      25  
      26  #include <stdio.h>
      27  #include <stdlib.h>
      28  #include <errno.h>
      29  #include <string.h>
      30  #include <getopt.h>
      31  #include <unistd.h>
      32  #include <sys/types.h>
      33  #include <sys/stat.h>
      34  
      35  #include <libmount.h>
      36  
      37  #include "nls.h"
      38  #include "xalloc.h"
      39  #include "c.h"
      40  #include "closestream.h"
      41  #include "pathnames.h"
      42  
      43  #define MOUNTPOINT_EXIT_NOMNT	32
      44  
      45  struct mountpoint_control {
      46  	char *path;
      47  	dev_t dev;
      48  	struct stat st;
      49  	unsigned int
      50  		dev_devno:1,
      51  		fs_devno:1,
      52  		nofollow:1,
      53  		quiet:1;
      54  };
      55  
      56  static int dir_to_device(struct mountpoint_control *ctl)
      57  {
      58  	struct libmnt_table *tb = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
      59  	struct libmnt_fs *fs;
      60  	struct libmnt_cache *cache;
      61  	int rc = -1;
      62  
      63  	if (!tb) {
      64  		/*
      65  		 * Fallback. Traditional way to detect mountpoints. This way
      66  		 * is independent on /proc, but not able to detect bind mounts.
      67  		 */
      68  		struct stat pst;
      69  		char buf[PATH_MAX], *cn;
      70  		int len;
      71  
      72  		cn = mnt_resolve_path(ctl->path, NULL);	/* canonicalize */
      73  
      74  		len = snprintf(buf, sizeof(buf), "%s/..", cn ? cn : ctl->path);
      75  		free(cn);
      76  
      77  		if (len < 0 || (size_t) len >= sizeof(buf))
      78  			return -1;
      79  		if (stat(buf, &pst) !=0)
      80  			return -1;
      81  
      82  		if (ctl->st.st_dev != pst.st_dev || ctl->st.st_ino == pst.st_ino) {
      83  			ctl->dev = ctl->st.st_dev;
      84  			return 0;
      85  		}
      86  
      87  		return -1;
      88  	}
      89  
      90  	/* to canonicalize all necessary paths */
      91  	cache = mnt_new_cache();
      92  	mnt_table_set_cache(tb, cache);
      93  	mnt_unref_cache(cache);
      94  
      95  	fs = mnt_table_find_target(tb, ctl->path, MNT_ITER_BACKWARD);
      96  	if (fs && mnt_fs_get_target(fs)) {
      97  		ctl->dev = mnt_fs_get_devno(fs);
      98  		rc = 0;
      99  	}
     100  
     101  	mnt_unref_table(tb);
     102  	return rc;
     103  }
     104  
     105  static int print_devno(const struct mountpoint_control *ctl)
     106  {
     107  	if (!S_ISBLK(ctl->st.st_mode)) {
     108  		if (!ctl->quiet)
     109  			warnx(_("%s: not a block device"), ctl->path);
     110  		return -1;
     111  	}
     112  	printf("%u:%u\n", major(ctl->st.st_rdev), minor(ctl->st.st_rdev));
     113  	return 0;
     114  }
     115  
     116  static void __attribute__((__noreturn__)) usage(void)
     117  {
     118  	FILE *out = stdout;
     119  	fputs(USAGE_HEADER, out);
     120  	fprintf(out,
     121  	      _(" %1$s [-qd] /path/to/directory\n"
     122  		" %1$s -x /dev/device\n"), program_invocation_short_name);
     123  
     124  	fputs(USAGE_SEPARATOR, out);
     125  	fputs(_("Check whether a directory or file is a mountpoint.\n"), out);
     126  
     127  	fputs(USAGE_OPTIONS, out);
     128  	fputs(_(" -q, --quiet        quiet mode - don't print anything\n"
     129  		"     --nofollow     do not follow symlink\n"
     130  		" -d, --fs-devno     print maj:min device number of the filesystem\n"
     131  		" -x, --devno        print maj:min device number of the block device\n"), out);
     132  	fputs(USAGE_SEPARATOR, out);
     133  	printf(USAGE_HELP_OPTIONS(20));
     134  	printf(USAGE_MAN_TAIL("mountpoint(1)"));
     135  
     136  	exit(EXIT_SUCCESS);
     137  }
     138  
     139  int main(int argc, char **argv)
     140  {
     141  	int c;
     142  	struct mountpoint_control ctl = { NULL };
     143  
     144  	enum {
     145  		OPT_NOFOLLOW = CHAR_MAX + 1
     146  	};
     147  
     148  	static const struct option longopts[] = {
     149  		{ "quiet",    no_argument, NULL, 'q' },
     150  		{ "nofollow", no_argument, NULL, OPT_NOFOLLOW },
     151  		{ "fs-devno", no_argument, NULL, 'd' },
     152  		{ "devno",    no_argument, NULL, 'x' },
     153  		{ "help",     no_argument, NULL, 'h' },
     154  		{ "version",  no_argument, NULL, 'V' },
     155  		{ NULL, 0, NULL, 0 }
     156  	};
     157  
     158  	setlocale(LC_ALL, "");
     159  	bindtextdomain(PACKAGE, LOCALEDIR);
     160  	textdomain(PACKAGE);
     161  	close_stdout_atexit();
     162  
     163  	mnt_init_debug(0);
     164  
     165  	while ((c = getopt_long(argc, argv, "qdxhV", longopts, NULL)) != -1) {
     166  
     167  		switch(c) {
     168  		case 'q':
     169  			ctl.quiet = 1;
     170  			break;
     171  		case OPT_NOFOLLOW:
     172  			ctl.nofollow = 1;
     173  			break;
     174  		case 'd':
     175  			ctl.fs_devno = 1;
     176  			break;
     177  		case 'x':
     178  			ctl.dev_devno = 1;
     179  			break;
     180  
     181  		case 'h':
     182  			usage();
     183  		case 'V':
     184  			print_version(EXIT_SUCCESS);
     185  		default:
     186  			errtryhelp(EXIT_FAILURE);
     187  		}
     188  	}
     189  
     190  	if (optind + 1 != argc) {
     191  		warnx(_("bad usage"));
     192  		errtryhelp(EXIT_FAILURE);
     193  	}
     194  	if (ctl.nofollow && ctl.dev_devno)
     195  		errx(EXIT_FAILURE, _("%s and %s are mutually exclusive"),
     196  		     "--devno", "--nofollow");
     197  
     198  	ctl.path = argv[optind];
     199  	c = ctl.nofollow ? lstat(ctl.path, &ctl.st) : stat(ctl.path, &ctl.st);
     200  	if (c) {
     201  		if (!ctl.quiet)
     202  			err(EXIT_FAILURE, "%s", ctl.path);
     203  		return EXIT_FAILURE;
     204  	}
     205  	if (ctl.dev_devno)
     206  		return print_devno(&ctl) ? MOUNTPOINT_EXIT_NOMNT : EXIT_SUCCESS;
     207  
     208  	if ((ctl.nofollow && S_ISLNK(ctl.st.st_mode)) || dir_to_device(&ctl)) {
     209  		if (!ctl.quiet)
     210  			printf(_("%s is not a mountpoint\n"), ctl.path);
     211  		return MOUNTPOINT_EXIT_NOMNT;
     212  	}
     213  	if (ctl.fs_devno)
     214  		printf("%u:%u\n", major(ctl.dev), minor(ctl.dev));
     215  	else if (!ctl.quiet)
     216  		printf(_("%s is a mountpoint\n"), ctl.path);
     217  
     218  	return EXIT_SUCCESS;
     219  }