attempt fix #5578
[oweals/gnunet.git] / src / util / crypto_hash_file.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2001-2013 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14     
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19
20 */
21 /**
22  * @file util/crypto_hash_file.c
23  * @brief incremental hashing of files
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include <gcrypt.h>
29
30 #define LOG(kind,...) GNUNET_log_from (kind, "util-crypto-hash-file", __VA_ARGS__)
31
32 #define LOG_STRERROR_FILE(kind,syscall,filename) GNUNET_log_from_strerror_file (kind, "util-crypto-hash-file", syscall, filename)
33
34
35 /**
36  * Context used when hashing a file.
37  */
38 struct GNUNET_CRYPTO_FileHashContext
39 {
40
41   /**
42    * Function to call upon completion.
43    */
44   GNUNET_CRYPTO_HashCompletedCallback callback;
45
46   /**
47    * Closure for callback.
48    */
49   void *callback_cls;
50
51   /**
52    * IO buffer.
53    */
54   unsigned char *buffer;
55
56   /**
57    * Name of the file we are hashing.
58    */
59   char *filename;
60
61   /**
62    * File descriptor.
63    */
64   struct GNUNET_DISK_FileHandle *fh;
65
66   /**
67    * Cummulated hash.
68    */
69   gcry_md_hd_t md;
70
71   /**
72    * Size of the file.
73    */
74   uint64_t fsize;
75
76   /**
77    * Current offset.
78    */
79   uint64_t offset;
80
81   /**
82    * Current task for hashing.
83    */
84   struct GNUNET_SCHEDULER_Task * task;
85
86   /**
87    * Priority we use.
88    */
89   enum GNUNET_SCHEDULER_Priority priority;
90
91   /**
92    * Blocksize.
93    */
94   size_t bsize;
95
96 };
97
98
99 /**
100  * Report result of hash computation to callback
101  * and free associated resources.
102  */
103 static void
104 file_hash_finish (struct GNUNET_CRYPTO_FileHashContext *fhc,
105                   const struct GNUNET_HashCode * res)
106 {
107   fhc->callback (fhc->callback_cls, res);
108   GNUNET_free (fhc->filename);
109   if (!GNUNET_DISK_handle_invalid (fhc->fh))
110     GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fhc->fh));
111   gcry_md_close (fhc->md);
112   GNUNET_free (fhc);            /* also frees fhc->buffer */
113 }
114
115
116 /**
117  * File hashing task.
118  *
119  * @param cls closure
120  */
121 static void
122 file_hash_task (void *cls)
123 {
124   struct GNUNET_CRYPTO_FileHashContext *fhc = cls;
125   struct GNUNET_HashCode *res;
126   size_t delta;
127   ssize_t sret;
128
129   fhc->task = NULL;
130   GNUNET_assert (fhc->offset <= fhc->fsize);
131   delta = fhc->bsize;
132   if (fhc->fsize - fhc->offset < delta)
133     delta = fhc->fsize - fhc->offset;
134   sret = GNUNET_DISK_file_read (fhc->fh,
135                                 fhc->buffer,
136                                 delta);
137   if ( (sret < 0) ||
138        (delta != (size_t) sret) )
139   {
140     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING,
141                        "read",
142                        fhc->filename);
143     file_hash_finish (fhc,
144                       NULL);
145     return;
146   }
147   gcry_md_write (fhc->md,
148                  fhc->buffer,
149                  delta);
150   fhc->offset += delta;
151   if (fhc->offset == fhc->fsize)
152   {
153     res = (struct GNUNET_HashCode *) gcry_md_read (fhc->md,
154                                                    GCRY_MD_SHA512);
155     file_hash_finish (fhc, res);
156     return;
157   }
158   fhc->task = GNUNET_SCHEDULER_add_with_priority (fhc->priority,
159                                                   &file_hash_task,
160                                                   fhc);
161 }
162
163
164 /**
165  * Compute the hash of an entire file.
166  *
167  * @param priority scheduling priority to use
168  * @param filename name of file to hash
169  * @param blocksize number of bytes to process in one task
170  * @param callback function to call upon completion
171  * @param callback_cls closure for @a callback
172  * @return NULL on (immediate) errror
173  */
174 struct GNUNET_CRYPTO_FileHashContext *
175 GNUNET_CRYPTO_hash_file (enum GNUNET_SCHEDULER_Priority priority,
176                          const char *filename,
177                          size_t blocksize,
178                          GNUNET_CRYPTO_HashCompletedCallback callback,
179                          void *callback_cls)
180 {
181   struct GNUNET_CRYPTO_FileHashContext *fhc;
182
183   GNUNET_assert (blocksize > 0);
184   fhc =
185       GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_FileHashContext) + blocksize);
186   fhc->callback = callback;
187   fhc->callback_cls = callback_cls;
188   fhc->buffer = (unsigned char *) &fhc[1];
189   fhc->filename = GNUNET_strdup (filename);
190   if (GPG_ERR_NO_ERROR != gcry_md_open (&fhc->md, GCRY_MD_SHA512, 0))
191   {
192     GNUNET_break (0);
193     GNUNET_free (fhc);
194     return NULL;
195   }
196   fhc->bsize = blocksize;
197   if (GNUNET_OK !=
198       GNUNET_DISK_file_size (filename,
199                              &fhc->fsize,
200                              GNUNET_NO,
201                              GNUNET_YES))
202   {
203     GNUNET_free (fhc->filename);
204     GNUNET_free (fhc);
205     return NULL;
206   }
207   fhc->fh = GNUNET_DISK_file_open (filename,
208                                    GNUNET_DISK_OPEN_READ,
209                                    GNUNET_DISK_PERM_NONE);
210   if (! fhc->fh)
211   {
212     GNUNET_free (fhc->filename);
213     GNUNET_free (fhc);
214     return NULL;
215   }
216   fhc->priority = priority;
217   fhc->task = GNUNET_SCHEDULER_add_with_priority (priority,
218                                                   &file_hash_task,
219                                                   fhc);
220   return fhc;
221 }
222
223
224 /**
225  * Cancel a file hashing operation.
226  *
227  * @param fhc operation to cancel (callback must not yet have been invoked)
228  */
229 void
230 GNUNET_CRYPTO_hash_file_cancel (struct GNUNET_CRYPTO_FileHashContext *fhc)
231 {
232   GNUNET_SCHEDULER_cancel (fhc->task);
233   GNUNET_free (fhc->filename);
234   GNUNET_break (GNUNET_OK ==
235                 GNUNET_DISK_file_close (fhc->fh));
236   GNUNET_free (fhc);
237 }
238
239 /* end of crypto_hash_file.c */