2 * fstrim.c -- discard the part (or whole) of mounted filesystem.
4 * Copyright (C) 2010 Red Hat, Inc. All rights reserved.
5 * Written by Lukas Czerner <lczerner@redhat.com>
6 * Karel Zak <kzak@redhat.com>
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.
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.
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/>.
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.
36 #include <sys/ioctl.h>
43 #include "closestream.h"
44 #include "pathnames.h"
56 #define FITRIM _IOWR('X', 121, struct fstrim_range)
59 struct fstrim_control
{
60 struct fstrim_range range
;
62 unsigned int verbose
: 1,
68 static int is_directory(const char *path
, int silent
)
72 if (stat(path
, &sb
) == -1) {
74 warn(_("stat of %s failed"), path
);
77 if (!S_ISDIR(sb
.st_mode
)) {
79 warnx(_("%s: not a directory"), path
);
85 /* returns: 0 = success, 1 = unsupported, < 0 = error */
86 static int fstrim_filesystem(struct fstrim_control
*ctl
, const char *path
, const char *devname
)
89 struct fstrim_range range
;
90 char *rpath
= realpath(path
, NULL
);
93 warn(_("cannot get realpath: %s"), path
);
97 /* kernel modifies the range */
98 memcpy(&range
, &ctl
->range
, sizeof(range
));
100 fd
= open(rpath
, O_RDONLY
);
102 warn(_("cannot open %s"), path
);
109 printf(_("%s: 0 B (dry run) trimmed on %s\n"), path
, devname
);
111 printf(_("%s: 0 B (dry run) trimmed\n"), path
);
117 if (ioctl(fd
, FITRIM
, &range
)) {
128 warn(_("%s: FITRIM ioctl failed"), path
);
133 char *str
= size_to_human_string(
134 SIZE_SUFFIX_3LETTER
| SIZE_SUFFIX_SPACE
,
135 (uint64_t) range
.len
);
137 /* TRANSLATORS: The standard value here is a very large number. */
138 printf(_("%s: %s (%" PRIu64
" bytes) trimmed on %s\n"),
139 path
, str
, (uint64_t) range
.len
, devname
);
141 /* TRANSLATORS: The standard value here is a very large number. */
142 printf(_("%s: %s (%" PRIu64
" bytes) trimmed\n"),
143 path
, str
, (uint64_t) range
.len
);
156 static int has_discard(const char *devname
, struct path_cxt
**wholedisk
)
158 struct path_cxt
*pc
= NULL
;
161 int rc
= -1, rdonly
= 0;
163 dev
= sysfs_devname_to_devno(devname
);
167 pc
= ul_new_sysfs_path(dev
, NULL
, NULL
);
172 * This is tricky to read the info from sys/, because the queue
173 * attributes are provided for whole devices (disk) only. We're trying
174 * to reuse the whole-disk sysfs context to optimize this stuff (as
175 * system usually have just one disk only).
177 rc
= sysfs_blkdev_get_wholedisk(pc
, NULL
, 0, &disk
);
178 if (rc
!= 0 || !disk
)
182 /* Partition, try reuse whole-disk context if valid for the
183 * current device, otherwise create new context for the
186 if (*wholedisk
&& sysfs_blkdev_get_devno(*wholedisk
) != disk
) {
187 ul_unref_path(*wholedisk
);
191 *wholedisk
= ul_new_sysfs_path(disk
, NULL
, NULL
);
195 sysfs_blkdev_set_parent(pc
, *wholedisk
);
198 rc
= ul_path_read_u64(pc
, &dg
, "queue/discard_granularity");
200 ul_path_scanf(pc
, "ro", "%d", &rdonly
);
203 return rc
== 0 && dg
> 0 && rdonly
== 0;
210 static int uniq_fs_target_cmp(
211 struct libmnt_table
*tb
__attribute__((__unused__
)),
215 return !mnt_fs_streq_target(a
, mnt_fs_get_target(b
));
218 static int uniq_fs_source_cmp(
219 struct libmnt_table
*tb
__attribute__((__unused__
)),
223 if (mnt_fs_is_pseudofs(a
) || mnt_fs_is_netfs(a
) ||
224 mnt_fs_is_pseudofs(b
) || mnt_fs_is_netfs(b
))
227 return !mnt_fs_streq_srcpath(a
, mnt_fs_get_srcpath(b
));
231 * fstrim --all follows "mount -a" return codes:
235 * 64 = some failed, some success
237 static int fstrim_all(struct fstrim_control
*ctl
)
239 struct libmnt_fs
*fs
;
240 struct libmnt_iter
*itr
;
241 struct libmnt_table
*tab
;
242 struct libmnt_cache
*cache
= NULL
;
243 struct path_cxt
*wholedisk
= NULL
;
244 int cnt
= 0, cnt_err
= 0;
245 const char *filename
= _PATH_PROC_MOUNTINFO
;
248 ul_path_init_debug();
251 filename
= mnt_get_fstab_path();
253 tab
= mnt_new_table_from_file(filename
);
255 err(MNT_EX_FAIL
, _("failed to parse %s"), filename
);
257 /* de-duplicate by mountpoints */
258 mnt_table_uniq_fs(tab
, 0, uniq_fs_target_cmp
);
261 char *rootdev
= NULL
;
263 cache
= mnt_new_cache();
265 err(MNT_EX_FAIL
, _("failed to initialize libmount cache"));
267 /* Make sure we trim also root FS on --fstab */
268 if (mnt_table_find_target(tab
, "/", MNT_ITER_FORWARD
) == NULL
&&
269 mnt_guess_system_root(0, cache
, &rootdev
) == 0) {
273 err(MNT_EX_FAIL
, _("failed to allocate FS handler"));
274 mnt_fs_set_target(fs
, "/");
275 mnt_fs_set_source(fs
, rootdev
);
276 mnt_fs_set_fstype(fs
, "auto");
277 mnt_table_add_fs(tab
, fs
);
283 itr
= mnt_new_iter(MNT_ITER_BACKWARD
);
285 err(MNT_EX_FAIL
, _("failed to initialize libmount iterator"));
287 /* Remove useless entries and canonicalize the table */
288 while (mnt_table_next_fs(tab
, itr
, &fs
) == 0) {
289 const char *src
= mnt_fs_get_srcpath(fs
),
290 *tgt
= mnt_fs_get_target(fs
);
292 if (!tgt
|| mnt_fs_is_pseudofs(fs
) || mnt_fs_is_netfs(fs
)) {
293 mnt_table_remove_fs(tab
, fs
);
297 /* convert LABEL= (etc.) from fstab to paths */
299 const char *spec
= mnt_fs_get_source(fs
);
302 mnt_table_remove_fs(tab
, fs
);
305 src
= mnt_resolve_spec(spec
, cache
);
306 mnt_fs_set_source(fs
, src
);
309 if (!src
|| *src
!= '/') {
310 mnt_table_remove_fs(tab
, fs
);
315 /* de-duplicate by source */
316 mnt_table_uniq_fs(tab
, MNT_UNIQ_FORWARD
, uniq_fs_source_cmp
);
318 mnt_reset_iter(itr
, MNT_ITER_BACKWARD
);
321 while (mnt_table_next_fs(tab
, itr
, &fs
) == 0) {
322 const char *src
= mnt_fs_get_srcpath(fs
),
323 *tgt
= mnt_fs_get_target(fs
);
327 /* Is it really accessible mountpoint? Not all mountpoints are
328 * accessible (maybe over mounted by another filesystem) */
329 path
= mnt_get_mountpoint(tgt
);
330 if (path
&& strcmp(path
, tgt
) == 0)
334 continue; /* overlaying mount */
336 /* FITRIM on read-only filesystem can fail, and it can fail */
337 if (access(tgt
, W_OK
) != 0) {
344 if (!is_directory(tgt
, 1) ||
345 !has_discard(src
, &wholedisk
))
350 * We're able to detect that the device supports discard, but
351 * things also depend on filesystem or device mapping, for
352 * example LUKS (by default) does not support FSTRIM.
354 * This is reason why we ignore EOPNOTSUPP and ENOTTY errors
355 * from discard ioctl.
357 rc
= fstrim_filesystem(ctl
, tgt
, src
);
360 else if (rc
== 1 && !ctl
->quiet
)
361 warnx(_("%s: the discard operation is not supported"), tgt
);
365 ul_unref_path(wholedisk
);
366 mnt_unref_table(tab
);
367 mnt_unref_cache(cache
);
369 if (cnt
&& cnt
== cnt_err
)
370 return MNT_EX_FAIL
; /* all failed */
372 return MNT_EX_SOMEOK
; /* some ok */
374 return MNT_EX_SUCCESS
;
377 static void __attribute__((__noreturn__
)) usage(void)
380 fputs(USAGE_HEADER
, out
);
382 _(" %s [options] <mount point>\n"), program_invocation_short_name
);
384 fputs(USAGE_SEPARATOR
, out
);
385 fputs(_("Discard unused blocks on a mounted filesystem.\n"), out
);
387 fputs(USAGE_OPTIONS
, out
);
388 fputs(_(" -a, --all trim all supported mounted filesystems\n"), out
);
389 fputs(_(" -A, --fstab trim all supported mounted filesystems from /etc/fstab\n"), out
);
390 fputs(_(" -o, --offset <num> the offset in bytes to start discarding from\n"), out
);
391 fputs(_(" -l, --length <num> the number of bytes to discard\n"), out
);
392 fputs(_(" -m, --minimum <num> the minimum extent length to discard\n"), out
);
393 fputs(_(" -v, --verbose print number of discarded bytes\n"), out
);
394 fputs(_(" --quiet suppress trim error messages\n"), out
);
395 fputs(_(" -n, --dry-run does everything, but trim\n"), out
);
397 fputs(USAGE_SEPARATOR
, out
);
398 printf(USAGE_HELP_OPTIONS(21));
399 printf(USAGE_MAN_TAIL("fstrim(8)"));
403 int main(int argc
, char **argv
)
407 struct fstrim_control ctl
= {
408 .range
= { .len
= ULLONG_MAX
}
411 OPT_QUIET
= CHAR_MAX
+ 1
414 static const struct option longopts
[] = {
415 { "all", no_argument
, NULL
, 'a' },
416 { "fstab", no_argument
, NULL
, 'A' },
417 { "help", no_argument
, NULL
, 'h' },
418 { "version", no_argument
, NULL
, 'V' },
419 { "offset", required_argument
, NULL
, 'o' },
420 { "length", required_argument
, NULL
, 'l' },
421 { "minimum", required_argument
, NULL
, 'm' },
422 { "verbose", no_argument
, NULL
, 'v' },
423 { "quiet", no_argument
, NULL
, OPT_QUIET
},
424 { "dry-run", no_argument
, NULL
, 'n' },
428 setlocale(LC_ALL
, "");
429 bindtextdomain(PACKAGE
, LOCALEDIR
);
431 close_stdout_atexit();
433 while ((c
= getopt_long(argc
, argv
, "Aahl:m:no:Vv", longopts
, NULL
)) != -1) {
445 ctl
.range
.len
= strtosize_or_err(optarg
,
446 _("failed to parse length"));
449 ctl
.range
.start
= strtosize_or_err(optarg
,
450 _("failed to parse offset"));
453 ctl
.range
.minlen
= strtosize_or_err(optarg
,
454 _("failed to parse minimum extent length"));
465 print_version(EXIT_SUCCESS
);
467 errtryhelp(EXIT_FAILURE
);
473 errx(EXIT_FAILURE
, _("no mountpoint specified"));
474 path
= argv
[optind
++];
477 if (optind
!= argc
) {
478 warnx(_("unexpected number of arguments"));
479 errtryhelp(EXIT_FAILURE
);
483 return fstrim_all(&ctl
); /* MNT_EX_* codes */
485 if (!is_directory(path
, 0))
488 rc
= fstrim_filesystem(&ctl
, path
, NULL
);
489 if (rc
== 1 && !ctl
.quiet
)
490 warnx(_("%s: the discard operation is not supported"), path
);
492 return rc
== 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;