]> git.ipfire.org Git - thirdparty/util-linux.git/blame - sys-utils/fstrim.c
docs: mention nice(1) in renice(1) manual page
[thirdparty/util-linux.git] / sys-utils / fstrim.c
CommitLineData
d9e2d0dd
LC
1/*
2 * fstrim.c -- discard the part (or whole) of mounted filesystem.
3 *
fce05e96 4 * Copyright (C) 2010 Red Hat, Inc. All rights reserved.
d9e2d0dd
LC
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
e3d61a45 24 * discarded, or simply discard whole filesystem.
d9e2d0dd 25 */
fce05e96 26
d9e2d0dd
LC
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>
d9e2d0dd
LC
35
36#include <sys/ioctl.h>
37#include <sys/stat.h>
38#include <linux/fs.h>
39
40#include "nls.h"
41#include "strutils.h"
eb76ca98 42#include "c.h"
efb8854f 43#include "closestream.h"
36c370cb
KZ
44#include "pathnames.h"
45#include "sysfs.h"
46#include "exitcodes.h"
47
48#include <libmount.h>
d9e2d0dd
LC
49
50#ifndef FITRIM
51struct fstrim_range {
52 uint64_t start;
53 uint64_t len;
54 uint64_t minlen;
55};
fce05e96 56#define FITRIM _IOWR('X', 121, struct fstrim_range)
d9e2d0dd
LC
57#endif
58
36c370cb
KZ
59/* returns: 0 = success, 1 = unsupported, < 0 = error */
60static int fstrim_filesystem(const char *path, struct fstrim_range *rangetpl,
61 int verbose)
62{
63 int fd;
64 struct stat sb;
65 struct fstrim_range range;
66
67 /* kernel modifies the range */
68 memcpy(&range, rangetpl, sizeof(range));
69
e612ead9
SK
70 fd = open(path, O_RDONLY);
71 if (fd < 0) {
72 warn(_("cannot open %s"), path);
73 return -1;
74 }
75 if (fstat(fd, &sb) == -1) {
36c370cb
KZ
76 warn(_("stat failed %s"), path);
77 return -1;
78 }
79 if (!S_ISDIR(sb.st_mode)) {
80 warnx(_("%s: not a directory"), path);
81 return -1;
82 }
36c370cb
KZ
83 errno = 0;
84 if (ioctl(fd, FITRIM, &range)) {
85 int rc = errno == EOPNOTSUPP || errno == ENOTTY ? 1 : -1;
86
87 if (rc != 1)
88 warn(_("%s: FITRIM ioctl failed"), path);
89 close(fd);
90 return rc;
91 }
92
93 if (verbose) {
94 char *str = size_to_human_string(
95 SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE,
96 (uint64_t) range.len);
97 /* TRANSLATORS: The standard value here is a very large number. */
98 printf(_("%s: %s (%" PRIu64 " bytes) trimmed\n"),
99 path, str, (uint64_t) range.len);
100 free(str);
101 }
102 close(fd);
103 return 0;
104}
105
106static int has_discard(const char *devname, struct sysfs_cxt *wholedisk)
107{
108 struct sysfs_cxt cxt, *parent = NULL;
109 uint64_t dg = 0;
110 dev_t disk = 0, dev;
111 int rc;
112
113 dev = sysfs_devname_to_devno(devname, NULL);
114 if (!dev)
115 return 1;
116 /*
117 * This is tricky to read the info from sys/, because the queue
118 * atrributes are provided for whole devices (disk) only. We're trying
119 * to reuse the whole-disk sysfs context to optimize this stuff (as
0e65dcde 120 * system usually have just one disk only).
36c370cb
KZ
121 */
122 if (sysfs_devno_to_wholedisk(dev, NULL, 0, &disk) || !disk)
123 return 1;
124 if (dev != disk) {
125 if (wholedisk->devno != disk) {
126 sysfs_deinit(wholedisk);
127 if (sysfs_init(wholedisk, disk, NULL))
128 return 1;
129 }
130 parent = wholedisk;
131 }
132
133 rc = sysfs_init(&cxt, dev, parent);
134 if (!rc)
135 rc = sysfs_read_u64(&cxt, "queue/discard_granularity", &dg);
136
137 sysfs_deinit(&cxt);
138 return rc == 0 && dg > 0;
139}
140
e05a3400
KZ
141
142static int uniq_fs_target_cmp(
143 struct libmnt_table *tb __attribute__((__unused__)),
144 struct libmnt_fs *a,
145 struct libmnt_fs *b)
146{
147 return !mnt_fs_streq_target(a, mnt_fs_get_target(b));
148}
149
36c370cb
KZ
150/*
151 * fstrim --all follows "mount -a" return codes:
152 *
153 * 0 = all success
154 * 32 = all failed
155 * 64 = some failed, some success
156 */
157static int fstrim_all(struct fstrim_range *rangetpl, int verbose)
158{
159 struct libmnt_fs *fs;
160 struct libmnt_iter *itr;
161 struct libmnt_table *tab;
162 struct sysfs_cxt wholedisk = UL_SYSFSCXT_EMPTY;
163 int cnt = 0, cnt_err = 0;
164
e05a3400
KZ
165 mnt_init_debug(0);
166
36c370cb
KZ
167 itr = mnt_new_iter(MNT_ITER_BACKWARD);
168 if (!itr)
169 err(MOUNT_EX_FAIL, _("failed to initialize libmount iterator"));
170
171 tab = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
172 if (!tab)
173 err(MOUNT_EX_FAIL, _("failed to parse %s"), _PATH_PROC_MOUNTINFO);
174
e05a3400
KZ
175 /* de-duplicate the table */
176 mnt_table_uniq_fs(tab, 0, uniq_fs_target_cmp);
177
36c370cb
KZ
178 while (mnt_table_next_fs(tab, itr, &fs) == 0) {
179 const char *src = mnt_fs_get_srcpath(fs),
180 *tgt = mnt_fs_get_target(fs);
181 char *path;
182 int rc = 1;
183
184 if (!src || !tgt || *src != '/' ||
185 mnt_fs_is_pseudofs(fs) ||
186 mnt_fs_is_netfs(fs))
187 continue;
188
189 /* Is it really accessible mountpoint? Not all mountpoints are
190 * accessible (maybe over mounted by another fylesystem) */
191 path = mnt_get_mountpoint(tgt);
192 if (path && strcmp(path, tgt) == 0)
193 rc = 0;
194 free(path);
195 if (rc)
196 continue; /* overlaying mount */
197
198 if (!has_discard(src, &wholedisk))
199 continue;
200 cnt++;
201
202 /*
203 * We're able to detect that the device supports discard, but
204 * things also depend on filesystem or device mapping, for
205 * example vfat or LUKS (by default) does not support FSTRIM.
206 *
207 * This is reason why we ignore EOPNOTSUPP and ENOTTY errors
208 * from discard ioctl.
209 */
210 if (fstrim_filesystem(tgt, rangetpl, verbose) < 0)
211 cnt_err++;
212 }
213
214 sysfs_deinit(&wholedisk);
e05a3400 215 mnt_unref_table(tab);
df1a1ca0 216 mnt_free_iter(itr);
36c370cb
KZ
217
218 if (cnt && cnt == cnt_err)
219 return MOUNT_EX_FAIL; /* all failed */
220 if (cnt && cnt_err)
221 return MOUNT_EX_SOMEOK; /* some ok */
222
223 return EXIT_SUCCESS;
224}
225
d9e2d0dd
LC
226static void __attribute__((__noreturn__)) usage(FILE *out)
227{
47a9edbd 228 fputs(USAGE_HEADER, out);
fce05e96
KZ
229 fprintf(out,
230 _(" %s [options] <mount point>\n"), program_invocation_short_name);
451dbcfa
BS
231
232 fputs(USAGE_SEPARATOR, out);
233 fputs(_("Discard unused blocks on a mounted filesystem.\n"), out);
234
fce05e96 235 fputs(USAGE_OPTIONS, out);
d6bbe804
BS
236 fputs(_(" -a, --all trim all mounted filesystems that are supported\n"), out);
237 fputs(_(" -o, --offset <num> the offset in bytes to start discarding from\n"), out);
238 fputs(_(" -l, --length <num> the number of bytes to discard\n"), out);
239 fputs(_(" -m, --minimum <num> the minimum extent length to discard\n"), out);
a60fa93c
KZ
240 fputs(_(" -v, --verbose print number of discarded bytes\n"), out);
241
47a9edbd
SK
242 fputs(USAGE_SEPARATOR, out);
243 fputs(USAGE_HELP, out);
244 fputs(USAGE_VERSION, out);
fce05e96 245 fprintf(out, USAGE_MAN_TAIL("fstrim(8)"));
d9e2d0dd
LC
246 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
247}
248
c84ed54c
KZ
249int main(int argc, char **argv)
250{
b35c3727 251 char *path = NULL;
36c370cb 252 int c, rc, verbose = 0, all = 0;
fce05e96 253 struct fstrim_range range;
c84ed54c 254
fce05e96 255 static const struct option longopts[] = {
36c370cb 256 { "all", 0, 0, 'a' },
d9e2d0dd 257 { "help", 0, 0, 'h' },
47a9edbd 258 { "version", 0, 0, 'V' },
d9e2d0dd
LC
259 { "offset", 1, 0, 'o' },
260 { "length", 1, 0, 'l' },
261 { "minimum", 1, 0, 'm' },
262 { "verbose", 0, 0, 'v' },
263 { NULL, 0, 0, 0 }
264 };
265
266 setlocale(LC_ALL, "");
267 bindtextdomain(PACKAGE, LOCALEDIR);
268 textdomain(PACKAGE);
efb8854f 269 atexit(close_stdout);
d9e2d0dd 270
fce05e96
KZ
271 memset(&range, 0, sizeof(range));
272 range.len = ULLONG_MAX;
d9e2d0dd 273
36c370cb 274 while ((c = getopt_long(argc, argv, "ahVo:l:m:v", longopts, NULL)) != -1) {
d9e2d0dd 275 switch(c) {
36c370cb
KZ
276 case 'a':
277 all = 1;
278 break;
d9e2d0dd
LC
279 case 'h':
280 usage(stdout);
281 break;
47a9edbd
SK
282 case 'V':
283 printf(UTIL_LINUX_VERSION);
284 return EXIT_SUCCESS;
d9e2d0dd 285 case 'l':
fce05e96 286 range.len = strtosize_or_err(optarg,
28c71021 287 _("failed to parse length"));
d9e2d0dd
LC
288 break;
289 case 'o':
fce05e96 290 range.start = strtosize_or_err(optarg,
28c71021 291 _("failed to parse offset"));
d9e2d0dd
LC
292 break;
293 case 'm':
fce05e96 294 range.minlen = strtosize_or_err(optarg,
28c71021 295 _("failed to parse minimum extent length"));
d9e2d0dd
LC
296 break;
297 case 'v':
298 verbose = 1;
299 break;
300 default:
301 usage(stderr);
302 break;
303 }
fce05e96 304 }
d9e2d0dd 305
36c370cb
KZ
306 if (!all) {
307 if (optind == argc)
308 errx(EXIT_FAILURE, _("no mountpoint specified"));
309 path = argv[optind++];
310 }
d9e2d0dd
LC
311
312 if (optind != argc) {
313 warnx(_("unexpected number of arguments"));
314 usage(stderr);
315 }
316
36c370cb
KZ
317 if (all)
318 rc = fstrim_all(&range, verbose);
319 else {
320 rc = fstrim_filesystem(path, &range, verbose);
321 if (rc == 1) {
1d231190 322 warnx(_("%s: the discard operation is not supported"), path);
36c370cb
KZ
323 rc = EXIT_FAILURE;
324 }
fe98b180 325 }
36c370cb
KZ
326
327 return rc;
d9e2d0dd 328}