view lib/af_alg.c @ 39313:6b0fda728c9b

sha1sum: use AF_ALG when available 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 <mcroce@redhat.com>
author Matteo Croce <mcroce@redhat.com>
date Sat, 28 Apr 2018 15:32:55 +0200
parents
children dc3b666f1382
line wrap: on
line source

/* 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 <mcroce@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