-Merge branch 'master' of ssh://gnunet.org/gnunet into gsoc2018/rest_api
[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 */
19 /**
20  * @file util/crypto_hash_file.c
21  * @brief incremental hashing of files
22  * @author Christian Grothoff
23  */
24 #include "platform.h"
25 #include "gnunet_util_lib.h"
26 #include <gcrypt.h>
27
28 #define LOG(kind,...) GNUNET_log_from (kind, "util-crypto-hash-file", __VA_ARGS__)
29
30 #define LOG_STRERROR_FILE(kind,syscall,filename) GNUNET_log_from_strerror_file (kind, "util-crypto-hash-file", syscall, filename)
31
32
33 /**
34  * Context used when hashing a file.
35  */
36 struct GNUNET_CRYPTO_FileHashContext
37 {
38
39   /**
40    * Function to call upon completion.
41    */
42   GNUNET_CRYPTO_HashCompletedCallback callback;
43
44   /**
45    * Closure for callback.
46    */
47   void *callback_cls;
48
49   /**
50    * IO buffer.
51    */
52   unsigned char *buffer;
53
54   /**
55    * Name of the file we are hashing.
56    */
57   char *filename;
58
59   /**
60    * File descriptor.
61    */
62   struct GNUNET_DISK_FileHandle *fh;
63
64   /**
65    * Cummulated hash.
66    */
67   gcry_md_hd_t md;
68
69   /**
70    * Size of the file.
71    */
72   uint64_t fsize;
73
74   /**
75    * Current offset.
76    */
77   uint64_t offset;
78
79   /**
80    * Current task for hashing.
81    */
82   struct GNUNET_SCHEDULER_Task * task;
83
84   /**
85    * Priority we use.
86    */
87   enum GNUNET_SCHEDULER_Priority priority;
88
89   /**
90    * Blocksize.
91    */
92   size_t bsize;
93
94 };
95
96
97 /**
98  * Report result of hash computation to callback
99  * and free associated resources.
100  */
101 static void
102 file_hash_finish (struct GNUNET_CRYPTO_FileHashContext *fhc,
103                   const struct GNUNET_HashCode * res)
104 {
105   fhc->callback (fhc->callback_cls, res);
106   GNUNET_free (fhc->filename);
107   if (!GNUNET_DISK_handle_invalid (fhc->fh))
108     GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fhc->fh));
109   gcry_md_close (fhc->md);
110   GNUNET_free (fhc);            /* also frees fhc->buffer */
111 }
112
113
114 /**
115  * File hashing task.
116  *
117  * @param cls closure
118  */
119 static void
120 file_hash_task (void *cls)
121 {
122   struct GNUNET_CRYPTO_FileHashContext *fhc = cls;
123   struct GNUNET_HashCode *res;
124   size_t delta;
125   ssize_t sret;
126
127   fhc->task = NULL;
128   GNUNET_assert (fhc->offset <= fhc->fsize);
129   delta = fhc->bsize;
130   if (fhc->fsize - fhc->offset < delta)
131     delta = fhc->fsize - fhc->offset;
132   sret = GNUNET_DISK_file_read (fhc->fh,
133                                 fhc->buffer,
134                                 delta);
135   if ( (sret < 0) ||
136        (delta != (size_t) sret) )
137   {
138     LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_WARNING,
139                        "read",
140                        fhc->filename);
141     file_hash_finish (fhc,
142                       NULL);
143     return;
144   }
145   gcry_md_write (fhc->md,
146                  fhc->buffer,
147                  delta);
148   fhc->offset += delta;
149   if (fhc->offset == fhc->fsize)
150   {
151     res = (struct GNUNET_HashCode *) gcry_md_read (fhc->md,
152                                                    GCRY_MD_SHA512);
153     file_hash_finish (fhc, res);
154     return;
155   }
156   fhc->task = GNUNET_SCHEDULER_add_with_priority (fhc->priority,
157                                                   &file_hash_task,
158                                                   fhc);
159 }
160
161
162 /**
163  * Compute the hash of an entire file.
164  *
165  * @param priority scheduling priority to use
166  * @param filename name of file to hash
167  * @param blocksize number of bytes to process in one task
168  * @param callback function to call upon completion
169  * @param callback_cls closure for @a callback
170  * @return NULL on (immediate) errror
171  */
172 struct GNUNET_CRYPTO_FileHashContext *
173 GNUNET_CRYPTO_hash_file (enum GNUNET_SCHEDULER_Priority priority,
174                          const char *filename,
175                          size_t blocksize,
176                          GNUNET_CRYPTO_HashCompletedCallback callback,
177                          void *callback_cls)
178 {
179   struct GNUNET_CRYPTO_FileHashContext *fhc;
180
181   GNUNET_assert (blocksize > 0);
182   fhc =
183       GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_FileHashContext) + blocksize);
184   fhc->callback = callback;
185   fhc->callback_cls = callback_cls;
186   fhc->buffer = (unsigned char *) &fhc[1];
187   fhc->filename = GNUNET_strdup (filename);
188   if (GPG_ERR_NO_ERROR != gcry_md_open (&fhc->md, GCRY_MD_SHA512, 0))
189   {
190     GNUNET_break (0);
191     GNUNET_free (fhc);
192     return NULL;
193   }
194   fhc->bsize = blocksize;
195   if (GNUNET_OK !=
196       GNUNET_DISK_file_size (filename,
197                              &fhc->fsize,
198                              GNUNET_NO,
199                              GNUNET_YES))
200   {
201     GNUNET_free (fhc->filename);
202     GNUNET_free (fhc);
203     return NULL;
204   }
205   fhc->fh = GNUNET_DISK_file_open (filename,
206                                    GNUNET_DISK_OPEN_READ,
207                                    GNUNET_DISK_PERM_NONE);
208   if (! fhc->fh)
209   {
210     GNUNET_free (fhc->filename);
211     GNUNET_free (fhc);
212     return NULL;
213   }
214   fhc->priority = priority;
215   fhc->task = GNUNET_SCHEDULER_add_with_priority (priority,
216                                                   &file_hash_task,
217                                                   fhc);
218   return fhc;
219 }
220
221
222 /**
223  * Cancel a file hashing operation.
224  *
225  * @param fhc operation to cancel (callback must not yet have been invoked)
226  */
227 void
228 GNUNET_CRYPTO_hash_file_cancel (struct GNUNET_CRYPTO_FileHashContext *fhc)
229 {
230   GNUNET_SCHEDULER_cancel (fhc->task);
231   GNUNET_free (fhc->filename);
232   GNUNET_break (GNUNET_OK ==
233                 GNUNET_DISK_file_close (fhc->fh));
234   GNUNET_free (fhc);
235 }
236
237 /* end of crypto_hash_file.c */