diff options
author | Christian Grothoff <christian@grothoff.org> | 2009-08-21 08:41:21 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2009-08-21 08:41:21 +0000 |
commit | 9a10e9c06a3b08c8ab73edb7d2093a6d452ecc05 (patch) | |
tree | d9ee186c9f61fdc90f128cf39ed113112b21e8ec /src/fs/fs_directory.c | |
parent | ab1934147f74c835b525864b770795f858e37b89 (diff) | |
download | gnunet-9a10e9c06a3b08c8ab73edb7d2093a6d452ecc05.tar.gz gnunet-9a10e9c06a3b08c8ab73edb7d2093a6d452ecc05.zip |
fixes
Diffstat (limited to 'src/fs/fs_directory.c')
-rw-r--r-- | src/fs/fs_directory.c | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/src/fs/fs_directory.c b/src/fs/fs_directory.c new file mode 100644 index 000000000..1ee707cd1 --- /dev/null +++ b/src/fs/fs_directory.c | |||
@@ -0,0 +1,461 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | (C) 2003, 2004, 2006, 2009 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | GNUnet is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 2, or (at your | ||
8 | 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 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with GNUnet; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file fs/fs_directory.c | ||
23 | * @brief Helper functions for building directories. | ||
24 | * @author Christian Grothoff | ||
25 | * | ||
26 | * TODO: | ||
27 | * - add support for embedded file data (use padding room!) | ||
28 | * - add directory builder API to gnunet_fs_service | ||
29 | * - modify directory builder API to support incremental | ||
30 | * generation of directories (to allow directories that | ||
31 | * would not fit into memory to be created) | ||
32 | * - modify directory processor API to support incremental | ||
33 | * iteration over FULL directories (without missing entries) | ||
34 | * to allow access to directories that do not fit entirely | ||
35 | * into memory | ||
36 | */ | ||
37 | #include "platform.h" | ||
38 | #include "gnunet_fs_service.h" | ||
39 | #include "fs.h" | ||
40 | |||
41 | |||
42 | /** | ||
43 | * Does the meta-data claim that this is a directory? | ||
44 | * Checks if the mime-type is that of a GNUnet directory. | ||
45 | * | ||
46 | * @return GNUNET_YES if it is, GNUNET_NO if it is not, GNUNET_SYSERR if | ||
47 | * we have no mime-type information (treat as 'GNUNET_NO') | ||
48 | */ | ||
49 | int | ||
50 | GNUNET_FS_meta_data_test_for_directory (const struct GNUNET_CONTAINER_MetaData *md) | ||
51 | { | ||
52 | char *mime; | ||
53 | int ret; | ||
54 | |||
55 | mime = GNUNET_CONTAINER_meta_data_get_by_type (md, EXTRACTOR_MIMETYPE); | ||
56 | if (mime == NULL) | ||
57 | return GNUNET_SYSERR; | ||
58 | ret = (0 == strcmp (mime, GNUNET_FS_DIRECTORY_MIME)) ? GNUNET_YES : GNUNET_NO; | ||
59 | GNUNET_free (mime); | ||
60 | return ret; | ||
61 | } | ||
62 | |||
63 | |||
64 | /** | ||
65 | * Set the MIMETYPE information for the given | ||
66 | * metadata to "application/gnunet-directory". | ||
67 | * | ||
68 | * @param md metadata to add mimetype to | ||
69 | */ | ||
70 | void | ||
71 | GNUNET_FS_meta_data_make_directory (struct GNUNET_CONTAINER_MetaData *md) | ||
72 | { | ||
73 | char *mime; | ||
74 | |||
75 | mime = GNUNET_CONTAINER_meta_data_get_by_type (md, EXTRACTOR_MIMETYPE); | ||
76 | if (mime != NULL) | ||
77 | { | ||
78 | GNUNET_break (0 == strcmp (mime, | ||
79 | GNUNET_FS_DIRECTORY_MIME)); | ||
80 | GNUNET_free (mime); | ||
81 | return; | ||
82 | } | ||
83 | GNUNET_CONTAINER_meta_data_insert (md, | ||
84 | EXTRACTOR_MIMETYPE, | ||
85 | GNUNET_FS_DIRECTORY_MIME); | ||
86 | } | ||
87 | |||
88 | |||
89 | /** | ||
90 | * Iterate over all entries in a directory. Note that directories | ||
91 | * are structured such that it is possible to iterate over the | ||
92 | * individual blocks as well as over the entire directory. Thus | ||
93 | * a client can call this function on the buffer in the | ||
94 | * GNUNET_FS_ProgressCallback. Also, directories can optionally | ||
95 | * include the contents of (small) files embedded in the directory | ||
96 | * itself; for those files, the processor may be given the | ||
97 | * contents of the file directly by this function. | ||
98 | * <p> | ||
99 | * | ||
100 | * Note that this function maybe called on parts of directories. Thus | ||
101 | * parser errors should not be reported _at all_ (with GNUNET_break). | ||
102 | * Still, if some entries can be recovered despite these parsing | ||
103 | * errors, the function should try to do this. | ||
104 | * | ||
105 | * @param size number of bytes in data | ||
106 | * @param data pointer to the beginning of the directory | ||
107 | * @param offset offset of data in the directory | ||
108 | * @param dep function to call on each entry | ||
109 | * @param dep_cls closure for dep | ||
110 | */ | ||
111 | void | ||
112 | GNUNET_FS_directory_list_contents (size_t size, | ||
113 | const void *data, | ||
114 | uint64_t offset, | ||
115 | GNUNET_FS_DirectoryEntryProcessor dep, | ||
116 | void *dep_cls) | ||
117 | { | ||
118 | const char *cdata = data; | ||
119 | char *emsg; | ||
120 | uint64_t pos; | ||
121 | uint64_t align; | ||
122 | uint32_t mdSize; | ||
123 | uint64_t epos; | ||
124 | struct GNUNET_FS_Uri *uri; | ||
125 | struct GNUNET_CONTAINER_MetaData *md; | ||
126 | char *filename; | ||
127 | |||
128 | pos = offset; | ||
129 | if ( (pos == 0) && | ||
130 | (size >= 8 + sizeof (uint32_t)) && | ||
131 | (0 == memcmp (cdata, GNUNET_FS_DIRECTORY_MAGIC, 8)) ) | ||
132 | { | ||
133 | memcpy (&mdSize, &cdata[8], sizeof (uint32_t)); | ||
134 | mdSize = ntohl (mdSize); | ||
135 | if (mdSize > size - 8 - sizeof (uint32_t)) | ||
136 | { | ||
137 | /* invalid size */ | ||
138 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
139 | _("Not a GNUnet directory.\n")); | ||
140 | return; | ||
141 | } | ||
142 | md = GNUNET_CONTAINER_meta_data_deserialize (&cdata[8 + | ||
143 | sizeof (uint32_t)], | ||
144 | mdSize); | ||
145 | if (md == NULL) | ||
146 | { | ||
147 | GNUNET_break (0); | ||
148 | return; /* malformed ! */ | ||
149 | } | ||
150 | dep (dep_cls, | ||
151 | NULL, | ||
152 | NULL, | ||
153 | md, | ||
154 | 0, | ||
155 | NULL); | ||
156 | GNUNET_CONTAINER_meta_data_destroy (md); | ||
157 | pos = 8 + sizeof (uint32_t) + mdSize; | ||
158 | } | ||
159 | while (pos < size) | ||
160 | { | ||
161 | /* find end of URI */ | ||
162 | if (cdata[pos] == '\0') | ||
163 | { | ||
164 | /* URI is never empty, must be end of block, | ||
165 | skip to next alignment */ | ||
166 | align = | ||
167 | ((pos / GNUNET_FS_DBLOCK_SIZE) + 1) * GNUNET_FS_DBLOCK_SIZE; | ||
168 | if (align == pos) | ||
169 | { | ||
170 | /* if we were already aligned, still skip a block! */ | ||
171 | align += GNUNET_FS_DBLOCK_SIZE; | ||
172 | } | ||
173 | pos = align; | ||
174 | if (pos >= size) | ||
175 | { | ||
176 | /* malformed - or partial download... */ | ||
177 | break; | ||
178 | } | ||
179 | } | ||
180 | epos = pos; | ||
181 | while ((epos < size) && (cdata[epos] != '\0')) | ||
182 | epos++; | ||
183 | if (epos >= size) | ||
184 | return; /* malformed - or partial download */ | ||
185 | |||
186 | uri = GNUNET_FS_uri_parse (&cdata[pos], &emsg); | ||
187 | pos = epos + 1; | ||
188 | if (uri == NULL) | ||
189 | { | ||
190 | GNUNET_free (emsg); | ||
191 | pos--; /* go back to '\0' to force going to next alignment */ | ||
192 | continue; | ||
193 | } | ||
194 | if (GNUNET_FS_uri_test_ksk (uri)) | ||
195 | { | ||
196 | GNUNET_FS_uri_destroy (uri); | ||
197 | GNUNET_break (0); | ||
198 | return; /* illegal in directory! */ | ||
199 | } | ||
200 | |||
201 | memcpy (&mdSize, &cdata[pos], sizeof (uint32_t)); | ||
202 | mdSize = ntohl (mdSize); | ||
203 | pos += sizeof (uint32_t); | ||
204 | if (pos + mdSize > size) | ||
205 | { | ||
206 | GNUNET_FS_uri_destroy (uri); | ||
207 | return; /* malformed - or partial download */ | ||
208 | } | ||
209 | |||
210 | md = GNUNET_CONTAINER_meta_data_deserialize (&cdata[pos], mdSize); | ||
211 | if (md == NULL) | ||
212 | { | ||
213 | GNUNET_FS_uri_destroy (uri); | ||
214 | GNUNET_break (0); | ||
215 | return; /* malformed ! */ | ||
216 | } | ||
217 | pos += mdSize; | ||
218 | /* FIXME: add support for embedded data */ | ||
219 | filename = GNUNET_CONTAINER_meta_data_get_by_type (md, | ||
220 | EXTRACTOR_FILENAME); | ||
221 | if (dep != NULL) | ||
222 | dep (dep_cls, | ||
223 | filename, | ||
224 | uri, | ||
225 | md, | ||
226 | 0, | ||
227 | NULL); | ||
228 | GNUNET_free_non_null (filename); | ||
229 | GNUNET_CONTAINER_meta_data_destroy (md); | ||
230 | GNUNET_FS_uri_destroy (uri); | ||
231 | } | ||
232 | } | ||
233 | |||
234 | #if 0 | ||
235 | |||
236 | |||
237 | /** | ||
238 | * Given the start and end position of a block of | ||
239 | * data, return the end position of that data | ||
240 | * after alignment to the GNUNET_FS_DBLOCK_SIZE. | ||
241 | */ | ||
242 | static uint64_t | ||
243 | do_align (uint64_t start_position, | ||
244 | uint64_t end_position) | ||
245 | { | ||
246 | uint64_t align; | ||
247 | |||
248 | align = (end_position / GNUNET_FS_DBLOCK_SIZE) * GNUNET_FS_DBLOCK_SIZE; | ||
249 | if ((start_position < align) && (end_position > align)) | ||
250 | return align + end_position - start_position; | ||
251 | return end_position; | ||
252 | } | ||
253 | |||
254 | |||
255 | /** | ||
256 | * Compute a permuation of the blocks to | ||
257 | * minimize the cost of alignment. Greedy packer. | ||
258 | * | ||
259 | * @param start starting position for the first block | ||
260 | * @param count size of the two arrays | ||
261 | * @param sizes the sizes of the individual blocks | ||
262 | * @param perm the permutation of the blocks (updated) | ||
263 | */ | ||
264 | static void | ||
265 | block_align (uint64_t start, | ||
266 | unsigned int count, | ||
267 | const uint64_t *sizes, | ||
268 | unsigned int *perm) | ||
269 | { | ||
270 | unsigned int i; | ||
271 | unsigned int j; | ||
272 | unsigned int tmp; | ||
273 | unsigned int best; | ||
274 | int64_t badness; | ||
275 | uint64_t cpos; | ||
276 | uint64_t cend; | ||
277 | int64_t cbad; | ||
278 | unsigned int cval; | ||
279 | |||
280 | cpos = start; | ||
281 | for (i = 0; i < count; i++) | ||
282 | { | ||
283 | start = cpos; | ||
284 | badness = 0x7FFFFFFF; | ||
285 | best = -1; | ||
286 | for (j = i; j < count; j++) | ||
287 | { | ||
288 | cval = perm[j]; | ||
289 | cend = cpos + sizes[cval]; | ||
290 | if (cpos % GNUNET_FS_DBLOCK_SIZE == 0) | ||
291 | { | ||
292 | /* prefer placing the largest blocks first */ | ||
293 | cbad = -(cend % GNUNET_FS_DBLOCK_SIZE); | ||
294 | } | ||
295 | else | ||
296 | { | ||
297 | if (cpos / GNUNET_FS_DBLOCK_SIZE == | ||
298 | cend / GNUNET_FS_DBLOCK_SIZE) | ||
299 | { | ||
300 | /* Data fits into the same block! Prefer small left-overs! */ | ||
301 | cbad = | ||
302 | GNUNET_FS_DBLOCK_SIZE - cend % GNUNET_FS_DBLOCK_SIZE; | ||
303 | } | ||
304 | else | ||
305 | { | ||
306 | /* Would have to waste space to re-align, add big factor, this | ||
307 | case is a real loss (proportional to space wasted)! */ | ||
308 | cbad = | ||
309 | GNUNET_FS_DBLOCK_SIZE * (GNUNET_FS_DBLOCK_SIZE - | ||
310 | cpos % | ||
311 | GNUNET_FS_DBLOCK_SIZE); | ||
312 | } | ||
313 | } | ||
314 | if (cbad < badness) | ||
315 | { | ||
316 | best = j; | ||
317 | badness = cbad; | ||
318 | } | ||
319 | } | ||
320 | tmp = perm[i]; | ||
321 | perm[i] = perm[best]; | ||
322 | perm[best] = tmp; | ||
323 | cpos += sizes[perm[i]]; | ||
324 | cpos = do_align (start, cpos); | ||
325 | } | ||
326 | } | ||
327 | |||
328 | |||
329 | /** | ||
330 | * Create a directory. We allow packing more than one variable | ||
331 | * size entry into one block (and an entry could also span more | ||
332 | * than one block), but an entry that is smaller than a single | ||
333 | * block will never cross the block boundary. This is done to | ||
334 | * allow processing entries of a directory already even if the | ||
335 | * download is still partial.<p> | ||
336 | * | ||
337 | * The first block begins with the directories MAGIC signature, | ||
338 | * followed by the meta-data about the directory itself.<p> | ||
339 | * | ||
340 | * After that, the directory consists of block-aligned pairs | ||
341 | * of URIs (0-terminated strings) and serialized meta-data. | ||
342 | * | ||
343 | * @param data pointer set to the beginning of the directory | ||
344 | * @param len set to number of bytes in data | ||
345 | * @param count number of entries in uris and mds | ||
346 | * @param uris URIs of the files in the directory | ||
347 | * @param mds meta-data for the files (must match | ||
348 | * respective values at same offset in in uris) | ||
349 | * @param mdir meta-data for the directory | ||
350 | * @return GNUNET_OK on success, GNUNET_SYSERR on error | ||
351 | */ | ||
352 | int | ||
353 | GNUNET_FS_directory_create (char **data, | ||
354 | size_t *len, | ||
355 | unsigned int count, | ||
356 | const struct GNUNET_FS_Uri **uris, | ||
357 | const struct GNUNET_CONTAINER_MetaData **mds, | ||
358 | const struct GNUNET_CONTAINER_MetaData *mdir) | ||
359 | { | ||
360 | unsigned int i; | ||
361 | unsigned int j; | ||
362 | uint64_t psize; | ||
363 | uint64_t size; | ||
364 | uint64_t pos; | ||
365 | char **ucs; | ||
366 | int ret; | ||
367 | uint64_t *sizes; | ||
368 | unsigned int *perm; | ||
369 | |||
370 | for (i = 0; i < count; i++) | ||
371 | { | ||
372 | if (GNUNET_FS_uri_test_ksk (fis[i].uri)) | ||
373 | { | ||
374 | GNUNET_break (0); | ||
375 | return GNUNET_SYSERR; /* illegal in directory! */ | ||
376 | } | ||
377 | } | ||
378 | ucs = GNUNET_malloc (sizeof (char *) * count); | ||
379 | size = 8 + sizeof (unsigned int); | ||
380 | size += GNUNET_meta_data_get_serialized_size (meta, GNUNET_SERIALIZE_FULL); | ||
381 | sizes = GNUNET_malloc (count * sizeof (unsigned long long)); | ||
382 | perm = GNUNET_malloc (count * sizeof (int)); | ||
383 | for (i = 0; i < count; i++) | ||
384 | { | ||
385 | perm[i] = i; | ||
386 | ucs[i] = GNUNET_FS_uri_to_string (fis[i].uri); | ||
387 | GNUNET_assert (ucs[i] != NULL); | ||
388 | psize = | ||
389 | GNUNET_meta_data_get_serialized_size (fis[i].meta, | ||
390 | GNUNET_SERIALIZE_FULL); | ||
391 | if (psize == -1) | ||
392 | { | ||
393 | GNUNET_break (0); | ||
394 | GNUNET_free (sizes); | ||
395 | GNUNET_free (perm); | ||
396 | while (i >= 0) | ||
397 | GNUNET_free (ucs[i--]); | ||
398 | GNUNET_free (ucs); | ||
399 | return GNUNET_SYSERR; | ||
400 | } | ||
401 | sizes[i] = psize + sizeof (unsigned int) + strlen (ucs[i]) + 1; | ||
402 | } | ||
403 | /* permutate entries to minimize alignment cost */ | ||
404 | block_align (size, count, sizes, perm); | ||
405 | |||
406 | /* compute final size with alignment */ | ||
407 | for (i = 0; i < count; i++) | ||
408 | { | ||
409 | psize = size; | ||
410 | size += sizes[perm[i]]; | ||
411 | size = do_align (psize, size); | ||
412 | } | ||
413 | *len = size; | ||
414 | *data = GNUNET_malloc (size); | ||
415 | memset (*data, 0, size); | ||
416 | |||
417 | pos = 8; | ||
418 | memcpy (*data, GNUNET_DIRECTORY_MAGIC, 8); | ||
419 | |||
420 | ret = GNUNET_CONTAINER_meta_data_serialize (meta, | ||
421 | &(*data)[pos + | ||
422 | sizeof (unsigned int)], | ||
423 | size - pos - sizeof (unsigned int), | ||
424 | GNUNET_SERIALIZE_FULL); | ||
425 | GNUNET_assert (ret != GNUNET_SYSERR); | ||
426 | ret = htonl (ret); | ||
427 | memcpy (&(*data)[pos], &ret, sizeof (unsigned int)); | ||
428 | pos += ntohl (ret) + sizeof (unsigned int); | ||
429 | |||
430 | for (j = 0; j < count; j++) | ||
431 | { | ||
432 | i = perm[j]; | ||
433 | psize = pos; | ||
434 | pos += sizes[i]; | ||
435 | pos = do_align (psize, pos); | ||
436 | pos -= sizes[i]; /* go back to beginning */ | ||
437 | memcpy (&(*data)[pos], ucs[i], strlen (ucs[i]) + 1); | ||
438 | pos += strlen (ucs[i]) + 1; | ||
439 | GNUNET_free (ucs[i]); | ||
440 | ret = GNUNET_CONTAINER_meta_data_serialize (mds[i], | ||
441 | &(*data)[pos + | ||
442 | sizeof (unsigned int)], | ||
443 | size - pos - | ||
444 | sizeof (unsigned int), | ||
445 | GNUNET_SERIALIZE_FULL); | ||
446 | GNUNET_assert (ret != GNUNET_SYSERR); | ||
447 | ret = htonl (ret); | ||
448 | memcpy (&(*data)[pos], &ret, sizeof (unsigned int)); | ||
449 | pos += ntohl (ret) + sizeof (unsigned int); | ||
450 | } | ||
451 | GNUNET_free (sizes); | ||
452 | GNUNET_free (perm); | ||
453 | GNUNET_free (ucs); | ||
454 | GNUNET_assert (pos == size); | ||
455 | return GNUNET_OK; | ||
456 | } | ||
457 | |||
458 | |||
459 | #endif | ||
460 | |||
461 | /* end of fs_directory.c */ | ||