diff options
Diffstat (limited to 'src/util/disk.c')
-rw-r--r-- | src/util/disk.c | 954 |
1 files changed, 954 insertions, 0 deletions
diff --git a/src/util/disk.c b/src/util/disk.c new file mode 100644 index 000000000..f0fe9341b --- /dev/null +++ b/src/util/disk.c | |||
@@ -0,0 +1,954 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | (C) 2001, 2002, 2005, 2006 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 util/disk.c | ||
23 | * @brief disk IO convenience methods | ||
24 | * @author Christian Grothoff | ||
25 | */ | ||
26 | |||
27 | #include "platform.h" | ||
28 | #include "gnunet_common.h" | ||
29 | #include "gnunet_directories.h" | ||
30 | #include "gnunet_disk_lib.h" | ||
31 | #include "gnunet_scheduler_lib.h" | ||
32 | #include "gnunet_strings_lib.h" | ||
33 | |||
34 | |||
35 | #if LINUX || CYGWIN | ||
36 | #include <sys/vfs.h> | ||
37 | #else | ||
38 | #ifdef SOMEBSD | ||
39 | #include <sys/param.h> | ||
40 | #include <sys/mount.h> | ||
41 | #else | ||
42 | #ifdef OSX | ||
43 | #include <sys/param.h> | ||
44 | #include <sys/mount.h> | ||
45 | #else | ||
46 | #ifdef SOLARIS | ||
47 | #include <sys/types.h> | ||
48 | #include <sys/statvfs.h> | ||
49 | #else | ||
50 | #ifdef MINGW | ||
51 | #define _IFMT 0170000 /* type of file */ | ||
52 | #define _IFLNK 0120000 /* symbolic link */ | ||
53 | #define S_ISLNK(m) (((m)&_IFMT) == _IFLNK) | ||
54 | #else | ||
55 | #error PORT-ME: need to port statfs (how much space is left on the drive?) | ||
56 | #endif | ||
57 | #endif | ||
58 | #endif | ||
59 | #endif | ||
60 | #endif | ||
61 | |||
62 | #ifndef SOMEBSD | ||
63 | #ifndef WINDOWS | ||
64 | #ifndef OSX | ||
65 | #include <wordexp.h> | ||
66 | #endif | ||
67 | #endif | ||
68 | #endif | ||
69 | |||
70 | typedef struct | ||
71 | { | ||
72 | unsigned long long total; | ||
73 | int include_sym_links; | ||
74 | } GetFileSizeData; | ||
75 | |||
76 | static int | ||
77 | getSizeRec (void *ptr, const char *fn) | ||
78 | { | ||
79 | GetFileSizeData *gfsd = ptr; | ||
80 | #ifdef HAVE_STAT64 | ||
81 | struct stat64 buf; | ||
82 | #else | ||
83 | struct stat buf; | ||
84 | #endif | ||
85 | |||
86 | #ifdef HAVE_STAT64 | ||
87 | if (0 != STAT64 (fn, &buf)) | ||
88 | { | ||
89 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat64", fn); | ||
90 | return GNUNET_SYSERR; | ||
91 | } | ||
92 | #else | ||
93 | if (0 != STAT (fn, &buf)) | ||
94 | { | ||
95 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", fn); | ||
96 | return GNUNET_SYSERR; | ||
97 | } | ||
98 | #endif | ||
99 | if ((!S_ISLNK (buf.st_mode)) || (gfsd->include_sym_links == GNUNET_YES)) | ||
100 | gfsd->total += buf.st_size; | ||
101 | if ((S_ISDIR (buf.st_mode)) && | ||
102 | (0 == ACCESS (fn, X_OK)) && | ||
103 | ((!S_ISLNK (buf.st_mode)) || (gfsd->include_sym_links == GNUNET_YES))) | ||
104 | { | ||
105 | if (GNUNET_SYSERR == GNUNET_DISK_directory_scan (fn, &getSizeRec, gfsd)) | ||
106 | return GNUNET_SYSERR; | ||
107 | } | ||
108 | return GNUNET_OK; | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * Get the size of the file (or directory) | ||
113 | * of the given file (in bytes). | ||
114 | * | ||
115 | * @return GNUNET_SYSERR on error, GNUNET_OK on success | ||
116 | */ | ||
117 | int | ||
118 | GNUNET_DISK_file_size (const char *filename, | ||
119 | unsigned long long *size, int includeSymLinks) | ||
120 | { | ||
121 | GetFileSizeData gfsd; | ||
122 | int ret; | ||
123 | |||
124 | GNUNET_assert (size != NULL); | ||
125 | gfsd.total = 0; | ||
126 | gfsd.include_sym_links = includeSymLinks; | ||
127 | ret = getSizeRec (&gfsd, filename); | ||
128 | *size = gfsd.total; | ||
129 | return ret; | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * Get the number of blocks that are left on the partition that | ||
134 | * contains the given file (for normal users). | ||
135 | * | ||
136 | * @param part a file on the partition to check | ||
137 | * @return -1 on errors, otherwise the number of free blocks | ||
138 | */ | ||
139 | long | ||
140 | GNUNET_DISK_get_blocks_available (const char *part) | ||
141 | { | ||
142 | #ifdef SOLARIS | ||
143 | struct statvfs buf; | ||
144 | |||
145 | if (0 != statvfs (part, &buf)) | ||
146 | { | ||
147 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "statfs", part); | ||
148 | return -1; | ||
149 | } | ||
150 | return buf.f_bavail; | ||
151 | #elif MINGW | ||
152 | DWORD dwDummy; | ||
153 | DWORD dwBlocks; | ||
154 | char szDrive[4]; | ||
155 | |||
156 | memcpy (szDrive, part, 3); | ||
157 | szDrive[3] = 0; | ||
158 | if (!GetDiskFreeSpace (szDrive, &dwDummy, &dwDummy, &dwBlocks, &dwDummy)) | ||
159 | { | ||
160 | GNUNET_GE_LOG (GNUNET_ERROR_TYPE_WARNING, | ||
161 | _("`%s' failed for drive `%s': %u\n"), | ||
162 | "GetDiskFreeSpace", szDrive, GetLastError ()); | ||
163 | |||
164 | return -1; | ||
165 | } | ||
166 | return dwBlocks; | ||
167 | #else | ||
168 | struct statfs s; | ||
169 | if (0 != statfs (part, &s)) | ||
170 | { | ||
171 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "statfs", part); | ||
172 | return -1; | ||
173 | } | ||
174 | return s.f_bavail; | ||
175 | #endif | ||
176 | } | ||
177 | |||
178 | /** | ||
179 | * Test if fil is a directory. | ||
180 | * | ||
181 | * @return GNUNET_YES if yes, GNUNET_NO if not, GNUNET_SYSERR if it | ||
182 | * does not exist | ||
183 | */ | ||
184 | int | ||
185 | GNUNET_DISK_directory_test (const char *fil) | ||
186 | { | ||
187 | struct stat filestat; | ||
188 | int ret; | ||
189 | |||
190 | ret = STAT (fil, &filestat); | ||
191 | if (ret != 0) | ||
192 | { | ||
193 | if (errno != ENOENT) | ||
194 | { | ||
195 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", fil); | ||
196 | return GNUNET_SYSERR; | ||
197 | } | ||
198 | return GNUNET_NO; | ||
199 | } | ||
200 | if (!S_ISDIR (filestat.st_mode)) | ||
201 | return GNUNET_NO; | ||
202 | if (ACCESS (fil, R_OK | X_OK) < 0) | ||
203 | { | ||
204 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "access", fil); | ||
205 | return GNUNET_SYSERR; | ||
206 | } | ||
207 | return GNUNET_YES; | ||
208 | } | ||
209 | |||
210 | /** | ||
211 | * Check that fil corresponds to a filename | ||
212 | * (of a file that exists and that is not a directory). | ||
213 | * @returns GNUNET_YES if yes, GNUNET_NO if not a file, GNUNET_SYSERR if something | ||
214 | * else (will print an error message in that case, too). | ||
215 | */ | ||
216 | int | ||
217 | GNUNET_DISK_file_test (const char *fil) | ||
218 | { | ||
219 | struct stat filestat; | ||
220 | int ret; | ||
221 | char *rdir; | ||
222 | |||
223 | rdir = GNUNET_STRINGS_filename_expand (fil); | ||
224 | if (rdir == NULL) | ||
225 | return GNUNET_SYSERR; | ||
226 | |||
227 | ret = STAT (rdir, &filestat); | ||
228 | if (ret != 0) | ||
229 | { | ||
230 | if (errno != ENOENT) | ||
231 | { | ||
232 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", rdir); | ||
233 | GNUNET_free (rdir); | ||
234 | return GNUNET_SYSERR; | ||
235 | } | ||
236 | GNUNET_free (rdir); | ||
237 | return GNUNET_NO; | ||
238 | } | ||
239 | if (!S_ISREG (filestat.st_mode)) | ||
240 | { | ||
241 | GNUNET_free (rdir); | ||
242 | return GNUNET_NO; | ||
243 | } | ||
244 | if (ACCESS (rdir, R_OK) < 0) | ||
245 | { | ||
246 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "access", rdir); | ||
247 | GNUNET_free (rdir); | ||
248 | return GNUNET_SYSERR; | ||
249 | } | ||
250 | GNUNET_free (rdir); | ||
251 | return GNUNET_YES; | ||
252 | } | ||
253 | |||
254 | /** | ||
255 | * Implementation of "mkdir -p" | ||
256 | * @param dir the directory to create | ||
257 | * @returns GNUNET_OK on success, GNUNET_SYSERR on failure | ||
258 | */ | ||
259 | int | ||
260 | GNUNET_DISK_directory_create (const char *dir) | ||
261 | { | ||
262 | char *rdir; | ||
263 | int len; | ||
264 | int pos; | ||
265 | int ret = GNUNET_OK; | ||
266 | |||
267 | rdir = GNUNET_STRINGS_filename_expand (dir); | ||
268 | if (rdir == NULL) | ||
269 | return GNUNET_SYSERR; | ||
270 | |||
271 | len = strlen (rdir); | ||
272 | #ifndef MINGW | ||
273 | pos = 1; /* skip heading '/' */ | ||
274 | #else | ||
275 | /* Local or Network path? */ | ||
276 | if (strncmp (rdir, "\\\\", 2) == 0) | ||
277 | { | ||
278 | pos = 2; | ||
279 | while (rdir[pos]) | ||
280 | { | ||
281 | if (rdir[pos] == '\\') | ||
282 | { | ||
283 | pos++; | ||
284 | break; | ||
285 | } | ||
286 | pos++; | ||
287 | } | ||
288 | } | ||
289 | else | ||
290 | { | ||
291 | pos = 3; /* strlen("C:\\") */ | ||
292 | } | ||
293 | #endif | ||
294 | while (pos <= len) | ||
295 | { | ||
296 | if ((rdir[pos] == DIR_SEPARATOR) || (pos == len)) | ||
297 | { | ||
298 | rdir[pos] = '\0'; | ||
299 | ret = GNUNET_DISK_directory_test (rdir); | ||
300 | if (ret == GNUNET_SYSERR) | ||
301 | { | ||
302 | GNUNET_free (rdir); | ||
303 | return GNUNET_SYSERR; | ||
304 | } | ||
305 | if (ret == GNUNET_NO) | ||
306 | { | ||
307 | #ifndef MINGW | ||
308 | ret = mkdir (rdir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); /* 755 */ | ||
309 | #else | ||
310 | ret = mkdir (rdir); | ||
311 | #endif | ||
312 | if ((ret != 0) && (errno != EEXIST)) | ||
313 | { | ||
314 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "mkdir", | ||
315 | rdir); | ||
316 | GNUNET_free (rdir); | ||
317 | return GNUNET_SYSERR; | ||
318 | } | ||
319 | } | ||
320 | rdir[pos] = DIR_SEPARATOR; | ||
321 | } | ||
322 | pos++; | ||
323 | } | ||
324 | GNUNET_free (rdir); | ||
325 | return GNUNET_OK; | ||
326 | } | ||
327 | |||
328 | |||
329 | /** | ||
330 | * Create the directory structure for storing | ||
331 | * a file. | ||
332 | * | ||
333 | * @param filename name of a file in the directory | ||
334 | * @returns GNUNET_OK on success, | ||
335 | * GNUNET_SYSERR on failure, | ||
336 | * GNUNET_NO if the directory | ||
337 | * exists but is not writeable for us | ||
338 | */ | ||
339 | int | ||
340 | GNUNET_DISK_directory_create_for_file (const char *dir) | ||
341 | { | ||
342 | char *rdir; | ||
343 | int len; | ||
344 | int ret; | ||
345 | |||
346 | rdir = GNUNET_STRINGS_filename_expand (dir); | ||
347 | if (rdir == NULL) | ||
348 | return GNUNET_SYSERR; | ||
349 | len = strlen (rdir); | ||
350 | while ((len > 0) && (rdir[len] != DIR_SEPARATOR)) | ||
351 | len--; | ||
352 | rdir[len] = '\0'; | ||
353 | ret = GNUNET_DISK_directory_create (rdir); | ||
354 | if ((ret == GNUNET_OK) && (0 != ACCESS (rdir, W_OK))) | ||
355 | ret = GNUNET_NO; | ||
356 | GNUNET_free (rdir); | ||
357 | return ret; | ||
358 | } | ||
359 | |||
360 | /** | ||
361 | * Read the contents of a binary file into a buffer. | ||
362 | * @param fileName the name of the file, not freed, | ||
363 | * must already be expanded! | ||
364 | * @param len the maximum number of bytes to read | ||
365 | * @param result the buffer to write the result to | ||
366 | * @return the number of bytes read on success, -1 on failure | ||
367 | */ | ||
368 | int | ||
369 | GNUNET_DISK_file_read (const char *fileName, int len, void *result) | ||
370 | { | ||
371 | /* open file, must exist, open read only */ | ||
372 | int handle; | ||
373 | int size; | ||
374 | |||
375 | GNUNET_assert (fileName != NULL); | ||
376 | GNUNET_assert (len > 0); | ||
377 | if (len == 0) | ||
378 | return 0; | ||
379 | GNUNET_assert (result != NULL); | ||
380 | handle = GNUNET_DISK_file_open (fileName, O_RDONLY, S_IRUSR); | ||
381 | if (handle < 0) | ||
382 | return -1; | ||
383 | size = READ (handle, result, len); | ||
384 | GNUNET_DISK_file_close (fileName, handle); | ||
385 | return size; | ||
386 | } | ||
387 | |||
388 | |||
389 | /** | ||
390 | * Convert string to value ('755' for chmod-call) | ||
391 | */ | ||
392 | static int | ||
393 | atoo (const char *s) | ||
394 | { | ||
395 | int n = 0; | ||
396 | |||
397 | while (('0' <= *s) && (*s < '8')) | ||
398 | { | ||
399 | n <<= 3; | ||
400 | n += *s++ - '0'; | ||
401 | } | ||
402 | return n; | ||
403 | } | ||
404 | |||
405 | /** | ||
406 | * Write a buffer to a file. | ||
407 | * @param fileName the name of the file, NOT freed! | ||
408 | * @param buffer the data to write | ||
409 | * @param n number of bytes to write | ||
410 | * @param mode permissions to set on the file | ||
411 | * @return GNUNET_OK on success, GNUNET_SYSERR on error | ||
412 | */ | ||
413 | int | ||
414 | GNUNET_DISK_file_write (const char *fileName, | ||
415 | const void *buffer, unsigned int n, const char *mode) | ||
416 | { | ||
417 | int handle; | ||
418 | char *fn; | ||
419 | |||
420 | /* open file, open with 600, create if not | ||
421 | present, otherwise overwrite */ | ||
422 | GNUNET_assert (fileName != NULL); | ||
423 | fn = GNUNET_STRINGS_filename_expand (fileName); | ||
424 | handle = GNUNET_DISK_file_open (fn, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); | ||
425 | if (handle == -1) | ||
426 | { | ||
427 | GNUNET_free (fn); | ||
428 | return GNUNET_SYSERR; | ||
429 | } | ||
430 | GNUNET_assert ((n == 0) || (buffer != NULL)); | ||
431 | /* write the buffer take length from the beginning */ | ||
432 | if (n != WRITE (handle, buffer, n)) | ||
433 | { | ||
434 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn); | ||
435 | GNUNET_DISK_file_close (fn, handle); | ||
436 | GNUNET_free (fn); | ||
437 | return GNUNET_SYSERR; | ||
438 | } | ||
439 | GNUNET_DISK_file_close (fn, handle); | ||
440 | if (0 != CHMOD (fn, atoo (mode))) | ||
441 | { | ||
442 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "chmod", fn); | ||
443 | } | ||
444 | GNUNET_free (fn); | ||
445 | return GNUNET_OK; | ||
446 | } | ||
447 | |||
448 | /** | ||
449 | * Scan a directory for files. The name of the directory | ||
450 | * must be expanded first (!). | ||
451 | * @param dirName the name of the directory | ||
452 | * @param callback the method to call for each file, | ||
453 | * can be NULL, in that case, we only count | ||
454 | * @param data argument to pass to callback | ||
455 | * @return the number of files found, GNUNET_SYSERR on error or | ||
456 | * ieration aborted by callback returning GNUNET_SYSERR | ||
457 | */ | ||
458 | int | ||
459 | GNUNET_DISK_directory_scan (const char *dirName, | ||
460 | GNUNET_FileNameCallback callback, void *data) | ||
461 | { | ||
462 | DIR *dinfo; | ||
463 | struct dirent *finfo; | ||
464 | struct stat istat; | ||
465 | int count = 0; | ||
466 | char *name; | ||
467 | char *dname; | ||
468 | unsigned int name_len; | ||
469 | unsigned int n_size; | ||
470 | |||
471 | GNUNET_assert (dirName != NULL); | ||
472 | dname = GNUNET_STRINGS_filename_expand (dirName); | ||
473 | while ((strlen (dname) > 0) && (dname[strlen (dname) - 1] == DIR_SEPARATOR)) | ||
474 | dname[strlen (dname) - 1] = '\0'; | ||
475 | if (0 != STAT (dname, &istat)) | ||
476 | { | ||
477 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", dname); | ||
478 | GNUNET_free (dname); | ||
479 | return GNUNET_SYSERR; | ||
480 | } | ||
481 | if (!S_ISDIR (istat.st_mode)) | ||
482 | { | ||
483 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
484 | _("Expected `%s' to be a directory!\n"), dirName); | ||
485 | GNUNET_free (dname); | ||
486 | return GNUNET_SYSERR; | ||
487 | } | ||
488 | errno = 0; | ||
489 | dinfo = OPENDIR (dname); | ||
490 | if ((errno == EACCES) || (dinfo == NULL)) | ||
491 | { | ||
492 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "opendir", dname); | ||
493 | if (dinfo != NULL) | ||
494 | closedir (dinfo); | ||
495 | GNUNET_free (dname); | ||
496 | return GNUNET_SYSERR; | ||
497 | } | ||
498 | name_len = 256; | ||
499 | n_size = strlen (dname) + name_len + 2; | ||
500 | name = GNUNET_malloc (n_size); | ||
501 | while ((finfo = readdir (dinfo)) != NULL) | ||
502 | { | ||
503 | if ((0 == strcmp (finfo->d_name, ".")) || | ||
504 | (0 == strcmp (finfo->d_name, ".."))) | ||
505 | continue; | ||
506 | if (callback != NULL) | ||
507 | { | ||
508 | if (name_len < strlen (finfo->d_name)) | ||
509 | { | ||
510 | GNUNET_free (name); | ||
511 | name_len = strlen (finfo->d_name); | ||
512 | n_size = strlen (dname) + name_len + 2; | ||
513 | name = GNUNET_malloc (n_size); | ||
514 | } | ||
515 | /* dname can end in "/" only if dname == "/"; | ||
516 | if dname does not end in "/", we need to add | ||
517 | a "/" (otherwise, we must not!) */ | ||
518 | GNUNET_snprintf (name, | ||
519 | n_size, | ||
520 | "%s%s%s", | ||
521 | dname, | ||
522 | (strcmp (dname, DIR_SEPARATOR_STR) == | ||
523 | 0) ? "" : DIR_SEPARATOR_STR, finfo->d_name); | ||
524 | if (GNUNET_OK != callback (data, name)) | ||
525 | { | ||
526 | closedir (dinfo); | ||
527 | GNUNET_free (name); | ||
528 | GNUNET_free (dname); | ||
529 | return GNUNET_SYSERR; | ||
530 | } | ||
531 | } | ||
532 | count++; | ||
533 | } | ||
534 | closedir (dinfo); | ||
535 | GNUNET_free (name); | ||
536 | GNUNET_free (dname); | ||
537 | return count; | ||
538 | } | ||
539 | |||
540 | |||
541 | /** | ||
542 | * Opaque handle used for iterating over a directory. | ||
543 | */ | ||
544 | struct GNUNET_DISK_DirectoryIterator | ||
545 | { | ||
546 | /** | ||
547 | * Our scheduler. | ||
548 | */ | ||
549 | struct GNUNET_SCHEDULER_Handle *sched; | ||
550 | |||
551 | /** | ||
552 | * Function to call on directory entries. | ||
553 | */ | ||
554 | GNUNET_DISK_DirectoryIteratorCallback callback; | ||
555 | |||
556 | /** | ||
557 | * Closure for callback. | ||
558 | */ | ||
559 | void *callback_cls; | ||
560 | |||
561 | /** | ||
562 | * Reference to directory. | ||
563 | */ | ||
564 | DIR *directory; | ||
565 | |||
566 | /** | ||
567 | * Directory name. | ||
568 | */ | ||
569 | char *dirname; | ||
570 | |||
571 | /** | ||
572 | * Next filename to process. | ||
573 | */ | ||
574 | char *next_name; | ||
575 | |||
576 | /** | ||
577 | * Our priority. | ||
578 | */ | ||
579 | enum GNUNET_SCHEDULER_Priority priority; | ||
580 | |||
581 | }; | ||
582 | |||
583 | |||
584 | /** | ||
585 | * Task used by the directory iterator. | ||
586 | */ | ||
587 | static void | ||
588 | directory_iterator_task (void *cls, | ||
589 | const struct GNUNET_SCHEDULER_TaskContext *tc) | ||
590 | { | ||
591 | struct GNUNET_DISK_DirectoryIterator *iter = cls; | ||
592 | char *name; | ||
593 | |||
594 | name = iter->next_name; | ||
595 | GNUNET_assert (name != NULL); | ||
596 | iter->next_name = NULL; | ||
597 | iter->callback (iter->callback_cls, iter, name, iter->dirname); | ||
598 | GNUNET_free (name); | ||
599 | } | ||
600 | |||
601 | |||
602 | /** | ||
603 | * This function must be called during the DiskIteratorCallback | ||
604 | * (exactly once) to schedule the task to process the next | ||
605 | * filename in the directory (if there is one). | ||
606 | * | ||
607 | * @param iter opaque handle for the iterator | ||
608 | * @param can set to GNUNET_YES to terminate the iteration early | ||
609 | * @return GNUNET_YES if iteration will continue, | ||
610 | * GNUNET_NO if this was the last entry (and iteration is complete), | ||
611 | * GNUNET_SYSERR if abort was YES | ||
612 | */ | ||
613 | int | ||
614 | GNUNET_DISK_directory_iterator_next (struct GNUNET_DISK_DirectoryIterator | ||
615 | *iter, int can) | ||
616 | { | ||
617 | struct dirent *finfo; | ||
618 | |||
619 | GNUNET_assert (iter->next_name == NULL); | ||
620 | if (can == GNUNET_YES) | ||
621 | { | ||
622 | closedir (iter->directory); | ||
623 | GNUNET_free (iter->dirname); | ||
624 | GNUNET_free (iter); | ||
625 | return GNUNET_SYSERR; | ||
626 | } | ||
627 | while (NULL != (finfo = readdir (iter->directory))) | ||
628 | { | ||
629 | if ((0 == strcmp (finfo->d_name, ".")) || | ||
630 | (0 == strcmp (finfo->d_name, ".."))) | ||
631 | continue; | ||
632 | GNUNET_asprintf (&iter->next_name, | ||
633 | "%s%s%s", | ||
634 | iter->dirname, DIR_SEPARATOR_STR, finfo->d_name); | ||
635 | break; | ||
636 | } | ||
637 | if (finfo == NULL) | ||
638 | { | ||
639 | GNUNET_DISK_directory_iterator_next (iter, GNUNET_YES); | ||
640 | return GNUNET_NO; | ||
641 | } | ||
642 | GNUNET_SCHEDULER_add_after (iter->sched, | ||
643 | GNUNET_YES, | ||
644 | iter->priority, | ||
645 | GNUNET_SCHEDULER_NO_PREREQUISITE_TASK, | ||
646 | &directory_iterator_task, iter); | ||
647 | return GNUNET_YES; | ||
648 | } | ||
649 | |||
650 | |||
651 | /** | ||
652 | * Scan a directory for files using the scheduler to run a task for | ||
653 | * each entry. The name of the directory must be expanded first (!). | ||
654 | * If a scheduler does not need to be used, GNUNET_DISK_directory_scan | ||
655 | * may provide a simpler API. | ||
656 | * | ||
657 | * @param sched scheduler to use | ||
658 | * @param prio priority to use | ||
659 | * @param dirName the name of the directory | ||
660 | * @param callback the method to call for each file | ||
661 | * @param callback_cls closure for callback | ||
662 | */ | ||
663 | void | ||
664 | GNUNET_DISK_directory_iterator_start (struct GNUNET_SCHEDULER_Handle *sched, | ||
665 | enum GNUNET_SCHEDULER_Priority prio, | ||
666 | const char *dirName, | ||
667 | GNUNET_DISK_DirectoryIteratorCallback | ||
668 | callback, void *callback_cls) | ||
669 | { | ||
670 | struct GNUNET_DISK_DirectoryIterator *di; | ||
671 | |||
672 | di = GNUNET_malloc (sizeof (struct GNUNET_DISK_DirectoryIterator)); | ||
673 | di->sched = sched; | ||
674 | di->callback = callback; | ||
675 | di->callback_cls = callback_cls; | ||
676 | di->directory = OPENDIR (dirName); | ||
677 | di->dirname = GNUNET_strdup (dirName); | ||
678 | di->priority = prio; | ||
679 | GNUNET_DISK_directory_iterator_next (di, GNUNET_NO); | ||
680 | } | ||
681 | |||
682 | |||
683 | static int | ||
684 | remove_helper (void *unused, const char *fn) | ||
685 | { | ||
686 | GNUNET_DISK_directory_remove (fn); | ||
687 | return GNUNET_OK; | ||
688 | } | ||
689 | |||
690 | /** | ||
691 | * Remove all files in a directory (rm -rf). Call with | ||
692 | * caution. | ||
693 | * | ||
694 | * | ||
695 | * @param fileName the file to remove | ||
696 | * @return GNUNET_OK on success, GNUNET_SYSERR on error | ||
697 | */ | ||
698 | int | ||
699 | GNUNET_DISK_directory_remove (const char *fileName) | ||
700 | { | ||
701 | struct stat istat; | ||
702 | |||
703 | if (0 != LSTAT (fileName, &istat)) | ||
704 | return GNUNET_NO; /* file may not exist... */ | ||
705 | if (UNLINK (fileName) == 0) | ||
706 | return GNUNET_OK; | ||
707 | if ((errno != EISDIR) && | ||
708 | /* EISDIR is not sufficient in all cases, e.g. | ||
709 | sticky /tmp directory may result in EPERM on BSD. | ||
710 | So we also explicitly check "isDirectory" */ | ||
711 | (GNUNET_YES != GNUNET_DISK_directory_test (fileName))) | ||
712 | { | ||
713 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "rmdir", fileName); | ||
714 | return GNUNET_SYSERR; | ||
715 | } | ||
716 | if (GNUNET_SYSERR == | ||
717 | GNUNET_DISK_directory_scan (fileName, remove_helper, NULL)) | ||
718 | return GNUNET_SYSERR; | ||
719 | if (0 != RMDIR (fileName)) | ||
720 | { | ||
721 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "rmdir", fileName); | ||
722 | return GNUNET_SYSERR; | ||
723 | } | ||
724 | return GNUNET_OK; | ||
725 | } | ||
726 | |||
727 | void | ||
728 | GNUNET_DISK_file_close (const char *filename, int fd) | ||
729 | { | ||
730 | if (0 != CLOSE (fd)) | ||
731 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "close", filename); | ||
732 | } | ||
733 | |||
734 | int | ||
735 | GNUNET_DISK_file_open (const char *filename, int oflag, ...) | ||
736 | { | ||
737 | char *fn; | ||
738 | int mode; | ||
739 | int ret; | ||
740 | #ifdef MINGW | ||
741 | char szFile[_MAX_PATH + 1]; | ||
742 | long lRet; | ||
743 | |||
744 | if ((lRet = plibc_conv_to_win_path (filename, szFile)) != ERROR_SUCCESS) | ||
745 | { | ||
746 | errno = ENOENT; | ||
747 | SetLastError (lRet); | ||
748 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, | ||
749 | "plibc_conv_to_win_path", filename); | ||
750 | return -1; | ||
751 | } | ||
752 | fn = GNUNET_strdup (szFile); | ||
753 | #else | ||
754 | fn = GNUNET_STRINGS_filename_expand (filename); | ||
755 | #endif | ||
756 | if (oflag & O_CREAT) | ||
757 | { | ||
758 | va_list arg; | ||
759 | va_start (arg, oflag); | ||
760 | mode = va_arg (arg, int); | ||
761 | va_end (arg); | ||
762 | } | ||
763 | else | ||
764 | { | ||
765 | mode = 0; | ||
766 | } | ||
767 | #ifdef MINGW | ||
768 | /* set binary mode */ | ||
769 | oflag |= O_BINARY; | ||
770 | #endif | ||
771 | ret = OPEN (fn, oflag, mode); | ||
772 | if (ret == -1) | ||
773 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "open", fn); | ||
774 | GNUNET_free (fn); | ||
775 | return ret; | ||
776 | } | ||
777 | |||
778 | #define COPY_BLK_SIZE 65536 | ||
779 | |||
780 | /** | ||
781 | * Copy a file. | ||
782 | * @return GNUNET_OK on success, GNUNET_SYSERR on error | ||
783 | */ | ||
784 | int | ||
785 | GNUNET_DISK_file_copy (const char *src, const char *dst) | ||
786 | { | ||
787 | char *buf; | ||
788 | unsigned long long pos; | ||
789 | unsigned long long size; | ||
790 | unsigned long long len; | ||
791 | int in; | ||
792 | int out; | ||
793 | |||
794 | if (GNUNET_OK != GNUNET_DISK_file_size (src, &size, GNUNET_YES)) | ||
795 | return GNUNET_SYSERR; | ||
796 | pos = 0; | ||
797 | in = GNUNET_DISK_file_open (src, O_RDONLY | O_LARGEFILE); | ||
798 | if (in == -1) | ||
799 | return GNUNET_SYSERR; | ||
800 | out = GNUNET_DISK_file_open (dst, | ||
801 | O_LARGEFILE | O_WRONLY | O_CREAT | O_EXCL, | ||
802 | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); | ||
803 | if (out == -1) | ||
804 | { | ||
805 | GNUNET_DISK_file_close (src, in); | ||
806 | return GNUNET_SYSERR; | ||
807 | } | ||
808 | buf = GNUNET_malloc (COPY_BLK_SIZE); | ||
809 | while (pos < size) | ||
810 | { | ||
811 | len = COPY_BLK_SIZE; | ||
812 | if (len > size - pos) | ||
813 | len = size - pos; | ||
814 | if (len != READ (in, buf, len)) | ||
815 | goto FAIL; | ||
816 | if (len != WRITE (out, buf, len)) | ||
817 | goto FAIL; | ||
818 | pos += len; | ||
819 | } | ||
820 | GNUNET_free (buf); | ||
821 | GNUNET_DISK_file_close (src, in); | ||
822 | GNUNET_DISK_file_close (dst, out); | ||
823 | return GNUNET_OK; | ||
824 | FAIL: | ||
825 | GNUNET_free (buf); | ||
826 | GNUNET_DISK_file_close (src, in); | ||
827 | GNUNET_DISK_file_close (dst, out); | ||
828 | return GNUNET_SYSERR; | ||
829 | } | ||
830 | |||
831 | |||
832 | /** | ||
833 | * @brief Removes special characters as ':' from a filename. | ||
834 | * @param fn the filename to canonicalize | ||
835 | */ | ||
836 | void | ||
837 | GNUNET_DISK_filename_canonicalize (char *fn) | ||
838 | { | ||
839 | char *idx; | ||
840 | char c; | ||
841 | |||
842 | idx = fn; | ||
843 | while (*idx) | ||
844 | { | ||
845 | c = *idx; | ||
846 | |||
847 | if (c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' || | ||
848 | c == '"' || c == '<' || c == '>' || c == '|') | ||
849 | { | ||
850 | *idx = '_'; | ||
851 | } | ||
852 | |||
853 | idx++; | ||
854 | } | ||
855 | } | ||
856 | |||
857 | |||
858 | |||
859 | /** | ||
860 | * @brief Change owner of a file | ||
861 | */ | ||
862 | int | ||
863 | GNUNET_DISK_file_change_owner (const char *filename, const char *user) | ||
864 | { | ||
865 | #ifndef MINGW | ||
866 | struct passwd *pws; | ||
867 | |||
868 | pws = getpwnam (user); | ||
869 | if (pws == NULL) | ||
870 | { | ||
871 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
872 | _("Cannot obtain information about user `%s': %s\n"), | ||
873 | user, STRERROR (errno)); | ||
874 | return GNUNET_SYSERR; | ||
875 | } | ||
876 | if (0 != chown (filename, pws->pw_uid, pws->pw_gid)) | ||
877 | GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "chown", filename); | ||
878 | #endif | ||
879 | return GNUNET_OK; | ||
880 | } | ||
881 | |||
882 | |||
883 | /** | ||
884 | * Construct full path to a file inside of the private | ||
885 | * directory used by GNUnet. Also creates the corresponding | ||
886 | * directory. If the resulting name is supposed to be | ||
887 | * a directory, end the last argument in '/' (or pass | ||
888 | * DIR_SEPARATOR_STR as the last argument before NULL). | ||
889 | * | ||
890 | * @param serviceName name of the service | ||
891 | * @param varargs is NULL-terminated list of | ||
892 | * path components to append to the | ||
893 | * private directory name. | ||
894 | * @return the constructed filename | ||
895 | */ | ||
896 | char * | ||
897 | GNUNET_DISK_get_home_filename (struct GNUNET_CONFIGURATION_Handle *cfg, | ||
898 | const char *serviceName, ...) | ||
899 | { | ||
900 | const char *c; | ||
901 | char *pfx; | ||
902 | char *ret; | ||
903 | va_list ap; | ||
904 | unsigned int needed; | ||
905 | |||
906 | if (GNUNET_OK != | ||
907 | GNUNET_CONFIGURATION_get_value_filename (cfg, | ||
908 | serviceName, "HOME", &pfx)) | ||
909 | return NULL; | ||
910 | if (pfx == NULL) | ||
911 | { | ||
912 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
913 | _("No `%s' specified for service `%s' in configuration.\n"), | ||
914 | "HOME", serviceName); | ||
915 | return NULL; | ||
916 | } | ||
917 | needed = strlen (pfx) + 2; | ||
918 | if ((pfx[strlen (pfx) - 1] != '/') && (pfx[strlen (pfx) - 1] != '\\')) | ||
919 | needed++; | ||
920 | va_start (ap, serviceName); | ||
921 | while (1) | ||
922 | { | ||
923 | c = va_arg (ap, const char *); | ||
924 | if (c == NULL) | ||
925 | break; | ||
926 | needed += strlen (c); | ||
927 | if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\')) | ||
928 | needed++; | ||
929 | } | ||
930 | va_end (ap); | ||
931 | ret = GNUNET_malloc (needed); | ||
932 | strcpy (ret, pfx); | ||
933 | GNUNET_free (pfx); | ||
934 | va_start (ap, serviceName); | ||
935 | while (1) | ||
936 | { | ||
937 | c = va_arg (ap, const char *); | ||
938 | if (c == NULL) | ||
939 | break; | ||
940 | if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\')) | ||
941 | strcat (ret, DIR_SEPARATOR_STR); | ||
942 | strcat (ret, c); | ||
943 | } | ||
944 | va_end (ap); | ||
945 | if ((ret[strlen (ret) - 1] != '/') && (ret[strlen (ret) - 1] != '\\')) | ||
946 | GNUNET_DISK_directory_create_for_file (ret); | ||
947 | else | ||
948 | GNUNET_DISK_directory_create (ret); | ||
949 | return ret; | ||
950 | } | ||
951 | |||
952 | |||
953 | |||
954 | /* end of disk.c */ | ||