leakfix
[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", expfn);
1043     GNUNET_free (expfn);
1044     return NULL;
1045   }
1046 #else
1047   access = 0;
1048   disp = OPEN_ALWAYS;
1049
1050   if (flags & GNUNET_DISK_OPEN_READ)
1051     access = FILE_READ_DATA;
1052   if (flags & GNUNET_DISK_OPEN_WRITE)
1053     access = FILE_WRITE_DATA;
1054
1055   if (flags & GNUNET_DISK_OPEN_FAILIFEXISTS)
1056     disp = CREATE_NEW;
1057   if (flags & GNUNET_DISK_OPEN_TRUNCATE)
1058     disp = TRUNCATE_EXISTING;
1059   if (flags & GNUNET_DISK_OPEN_CREATE)
1060     disp |= OPEN_ALWAYS;
1061
1062   /* TODO: access priviledges? */
1063   h = CreateFile (expfn, access, FILE_SHARE_DELETE | FILE_SHARE_READ
1064       | FILE_SHARE_WRITE, NULL, disp, FILE_ATTRIBUTE_NORMAL, NULL);
1065   if (h == INVALID_HANDLE_VALUE)
1066   {
1067     SetErrnoFromWinError (GetLastError ());
1068     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "open", expfn);
1069     GNUNET_free (expfn);
1070     return NULL;
1071   }
1072
1073   if (flags & GNUNET_DISK_OPEN_APPEND)
1074     if (SetFilePointer (h, 0, 0, FILE_END) == INVALID_SET_FILE_POINTER)
1075     {
1076       SetErrnoFromWinError (GetLastError ());
1077       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "SetFilePointer", expfn);
1078       CloseHandle (h);
1079       GNUNET_free (expfn);
1080       return NULL;
1081     }
1082 #endif
1083
1084   ret = GNUNET_malloc(sizeof(struct GNUNET_DISK_FileHandle));
1085 #ifdef MINGW
1086   ret->h = h;
1087 #else
1088   ret->fd = fd;
1089 #endif
1090   GNUNET_free (expfn);
1091   return ret;
1092 }
1093
1094 /**
1095  * Close an open file
1096  * @param h file handle
1097  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1098  */
1099 int
1100 GNUNET_DISK_file_close (struct GNUNET_DISK_FileHandle *h)
1101 {
1102   if (h == NULL)
1103     {
1104       errno = EINVAL;
1105       return GNUNET_SYSERR;
1106     }
1107
1108 #if MINGW
1109   if (!CloseHandle (h->h))
1110   {
1111     SetErrnoFromWinError (GetLastError ());
1112     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "close");
1113     GNUNET_free (h);
1114     return GNUNET_SYSERR;
1115   }
1116 #else
1117   if (close (h->fd) != 0)
1118   {
1119     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "close");
1120     GNUNET_free (h);
1121     return GNUNET_SYSERR;
1122   }
1123 #endif
1124   GNUNET_free (h);
1125   return GNUNET_OK;
1126 }
1127
1128 /**
1129  * Construct full path to a file inside of the private
1130  * directory used by GNUnet.  Also creates the corresponding
1131  * directory.  If the resulting name is supposed to be
1132  * a directory, end the last argument in '/' (or pass
1133  * DIR_SEPARATOR_STR as the last argument before NULL).
1134  *
1135  * @param serviceName name of the service
1136  * @param varargs is NULL-terminated list of
1137  *                path components to append to the
1138  *                private directory name.
1139  * @return the constructed filename
1140  */
1141 char *
1142 GNUNET_DISK_get_home_filename (struct GNUNET_CONFIGURATION_Handle *cfg,
1143                                const char *serviceName, ...)
1144 {
1145   const char *c;
1146   char *pfx;
1147   char *ret;
1148   va_list ap;
1149   unsigned int needed;
1150
1151   if (GNUNET_OK !=
1152       GNUNET_CONFIGURATION_get_value_filename (cfg,
1153                                                serviceName, "HOME", &pfx))
1154     return NULL;
1155   if (pfx == NULL)
1156     {
1157       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1158                   _("No `%s' specified for service `%s' in configuration.\n"),
1159                   "HOME", serviceName);
1160       return NULL;
1161     }
1162   needed = strlen (pfx) + 2;
1163   if ((pfx[strlen (pfx) - 1] != '/') && (pfx[strlen (pfx) - 1] != '\\'))
1164     needed++;
1165   va_start (ap, serviceName);
1166   while (1)
1167     {
1168       c = va_arg (ap, const char *);
1169       if (c == NULL)
1170         break;
1171       needed += strlen (c);
1172       if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\'))
1173         needed++;
1174     }
1175   va_end (ap);
1176   ret = GNUNET_malloc (needed);
1177   strcpy (ret, pfx);
1178   GNUNET_free (pfx);
1179   va_start (ap, serviceName);
1180   while (1)
1181     {
1182       c = va_arg (ap, const char *);
1183       if (c == NULL)
1184         break;
1185       if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\'))
1186         strcat (ret, DIR_SEPARATOR_STR);
1187       strcat (ret, c);
1188     }
1189   va_end (ap);
1190   if ((ret[strlen (ret) - 1] != '/') && (ret[strlen (ret) - 1] != '\\'))
1191     GNUNET_DISK_directory_create_for_file (ret);
1192   else
1193     GNUNET_DISK_directory_create (ret);
1194   return ret;
1195 }
1196
1197 struct GNUNET_DISK_MapHandle
1198 {
1199 #ifdef MINGW
1200   HANDLE h;
1201 #else
1202   void *addr;
1203   size_t len;
1204 #endif
1205 };
1206
1207
1208 /**
1209  * Map a file into memory
1210  * @param h open file handle
1211  * @param m handle to the new mapping
1212  * @param access access specification, GNUNET_DISK_MAP_xxx
1213  * @param len size of the mapping
1214  * @return pointer to the mapped memory region, NULL on failure
1215  */
1216 void *
1217 GNUNET_DISK_file_map (const struct GNUNET_DISK_FileHandle *h, struct GNUNET_DISK_MapHandle **m,
1218     int access, size_t len)
1219 {
1220   if (h == NULL)
1221     {
1222       errno = EINVAL;
1223       return NULL;
1224     }
1225
1226 #ifdef MINGW
1227   DWORD mapAccess, protect;
1228   void *ret;
1229
1230   if (access & GNUNET_DISK_MAP_READ && access & GNUNET_DISK_MAP_WRITE)
1231     {
1232       protect = PAGE_READWRITE;
1233       mapAccess = FILE_MAP_ALL_ACCESS;
1234     }
1235   else if (access & GNUNET_DISK_MAP_READ)
1236     {
1237       protect = PAGE_READONLY;
1238       mapAccess = FILE_MAP_READ;
1239     }
1240   else if (access & GNUNET_DISK_MAP_WRITE)
1241     {
1242       protect = PAGE_READWRITE;
1243       mapAccess = FILE_MAP_WRITE;
1244     }
1245   else
1246     {
1247       GNUNET_break (0);
1248       return NULL;
1249     }
1250
1251   *m = GNUNET_malloc (sizeof (struct GNUNET_DISK_MapHandle));
1252   (*m)->h = CreateFileMapping (h->h, NULL, protect, 0, 0, NULL);
1253   if ((*m)->h == INVALID_HANDLE_VALUE)
1254     {
1255       SetErrnoFromWinError (GetLastError ());
1256       GNUNET_free (*m);
1257       return NULL;
1258     }
1259
1260   ret = MapViewOfFile ((*m)->h, mapAccess, 0, 0, len);
1261   if (!ret)
1262     {
1263       SetErrnoFromWinError (GetLastError ());
1264       CloseHandle ((*m)->h);
1265       GNUNET_free (*m);
1266     }
1267
1268   return ret;
1269 #else
1270   int prot;
1271
1272   prot = 0;
1273   if (access & GNUNET_DISK_MAP_READ)
1274     prot = PROT_READ;
1275   if (access & GNUNET_DISK_MAP_WRITE)
1276     prot |= PROT_WRITE;
1277   *m = GNUNET_malloc (sizeof (struct GNUNET_DISK_MapHandle));
1278   (*m)->addr = mmap (NULL, len, prot, MAP_SHARED, h->fd, 0);
1279   (*m)->len = len;
1280   return (*m)->addr;
1281 #endif
1282 }
1283
1284 /**
1285  * Unmap a file
1286  * @param h mapping handle
1287  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1288  */
1289 int
1290 GNUNET_DISK_file_unmap (struct GNUNET_DISK_MapHandle *h)
1291 {
1292   int ret;
1293   if (h == NULL)
1294     {
1295       errno = EINVAL;
1296       return GNUNET_SYSERR;
1297     }
1298
1299 #ifdef MINGW
1300   ret = UnmapViewOfFile (addr) ? GNUNET_OK : GNUNET_SYSERR;
1301   if (ret != GNUNET_OK)
1302     SetErrnoFromWinError (GetLastError ());
1303   if (!CloseHandle (h->h) && (ret == GNUNET_OK))
1304     {
1305       ret = GNUNET_SYSERR;
1306       SetErrnoFromWinError (GetLastError ());
1307     }
1308 #else
1309   ret = munmap (h->addr, h->len) != -1 ? GNUNET_OK : GNUNET_SYSERR;
1310 #endif
1311   GNUNET_free (h);
1312   return ret;
1313 }
1314
1315
1316 /**
1317  * Write file changes to disk
1318  * @param h handle to an open file
1319  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1320  */
1321 int
1322 GNUNET_DISK_file_sync (const struct GNUNET_DISK_FileHandle *h)
1323 {
1324   if (h == NULL)
1325     {
1326       errno = EINVAL;
1327       return GNUNET_SYSERR;
1328     }
1329
1330 #ifdef MINGW
1331   int ret;
1332
1333   ret = FlushFileBuffers (h->h) ? GNUNET_OK : GNUNET_SYSERR;
1334   if (ret != GNUNET_OK)
1335     SetErrnoFromWinError (GetLastError ());
1336   return ret;
1337 #else
1338   return fsync (h->fd) == -1 ? GNUNET_SYSERR : GNUNET_OK;
1339 #endif
1340 }
1341
1342 /* end of disk.c */