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