Linux supports accessing kernel cryptographic 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 | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/af_alg.h | 46 ++++++++++++++++++++++++ lib/sha1.c | 13 ++++++- modules/crypto/sha1 | 5 ++- 4 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 lib/af_alg.c create mode 100644 lib/af_alg.h diff --git a/lib/af_alg.c b/lib/af_alg.c new file mode 100644 index 000000000..0a1437d6c --- /dev/null +++ b/lib/af_alg.c @@ -0,0 +1,102 @@ +/* AF_ALG support + + 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/>. */ + +#include <config.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, int hashlen) +{ + struct sockaddr_alg salg = { + .salg_family = AF_ALG, + .salg_type = "hash", + }; + int ret, cfd, ofd; + static char buf[BLOCKSIZE]; + ssize_t size; + struct stat st; + + strcpy((char *)salg.salg_name, alg); + cfd = socket (AF_ALG, SOCK_SEQPACKET, 0); + if (cfd < 0) + return -EAFNOSUPPORT; + + 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_ofd; + } + + /* if file is a regular file, attempt sendfile() to pipe the data */ + if (!fstat(fileno(stream), &st) && S_ISREG(st.st_mode) && + st.st_size <= MAX_RW_COUNT) + { + if (sendfile(ofd, fileno(stream), NULL, st.st_size) == -1) + ret = -EIO; + else + ret = 0; + } else { + /* sendfile() not possible, do a classic read-write loop */ + while ((size = fread (buf, 1, sizeof (buf), stream))) + { + if (send (ofd, buf, size, size == sizeof (buf) ? MSG_MORE : 0) == -1) + { + ret = -EIO; + goto out_ofd; + } + } + } + + size = read (ofd, resblock, hashlen); + if (size != hashlen) + { + fprintf (stderr, "Error from read (%zd vs %d bytes): %s\n", + size, hashlen, strerror (errno)); + ret = -EIO; + } + else + { + ret = 0; + } +out_ofd: + close (ofd); +out_cfd: + close (cfd); + return ret; +} + +# ifdef __cplusplus +} +# endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/lib/af_alg.h b/lib/af_alg.h new file mode 100644 index 000000000..678c03aad --- /dev/null +++ b/lib/af_alg.h @@ -0,0 +1,46 @@ +/* AF_ALG support + + 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/>. */ + +#ifndef AF_ALG_H +# define AF_ALG_H 1 + +# include <unistd.h> +# include <stdio.h> +# include <string.h> +# include <errno.h> +# include <linux/if_alg.h> +# include <sys/socket.h> +# include <sys/stat.h> +# include <sys/sendfile.h> + +# ifdef __cplusplus +extern "C" { +# endif + +int +afalg_stream (FILE * stream, void *resblock, const char *alg, int hashlen); + +# ifdef __cplusplus +} +# endif + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/lib/sha1.c b/lib/sha1.c index 37d46b68e..dac6cac8a 100644 --- a/lib/sha1.c +++ b/lib/sha1.c @@ -37,6 +37,8 @@ # include "unlocked-io.h" #endif +#include "af_alg.h" + #ifdef WORDS_BIGENDIAN # define SWAP(n) (n) #else @@ -130,8 +132,17 @@ sha1_stream (FILE *stream, void *resblock) { struct sha1_ctx ctx; size_t sum; + int ret; + char *buffer; + + ret = afalg_stream(stream, resblock, "sha1", SHA1_DIGEST_SIZE); + if (!ret) + return 0; + + if (ret == -EIO) + return 1; - char *buffer = malloc (BLOCKSIZE + 72); + buffer = malloc (BLOCKSIZE + 72); if (!buffer) return 1; diff --git a/modules/crypto/sha1 b/modules/crypto/sha1 index d65f99418..a2b7df211 100644 --- a/modules/crypto/sha1 +++ b/modules/crypto/sha1 @@ -5,6 +5,8 @@ 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 @@ -17,10 +19,11 @@ configure.ac: gl_SHA1 Makefile.am: -lib_SOURCES += sha1.c +lib_SOURCES += sha1.c af_alg.c Include: "sha1.h" +"af_alg.h" Link: $(LIB_CRYPTO) -- 2.14.3