(root)/
util-linux-2.39/
sys-utils/
fstrim.c
       1  /*
       2   * fstrim.c -- discard the part (or whole) of mounted filesystem.
       3   *
       4   * Copyright (C) 2010 Red Hat, Inc. All rights reserved.
       5   * Written by Lukas Czerner <lczerner@redhat.com>
       6   *            Karel Zak <kzak@redhat.com>
       7   *
       8   * This program is free software: you can redistribute it and/or modify
       9   * it under the terms of the GNU General Public License as published by
      10   * the Free Software Foundation, either version 2 of the License, or
      11   * (at your option) any later version.
      12   *
      13   * This program is distributed in the hope that it will be useful,
      14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16   * GNU General Public License for more details.
      17   *
      18   * You should have received a copy of the GNU General Public License
      19   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
      20   *
      21   *
      22   * This program uses FITRIM ioctl to discard parts or the whole filesystem
      23   * online (mounted). You can specify range (start and length) to be
      24   * discarded, or simply discard whole filesystem.
      25   */
      26  
      27  #include <string.h>
      28  #include <unistd.h>
      29  #include <stdlib.h>
      30  #include <stdio.h>
      31  #include <stdint.h>
      32  #include <fcntl.h>
      33  #include <limits.h>
      34  #include <getopt.h>
      35  
      36  #include <sys/ioctl.h>
      37  #include <sys/stat.h>
      38  #include <sys/vfs.h>
      39  #include <linux/fs.h>
      40  
      41  #include "nls.h"
      42  #include "xalloc.h"
      43  #include "strutils.h"
      44  #include "c.h"
      45  #include "closestream.h"
      46  #include "pathnames.h"
      47  #include "sysfs.h"
      48  #include "optutils.h"
      49  #include "statfs_magic.h"
      50  
      51  #include <libmount.h>
      52  
      53  
      54  #ifndef FITRIM
      55  struct fstrim_range {
      56  	uint64_t start;
      57  	uint64_t len;
      58  	uint64_t minlen;
      59  };
      60  #define FITRIM		_IOWR('X', 121, struct fstrim_range)
      61  #endif
      62  
      63  struct fstrim_control {
      64  	struct fstrim_range range;
      65  	char *type_pattern;
      66  
      67  	unsigned int verbose : 1,
      68  		     quiet_unsupp : 1,
      69  		     dryrun : 1;
      70  };
      71  
      72  static int is_directory(const char *path, int silent)
      73  {
      74  	struct stat sb;
      75  
      76  	if (stat(path, &sb) == -1) {
      77  		if (!silent)
      78  			warn(_("stat of %s failed"), path);
      79  		return 0;
      80  	}
      81  	if (!S_ISDIR(sb.st_mode)) {
      82  		if (!silent)
      83  			warnx(_("%s: not a directory"), path);
      84  		return 0;
      85  	}
      86  	return 1;
      87  }
      88  
      89  /* returns: 0 = success, 1 = unsupported, < 0 = error */
      90  static int fstrim_filesystem(struct fstrim_control *ctl, const char *path, const char *devname)
      91  {
      92  	int fd = -1, rc;
      93  	struct fstrim_range range;
      94  	char *rpath = realpath(path, NULL);
      95  
      96  	if (!rpath) {
      97  		warn(_("cannot get realpath: %s"), path);
      98  		rc = -errno;
      99  		goto done;
     100  	}
     101  	/* kernel modifies the range */
     102  	memcpy(&range, &ctl->range, sizeof(range));
     103  
     104  	fd = open(rpath, O_RDONLY);
     105  	if (fd < 0) {
     106  		warn(_("cannot open %s"), path);
     107  		rc = -errno;
     108  		goto done;
     109  	}
     110  
     111  	if (ctl->dryrun) {
     112  		if (devname)
     113  			printf(_("%s: 0 B (dry run) trimmed on %s\n"), path, devname);
     114  		else
     115  			printf(_("%s: 0 B (dry run) trimmed\n"), path);
     116  		rc = 0;
     117  		goto done;
     118  	}
     119  
     120  	errno = 0;
     121  	if (ioctl(fd, FITRIM, &range)) {
     122  		switch (errno) {
     123  		case EBADF:
     124  		case ENOTTY:
     125  		case EOPNOTSUPP:
     126  		case ENOSYS:
     127  			rc = 1;
     128  			break;
     129  		default:
     130  			rc = -errno;
     131  		}
     132  		if (rc < 0)
     133  			warn(_("%s: FITRIM ioctl failed"), path);
     134  		goto done;
     135  	}
     136  
     137  	if (ctl->verbose) {
     138  		char *str = size_to_human_string(
     139  				SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE,
     140  				(uint64_t) range.len);
     141  		if (devname)
     142  			/* TRANSLATORS: The standard value here is a very large number. */
     143  			printf(_("%s: %s (%" PRIu64 " bytes) trimmed on %s\n"),
     144  				path, str, (uint64_t) range.len, devname);
     145  		else
     146  			/* TRANSLATORS: The standard value here is a very large number. */
     147  			printf(_("%s: %s (%" PRIu64 " bytes) trimmed\n"),
     148  				path, str, (uint64_t) range.len);
     149  
     150  		free(str);
     151  	}
     152  
     153  	rc = 0;
     154  done:
     155  	if (fd >= 0)
     156  		close(fd);
     157  	free(rpath);
     158  	return rc;
     159  }
     160  
     161  static int has_discard(const char *devname, struct path_cxt **wholedisk)
     162  {
     163  	struct path_cxt *pc = NULL;
     164  	uint64_t dg = 0;
     165  	dev_t disk = 0, dev;
     166  	int rc = -1, rdonly = 0;
     167  
     168  	dev = sysfs_devname_to_devno(devname);
     169  	if (!dev)
     170  		goto fail;
     171  
     172  	pc = ul_new_sysfs_path(dev, NULL, NULL);
     173  	if (!pc)
     174  		goto fail;
     175  
     176  	/*
     177  	 * This is tricky to read the info from sys/, because the queue
     178  	 * attributes are provided for whole devices (disk) only. We're trying
     179  	 * to reuse the whole-disk sysfs context to optimize this stuff (as
     180  	 * system usually have just one disk only).
     181  	 */
     182  	rc = sysfs_blkdev_get_wholedisk(pc, NULL, 0, &disk);
     183  	if (rc != 0 || !disk)
     184  		goto fail;
     185  
     186  	if (dev != disk) {
     187  		/* Partition, try reuse whole-disk context if valid for the
     188  		 * current device, otherwise create new context for the
     189  		 * whole-disk.
     190  		 */
     191  		if (*wholedisk && sysfs_blkdev_get_devno(*wholedisk) != disk) {
     192  			ul_unref_path(*wholedisk);
     193  			*wholedisk = NULL;
     194  		}
     195  		if (!*wholedisk) {
     196  			*wholedisk = ul_new_sysfs_path(disk, NULL, NULL);
     197  			if (!*wholedisk)
     198  				goto fail;
     199  		}
     200  		sysfs_blkdev_set_parent(pc, *wholedisk);
     201  	}
     202  
     203  	rc = ul_path_read_u64(pc, &dg, "queue/discard_granularity");
     204  	if (!rc)
     205  		ul_path_scanf(pc, "ro", "%d", &rdonly);
     206  
     207  	ul_unref_path(pc);
     208  	return rc == 0 && dg > 0 && rdonly == 0;
     209  fail:
     210  	ul_unref_path(pc);
     211  	return 1;
     212  }
     213  
     214  static int is_unwanted_fs(struct libmnt_fs *fs, const char *tgt, const char *types)
     215  {
     216  	struct statfs vfs;
     217  	int fd, rc;
     218  
     219  	if (mnt_fs_is_pseudofs(fs))
     220  		return 1;
     221  	if (mnt_fs_is_netfs(fs))
     222  		return 1;
     223  	if (mnt_fs_is_swaparea(fs))
     224  		return 1;
     225  	if (mnt_fs_match_fstype(fs, "autofs"))
     226  		return 1;
     227  	if (mnt_fs_match_options(fs, "ro"))
     228  		return 1;
     229  	if (mnt_fs_match_options(fs, "+X-fstrim.notrim"))
     230  		return 1;
     231  	if (types && mnt_fs_match_fstype(fs, types) == 0)
     232  		return 1;
     233  
     234  	fd = open(tgt, O_PATH);
     235  	if (fd < 0)
     236  		return 1;
     237  	rc = fstatfs(fd, &vfs) != 0 || vfs.f_type == STATFS_AUTOFS_MAGIC;
     238  	close(fd);
     239  	if (rc)
     240  		return 1;
     241  
     242  	/* FITRIM on read-only filesystem can fail, and it can fail */
     243  	if (access(tgt, W_OK) != 0) {
     244  		if (errno == EROFS)
     245  			return 1;
     246  		if (errno == EACCES)
     247  			return 1;
     248  	}
     249  	return 0;
     250  }
     251  
     252  static int uniq_fs_target_cmp(
     253  		struct libmnt_table *tb __attribute__((__unused__)),
     254  		struct libmnt_fs *a,
     255  		struct libmnt_fs *b)
     256  {
     257  	return !mnt_fs_streq_target(a, mnt_fs_get_target(b));
     258  }
     259  
     260  static int uniq_fs_source_cmp(
     261  		struct libmnt_table *tb __attribute__((__unused__)),
     262  		struct libmnt_fs *a,
     263  		struct libmnt_fs *b)
     264  {
     265  	if (mnt_fs_is_pseudofs(a) || mnt_fs_is_netfs(a) ||
     266  	    mnt_fs_is_pseudofs(b) || mnt_fs_is_netfs(b))
     267  		return 1;
     268  
     269  	return !mnt_fs_streq_srcpath(a, mnt_fs_get_srcpath(b));
     270  }
     271  
     272  /*
     273   * -1 = tab empty
     274   *  0 = all success
     275   * 32 = all failed
     276   * 64 = some failed, some success
     277   */
     278  static int fstrim_all_from_file(struct fstrim_control *ctl, const char *filename)
     279  {
     280  	struct libmnt_fs *fs;
     281  	struct libmnt_iter *itr;
     282  	struct libmnt_table *tab;
     283  	struct libmnt_cache *cache = NULL;
     284  	struct path_cxt *wholedisk = NULL;
     285  	int cnt = 0, cnt_err = 0;
     286  	int fstab = 0;
     287  
     288  	tab = mnt_new_table_from_file(filename);
     289  	if (!tab)
     290  		err(MNT_EX_FAIL, _("failed to parse %s"), filename);
     291  
     292  	if (mnt_table_is_empty(tab)) {
     293  		mnt_unref_table(tab);
     294  		return -1;
     295  	}
     296  
     297  	if (streq_paths(filename, "/etc/fstab"))
     298  		fstab = 1;
     299  
     300  	/* de-duplicate by mountpoints */
     301  	mnt_table_uniq_fs(tab, 0, uniq_fs_target_cmp);
     302  
     303  	if (fstab) {
     304  		char *rootdev = NULL;
     305  
     306  		cache = mnt_new_cache();
     307  		if (!cache)
     308  			err(MNT_EX_FAIL, _("failed to initialize libmount cache"));
     309  
     310  		/* Make sure we trim also root FS on fstab */
     311  		if (mnt_table_find_target(tab, "/", MNT_ITER_FORWARD) == NULL &&
     312  		    mnt_guess_system_root(0, cache, &rootdev) == 0) {
     313  
     314  			fs = mnt_new_fs();
     315  			if (!fs)
     316  				err(MNT_EX_FAIL, _("failed to allocate FS handler"));
     317  			mnt_fs_set_target(fs, "/");
     318  			mnt_fs_set_source(fs, rootdev);
     319  			mnt_fs_set_fstype(fs, "auto");
     320  			mnt_table_add_fs(tab, fs);
     321  			mnt_unref_fs(fs);
     322  			fs = NULL;
     323  		}
     324  		free(rootdev);
     325  	}
     326  
     327  	itr = mnt_new_iter(MNT_ITER_BACKWARD);
     328  	if (!itr)
     329  		err(MNT_EX_FAIL, _("failed to initialize libmount iterator"));
     330  
     331  	/* Remove useless entries and canonicalize the table */
     332  	while (mnt_table_next_fs(tab, itr, &fs) == 0) {
     333  		const char *src = mnt_fs_get_srcpath(fs),
     334  			   *tgt = mnt_fs_get_target(fs);
     335  		char *path;
     336  		int rc = 1;
     337  
     338  		if (!tgt || is_unwanted_fs(fs, tgt, ctl->type_pattern)) {
     339  			mnt_table_remove_fs(tab, fs);
     340  			continue;
     341  		}
     342  
     343  		/* convert LABEL= (etc.) from fstab to paths */
     344  		if (!src && cache) {
     345  			const char *spec = mnt_fs_get_source(fs);
     346  
     347  			if (!spec) {
     348  				mnt_table_remove_fs(tab, fs);
     349  				continue;
     350  			}
     351  			src = mnt_resolve_spec(spec, cache);
     352  			mnt_fs_set_source(fs, src);
     353  		}
     354  
     355  		if (!src || *src != '/') {
     356  			mnt_table_remove_fs(tab, fs);
     357  			continue;
     358  		}
     359  
     360  		/* Is it really accessible mountpoint? Not all mountpoints are
     361  		 * accessible (maybe over mounted by another filesystem) */
     362  		path = mnt_get_mountpoint(tgt);
     363  		if (path && streq_paths(path, tgt))
     364  			rc = 0;
     365  		free(path);
     366  		if (rc) {
     367  			mnt_table_remove_fs(tab, fs);
     368  			continue;	/* overlaying mount */
     369  		}
     370  
     371  		if (!is_directory(tgt, 1) ||
     372  		    !has_discard(src, &wholedisk)) {
     373  			mnt_table_remove_fs(tab, fs);
     374  			continue;
     375  		}
     376  	}
     377  
     378  	/* de-duplicate by source */
     379  	mnt_table_uniq_fs(tab, MNT_UNIQ_FORWARD, uniq_fs_source_cmp);
     380  
     381  	mnt_reset_iter(itr, MNT_ITER_BACKWARD);
     382  
     383  	/* Do FITRIM */
     384  	while (mnt_table_next_fs(tab, itr, &fs) == 0) {
     385  		const char *src = mnt_fs_get_srcpath(fs),
     386  			   *tgt = mnt_fs_get_target(fs);
     387  		int rc;
     388  
     389  		cnt++;
     390  
     391  		/*
     392  		 * We're able to detect that the device supports discard, but
     393  		 * things also depend on filesystem or device mapping, for
     394  		 * example LUKS (by default) does not support FSTRIM.
     395  		 *
     396  		 * This is reason why we ignore EOPNOTSUPP and ENOTTY errors
     397  		 * from discard ioctl.
     398  		 */
     399  		rc = fstrim_filesystem(ctl, tgt, src);
     400  		if (rc < 0)
     401  		       cnt_err++;
     402  		else if (rc == 1 && !ctl->quiet_unsupp)
     403  			warnx(_("%s: the discard operation is not supported"), tgt);
     404  	}
     405  	mnt_free_iter(itr);
     406  
     407  	ul_unref_path(wholedisk);
     408  	mnt_unref_table(tab);
     409  	mnt_unref_cache(cache);
     410  
     411  	if (cnt && cnt == cnt_err)
     412  		return MNT_EX_FAIL;		/* all failed */
     413  	if (cnt && cnt_err)
     414  		return MNT_EX_SOMEOK;		/* some ok */
     415  
     416  	return MNT_EX_SUCCESS;
     417  }
     418  
     419  /*
     420   * fstrim --all follows "mount -a" return codes:
     421   *
     422   * 0  = all success
     423   * 32 = all failed
     424   * 64 = some failed, some success
     425   */
     426  static int fstrim_all(struct fstrim_control *ctl, const char *tabs)
     427  {
     428  	char *list = xstrdup(tabs);
     429  	char *file;
     430  	int rc = MNT_EX_FAIL;
     431  
     432  	mnt_init_debug(0);
     433  	ul_path_init_debug();
     434  
     435  	for (file = strtok(list, ":"); file; file = strtok(NULL, ":")) {
     436  		struct stat st;
     437  
     438  		if (stat(file, &st) < 0 || !S_ISREG(st.st_mode))
     439  			continue;
     440  
     441  		rc = fstrim_all_from_file(ctl, file);
     442  		if (rc >= 0)
     443  			break;	/* stop after first non-empty file */
     444  	}
     445  	free(list);
     446  	return rc;
     447  }
     448  
     449  static void __attribute__((__noreturn__)) usage(void)
     450  {
     451  	FILE *out = stdout;
     452  	fputs(USAGE_HEADER, out);
     453  	fprintf(out,
     454  	      _(" %s [options] <mount point>\n"), program_invocation_short_name);
     455  
     456  	fputs(USAGE_SEPARATOR, out);
     457  	fputs(_("Discard unused blocks on a mounted filesystem.\n"), out);
     458  
     459  	fputs(USAGE_OPTIONS, out);
     460  	fputs(_(" -a, --all                trim mounted filesystems\n"), out);
     461  	fputs(_(" -A, --fstab              trim filesystems from /etc/fstab\n"), out);
     462  	fputs(_(" -I, --listed-in <list>   trim filesystems listed in specified files\n"), out);
     463  	fputs(_(" -o, --offset <num>       the offset in bytes to start discarding from\n"), out);
     464  	fputs(_(" -l, --length <num>       the number of bytes to discard\n"), out);
     465  	fputs(_(" -m, --minimum <num>      the minimum extent length to discard\n"), out);
     466  	fputs(_(" -t, --types <list>       limit the set of filesystem types\n"), out);
     467  	fputs(_(" -v, --verbose            print number of discarded bytes\n"), out);
     468  	fputs(_("     --quiet-unsupported  suppress error messages if trim unsupported\n"), out);
     469  	fputs(_(" -n, --dry-run            does everything, but trim\n"), out);
     470  
     471  	fputs(USAGE_SEPARATOR, out);
     472  	printf(USAGE_HELP_OPTIONS(21));
     473  
     474  	fputs(USAGE_ARGUMENTS, out);
     475  	printf(USAGE_ARG_SIZE(_("<num>")));
     476  
     477  	printf(USAGE_MAN_TAIL("fstrim(8)"));
     478  	exit(EXIT_SUCCESS);
     479  }
     480  
     481  int main(int argc, char **argv)
     482  {
     483  	char *path = NULL;
     484  	char *tabs = NULL;
     485  	int c, rc, all = 0;
     486  	struct fstrim_control ctl = {
     487  			.range = { .len = ULLONG_MAX }
     488  	};
     489  	enum {
     490  		OPT_QUIET_UNSUPP = CHAR_MAX + 1
     491  	};
     492  
     493  	static const struct option longopts[] = {
     494  	    { "all",       no_argument,       NULL, 'a' },
     495  	    { "fstab",     no_argument,       NULL, 'A' },
     496  	    { "help",      no_argument,       NULL, 'h' },
     497  	    { "listed-in", required_argument, NULL, 'I' },
     498  	    { "version",   no_argument,       NULL, 'V' },
     499  	    { "offset",    required_argument, NULL, 'o' },
     500  	    { "length",    required_argument, NULL, 'l' },
     501  	    { "minimum",   required_argument, NULL, 'm' },
     502  	    { "types",     required_argument, NULL, 't' },
     503  	    { "verbose",   no_argument,       NULL, 'v' },
     504  	    { "quiet-unsupported", no_argument,       NULL, OPT_QUIET_UNSUPP },
     505  	    { "dry-run",   no_argument,       NULL, 'n' },
     506  	    { NULL, 0, NULL, 0 }
     507  	};
     508  
     509  	static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
     510  		{ 'A','I','a' },
     511  		{ 0 }
     512  	};
     513  	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
     514  
     515  	setlocale(LC_ALL, "");
     516  	bindtextdomain(PACKAGE, LOCALEDIR);
     517  	textdomain(PACKAGE);
     518  	close_stdout_atexit();
     519  
     520  	while ((c = getopt_long(argc, argv, "AahI:l:m:no:t:Vv", longopts, NULL)) != -1) {
     521  
     522  		err_exclusive_options(c, longopts, excl, excl_st);
     523  
     524  		switch(c) {
     525  		case 'A':
     526  			all = 1;
     527  			tabs = _PATH_MNTTAB;	/* fstab */
     528  			break;
     529  		case 'a':
     530  			all = 1;
     531  			tabs = _PATH_PROC_MOUNTINFO; /* mountinfo */
     532  			break;
     533  		case 'I':
     534  			all = 1;
     535  			tabs = optarg;
     536  			break;
     537  		case 'n':
     538  			ctl.dryrun = 1;
     539  			break;
     540  		case 'l':
     541  			ctl.range.len = strtosize_or_err(optarg,
     542  					_("failed to parse length"));
     543  			break;
     544  		case 'o':
     545  			ctl.range.start = strtosize_or_err(optarg,
     546  					_("failed to parse offset"));
     547  			break;
     548  		case 'm':
     549  			ctl.range.minlen = strtosize_or_err(optarg,
     550  					_("failed to parse minimum extent length"));
     551  			break;
     552  		case 't':
     553  			ctl.type_pattern = optarg;
     554  			break;
     555  		case 'v':
     556  			ctl.verbose = 1;
     557  			break;
     558  		case OPT_QUIET_UNSUPP:
     559  			ctl.quiet_unsupp = 1;
     560  			break;
     561  		case 'h':
     562  			usage();
     563  		case 'V':
     564  			print_version(EXIT_SUCCESS);
     565  		default:
     566  			errtryhelp(EXIT_FAILURE);
     567  		}
     568  	}
     569  
     570  	if (!all) {
     571  		if (optind == argc)
     572  			errx(EXIT_FAILURE, _("no mountpoint specified"));
     573  		path = argv[optind++];
     574  	}
     575  
     576  	if (optind != argc) {
     577  		warnx(_("unexpected number of arguments"));
     578  		errtryhelp(EXIT_FAILURE);
     579  	}
     580  
     581  	if (all)
     582  		return fstrim_all(&ctl, tabs);	/* MNT_EX_* codes */
     583  
     584  	if (!is_directory(path, 0))
     585  		return EXIT_FAILURE;
     586  
     587  	rc = fstrim_filesystem(&ctl, path, NULL);
     588  	if (rc == 1 && ctl.quiet_unsupp)
     589  		rc = 0;
     590  	if (rc == 1)
     591  		warnx(_("%s: the discard operation is not supported"), path);
     592  
     593  	return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
     594  }