Initial revision. master
authorEmil Mikulic <emikulic@gmail.com>
Tue, 9 Apr 2013 20:12:27 +0000 (06:12 +1000)
committerEmil Mikulic <emikulic@gmail.com>
Tue, 9 Apr 2013 20:12:34 +0000 (06:12 +1000)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
trim.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..fa27412
--- /dev/null
@@ -0,0 +1,2 @@
+trim
+trim.o
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..9a16bac
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+PROG=  trim
+MAN=
+WARNS= 6
+.include <bsd.prog.mk>
+# vim:set noet:
diff --git a/trim.c b/trim.c
new file mode 100644 (file)
index 0000000..04e53b4
--- /dev/null
+++ b/trim.c
@@ -0,0 +1,180 @@
+/*-
+ * Copyright (c) 2013 Emil Mikulic <emikulic@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * [ http://www.openbsd.org/cgi-bin/cvsweb/src/share/misc/license.template ]
+ *
+ * trim: uses DIOCGDELETE (BIO_DELETE) to TRIM SSDs and other devices that
+ * support it.
+ */
+
+#include <sys/disk.h>
+#include <sys/ioctl.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/* Fill a range of the device with non-zeroes. */
+static void fill(const int fd, const off_t ofs, const size_t len) {
+  static char* buf = NULL;
+  static size_t buf_size = 0;
+  ssize_t ret;
+
+  if (buf_size < len) {
+    if (buf)
+      free(buf);
+    buf = malloc(len);
+    if (buf == NULL)
+      errx(1, "malloc(%zd) failed", len);
+    buf_size = len;
+    memset(buf, 255, len);
+  }
+  if ((ret = pwrite(fd, buf, len, ofs)) != (ssize_t)len)
+    err(1, "pwrite(%zd bytes at offset %jd) returned %jd",
+        len,
+        (intmax_t)ofs,
+        (intmax_t)ret);
+}
+
+/* Returns 1 if the range is zeroed out. */
+static int verify_zero(const int fd, const off_t ofs, const size_t len) {
+  static char* buf = NULL;
+  static size_t buf_size = 0;
+  ssize_t ret;
+  size_t i;
+
+  if (buf_size < len) {
+    if (buf)
+      free(buf);
+    buf = malloc(len);
+    if (buf == NULL)
+      errx(1, "malloc(%zd) failed", len);
+    buf_size = len;
+  }
+  if ((ret = pread(fd, buf, len, ofs)) != (ssize_t)len)
+    err(1, "pread(%zd bytes at offset %jd) returned %jd",
+        len,
+        (intmax_t)ofs,
+        (intmax_t)ret);
+  for (i = 0; i < len; i++)
+    if (buf[i] != 0)
+      return 0;
+  return 1;
+}
+
+/* This is where the magic happens. */
+static void trim(const int fd, const off_t ofs, const size_t len) {
+  off_t arg[2];
+  arg[0] = ofs;
+  arg[1] = (off_t)len;
+  if (ioctl(fd, DIOCGDELETE, arg) == -1)
+    err(1, "ioctl(DIOCGDELETE) at %jd, size %jd, failed",
+        (intmax_t)arg[0],
+        (intmax_t)arg[1]);
+}
+
+/* Probe for the smallest range that the device can trim. */
+static size_t probe_trim_size(const int fd, const u_int sectorsize) {
+  size_t trimsize;
+  const int limit = 1024;  /* sectors, when probing */
+  const int retries = 3;  /* seconds, when probing */
+  int retry;
+
+  for (trimsize = sectorsize; trimsize < sectorsize * limit; trimsize *= 2) {
+    fprintf(stderr, "trying to trim %zd bytes... ", trimsize);
+    if (verify_zero(fd, 0, trimsize)) {
+      fprintf(stderr, "(dirtying up first) ");
+      fill(fd, 0, trimsize);
+      if (verify_zero(fd, 0, trimsize))
+        errx(1, "device returned zeros after writing non-zeros to it");
+    }
+    for (retry = 0; retry < retries; retry++) {
+      if (retry > 0) {
+        sleep(1);
+      }
+      trim(fd, 0, trimsize);
+      if (verify_zero(fd, 0, trimsize)) {
+        fprintf(stderr, "success!\n");
+        return trimsize;
+      } else {
+        fprintf(stderr, "(failure) ");
+      }
+    }
+    fprintf(stderr, "didn't work.\n");
+  }
+  errx(1, "this device doesn't appear to support trim");
+}
+
+int main(int argc, char **argv) {
+  int fd;
+  off_t mediasize;
+  u_int sectorsize;
+  size_t trimsize;
+  off_t pos;
+
+  if (argc != 2) {
+    fprintf(stderr, "usage: %s /dev/...\n", argv[0]);
+    exit(1);
+  }
+  if ((fd = open(argv[1], O_RDWR)) == -1)
+    err(1, "open(%s) failed", argv[1]);
+  if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) == -1)
+    err(1, "ioctl(DIOCGMEDIASIZE) failed");
+  if (ioctl(fd, DIOCGSECTORSIZE, &sectorsize) == -1)
+    err(1, "ioctl(DIOCGSECTORSIZE) failed");
+  printf("%s: mediasize %jd, sectorsize %u.\n",
+         argv[1],
+         (intmax_t)mediasize,
+         sectorsize);
+  trimsize = probe_trim_size(fd, sectorsize);
+
+  /* For performance. */
+  trimsize *= 1024;
+  printf("trimming blocks of length %zd\n", trimsize);
+
+  for (pos = 0; pos < mediasize; pos += trimsize) {
+    struct timespec t0;
+    struct timespec t1;
+
+    fprintf(stderr, "\r%jd / %jd (%.2f%%) ",
+            (intmax_t)pos,
+            (intmax_t)mediasize,
+            (double)pos * 100. / (double)mediasize);
+    if (clock_gettime(CLOCK_MONOTONIC, &t0) == -1)
+      err(1, "clock_gettime(CLOCK_MONOTONIC) failed");
+    trim(fd, pos, trimsize);
+    if (!verify_zero(fd, pos, trimsize))
+      printf("trim failed at pos %jd!\n", (intmax_t)pos);
+    if (clock_gettime(CLOCK_MONOTONIC, &t1) == -1)
+      err(1, "clock_gettime(CLOCK_MONOTONIC) failed");
+
+    t1.tv_sec -= t0.tv_sec;
+    if (t1.tv_nsec < t0.tv_nsec) {
+      t1.tv_nsec += 1000 * 1000 * 1000;
+      t1.tv_sec--;
+    }
+    t1.tv_nsec -= t0.tv_nsec;
+    fprintf(stderr, "%d.%09d sec ", (int)t1.tv_sec, (int)t1.tv_nsec);
+  }
+  fprintf(stderr, "\ndone!\n");
+  close(fd);
+  return 0;
+}
+/* vim:set ts=2 sw=2 et tw=80: */