Initial revision.
[trim] / trim.c
1 /*-
2  * Copyright (c) 2013 Emil Mikulic <emikulic@gmail.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  *
16  * [ http://www.openbsd.org/cgi-bin/cvsweb/src/share/misc/license.template ]
17  *
18  * trim: uses DIOCGDELETE (BIO_DELETE) to TRIM SSDs and other devices that
19  * support it.
20  */
21
22 #include <sys/disk.h>
23 #include <sys/ioctl.h>
24
25 #include <err.h>
26 #include <fcntl.h>
27 #include <stdint.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <unistd.h>
33
34 /* Fill a range of the device with non-zeroes. */
35 static void fill(const int fd, const off_t ofs, const size_t len) {
36   static char* buf = NULL;
37   static size_t buf_size = 0;
38   ssize_t ret;
39
40   if (buf_size < len) {
41     if (buf)
42       free(buf);
43     buf = malloc(len);
44     if (buf == NULL)
45       errx(1, "malloc(%zd) failed", len);
46     buf_size = len;
47     memset(buf, 255, len);
48   }
49   if ((ret = pwrite(fd, buf, len, ofs)) != (ssize_t)len)
50     err(1, "pwrite(%zd bytes at offset %jd) returned %jd",
51         len,
52         (intmax_t)ofs,
53         (intmax_t)ret);
54 }
55
56 /* Returns 1 if the range is zeroed out. */
57 static int verify_zero(const int fd, const off_t ofs, const size_t len) {
58   static char* buf = NULL;
59   static size_t buf_size = 0;
60   ssize_t ret;
61   size_t i;
62
63   if (buf_size < len) {
64     if (buf)
65       free(buf);
66     buf = malloc(len);
67     if (buf == NULL)
68       errx(1, "malloc(%zd) failed", len);
69     buf_size = len;
70   }
71   if ((ret = pread(fd, buf, len, ofs)) != (ssize_t)len)
72     err(1, "pread(%zd bytes at offset %jd) returned %jd",
73         len,
74         (intmax_t)ofs,
75         (intmax_t)ret);
76   for (i = 0; i < len; i++)
77     if (buf[i] != 0)
78       return 0;
79   return 1;
80 }
81
82 /* This is where the magic happens. */
83 static void trim(const int fd, const off_t ofs, const size_t len) {
84   off_t arg[2];
85   arg[0] = ofs;
86   arg[1] = (off_t)len;
87   if (ioctl(fd, DIOCGDELETE, arg) == -1)
88     err(1, "ioctl(DIOCGDELETE) at %jd, size %jd, failed",
89         (intmax_t)arg[0],
90         (intmax_t)arg[1]);
91 }
92
93 /* Probe for the smallest range that the device can trim. */
94 static size_t probe_trim_size(const int fd, const u_int sectorsize) {
95   size_t trimsize;
96   const int limit = 1024;  /* sectors, when probing */
97   const int retries = 3;  /* seconds, when probing */
98   int retry;
99
100   for (trimsize = sectorsize; trimsize < sectorsize * limit; trimsize *= 2) {
101     fprintf(stderr, "trying to trim %zd bytes... ", trimsize);
102     if (verify_zero(fd, 0, trimsize)) {
103       fprintf(stderr, "(dirtying up first) ");
104       fill(fd, 0, trimsize);
105       if (verify_zero(fd, 0, trimsize))
106         errx(1, "device returned zeros after writing non-zeros to it");
107     }
108     for (retry = 0; retry < retries; retry++) {
109       if (retry > 0) {
110         sleep(1);
111       }
112       trim(fd, 0, trimsize);
113       if (verify_zero(fd, 0, trimsize)) {
114         fprintf(stderr, "success!\n");
115         return trimsize;
116       } else {
117         fprintf(stderr, "(failure) ");
118       }
119     }
120     fprintf(stderr, "didn't work.\n");
121   }
122   errx(1, "this device doesn't appear to support trim");
123 }
124
125 int main(int argc, char **argv) {
126   int fd;
127   off_t mediasize;
128   u_int sectorsize;
129   size_t trimsize;
130   off_t pos;
131
132   if (argc != 2) {
133     fprintf(stderr, "usage: %s /dev/...\n", argv[0]);
134     exit(1);
135   }
136   if ((fd = open(argv[1], O_RDWR)) == -1)
137     err(1, "open(%s) failed", argv[1]);
138   if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) == -1)
139     err(1, "ioctl(DIOCGMEDIASIZE) failed");
140   if (ioctl(fd, DIOCGSECTORSIZE, &sectorsize) == -1)
141     err(1, "ioctl(DIOCGSECTORSIZE) failed");
142   printf("%s: mediasize %jd, sectorsize %u.\n",
143          argv[1],
144          (intmax_t)mediasize,
145          sectorsize);
146   trimsize = probe_trim_size(fd, sectorsize);
147
148   /* For performance. */
149   trimsize *= 1024;
150   printf("trimming blocks of length %zd\n", trimsize);
151
152   for (pos = 0; pos < mediasize; pos += trimsize) {
153     struct timespec t0;
154     struct timespec t1;
155
156     fprintf(stderr, "\r%jd / %jd (%.2f%%) ",
157             (intmax_t)pos,
158             (intmax_t)mediasize,
159             (double)pos * 100. / (double)mediasize);
160     if (clock_gettime(CLOCK_MONOTONIC, &t0) == -1)
161       err(1, "clock_gettime(CLOCK_MONOTONIC) failed");
162     trim(fd, pos, trimsize);
163     if (!verify_zero(fd, pos, trimsize))
164       printf("trim failed at pos %jd!\n", (intmax_t)pos);
165     if (clock_gettime(CLOCK_MONOTONIC, &t1) == -1)
166       err(1, "clock_gettime(CLOCK_MONOTONIC) failed");
167
168     t1.tv_sec -= t0.tv_sec;
169     if (t1.tv_nsec < t0.tv_nsec) {
170       t1.tv_nsec += 1000 * 1000 * 1000;
171       t1.tv_sec--;
172     }
173     t1.tv_nsec -= t0.tv_nsec;
174     fprintf(stderr, "%d.%09d sec ", (int)t1.tv_sec, (int)t1.tv_nsec);
175   }
176   fprintf(stderr, "\ndone!\n");
177   close(fd);
178   return 0;
179 }
180 /* vim:set ts=2 sw=2 et tw=80: */