/* This file is part of GNUnet. Copyright (C) 2001-2013 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file util/crypto_hash_file.c * @brief incremental hashing of files * @author Christian Grothoff */ #include "platform.h" #include "gnunet_util_lib.h" #include #define LOG(kind, ...) GNUNET_log_from (kind, "util-crypto-hash-file", \ __VA_ARGS__) #define LOG_STRERROR_FILE(kind, syscall, \ filename) GNUNET_log_from_strerror_file (kind, \ "util-crypto-hash-file", \ syscall, \ filename) /** * Context used when hashing a file. */ struct GNUNET_CRYPTO_FileHashContext { /** * Function to call upon completion. */ GNUNET_CRYPTO_HashCompletedCallback callback; /** * Closure for callback. */ void *callback_cls; /** * IO buffer. */ unsigned char *buffer; /** * Name of the file we are hashing. */ char *filename; /** * File descriptor. */ struct GNUNET_DISK_FileHandle *fh; /** * Cummulated hash. */ gcry_md_hd_t md; /** * Size of the file. */ uint64_t fsize; /** * Current offset. */ uint64_t offset; /** * Current task for hashing. */ struct GNUNET_SCHEDULER_Task *task; /** * Priority we use. */ enum GNUNET_SCHEDULER_Priority priority; /** * Blocksize. */ size_t bsize; }; /** * Report result of hash computation to callback * and free associated resources. */ static void file_hash_finish (struct GNUNET_CRYPTO_FileHashContext *fhc, const struct GNUNET_HashCode *res) { fhc->callback (fhc->callback_cls, res); GNUNET_free (fhc->filename); if (! GNUNET_DISK_handle_invalid (fhc->fh)) GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fhc->fh)); gcry_md_close (fhc->md); GNUNET_free (fhc); /* also frees fhc->buffer */ } /** * File hashing task. * * @param cls closure */ static void file_hash_task (void *cls) { struct GNUNET_CRYPTO_FileHashContext *fhc = cls; struct GNUNET_HashCode *res; size_t delta; ssize_t sret; fhc->task = NULL; GNUNET_assert (fhc->offset <= fhc->fsize); delta = fhc->bsize; if (fhc->fsize - fhc->offset < delta) delta = fhc->fsize - fhc->offset; sret = GNUNET_DISK_file_read (fhc->fh, fhc->buffer, delta); if ((sret < 0) || (delta != (size_t) sret)) { LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING, "read", fhc->filename); file_hash_finish (fhc, NULL); return; } gcry_md_write (fhc->md, fhc->buffer, delta); fhc->offset += delta; if (fhc->offset == fhc->fsize) { res = (struct GNUNET_HashCode *) gcry_md_read (fhc->md, GCRY_MD_SHA512); file_hash_finish (fhc, res); return; } fhc->task = GNUNET_SCHEDULER_add_with_priority (fhc->priority, &file_hash_task, fhc); } /** * Compute the hash of an entire file. * * @param priority scheduling priority to use * @param filename name of file to hash * @param blocksize number of bytes to process in one task * @param callback function to call upon completion * @param callback_cls closure for @a callback * @return NULL on (immediate) errror */ struct GNUNET_CRYPTO_FileHashContext * GNUNET_CRYPTO_hash_file (enum GNUNET_SCHEDULER_Priority priority, const char *filename, size_t blocksize, GNUNET_CRYPTO_HashCompletedCallback callback, void *callback_cls) { struct GNUNET_CRYPTO_FileHashContext *fhc; GNUNET_assert (blocksize > 0); fhc = GNUNET_malloc (sizeof(struct GNUNET_CRYPTO_FileHashContext) + blocksize); fhc->callback = callback; fhc->callback_cls = callback_cls; fhc->buffer = (unsigned char *) &fhc[1]; fhc->filename = GNUNET_strdup (filename); if (GPG_ERR_NO_ERROR != gcry_md_open (&fhc->md, GCRY_MD_SHA512, 0)) { GNUNET_break (0); GNUNET_free (fhc); return NULL; } fhc->bsize = blocksize; if (GNUNET_OK != GNUNET_DISK_file_size (filename, &fhc->fsize, GNUNET_NO, GNUNET_YES)) { GNUNET_free (fhc->filename); GNUNET_free (fhc); return NULL; } fhc->fh = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_NONE); if (! fhc->fh) { GNUNET_free (fhc->filename); GNUNET_free (fhc); return NULL; } fhc->priority = priority; fhc->task = GNUNET_SCHEDULER_add_with_priority (priority, &file_hash_task, fhc); return fhc; } /** * Cancel a file hashing operation. * * @param fhc operation to cancel (callback must not yet have been invoked) */ void GNUNET_CRYPTO_hash_file_cancel (struct GNUNET_CRYPTO_FileHashContext *fhc) { GNUNET_SCHEDULER_cancel (fhc->task); GNUNET_free (fhc->filename); GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fhc->fh)); GNUNET_free (fhc); } /* end of crypto_hash_file.c */