Linux supports accessing kernel crypto API via AF_ALG since version 2.6.38. Coreutils uses libcrypto when available and fallbacks to generic C implementation of various hashing functions.
Add a generic afalg_stream() function which uses AF_ALG to calculate the hash of a stream and use sendfile() when possible (regular file with size less or equal than 0x7ffff000 (2,147,479,552) bytes, AKA MAX_RW_COUNT). Use afalg_stream() only in sha1sum for now, but other hashes are possible. The speed gain really depends on the CPU type, on systems which doesn't use libcrypto ranges from ~10% to 320%. This is a test on a Intel(R) Xeon(R) CPU E3-1265L V2 and Debian stretch: $ truncate -s 2GB 2g.bin $ time sha1sum 2g.bin 752ef2367f479e79e4f0cded9c270c2890506ab0 2g.bin real 0m4.829s user 0m4.437s sys 0m0.391s $ time ./sha1sum-afalg 2g.bin 752ef2367f479e79e4f0cded9c270c2890506ab0 2g.bin real 0m3.164s user 0m0.000s sys 0m3.162s Signed-off-by: Matteo Croce <mcr...@redhat.com> --- lib/af_alg.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/af_alg.h | 49 ++++++++++++++++++++++ lib/sha1.c | 17 +++++++- m4/linux-if-alg.m4 | 29 +++++++++++++ modules/crypto/sha1 | 6 ++- 5 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 lib/af_alg.c create mode 100644 lib/af_alg.h create mode 100644 m4/linux-if-alg.m4 diff --git a/lib/af_alg.c b/lib/af_alg.c new file mode 100644 index 000000000..3af099e43 --- /dev/null +++ b/lib/af_alg.c @@ -0,0 +1,115 @@ +/* af_alg.c - Functions to compute message digest from file streams using + Linux kernel crypto API. + Copyright (C) 2018 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Matteo Croce <mcr...@redhat.com>, 2018. */ + +#include <config.h> + +#ifdef HAVE_LINUX_IF_ALG_H + +#include <unistd.h> +#include <string.h> +#include <linux/if_alg.h> +#include <sys/stat.h> +#include <sys/sendfile.h> +#include <sys/socket.h> + +#include "af_alg.h" + +/* from linux/include/linux/fs.h: (INT_MAX & PAGE_MASK). */ +#define MAX_RW_COUNT 0x7FFFF000 +#define BLOCKSIZE 32768 + +int +afalg_stream (FILE * stream, void *resblock, const char *alg, ssize_t hashlen) +{ + struct sockaddr_alg salg = { + .salg_family = AF_ALG, + .salg_type = "hash", + }; + int ret, cfd, ofd; + struct stat st; + + cfd = socket (AF_ALG, SOCK_SEQPACKET, 0); + if (cfd < 0) + return -EAFNOSUPPORT; + + /* avoid calling both strcpy and strlen. */ + for (int i = 0; (salg.salg_name[i] = alg[i]); i++) + if (i == sizeof salg.salg_name - 1) + return -EINVAL; + + ret = bind (cfd, (struct sockaddr *) &salg, sizeof salg); + if (ret < 0) + { + ret = -EAFNOSUPPORT; + goto out_cfd; + } + + ofd = accept (cfd, NULL, 0); + if (ofd < 0) + { + ret = -EAFNOSUPPORT; + goto out_cfd; + } + + /* if file is a regular file, attempt sendfile to pipe the data. */ + if (!fstat (fileno (stream), &st) + && (S_ISREG (st.st_mode) || S_TYPEISSHM (&st) || S_TYPEISTMO (&st)) + && st.st_size && st.st_size <= MAX_RW_COUNT) + { + if (sendfile (ofd, fileno (stream), NULL, st.st_size) != st.st_size) + { + ret = -EIO; + goto out_ofd; + } + else + ret = 0; + } + else + { + /* sendfile not possible, do a classic read-write loop. */ + ssize_t size; + char buf[BLOCKSIZE]; + while ((size = fread (buf, 1, sizeof buf, stream))) + { + if (send (ofd, buf, size, MSG_MORE) != size) + { + ret = -EIO; + goto out_ofd; + } + } + if (ferror (stream)) + { + ret = -EIO; + goto out_ofd; + } + } + + if (read (ofd, resblock, hashlen) != hashlen) + ret = -EIO; + else + ret = 0; + +out_ofd: + close (ofd); +out_cfd: + close (cfd); + return ret; +} + +#endif diff --git a/lib/af_alg.h b/lib/af_alg.h new file mode 100644 index 000000000..978e21d36 --- /dev/null +++ b/lib/af_alg.h @@ -0,0 +1,49 @@ +/* af_alg.h - Declaration of functions to compute message digest from + file streams using Linux kernel crypto API. + Copyright (C) 2018 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Matteo Croce <mcr...@redhat.com>, 2018. */ + +#ifndef AF_ALG_H +# define AF_ALG_H 1 + +# include <stdio.h> +# include <errno.h> + +# ifdef __cplusplus +extern "C" { +# endif + +# ifdef HAVE_LINUX_IF_ALG_H + +int +afalg_stream (FILE * stream, void *resblock, const char *alg, ssize_t hashlen); + +# else + +static int +afalg_stream (FILE * stream, void *resblock, const char *alg, ssize_t hashlen) +{ + return -EAFNOSUPPORT; +} + +# endif + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/lib/sha1.c b/lib/sha1.c index 37d46b68e..a84bf211a 100644 --- a/lib/sha1.c +++ b/lib/sha1.c @@ -33,6 +33,10 @@ #include <stdlib.h> #include <string.h> +#ifdef HAVE_LINUX_IF_ALG_H +# include "af_alg.h" +#endif + #if USE_UNLOCKED_IO # include "unlocked-io.h" #endif @@ -130,8 +134,19 @@ sha1_stream (FILE *stream, void *resblock) { struct sha1_ctx ctx; size_t sum; + char *buffer; +#ifdef HAVE_LINUX_IF_ALG_H + int ret; + + ret = afalg_stream(stream, resblock, "sha1", SHA1_DIGEST_SIZE); + if (!ret) + return 0; + + if (ret == -EIO) + return 1; +#endif - char *buffer = malloc (BLOCKSIZE + 72); + buffer = malloc (BLOCKSIZE + 72); if (!buffer) return 1; diff --git a/m4/linux-if-alg.m4 b/m4/linux-if-alg.m4 new file mode 100644 index 000000000..3ed18ae33 --- /dev/null +++ b/m4/linux-if-alg.m4 @@ -0,0 +1,29 @@ +dnl Check whether linux/if_alg.h has needed features. + +dnl Copyright 2018 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Matteo Croce. + +AC_DEFUN_ONCE([gl_LINUX_IF_ALG_H], +[ + AC_REQUIRE([gl_HEADER_SYS_SOCKET]) + AC_CACHE_CHECK([whether linux/if_alg.h has struct sockaddr_alg.], + [gl_cv_header_linux_if_alg_salg], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#include <sys/socket.h> + #include <linux/if_alg.h> + struct sockaddr_alg salg = { + .salg_family = AF_ALG, + .salg_type = "hash", + .salg_name = "sha1", + };]])], + [gl_cv_header_linux_if_alg_salg=yes], + [gl_cv_header_linux_if_alg_salg=no])]) + if test "$gl_cv_header_linux_if_alg_salg" = yes; then + AC_DEFINE([HAVE_LINUX_IF_ALG_H], [1], + [Define to 1 if you have `struct sockaddr_alg' defined.]) + fi +]) diff --git a/modules/crypto/sha1 b/modules/crypto/sha1 index d65f99418..dbd41d21b 100644 --- a/modules/crypto/sha1 +++ b/modules/crypto/sha1 @@ -5,8 +5,11 @@ Files: lib/gl_openssl.h lib/sha1.h lib/sha1.c +lib/af_alg.h +lib/af_alg.c m4/gl-openssl.m4 m4/sha1.m4 +m4/linux-if-alg.m4 Depends-on: extern-inline @@ -15,9 +18,10 @@ stdint configure.ac: gl_SHA1 +gl_LINUX_IF_ALG_H Makefile.am: -lib_SOURCES += sha1.c +lib_SOURCES += sha1.c af_alg.c Include: "sha1.h" -- 2.14.3