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"
46 #include "exitcodes.h"
56 #define FITRIM _IOWR('X', 121, struct fstrim_range)
59 /* returns: 0 = success, 1 = unsupported, < 0 = error */
60 static int fstrim_filesystem(const char *path
, struct fstrim_range
*rangetpl
,
65 struct fstrim_range range
;
67 /* kernel modifies the range */
68 memcpy(&range
, rangetpl
, sizeof(range
));
70 fd
= open(path
, O_RDONLY
);
72 warn(_("cannot open %s"), path
);
76 if (fstat(fd
, &sb
) == -1) {
77 warn(_("stat of %s failed"), path
);
81 if (!S_ISDIR(sb
.st_mode
)) {
82 warnx(_("%s: not a directory"), path
);
87 if (ioctl(fd
, FITRIM
, &range
)) {
88 rc
= errno
== EOPNOTSUPP
|| errno
== ENOTTY
? 1 : -errno
;
91 warn(_("%s: FITRIM ioctl failed"), path
);
96 char *str
= size_to_human_string(
97 SIZE_SUFFIX_3LETTER
| SIZE_SUFFIX_SPACE
,
98 (uint64_t) range
.len
);
99 /* TRANSLATORS: The standard value here is a very large number. */
100 printf(_("%s: %s (%" PRIu64
" bytes) trimmed\n"),
101 path
, str
, (uint64_t) range
.len
);
112 static int has_discard(const char *devname
, struct sysfs_cxt
*wholedisk
)
114 struct sysfs_cxt cxt
, *parent
= NULL
;
119 dev
= sysfs_devname_to_devno(devname
, NULL
);
123 * This is tricky to read the info from sys/, because the queue
124 * attributes are provided for whole devices (disk) only. We're trying
125 * to reuse the whole-disk sysfs context to optimize this stuff (as
126 * system usually have just one disk only).
128 if (sysfs_devno_to_wholedisk(dev
, NULL
, 0, &disk
) || !disk
)
131 if (wholedisk
->devno
!= disk
) {
132 sysfs_deinit(wholedisk
);
133 if (sysfs_init(wholedisk
, disk
, NULL
))
139 rc
= sysfs_init(&cxt
, dev
, parent
);
141 rc
= sysfs_read_u64(&cxt
, "queue/discard_granularity", &dg
);
144 return rc
== 0 && dg
> 0;
148 static int uniq_fs_target_cmp(
149 struct libmnt_table
*tb
__attribute__((__unused__
)),
153 return !mnt_fs_streq_target(a
, mnt_fs_get_target(b
));
156 static int uniq_fs_source_cmp(
157 struct libmnt_table
*tb
__attribute__((__unused__
)),
161 if (mnt_fs_is_pseudofs(a
) || mnt_fs_is_netfs(a
) ||
162 mnt_fs_is_pseudofs(b
) || mnt_fs_is_netfs(b
))
165 return !mnt_fs_streq_srcpath(a
, mnt_fs_get_srcpath(b
));
169 * fstrim --all follows "mount -a" return codes:
173 * 64 = some failed, some success
175 static int fstrim_all(struct fstrim_range
*rangetpl
, int verbose
)
177 struct libmnt_fs
*fs
;
178 struct libmnt_iter
*itr
;
179 struct libmnt_table
*tab
;
180 struct sysfs_cxt wholedisk
= UL_SYSFSCXT_EMPTY
;
181 int cnt
= 0, cnt_err
= 0;
185 itr
= mnt_new_iter(MNT_ITER_BACKWARD
);
187 err(MNT_EX_FAIL
, _("failed to initialize libmount iterator"));
189 tab
= mnt_new_table_from_file(_PATH_PROC_MOUNTINFO
);
191 err(MNT_EX_FAIL
, _("failed to parse %s"), _PATH_PROC_MOUNTINFO
);
193 /* de-duplicate by mountpoints */
194 mnt_table_uniq_fs(tab
, 0, uniq_fs_target_cmp
);
196 /* de-duplicate by source */
197 mnt_table_uniq_fs(tab
, MNT_UNIQ_FORWARD
, uniq_fs_source_cmp
);
199 while (mnt_table_next_fs(tab
, itr
, &fs
) == 0) {
200 const char *src
= mnt_fs_get_srcpath(fs
),
201 *tgt
= mnt_fs_get_target(fs
);
205 if (!src
|| !tgt
|| *src
!= '/' ||
206 mnt_fs_is_pseudofs(fs
) ||
210 /* Is it really accessible mountpoint? Not all mountpoints are
211 * accessible (maybe over mounted by another filesystem) */
212 path
= mnt_get_mountpoint(tgt
);
213 if (path
&& strcmp(path
, tgt
) == 0)
217 continue; /* overlaying mount */
219 if (!has_discard(src
, &wholedisk
))
224 * We're able to detect that the device supports discard, but
225 * things also depend on filesystem or device mapping, for
226 * example vfat or LUKS (by default) does not support FSTRIM.
228 * This is reason why we ignore EOPNOTSUPP and ENOTTY errors
229 * from discard ioctl.
231 if (fstrim_filesystem(tgt
, rangetpl
, verbose
) < 0)
235 sysfs_deinit(&wholedisk
);
236 mnt_unref_table(tab
);
239 if (cnt
&& cnt
== cnt_err
)
240 return MNT_EX_FAIL
; /* all failed */
242 return MNT_EX_SOMEOK
; /* some ok */
247 static void __attribute__((__noreturn__
)) usage(void)
250 fputs(USAGE_HEADER
, out
);
252 _(" %s [options] <mount point>\n"), program_invocation_short_name
);
254 fputs(USAGE_SEPARATOR
, out
);
255 fputs(_("Discard unused blocks on a mounted filesystem.\n"), out
);
257 fputs(USAGE_OPTIONS
, out
);
258 fputs(_(" -a, --all trim all mounted filesystems that are supported\n"), out
);
259 fputs(_(" -o, --offset <num> the offset in bytes to start discarding from\n"), out
);
260 fputs(_(" -l, --length <num> the number of bytes to discard\n"), out
);
261 fputs(_(" -m, --minimum <num> the minimum extent length to discard\n"), out
);
262 fputs(_(" -v, --verbose print number of discarded bytes\n"), out
);
264 fputs(USAGE_SEPARATOR
, out
);
265 fputs(USAGE_HELP
, out
);
266 fputs(USAGE_VERSION
, out
);
267 fprintf(out
, USAGE_MAN_TAIL("fstrim(8)"));
271 int main(int argc
, char **argv
)
274 int c
, rc
, verbose
= 0, all
= 0;
275 struct fstrim_range range
;
277 static const struct option longopts
[] = {
278 { "all", no_argument
, NULL
, 'a' },
279 { "help", no_argument
, NULL
, 'h' },
280 { "version", no_argument
, NULL
, 'V' },
281 { "offset", required_argument
, NULL
, 'o' },
282 { "length", required_argument
, NULL
, 'l' },
283 { "minimum", required_argument
, NULL
, 'm' },
284 { "verbose", no_argument
, NULL
, 'v' },
288 setlocale(LC_ALL
, "");
289 bindtextdomain(PACKAGE
, LOCALEDIR
);
291 atexit(close_stdout
);
293 memset(&range
, 0, sizeof(range
));
294 range
.len
= ULLONG_MAX
;
296 while ((c
= getopt_long(argc
, argv
, "ahVo:l:m:v", longopts
, NULL
)) != -1) {
305 printf(UTIL_LINUX_VERSION
);
308 range
.len
= strtosize_or_err(optarg
,
309 _("failed to parse length"));
312 range
.start
= strtosize_or_err(optarg
,
313 _("failed to parse offset"));
316 range
.minlen
= strtosize_or_err(optarg
,
317 _("failed to parse minimum extent length"));
323 errtryhelp(EXIT_FAILURE
);
330 errx(EXIT_FAILURE
, _("no mountpoint specified"));
331 path
= argv
[optind
++];
334 if (optind
!= argc
) {
335 warnx(_("unexpected number of arguments"));
336 errtryhelp(EXIT_FAILURE
);
340 rc
= fstrim_all(&range
, verbose
);
342 rc
= fstrim_filesystem(path
, &range
, verbose
);
344 warnx(_("%s: the discard operation is not supported"), path
);