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: */