lib/utils_crypt: optimize seek to keyfile-offset
authorDave Reisner <dreisner@archlinux.org>
Fri, 24 Aug 2012 21:45:25 +0000 (17:45 -0400)
committerMilan Broz <gmazyland@gmail.com>
Mon, 27 Aug 2012 11:10:00 +0000 (13:10 +0200)
Avoid using unbuffered reads when "seeking" to a keyfile offset. This is
abysmally slow when the key is hidden at the end of a large device.
Instead, try to actually call lseek, falling back on reading in chunks
of BUFSIZ bytes until the desired offset is reached.

Command line:

  cryptsetup luksOpen /dev/vdc1 home \
      --keyfile /dev/vdd --keyfile-size 4096 --keyfile-offset 123456789

Before:

  real   0m25.589s
  user   0m7.030s
  sys    0m18.479s

After:

  real   0m4.464s
  user   0m4.253s
  sys    0m0.157s

ChangeLog
lib/utils_crypt.c

index eccb9b4..17b4ceb 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+2012-08-27  Milan Broz  <gmazyland@gmail.com>
+       * Optimize seek to keyfile-offset (Issue #135, thx to dreisner).
+
 2012-08-12  Milan Broz  <gmazyland@gmail.com>
        * Allocate loop device late (only when real block device needed).
        * Rework underlying device/file access functions.
index c2111c7..4433dc7 100644 (file)
@@ -262,6 +262,49 @@ out_err:
 }
 
 /*
+ * A simple call to lseek(3) might not be possible for some inputs (e.g.
+ * reading from a pipe), so this function instead reads of up to BUFSIZ bytes
+ * at a time until the specified number of bytes. It returns -1 on read error
+ * or when it reaches EOF before the requested number of bytes have been
+ * discarded.
+ */
+static int keyfile_seek(int fd, size_t bytes)
+{
+       char tmp[BUFSIZ];
+       size_t next_read;
+       ssize_t bytes_r;
+       off_t r;
+
+       r = lseek(fd, bytes, SEEK_CUR);
+       if (r > 0)
+               return 0;
+       if (r < 0 && errno != ESPIPE)
+               return -1;
+
+       while (bytes > 0) {
+               /* figure out how much to read */
+               next_read = bytes > sizeof(tmp) ? sizeof(tmp) : bytes;
+
+               bytes_r = read(fd, tmp, next_read);
+               if (bytes_r < 0) {
+                       if (errno == EINTR)
+                               continue;
+
+                       /* read error */
+                       return -1;
+               }
+
+               if (bytes_r == 0)
+                       /* EOF */
+                       break;
+
+               bytes -= bytes_r;
+       }
+
+       return bytes == 0 ? 0 : -1;
+}
+
+/*
  * Note: --key-file=- is interpreted as a read from a binary file (stdin)
  * key_size_max == 0 means detect maximum according to input type (tty/file)
  * timeout and verify options only applies to tty input
@@ -274,7 +317,7 @@ int crypt_get_key(const char *prompt,
 {
        int fd, regular_file, read_stdin, char_read, unlimited_read = 0;
        int r = -EINVAL;
-       char *pass = NULL, tmp;
+       char *pass = NULL;
        size_t buflen, i, file_read_size;
        struct stat st;
 
@@ -342,11 +385,10 @@ int crypt_get_key(const char *prompt,
        }
 
        /* Discard keyfile_offset bytes on input */
-       for(i = 0; i < keyfile_offset; i++)
-               if (read(fd, &tmp, 1) != 1) {
-                       log_err(cd, _("Cannot seek to requested keyfile offset.\n"));
-                       goto out_err;
-               }
+       if (keyfile_offset && keyfile_seek(fd, keyfile_offset) < 0) {
+               log_err(cd, _("Cannot seek to requested keyfile offset.\n"));
+               goto out_err;
+       }
 
        for(i = 0; i < keyfile_size_max; i++) {
                if(i == buflen) {