e23bcb38511b202886b230323731bebef583e434
[xfstests-dev.git] / src / fstrim.c
1 /*
2  * fstrim.c -- discard the part (or whole) of mounted filesystem.
3  *
4  * Copyright (C) 2010 Red Hat, Inc., Lukas Czerner <lczerner@redhat.com>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * This program uses FITRIM ioctl to discard parts or the whole filesystem
20  * online (mounted). You can specify range (start and lenght) to be
21  * discarded, or simply discard while filesystem.
22  *
23  * Usage: fstrim [options] <mount point>
24  */
25
26 #include <string.h>
27 #include <unistd.h>
28 #include <stdlib.h>
29 #include <errno.h>
30 #include <stdio.h>
31 #include <stdint.h>
32 #include <fcntl.h>
33 #include <limits.h>
34 #include <stdarg.h>
35 #include <getopt.h>
36
37 #include <sys/ioctl.h>
38 #include <sys/stat.h>
39 #include <linux/fs.h>
40
41 #ifndef FITRIM
42 struct fstrim_range {
43         uint64_t start;
44         uint64_t len;
45         uint64_t minlen;
46 };
47 #define FITRIM          _IOWR('X', 121, struct fstrim_range)
48 #endif
49
50 const char *program_name = "fstrim";
51
52 struct options {
53         struct fstrim_range *range;
54         char mpoint[PATH_MAX];
55         char verbose;
56 };
57
58 static void usage(void)
59 {
60         fprintf(stderr,
61                 "Usage: %s [-s start] [-l length] [-m minimum-extent]"
62                 " [-v] {mountpoint}\n\t"
63                 "-s Starting Byte to discard from\n\t"
64                 "-l Number of Bytes to discard from the start\n\t"
65                 "-m Minimum extent length to discard\n\t"
66                 "-v Verbose - number of discarded bytes\n",
67                 program_name);
68 }
69
70 static void err_exit(const char *fmt, ...)
71 {
72         va_list pvar;
73         va_start(pvar, fmt);
74         vfprintf(stderr, fmt, pvar);
75         va_end(pvar);
76         usage();
77         exit(EXIT_FAILURE);
78 }
79
80 /**
81  * Get the number from argument. It can be number followed by
82  * units: k|K, m|M, g|G, t|T
83  */
84 static unsigned long long get_number(char **optarg)
85 {
86         char *opt, *end;
87         unsigned long long number, max;
88
89         /* get the max to avoid overflow */
90         max = ULLONG_MAX / 1024;
91         number = 0;
92         opt = *optarg;
93
94         if (*opt == '-') {
95                 err_exit("%s: %s (%s)\n", program_name,
96                          strerror(ERANGE), *optarg);
97         }
98
99         errno = 0;
100         number = strtoull(opt, &end , 0);
101         if (errno)
102                 err_exit("%s: %s (%s)\n", program_name,
103                          strerror(errno), *optarg);
104
105         /*
106          * Convert units to numbers. Fall-through stack is used for units
107          * so absence of breaks is intentional.
108          */
109         switch (*end) {
110         case 'T': /* terabytes */
111         case 't':
112                 if (number > max)
113                         err_exit("%s: %s (%s)\n", program_name,
114                                  strerror(ERANGE), *optarg);
115                 number *= 1024;
116         case 'G': /* gigabytes */
117         case 'g':
118                 if (number > max)
119                         err_exit("%s: %s (%s)\n", program_name,
120                                  strerror(ERANGE), *optarg);
121                 number *= 1024;
122         case 'M': /* megabytes */
123         case 'm':
124                 if (number > max)
125                         err_exit("%s: %s (%s)\n", program_name,
126                                  strerror(ERANGE), *optarg);
127                 number *= 1024;
128         case 'K': /* kilobytes */
129         case 'k':
130                 if (number > max)
131                         err_exit("%s: %s (%s)\n", program_name,
132                                  strerror(ERANGE), *optarg);
133                 number *= 1024;
134                 end++;
135         case '\0': /* end of the string */
136                 break;
137         default:
138                 err_exit("%s: %s (%s)\n", program_name,
139                          strerror(EINVAL), *optarg);
140                 return 0;
141         }
142
143         if (*end != '\0') {
144                 err_exit("%s: %s (%s)\n", program_name,
145                          strerror(EINVAL), *optarg);
146         }
147
148         return number;
149 }
150
151 static int parse_opts(int argc, char **argv, struct options *opts)
152 {
153         int c;
154
155         while ((c = getopt(argc, argv, "s:l:m:v")) != EOF) {
156                 switch (c) {
157                 case 's': /* starting point */
158                         opts->range->start = get_number(&optarg);
159                         break;
160                 case 'l': /* length */
161                         opts->range->len = get_number(&optarg);
162                         break;
163                 case 'm': /* minlen */
164                         opts->range->minlen = get_number(&optarg);
165                         break;
166                 case 'v': /* verbose */
167                         opts->verbose = 1;
168                         break;
169                 default:
170                         return EXIT_FAILURE;
171                 }
172         }
173
174         return 0;
175 }
176
177 int main(int argc, char **argv)
178 {
179         struct options *opts;
180         struct stat sb;
181         int fd, err = 0, ret = EXIT_FAILURE;
182
183         opts = malloc(sizeof(struct options));
184         if (!opts)
185                 err_exit("%s: malloc(): %s\n", program_name, strerror(errno));
186
187         opts->range = NULL;
188         opts->verbose = 0;
189
190         if (argc > 1)
191                 strncpy(opts->mpoint, argv[argc - 1], sizeof(opts->mpoint));
192
193         opts->range = calloc(1, sizeof(struct fstrim_range));
194         if (!opts->range) {
195                 fprintf(stderr, "%s: calloc(): %s\n", program_name,
196                         strerror(errno));
197                 goto free_opts;
198         }
199
200         opts->range->len = ULLONG_MAX;
201
202         if (argc > 2)
203                 err = parse_opts(argc, argv, opts);
204
205         if (err) {
206                 usage();
207                 goto free_opts;
208         }
209
210         if (strnlen(opts->mpoint, 1) < 1) {
211                 fprintf(stderr, "%s: You have to specify mount point.\n",
212                         program_name);
213                 usage();
214                 goto free_opts;
215         }
216
217         if (stat(opts->mpoint, &sb) == -1) {
218                 fprintf(stderr, "%s: %s: %s\n", program_name,
219                         opts->mpoint, strerror(errno));
220                 usage();
221                 goto free_opts;
222         }
223
224         if (!S_ISDIR(sb.st_mode)) {
225                 fprintf(stderr, "%s: %s: (%s)\n", program_name,
226                         opts->mpoint, strerror(ENOTDIR));
227                 usage();
228                 goto free_opts;
229         }
230
231         fd = open(opts->mpoint, O_RDONLY);
232         if (fd < 0) {
233                 fprintf(stderr, "%s: open(%s): %s\n", program_name,
234                         opts->mpoint, strerror(errno));
235                 goto free_opts;
236         }
237
238         if (ioctl(fd, FITRIM, opts->range)) {
239                 fprintf(stderr, "%s: FSTRIM: %s\n", program_name,
240                         strerror(errno));
241                 goto free_opts;
242         }
243
244         if ((opts->verbose) && (opts->range))
245                 fprintf(stdout, "%llu Bytes were trimmed\n", (unsigned long long)opts->range->len);
246
247         ret = EXIT_SUCCESS;
248
249 free_opts:
250         if (opts) {
251                 if (opts->range)
252                         free(opts->range);
253                 free(opts);
254         }
255
256         return ret;
257 }