airplane hackery
[oweals/gnunet.git] / src / util / disk.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2005, 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 util/disk.c
23  * @brief disk IO convenience methods
24  * @author Christian Grothoff
25  * @author Nils Durner
26  */
27
28 #include "platform.h"
29 #include "gnunet_common.h"
30 #include "gnunet_directories.h"
31 #include "gnunet_disk_lib.h"
32 #include "gnunet_scheduler_lib.h"
33 #include "gnunet_strings_lib.h"
34
35
36 #if LINUX || CYGWIN
37 #include <sys/vfs.h>
38 #else
39 #ifdef SOMEBSD
40 #include <sys/param.h>
41 #include <sys/mount.h>
42 #else
43 #ifdef OSX
44 #include <sys/param.h>
45 #include <sys/mount.h>
46 #else
47 #ifdef SOLARIS
48 #include <sys/types.h>
49 #include <sys/statvfs.h>
50 #else
51 #ifdef MINGW
52 #define         _IFMT           0170000 /* type of file */
53 #define         _IFLNK          0120000 /* symbolic link */
54 #define  S_ISLNK(m)     (((m)&_IFMT) == _IFLNK)
55 #else
56 #error PORT-ME: need to port statfs (how much space is left on the drive?)
57 #endif
58 #endif
59 #endif
60 #endif
61 #endif
62
63 #ifndef SOMEBSD
64 #ifndef WINDOWS
65 #ifndef OSX
66 #include <wordexp.h>
67 #endif
68 #endif
69 #endif
70
71 typedef struct
72 {
73   unsigned long long total;
74   int include_sym_links;
75 } GetFileSizeData;
76
77 struct GNUNET_IO_Handle
78 {
79 #if MINGW
80   HANDLE h;
81 #else
82   int fd;
83 #endif
84 };
85
86 static int
87 getSizeRec (void *ptr, const char *fn)
88 {
89   GetFileSizeData *gfsd = ptr;
90 #ifdef HAVE_STAT64
91   struct stat64 buf;
92 #else
93   struct stat buf;
94 #endif
95
96 #ifdef HAVE_STAT64
97   if (0 != STAT64 (fn, &buf))
98     {
99       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat64", fn);
100       return GNUNET_SYSERR;
101     }
102 #else
103   if (0 != STAT (fn, &buf))
104     {
105       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", fn);
106       return GNUNET_SYSERR;
107     }
108 #endif
109   if ((!S_ISLNK (buf.st_mode)) || (gfsd->include_sym_links == GNUNET_YES))
110     gfsd->total += buf.st_size;
111   if ((S_ISDIR (buf.st_mode)) &&
112       (0 == ACCESS (fn, X_OK)) &&
113       ((!S_ISLNK (buf.st_mode)) || (gfsd->include_sym_links == GNUNET_YES)))
114     {
115       if (GNUNET_SYSERR == GNUNET_DISK_directory_scan (fn, &getSizeRec, gfsd))
116         return GNUNET_SYSERR;
117     }
118   return GNUNET_OK;
119 }
120
121 /**
122  * Checks whether a handle is invalid
123  * @param h handle to check
124  * @return GNUNET_YES if invalid, GNUNET_NO if valid
125  */
126 int
127 GNUNET_DISK_handle_invalid (const struct GNUNET_IO_Handle *h)
128 {
129 #ifdef MINGW
130   return !h || h->h == INVALID_HANDLE_VALUE ? GNUNET_YES : GNUNET_NO;
131 #else
132   return !h || h->fd == -1 ? GNUNET_YES : GNUNET_NO;
133 #endif
134 }
135
136 /**
137  * Mark a handle as invalid
138  * @param h file handle
139  */
140 static void
141 GNUNET_DISK_handle_invalidate (struct GNUNET_IO_Handle *h)
142 {
143 #ifdef MINGW
144   h->h = INVALID_HANDLE_VALUE;
145 #else
146   h->fd = -1;
147 #endif
148 }
149
150 /**
151  * Move the read/write pointer in a file
152  * @param h handle of an open file
153  * @param offset position to move to
154  * @param whence specification to which position the offset parameter relates to
155  * @return the new position on success, GNUNET_SYSERR otherwise
156  */
157 off_t
158 GNUNET_DISK_file_seek (const struct GNUNET_IO_Handle *h, off_t offset,
159     enum GNUNET_DISK_Seek whence)
160 {
161   if (h == NULL)
162     {
163       errno = EINVAL;
164       return GNUNET_SYSERR;
165     }
166
167 #ifdef MINGW
168   DWORD ret;
169   static DWORD t[] = { [GNUNET_SEEK_SET] = FILE_BEGIN,
170       [GNUNET_SEEK_CUR] = FILE_CURRENT, [GNUNET_SEEK_END] = FILE_END };
171
172   ret = SetFilePointer (h->h, offset, NULL, t[whence]);
173   if (ret == INVALID_SET_FILE_POINTER)
174     {
175       SetErrnoFromWinError (GetLastError ());
176       return GNUNET_SYSERR;
177     }
178   return ret;
179 #else
180   static int t[] = { [GNUNET_SEEK_SET] = SEEK_SET,
181       [GNUNET_SEEK_CUR] = SEEK_CUR, [GNUNET_SEEK_END] = SEEK_END };
182
183   return lseek (h->fd, offset, t[whence]);
184 #endif
185 }
186
187 /**
188  * Get the size of the file (or directory)
189  * of the given file (in bytes).
190  *
191  * @return GNUNET_SYSERR on error, GNUNET_OK on success
192  */
193 int
194 GNUNET_DISK_file_size (const char *filename,
195                        unsigned long long *size, int includeSymLinks)
196 {
197   GetFileSizeData gfsd;
198   int ret;
199
200   GNUNET_assert (size != NULL);
201   gfsd.total = 0;
202   gfsd.include_sym_links = includeSymLinks;
203   ret = getSizeRec (&gfsd, filename);
204   *size = gfsd.total;
205   return ret;
206 }
207
208
209 /**
210  * Get the number of blocks that are left on the partition that
211  * contains the given file (for normal users).
212  *
213  * @param part a file on the partition to check
214  * @return -1 on errors, otherwise the number of free blocks
215  */
216 long
217 GNUNET_DISK_get_blocks_available (const char *part)
218 {
219 #ifdef SOLARIS
220   struct statvfs buf;
221
222   if (0 != statvfs (part, &buf))
223     {
224       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "statfs", part);
225       return -1;
226     }
227   return buf.f_bavail;
228 #elif MINGW
229   DWORD dwDummy;
230   DWORD dwBlocks;
231   char szDrive[4];
232
233   memcpy (szDrive, part, 3);
234   szDrive[3] = 0;
235   if (!GetDiskFreeSpace (szDrive, &dwDummy, &dwDummy, &dwBlocks, &dwDummy))
236     {
237       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
238                      _("`%s' failed for drive `%s': %u\n"),
239                      "GetDiskFreeSpace", szDrive, GetLastError ());
240
241       return -1;
242     }
243   return dwBlocks;
244 #else
245   struct statfs s;
246   if (0 != statfs (part, &s))
247     {
248       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "statfs", part);
249       return -1;
250     }
251   return s.f_bavail;
252 #endif
253 }
254
255 /**
256  * Test if fil is a directory.
257  *
258  * @return GNUNET_YES if yes, GNUNET_NO if not, GNUNET_SYSERR if it
259  *   does not exist
260  */
261 int
262 GNUNET_DISK_directory_test (const char *fil)
263 {
264   struct stat filestat;
265   int ret;
266
267   ret = STAT (fil, &filestat);
268   if (ret != 0)
269     {
270       if (errno != ENOENT)
271         {
272           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", fil);
273           return GNUNET_SYSERR;
274         }
275       return GNUNET_NO;
276     }
277   if (!S_ISDIR (filestat.st_mode))
278     return GNUNET_NO;
279   if (ACCESS (fil, R_OK | X_OK) < 0)
280     {
281       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "access", fil);
282       return GNUNET_SYSERR;
283     }
284   return GNUNET_YES;
285 }
286
287 /**
288  * Check that fil corresponds to a filename
289  * (of a file that exists and that is not a directory).
290  * @returns GNUNET_YES if yes, GNUNET_NO if not a file, GNUNET_SYSERR if something
291  * else (will print an error message in that case, too).
292  */
293 int
294 GNUNET_DISK_file_test (const char *fil)
295 {
296   struct stat filestat;
297   int ret;
298   char *rdir;
299
300   rdir = GNUNET_STRINGS_filename_expand (fil);
301   if (rdir == NULL)
302     return GNUNET_SYSERR;
303
304   ret = STAT (rdir, &filestat);
305   if (ret != 0)
306     {
307       if (errno != ENOENT)
308         {
309           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", rdir);
310           GNUNET_free (rdir);
311           return GNUNET_SYSERR;
312         }
313       GNUNET_free (rdir);
314       return GNUNET_NO;
315     }
316   if (!S_ISREG (filestat.st_mode))
317     {
318       GNUNET_free (rdir);
319       return GNUNET_NO;
320     }
321   if (ACCESS (rdir, R_OK) < 0)
322     {
323       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "access", rdir);
324       GNUNET_free (rdir);
325       return GNUNET_SYSERR;
326     }
327   GNUNET_free (rdir);
328   return GNUNET_YES;
329 }
330
331 /**
332  * Implementation of "mkdir -p"
333  * @param dir the directory to create
334  * @returns GNUNET_OK on success, GNUNET_SYSERR on failure
335  */
336 int
337 GNUNET_DISK_directory_create (const char *dir)
338 {
339   char *rdir;
340   int len;
341   int pos;
342   int ret = GNUNET_OK;
343
344   rdir = GNUNET_STRINGS_filename_expand (dir);
345   if (rdir == NULL)
346     return GNUNET_SYSERR;
347
348   len = strlen (rdir);
349 #ifndef MINGW
350   pos = 1;                      /* skip heading '/' */
351 #else
352   /* Local or Network path? */
353   if (strncmp (rdir, "\\\\", 2) == 0)
354     {
355       pos = 2;
356       while (rdir[pos])
357         {
358           if (rdir[pos] == '\\')
359             {
360               pos++;
361               break;
362             }
363           pos++;
364         }
365     }
366   else
367     {
368       pos = 3;                  /* strlen("C:\\") */
369     }
370 #endif
371   while (pos <= len)
372     {
373       if ((rdir[pos] == DIR_SEPARATOR) || (pos == len))
374         {
375           rdir[pos] = '\0';
376           ret = GNUNET_DISK_directory_test (rdir);
377           if (ret == GNUNET_SYSERR)
378             {
379               GNUNET_free (rdir);
380               return GNUNET_SYSERR;
381             }
382           if (ret == GNUNET_NO)
383             {
384 #ifndef MINGW
385               ret = mkdir (rdir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);  /* 755 */
386 #else
387               ret = mkdir (rdir);
388 #endif
389               if ((ret != 0) && (errno != EEXIST))
390                 {
391                   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "mkdir",
392                                             rdir);
393                   GNUNET_free (rdir);
394                   return GNUNET_SYSERR;
395                 }
396             }
397           rdir[pos] = DIR_SEPARATOR;
398         }
399       pos++;
400     }
401   GNUNET_free (rdir);
402   return GNUNET_OK;
403 }
404
405
406 /**
407  * Create the directory structure for storing
408  * a file.
409  *
410  * @param filename name of a file in the directory
411  * @returns GNUNET_OK on success,
412  *          GNUNET_SYSERR on failure,
413  *          GNUNET_NO if the directory
414  *          exists but is not writeable for us
415  */
416 int
417 GNUNET_DISK_directory_create_for_file (const char *dir)
418 {
419   char *rdir;
420   int len;
421   int ret;
422
423   rdir = GNUNET_STRINGS_filename_expand (dir);
424   if (rdir == NULL)
425     return GNUNET_SYSERR;
426   len = strlen (rdir);
427   while ((len > 0) && (rdir[len] != DIR_SEPARATOR))
428     len--;
429   rdir[len] = '\0';
430   ret = GNUNET_DISK_directory_create (rdir);
431   if ((ret == GNUNET_OK) && (0 != ACCESS (rdir, W_OK)))
432     ret = GNUNET_NO;
433   GNUNET_free (rdir);
434   return ret;
435 }
436
437 /**
438  * Read the contents of a binary file into a buffer.
439  * @param h handle to an open file
440  * @param result the buffer to write the result to
441  * @param len the maximum number of bytes to read
442  * @return the number of bytes read on success, GNUNET_SYSERR on failure
443  */
444 int
445 GNUNET_DISK_file_read (const struct GNUNET_IO_Handle *h, void *result, int len)
446 {
447   if (h == NULL)
448     {
449       errno = EINVAL;
450       return GNUNET_SYSERR;
451     }
452
453 #ifdef MINGW
454   DWORD bytesRead;
455
456   if (!ReadFile (h->h, result, len, &bytesRead, NULL))
457     {
458       SetErrnoFromWinError (GetLastError ());
459       return GNUNET_SYSERR;
460     }
461   return bytesRead;
462 #else
463   return read (h->fd, result, len);
464 #endif
465 }
466
467
468 /**
469  * Read the contents of a binary file into a buffer.
470  * @param fn file name
471  * @param result the buffer to write the result to
472  * @param len the maximum number of bytes to read
473  * @return number of bytes read, GNUNET_SYSERR on failure
474  */
475 int
476 GNUNET_DISK_fn_read (const char * const fn, void *result, int len)
477 {
478   struct GNUNET_IO_Handle *fh;
479   int ret;
480
481   fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_READ);
482   if (!fh)
483     return GNUNET_SYSERR;
484   ret = GNUNET_DISK_file_read (fh, result, len);
485   GNUNET_assert(GNUNET_OK == GNUNET_DISK_file_close(&fh));
486
487   return ret;
488 }
489
490
491 /**
492  * Write a buffer to a file.
493  * @param h handle to open file
494  * @param buffer the data to write
495  * @param n number of bytes to write
496  * @return number of bytes written on success, GNUNET_SYSERR on error
497  */
498 int
499 GNUNET_DISK_file_write (const struct GNUNET_IO_Handle *h, const void *buffer,
500     unsigned int n)
501 {
502   if (h == NULL)
503     {
504       errno = EINVAL;
505       return GNUNET_SYSERR;
506     }
507
508 #ifdef MINGW
509   DWORD bytesWritten;
510
511   if (!WriteFile (h->h, buffer, n, &bytesWritten, NULL))
512     {
513       SetErrnoFromWinError (GetLastError ());
514       return GNUNET_SYSERR;
515     }
516   return bytesWritten;
517 #else
518   return write (h->fd, buffer, n);
519 #endif
520 }
521
522 /**
523  * Write a buffer to a file.
524  * @param fn file name
525  * @param buffer the data to write
526  * @param n number of bytes to write
527  * @return GNUNET_OK on success, GNUNET_SYSERR on error
528  */
529 int
530 GNUNET_DISK_fn_write (const char * const fn, const void *buffer,
531     unsigned int n, int mode)
532 {
533   struct GNUNET_IO_Handle *fh;
534   int ret;
535
536   fh = GNUNET_DISK_file_open (fn, 
537                               GNUNET_DISK_OPEN_WRITE 
538                               | GNUNET_DISK_OPEN_TRUNCATE
539                               | GNUNET_DISK_OPEN_CREATE, mode);
540   if (!fh)
541     return GNUNET_SYSERR;
542   ret = (n == GNUNET_DISK_file_write (fh, buffer, n)) ? GNUNET_OK : GNUNET_SYSERR;
543   GNUNET_assert(GNUNET_OK == GNUNET_DISK_file_close(&fh));
544
545   return ret;
546 }
547
548 /**
549  * Scan a directory for files. The name of the directory
550  * must be expanded first (!).
551  * @param dirName the name of the directory
552  * @param callback the method to call for each file,
553  *        can be NULL, in that case, we only count
554  * @param data argument to pass to callback
555  * @return the number of files found, GNUNET_SYSERR on error or
556  *         ieration aborted by callback returning GNUNET_SYSERR
557  */
558 int
559 GNUNET_DISK_directory_scan (const char *dirName,
560                             GNUNET_FileNameCallback callback, void *data)
561 {
562   DIR *dinfo;
563   struct dirent *finfo;
564   struct stat istat;
565   int count = 0;
566   char *name;
567   char *dname;
568   unsigned int name_len;
569   unsigned int n_size;
570
571   GNUNET_assert (dirName != NULL);
572   dname = GNUNET_STRINGS_filename_expand (dirName);
573   while ((strlen (dname) > 0) && (dname[strlen (dname) - 1] == DIR_SEPARATOR))
574     dname[strlen (dname) - 1] = '\0';
575   if (0 != STAT (dname, &istat))
576     {
577       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", dname);
578       GNUNET_free (dname);
579       return GNUNET_SYSERR;
580     }
581   if (!S_ISDIR (istat.st_mode))
582     {
583       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
584                   _("Expected `%s' to be a directory!\n"), dirName);
585       GNUNET_free (dname);
586       return GNUNET_SYSERR;
587     }
588   errno = 0;
589   dinfo = OPENDIR (dname);
590   if ((errno == EACCES) || (dinfo == NULL))
591     {
592       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "opendir", dname);
593       if (dinfo != NULL)
594         closedir (dinfo);
595       GNUNET_free (dname);
596       return GNUNET_SYSERR;
597     }
598   name_len = 256;
599   n_size = strlen (dname) + name_len + 2;
600   name = GNUNET_malloc (n_size);
601   while ((finfo = readdir (dinfo)) != NULL)
602     {
603       if ((0 == strcmp (finfo->d_name, ".")) ||
604           (0 == strcmp (finfo->d_name, "..")))
605         continue;
606       if (callback != NULL)
607         {
608           if (name_len < strlen (finfo->d_name))
609             {
610               GNUNET_free (name);
611               name_len = strlen (finfo->d_name);
612               n_size = strlen (dname) + name_len + 2;
613               name = GNUNET_malloc (n_size);
614             }
615           /* dname can end in "/" only if dname == "/";
616              if dname does not end in "/", we need to add
617              a "/" (otherwise, we must not!) */
618           GNUNET_snprintf (name,
619                            n_size,
620                            "%s%s%s",
621                            dname,
622                            (strcmp (dname, DIR_SEPARATOR_STR) ==
623                             0) ? "" : DIR_SEPARATOR_STR, finfo->d_name);
624           if (GNUNET_OK != callback (data, name))
625             {
626               closedir (dinfo);
627               GNUNET_free (name);
628               GNUNET_free (dname);
629               return GNUNET_SYSERR;
630             }
631         }
632       count++;
633     }
634   closedir (dinfo);
635   GNUNET_free (name);
636   GNUNET_free (dname);
637   return count;
638 }
639
640
641 /**
642  * Opaque handle used for iterating over a directory.
643  */
644 struct GNUNET_DISK_DirectoryIterator
645 {
646   /**
647    * Our scheduler.
648    */
649   struct GNUNET_SCHEDULER_Handle *sched;
650
651   /**
652    * Function to call on directory entries.
653    */
654   GNUNET_DISK_DirectoryIteratorCallback callback;
655
656   /**
657    * Closure for callback.
658    */
659   void *callback_cls;
660
661   /**
662    * Reference to directory.
663    */
664   DIR *directory;
665
666   /**
667    * Directory name.
668    */
669   char *dirname;
670
671   /**
672    * Next filename to process.
673    */
674   char *next_name;
675
676   /**
677    * Our priority.
678    */
679   enum GNUNET_SCHEDULER_Priority priority;
680
681 };
682
683
684 /**
685  * Task used by the directory iterator.
686  */
687 static void
688 directory_iterator_task (void *cls,
689                          const struct GNUNET_SCHEDULER_TaskContext *tc)
690 {
691   struct GNUNET_DISK_DirectoryIterator *iter = cls;
692   char *name;
693
694   name = iter->next_name;
695   GNUNET_assert (name != NULL);
696   iter->next_name = NULL;
697   iter->callback (iter->callback_cls, iter, name, iter->dirname);
698   GNUNET_free (name);
699 }
700
701
702 /**
703  * This function must be called during the DiskIteratorCallback
704  * (exactly once) to schedule the task to process the next
705  * filename in the directory (if there is one).
706  *
707  * @param iter opaque handle for the iterator
708  * @param can set to GNUNET_YES to terminate the iteration early
709  * @return GNUNET_YES if iteration will continue,
710  *         GNUNET_NO if this was the last entry (and iteration is complete),
711  *         GNUNET_SYSERR if abort was YES
712  */
713 int
714 GNUNET_DISK_directory_iterator_next (struct GNUNET_DISK_DirectoryIterator
715                                      *iter, int can)
716 {
717   struct dirent *finfo;
718
719   GNUNET_assert (iter->next_name == NULL);
720   if (can == GNUNET_YES)
721     {
722       closedir (iter->directory);
723       GNUNET_free (iter->dirname);
724       GNUNET_free (iter);
725       return GNUNET_SYSERR;
726     }
727   while (NULL != (finfo = readdir (iter->directory)))
728     {
729       if ((0 == strcmp (finfo->d_name, ".")) ||
730           (0 == strcmp (finfo->d_name, "..")))
731         continue;
732       GNUNET_asprintf (&iter->next_name,
733                        "%s%s%s",
734                        iter->dirname, DIR_SEPARATOR_STR, finfo->d_name);
735       break;
736     }
737   if (finfo == NULL)
738     {
739       GNUNET_DISK_directory_iterator_next (iter, GNUNET_YES);
740       return GNUNET_NO;
741     }
742   GNUNET_SCHEDULER_add_after (iter->sched,
743                               GNUNET_YES,
744                               iter->priority,
745                               GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
746                               &directory_iterator_task, iter);
747   return GNUNET_YES;
748 }
749
750
751 /**
752  * Scan a directory for files using the scheduler to run a task for
753  * each entry.  The name of the directory must be expanded first (!).
754  * If a scheduler does not need to be used, GNUNET_DISK_directory_scan
755  * may provide a simpler API.
756  *
757  * @param sched scheduler to use
758  * @param prio priority to use
759  * @param dirName the name of the directory
760  * @param callback the method to call for each file
761  * @param callback_cls closure for callback
762  */
763 void
764 GNUNET_DISK_directory_iterator_start (struct GNUNET_SCHEDULER_Handle *sched,
765                                       enum GNUNET_SCHEDULER_Priority prio,
766                                       const char *dirName,
767                                       GNUNET_DISK_DirectoryIteratorCallback
768                                       callback, void *callback_cls)
769 {
770   struct GNUNET_DISK_DirectoryIterator *di;
771
772   di = GNUNET_malloc (sizeof (struct GNUNET_DISK_DirectoryIterator));
773   di->sched = sched;
774   di->callback = callback;
775   di->callback_cls = callback_cls;
776   di->directory = OPENDIR (dirName);
777   di->dirname = GNUNET_strdup (dirName);
778   di->priority = prio;
779   GNUNET_DISK_directory_iterator_next (di, GNUNET_NO);
780 }
781
782
783 static int
784 remove_helper (void *unused, const char *fn)
785 {
786   GNUNET_DISK_directory_remove (fn);
787   return GNUNET_OK;
788 }
789
790 /**
791  * Remove all files in a directory (rm -rf). Call with
792  * caution.
793  *
794  *
795  * @param fileName the file to remove
796  * @return GNUNET_OK on success, GNUNET_SYSERR on error
797  */
798 int
799 GNUNET_DISK_directory_remove (const char *fileName)
800 {
801   struct stat istat;
802
803   if (0 != LSTAT (fileName, &istat))
804     return GNUNET_NO;           /* file may not exist... */
805   if (UNLINK (fileName) == 0)
806     return GNUNET_OK;
807   if ((errno != EISDIR) &&
808       /* EISDIR is not sufficient in all cases, e.g.
809          sticky /tmp directory may result in EPERM on BSD.
810          So we also explicitly check "isDirectory" */
811       (GNUNET_YES != GNUNET_DISK_directory_test (fileName)))
812     {
813       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "rmdir", fileName);
814       return GNUNET_SYSERR;
815     }
816   if (GNUNET_SYSERR ==
817       GNUNET_DISK_directory_scan (fileName, remove_helper, NULL))
818     return GNUNET_SYSERR;
819   if (0 != RMDIR (fileName))
820     {
821       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "rmdir", fileName);
822       return GNUNET_SYSERR;
823     }
824   return GNUNET_OK;
825 }
826
827 #define COPY_BLK_SIZE 65536
828
829 /**
830  * Copy a file.
831  * @return GNUNET_OK on success, GNUNET_SYSERR on error
832  */
833 int
834 GNUNET_DISK_file_copy (const char *src, const char *dst)
835 {
836   char *buf;
837   unsigned long long pos;
838   unsigned long long size;
839   unsigned long long len;
840   struct GNUNET_IO_Handle *in, *out;
841
842   if (GNUNET_OK != GNUNET_DISK_file_size (src, &size, GNUNET_YES))
843     return GNUNET_SYSERR;
844   pos = 0;
845   in = GNUNET_DISK_file_open (src, GNUNET_DISK_OPEN_READ);
846   if (!in)
847     return GNUNET_SYSERR;
848   out = GNUNET_DISK_file_open (dst, GNUNET_DISK_OPEN_WRITE
849                                | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_FAILIFEXISTS,
850                                GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
851                                | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_GROUP_WRITE);
852   if (!out)
853     {
854       GNUNET_DISK_file_close (&in);
855       return GNUNET_SYSERR;
856     }
857   buf = GNUNET_malloc (COPY_BLK_SIZE);
858   while (pos < size)
859     {
860       len = COPY_BLK_SIZE;
861       if (len > size - pos)
862         len = size - pos;
863       if (len != GNUNET_DISK_file_read (in, buf, len))
864         goto FAIL;
865       if (len != GNUNET_DISK_file_write (out, buf, len))
866         goto FAIL;
867       pos += len;
868     }
869   GNUNET_free (buf);
870   GNUNET_DISK_file_close (&in);
871   GNUNET_DISK_file_close (&out);
872   return GNUNET_OK;
873 FAIL:
874   GNUNET_free (buf);
875   GNUNET_DISK_file_close (&in);
876   GNUNET_DISK_file_close (&out);
877   return GNUNET_SYSERR;
878 }
879
880
881 /**
882  * @brief Removes special characters as ':' from a filename.
883  * @param fn the filename to canonicalize
884  */
885 void
886 GNUNET_DISK_filename_canonicalize (char *fn)
887 {
888   char *idx;
889   char c;
890
891   idx = fn;
892   while (*idx)
893     {
894       c = *idx;
895
896       if (c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' ||
897           c == '"' || c == '<' || c == '>' || c == '|')
898         {
899           *idx = '_';
900         }
901
902       idx++;
903     }
904 }
905
906
907
908 /**
909  * @brief Change owner of a file
910  */
911 int
912 GNUNET_DISK_file_change_owner (const char *filename, const char *user)
913 {
914 #ifndef MINGW
915   struct passwd *pws;
916
917   pws = getpwnam (user);
918   if (pws == NULL)
919     {
920       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
921                   _("Cannot obtain information about user `%s': %s\n"),
922                   user, STRERROR (errno));
923       return GNUNET_SYSERR;
924     }
925   if (0 != chown (filename, pws->pw_uid, pws->pw_gid))
926     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "chown", filename);
927 #endif
928   return GNUNET_OK;
929 }
930
931
932 /**
933  * Lock a part of a file
934  * @param fh file handle
935  * @lockStart absolute position from where to lock
936  * @lockEnd absolute position until where to lock
937  * @return GNUNET_OK on success, GNUNET_SYSERR on error
938  */
939 int
940 GNUNET_DISK_file_lock(struct GNUNET_IO_Handle *fh, off_t lockStart,
941     off_t lockEnd)
942 {
943   if (fh == NULL)
944     {
945       errno = EINVAL;
946       return GNUNET_SYSERR;
947     }
948
949 #ifndef MINGW
950   struct flock fl;
951
952   memset(&fl, 0, sizeof(struct flock));
953   fl.l_type = F_WRLCK;
954   fl.l_whence = SEEK_SET;
955   fl.l_start = lockStart;
956   fl.l_len = lockEnd;
957
958   return fcntl(fh->fd, F_SETLK, &fl) != 0 ? GNUNET_SYSERR : GNUNET_OK;
959 #else
960   if (!LockFile(fh->h, 0, lockStart, 0, lockEnd))
961   {
962     SetErrnoFromWinError(GetLastError());
963     return GNUNET_SYSERR;
964   }
965
966   return GNUNET_OK;
967 #endif
968 }
969
970
971 /**
972  * Open a file
973  * @param fn file name to be opened
974  * @param flags opening flags, a combination of GNUNET_DISK_OPEN_xxx bit flags
975  * @param perm permissions for the newly created file
976  * @return IO handle on success, NULL on error
977  */
978 struct GNUNET_IO_Handle *
979 GNUNET_DISK_file_open (const char *fn, int flags, ...)
980 {
981   char *expfn;
982   struct GNUNET_IO_Handle *ret;
983 #ifdef MINGW
984   DWORD access;
985   DWORD disp;
986   HANDLE h;
987 #else
988   int oflags;
989   int mode;
990   int fd;
991 #endif
992
993   expfn = GNUNET_STRINGS_filename_expand (fn);
994
995 #ifndef MINGW
996   mode = 0;
997   oflags = 0;
998   if (GNUNET_DISK_OPEN_READWRITE == (flags & GNUNET_DISK_OPEN_READWRITE))
999     oflags = O_RDWR; /* note: O_RDWR is NOT always O_RDONLY | O_WRONLY */
1000   else if (flags & GNUNET_DISK_OPEN_READ)
1001     oflags = O_RDONLY;
1002   else if (flags & GNUNET_DISK_OPEN_WRITE)
1003     oflags = O_WRONLY;
1004   else
1005     {
1006       GNUNET_break (0);
1007       return NULL;
1008     }
1009   if (flags & GNUNET_DISK_OPEN_FAILIFEXISTS)
1010     oflags |= (O_CREAT & O_EXCL);
1011   if (flags & GNUNET_DISK_OPEN_TRUNCATE)
1012     oflags |= O_TRUNC;
1013   if (flags & GNUNET_DISK_OPEN_APPEND)
1014     oflags |= O_APPEND;
1015   if (flags & GNUNET_DISK_OPEN_CREATE)
1016     {
1017       int perm;
1018
1019       oflags |= O_CREAT;
1020
1021       va_list arg;
1022       va_start (arg, flags);
1023       perm = va_arg (arg, int);
1024       va_end (arg);
1025
1026       if (perm & GNUNET_DISK_PERM_USER_READ)
1027         mode |= S_IRUSR;
1028       if (perm & GNUNET_DISK_PERM_USER_WRITE)
1029         mode |= S_IWUSR;
1030       if (perm & GNUNET_DISK_PERM_USER_EXEC)
1031         mode |= S_IXUSR;
1032       if (perm & GNUNET_DISK_PERM_GROUP_READ)
1033         mode |= S_IRGRP;
1034       if (perm & GNUNET_DISK_PERM_GROUP_WRITE)
1035         mode |= S_IWGRP;
1036       if (perm & GNUNET_DISK_PERM_GROUP_EXEC)
1037         mode |= S_IXGRP;
1038       if (perm & GNUNET_DISK_PERM_OTHER_READ)
1039         mode |= S_IROTH;
1040       if (perm & GNUNET_DISK_PERM_OTHER_WRITE)
1041         mode |= S_IWOTH;
1042       if (perm & GNUNET_DISK_PERM_OTHER_EXEC)
1043         mode |= S_IXOTH;
1044     }
1045
1046   fd = open (expfn, oflags | O_LARGEFILE, mode);
1047   if (fd == -1)
1048   {
1049     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "open", fn);
1050     return NULL;
1051   }
1052 #else
1053   access = 0;
1054   disp = OPEN_ALWAYS;
1055
1056   if (flags & GNUNET_DISK_OPEN_READ)
1057     access = FILE_READ_DATA;
1058   if (flags & GNUNET_DISK_OPEN_WRITE)
1059     access = FILE_WRITE_DATA;
1060
1061   if (flags & GNUNET_DISK_OPEN_FAILIFEXISTS)
1062     disp = CREATE_NEW;
1063   if (flags & GNUNET_DISK_OPEN_TRUNCATE)
1064     disp = TRUNCATE_EXISTING;
1065   if (flags & GNUNET_DISK_OPEN_CREATE)
1066     disp |= OPEN_ALWAYS;
1067
1068   /* TODO: access priviledges? */
1069   h = CreateFile (expfn, access, FILE_SHARE_DELETE | FILE_SHARE_READ
1070       | FILE_SHARE_WRITE, NULL, disp, FILE_ATTRIBUTE_NORMAL, NULL);
1071   if (h == INVALID_HANDLE_VALUE)
1072   {
1073     SetErrnoFromWinError (GetLastError ());
1074     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "open", fn);
1075     return NULL;
1076   }
1077
1078   if (flags & GNUNET_DISK_OPEN_APPEND)
1079     if (SetFilePointer (h, 0, 0, FILE_END) == INVALID_SET_FILE_POINTER)
1080     {
1081       SetErrnoFromWinError (GetLastError ());
1082       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "SetFilePointer", fn);
1083       CloseHandle (h);
1084       return NULL;
1085     }
1086 #endif
1087
1088   ret = GNUNET_malloc(sizeof(struct GNUNET_IO_Handle));
1089 #ifdef MINGW
1090   ret->h = h;
1091 #else
1092   ret->fd = fd;
1093 #endif
1094
1095   return ret;
1096 }
1097
1098 /**
1099  * Close an open file
1100  * @param h file handle
1101  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1102  */
1103 int
1104 GNUNET_DISK_file_close (struct GNUNET_IO_Handle **h)
1105 {
1106   if (*h == NULL)
1107     {
1108       errno = EINVAL;
1109       return GNUNET_SYSERR;
1110     }
1111
1112 #if MINGW
1113   if (!CloseHandle ((*h)->h))
1114   {
1115     SetErrnoFromWinError (GetLastError ());
1116     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "close");
1117     return GNUNET_SYSERR;
1118   }
1119 #else
1120   if (close ((*h)->fd) != 0)
1121   {
1122     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "close");
1123     return GNUNET_SYSERR;
1124   }
1125 #endif
1126
1127   GNUNET_DISK_handle_invalidate (*h);
1128   free(*h);
1129   *h = NULL;
1130
1131   return GNUNET_OK;
1132 }
1133
1134 /**
1135  * Construct full path to a file inside of the private
1136  * directory used by GNUnet.  Also creates the corresponding
1137  * directory.  If the resulting name is supposed to be
1138  * a directory, end the last argument in '/' (or pass
1139  * DIR_SEPARATOR_STR as the last argument before NULL).
1140  *
1141  * @param serviceName name of the service
1142  * @param varargs is NULL-terminated list of
1143  *                path components to append to the
1144  *                private directory name.
1145  * @return the constructed filename
1146  */
1147 char *
1148 GNUNET_DISK_get_home_filename (struct GNUNET_CONFIGURATION_Handle *cfg,
1149                                const char *serviceName, ...)
1150 {
1151   const char *c;
1152   char *pfx;
1153   char *ret;
1154   va_list ap;
1155   unsigned int needed;
1156
1157   if (GNUNET_OK !=
1158       GNUNET_CONFIGURATION_get_value_filename (cfg,
1159                                                serviceName, "HOME", &pfx))
1160     return NULL;
1161   if (pfx == NULL)
1162     {
1163       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1164                   _("No `%s' specified for service `%s' in configuration.\n"),
1165                   "HOME", serviceName);
1166       return NULL;
1167     }
1168   needed = strlen (pfx) + 2;
1169   if ((pfx[strlen (pfx) - 1] != '/') && (pfx[strlen (pfx) - 1] != '\\'))
1170     needed++;
1171   va_start (ap, serviceName);
1172   while (1)
1173     {
1174       c = va_arg (ap, const char *);
1175       if (c == NULL)
1176         break;
1177       needed += strlen (c);
1178       if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\'))
1179         needed++;
1180     }
1181   va_end (ap);
1182   ret = GNUNET_malloc (needed);
1183   strcpy (ret, pfx);
1184   GNUNET_free (pfx);
1185   va_start (ap, serviceName);
1186   while (1)
1187     {
1188       c = va_arg (ap, const char *);
1189       if (c == NULL)
1190         break;
1191       if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\'))
1192         strcat (ret, DIR_SEPARATOR_STR);
1193       strcat (ret, c);
1194     }
1195   va_end (ap);
1196   if ((ret[strlen (ret) - 1] != '/') && (ret[strlen (ret) - 1] != '\\'))
1197     GNUNET_DISK_directory_create_for_file (ret);
1198   else
1199     GNUNET_DISK_directory_create (ret);
1200   return ret;
1201 }
1202
1203 /**
1204  * Map a file into memory
1205  * @param h open file handle
1206  * @param m handle to the new mapping
1207  * @param access access specification, GNUNET_DISK_MAP_xxx
1208  * @param len size of the mapping
1209  * @return pointer to the mapped memory region, NULL on failure
1210  */
1211 void *
1212 GNUNET_DISK_file_map (const struct GNUNET_IO_Handle *h, struct GNUNET_IO_Handle **m,
1213     int access, size_t len)
1214 {
1215   if (h == NULL)
1216     {
1217       errno = EINVAL;
1218       return NULL;
1219     }
1220
1221 #ifdef MINGW
1222   DWORD mapAccess, protect;
1223   void *ret;
1224
1225   if (access & GNUNET_DISK_MAP_READ && access & GNUNET_DISK_MAP_WRITE)
1226     {
1227       protect = PAGE_READWRITE;
1228       mapAccess = FILE_MAP_ALL_ACCESS;
1229     }
1230   else if (access & GNUNET_DISK_MAP_READ)
1231     {
1232       protect = PAGE_READONLY;
1233       mapAccess = FILE_MAP_READ;
1234     }
1235   else if (access & GNUNET_DISK_MAP_WRITE)
1236     {
1237       protect = PAGE_READWRITE;
1238       mapAccess = FILE_MAP_WRITE;
1239     }
1240   else
1241     {
1242       GNUNET_break (0);
1243       return NULL;
1244     }
1245
1246   *m = (struct GNUNET_IO_Handle *) GNUNET_malloc (sizeof (struct GNUNET_IO_Handle));
1247   (*m)->h = CreateFileMapping (h->h, NULL, protect, 0, 0, NULL);
1248   if ((*m)->h == INVALID_HANDLE_VALUE)
1249     {
1250       SetErrnoFromWinError (GetLastError ());
1251       GNUNET_free (*m);
1252       return NULL;
1253     }
1254
1255   ret = MapViewOfFile ((*m)->h, mapAccess, 0, 0, len);
1256   if (!ret)
1257     {
1258       SetErrnoFromWinError (GetLastError ());
1259       CloseHandle ((*m)->h);
1260       GNUNET_free (*m);
1261     }
1262
1263   return ret;
1264 #else
1265   int prot;
1266
1267   prot = 0;
1268   if (access & GNUNET_DISK_MAP_READ)
1269     prot = PROT_READ;
1270   if (access & GNUNET_DISK_MAP_WRITE)
1271     prot |= PROT_WRITE;
1272
1273   return mmap (NULL, len, prot, MAP_SHARED, h->fd, 0);
1274 #endif
1275 }
1276
1277 /**
1278  * Unmap a file
1279  * @param h mapping handle
1280  * @param addr pointer to the mapped memory region
1281  * @param len size of the mapping
1282  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1283  */
1284 int
1285 GNUNET_DISK_file_unmap (struct GNUNET_IO_Handle **h, void *addr, size_t len)
1286 {
1287 #ifdef MINGW
1288   int ret;
1289
1290   if (h == NULL || *h == NULL)
1291     {
1292       errno = EINVAL;
1293       return GNUNET_SYSERR;
1294     }
1295
1296   ret = UnmapViewOfFile (addr) ? GNUNET_OK : GNUNET_SYSERR;
1297   if (ret != GNUNET_OK)
1298     SetErrnoFromWinError (GetLastError ());
1299   if (!CloseHandle ((*h)->h) && ret == GNUNET_OK)
1300     {
1301       ret = GNUNET_SYSERR;
1302       SetErrnoFromWinError (GetLastError ());
1303     }
1304
1305   GNUNET_DISK_handle_invalidate (*h);
1306   GNUNET_free (*h);
1307   h = NULL;
1308
1309   return ret;
1310 #else
1311   int ret;
1312   ret = munmap (addr, len) != -1 ? GNUNET_OK : GNUNET_SYSERR;
1313   GNUNET_DISK_handle_invalidate (*h);
1314   return ret;
1315 #endif
1316 }
1317
1318 /**
1319  * Write file changes to disk
1320  * @param h handle to an open file
1321  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1322  */
1323 int
1324 GNUNET_DISK_file_sync (const struct GNUNET_IO_Handle *h)
1325 {
1326   if (h == NULL)
1327     {
1328       errno = EINVAL;
1329       return GNUNET_SYSERR;
1330     }
1331
1332 #ifdef MINGW
1333   int ret;
1334
1335   ret = FlushFileBuffers (h->h) ? GNUNET_OK : GNUNET_SYSERR;
1336   if (ret != GNUNET_OK)
1337     SetErrnoFromWinError (GetLastError ());
1338   return ret;
1339 #else
1340   return fsync (h->fd) == -1 ? GNUNET_SYSERR : GNUNET_OK;
1341 #endif
1342 }
1343
1344 /* end of disk.c */