-atoo
[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, GNUNET_DISK_OPEN_WRITE
537       | GNUNET_DISK_OPEN_CREATE, mode);
538   if (!fh)
539     return GNUNET_SYSERR;
540   ret = (n == GNUNET_DISK_file_write (fh, buffer, n)) ? GNUNET_OK : GNUNET_SYSERR;
541   GNUNET_assert(GNUNET_OK == GNUNET_DISK_file_close(&fh));
542
543   return ret;
544 }
545
546 /**
547  * Scan a directory for files. The name of the directory
548  * must be expanded first (!).
549  * @param dirName the name of the directory
550  * @param callback the method to call for each file,
551  *        can be NULL, in that case, we only count
552  * @param data argument to pass to callback
553  * @return the number of files found, GNUNET_SYSERR on error or
554  *         ieration aborted by callback returning GNUNET_SYSERR
555  */
556 int
557 GNUNET_DISK_directory_scan (const char *dirName,
558                             GNUNET_FileNameCallback callback, void *data)
559 {
560   DIR *dinfo;
561   struct dirent *finfo;
562   struct stat istat;
563   int count = 0;
564   char *name;
565   char *dname;
566   unsigned int name_len;
567   unsigned int n_size;
568
569   GNUNET_assert (dirName != NULL);
570   dname = GNUNET_STRINGS_filename_expand (dirName);
571   while ((strlen (dname) > 0) && (dname[strlen (dname) - 1] == DIR_SEPARATOR))
572     dname[strlen (dname) - 1] = '\0';
573   if (0 != STAT (dname, &istat))
574     {
575       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", dname);
576       GNUNET_free (dname);
577       return GNUNET_SYSERR;
578     }
579   if (!S_ISDIR (istat.st_mode))
580     {
581       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
582                   _("Expected `%s' to be a directory!\n"), dirName);
583       GNUNET_free (dname);
584       return GNUNET_SYSERR;
585     }
586   errno = 0;
587   dinfo = OPENDIR (dname);
588   if ((errno == EACCES) || (dinfo == NULL))
589     {
590       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "opendir", dname);
591       if (dinfo != NULL)
592         closedir (dinfo);
593       GNUNET_free (dname);
594       return GNUNET_SYSERR;
595     }
596   name_len = 256;
597   n_size = strlen (dname) + name_len + 2;
598   name = GNUNET_malloc (n_size);
599   while ((finfo = readdir (dinfo)) != NULL)
600     {
601       if ((0 == strcmp (finfo->d_name, ".")) ||
602           (0 == strcmp (finfo->d_name, "..")))
603         continue;
604       if (callback != NULL)
605         {
606           if (name_len < strlen (finfo->d_name))
607             {
608               GNUNET_free (name);
609               name_len = strlen (finfo->d_name);
610               n_size = strlen (dname) + name_len + 2;
611               name = GNUNET_malloc (n_size);
612             }
613           /* dname can end in "/" only if dname == "/";
614              if dname does not end in "/", we need to add
615              a "/" (otherwise, we must not!) */
616           GNUNET_snprintf (name,
617                            n_size,
618                            "%s%s%s",
619                            dname,
620                            (strcmp (dname, DIR_SEPARATOR_STR) ==
621                             0) ? "" : DIR_SEPARATOR_STR, finfo->d_name);
622           if (GNUNET_OK != callback (data, name))
623             {
624               closedir (dinfo);
625               GNUNET_free (name);
626               GNUNET_free (dname);
627               return GNUNET_SYSERR;
628             }
629         }
630       count++;
631     }
632   closedir (dinfo);
633   GNUNET_free (name);
634   GNUNET_free (dname);
635   return count;
636 }
637
638
639 /**
640  * Opaque handle used for iterating over a directory.
641  */
642 struct GNUNET_DISK_DirectoryIterator
643 {
644   /**
645    * Our scheduler.
646    */
647   struct GNUNET_SCHEDULER_Handle *sched;
648
649   /**
650    * Function to call on directory entries.
651    */
652   GNUNET_DISK_DirectoryIteratorCallback callback;
653
654   /**
655    * Closure for callback.
656    */
657   void *callback_cls;
658
659   /**
660    * Reference to directory.
661    */
662   DIR *directory;
663
664   /**
665    * Directory name.
666    */
667   char *dirname;
668
669   /**
670    * Next filename to process.
671    */
672   char *next_name;
673
674   /**
675    * Our priority.
676    */
677   enum GNUNET_SCHEDULER_Priority priority;
678
679 };
680
681
682 /**
683  * Task used by the directory iterator.
684  */
685 static void
686 directory_iterator_task (void *cls,
687                          const struct GNUNET_SCHEDULER_TaskContext *tc)
688 {
689   struct GNUNET_DISK_DirectoryIterator *iter = cls;
690   char *name;
691
692   name = iter->next_name;
693   GNUNET_assert (name != NULL);
694   iter->next_name = NULL;
695   iter->callback (iter->callback_cls, iter, name, iter->dirname);
696   GNUNET_free (name);
697 }
698
699
700 /**
701  * This function must be called during the DiskIteratorCallback
702  * (exactly once) to schedule the task to process the next
703  * filename in the directory (if there is one).
704  *
705  * @param iter opaque handle for the iterator
706  * @param can set to GNUNET_YES to terminate the iteration early
707  * @return GNUNET_YES if iteration will continue,
708  *         GNUNET_NO if this was the last entry (and iteration is complete),
709  *         GNUNET_SYSERR if abort was YES
710  */
711 int
712 GNUNET_DISK_directory_iterator_next (struct GNUNET_DISK_DirectoryIterator
713                                      *iter, int can)
714 {
715   struct dirent *finfo;
716
717   GNUNET_assert (iter->next_name == NULL);
718   if (can == GNUNET_YES)
719     {
720       closedir (iter->directory);
721       GNUNET_free (iter->dirname);
722       GNUNET_free (iter);
723       return GNUNET_SYSERR;
724     }
725   while (NULL != (finfo = readdir (iter->directory)))
726     {
727       if ((0 == strcmp (finfo->d_name, ".")) ||
728           (0 == strcmp (finfo->d_name, "..")))
729         continue;
730       GNUNET_asprintf (&iter->next_name,
731                        "%s%s%s",
732                        iter->dirname, DIR_SEPARATOR_STR, finfo->d_name);
733       break;
734     }
735   if (finfo == NULL)
736     {
737       GNUNET_DISK_directory_iterator_next (iter, GNUNET_YES);
738       return GNUNET_NO;
739     }
740   GNUNET_SCHEDULER_add_after (iter->sched,
741                               GNUNET_YES,
742                               iter->priority,
743                               GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
744                               &directory_iterator_task, iter);
745   return GNUNET_YES;
746 }
747
748
749 /**
750  * Scan a directory for files using the scheduler to run a task for
751  * each entry.  The name of the directory must be expanded first (!).
752  * If a scheduler does not need to be used, GNUNET_DISK_directory_scan
753  * may provide a simpler API.
754  *
755  * @param sched scheduler to use
756  * @param prio priority to use
757  * @param dirName the name of the directory
758  * @param callback the method to call for each file
759  * @param callback_cls closure for callback
760  */
761 void
762 GNUNET_DISK_directory_iterator_start (struct GNUNET_SCHEDULER_Handle *sched,
763                                       enum GNUNET_SCHEDULER_Priority prio,
764                                       const char *dirName,
765                                       GNUNET_DISK_DirectoryIteratorCallback
766                                       callback, void *callback_cls)
767 {
768   struct GNUNET_DISK_DirectoryIterator *di;
769
770   di = GNUNET_malloc (sizeof (struct GNUNET_DISK_DirectoryIterator));
771   di->sched = sched;
772   di->callback = callback;
773   di->callback_cls = callback_cls;
774   di->directory = OPENDIR (dirName);
775   di->dirname = GNUNET_strdup (dirName);
776   di->priority = prio;
777   GNUNET_DISK_directory_iterator_next (di, GNUNET_NO);
778 }
779
780
781 static int
782 remove_helper (void *unused, const char *fn)
783 {
784   GNUNET_DISK_directory_remove (fn);
785   return GNUNET_OK;
786 }
787
788 /**
789  * Remove all files in a directory (rm -rf). Call with
790  * caution.
791  *
792  *
793  * @param fileName the file to remove
794  * @return GNUNET_OK on success, GNUNET_SYSERR on error
795  */
796 int
797 GNUNET_DISK_directory_remove (const char *fileName)
798 {
799   struct stat istat;
800
801   if (0 != LSTAT (fileName, &istat))
802     return GNUNET_NO;           /* file may not exist... */
803   if (UNLINK (fileName) == 0)
804     return GNUNET_OK;
805   if ((errno != EISDIR) &&
806       /* EISDIR is not sufficient in all cases, e.g.
807          sticky /tmp directory may result in EPERM on BSD.
808          So we also explicitly check "isDirectory" */
809       (GNUNET_YES != GNUNET_DISK_directory_test (fileName)))
810     {
811       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "rmdir", fileName);
812       return GNUNET_SYSERR;
813     }
814   if (GNUNET_SYSERR ==
815       GNUNET_DISK_directory_scan (fileName, remove_helper, NULL))
816     return GNUNET_SYSERR;
817   if (0 != RMDIR (fileName))
818     {
819       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "rmdir", fileName);
820       return GNUNET_SYSERR;
821     }
822   return GNUNET_OK;
823 }
824
825 #define COPY_BLK_SIZE 65536
826
827 /**
828  * Copy a file.
829  * @return GNUNET_OK on success, GNUNET_SYSERR on error
830  */
831 int
832 GNUNET_DISK_file_copy (const char *src, const char *dst)
833 {
834   char *buf;
835   unsigned long long pos;
836   unsigned long long size;
837   unsigned long long len;
838   struct GNUNET_IO_Handle *in, *out;
839
840   if (GNUNET_OK != GNUNET_DISK_file_size (src, &size, GNUNET_YES))
841     return GNUNET_SYSERR;
842   pos = 0;
843   in = GNUNET_DISK_file_open (src, GNUNET_DISK_OPEN_READ);
844   if (!in)
845     return GNUNET_SYSERR;
846   out = GNUNET_DISK_file_open (dst, GNUNET_DISK_OPEN_WRITE
847       | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_FAILIFEXISTS,
848       GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
849           | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_GROUP_WRITE);
850   if (!out)
851     {
852       GNUNET_DISK_file_close (&in);
853       return GNUNET_SYSERR;
854     }
855   buf = GNUNET_malloc (COPY_BLK_SIZE);
856   while (pos < size)
857     {
858       len = COPY_BLK_SIZE;
859       if (len > size - pos)
860         len = size - pos;
861       if (len != GNUNET_DISK_file_read (in, buf, len))
862         goto FAIL;
863       if (len != GNUNET_DISK_file_write (out, buf, len))
864         goto FAIL;
865       pos += len;
866     }
867   GNUNET_free (buf);
868   GNUNET_DISK_file_close (&in);
869   GNUNET_DISK_file_close (&out);
870   return GNUNET_OK;
871 FAIL:
872   GNUNET_free (buf);
873   GNUNET_DISK_file_close (&in);
874   GNUNET_DISK_file_close (&out);
875   return GNUNET_SYSERR;
876 }
877
878
879 /**
880  * @brief Removes special characters as ':' from a filename.
881  * @param fn the filename to canonicalize
882  */
883 void
884 GNUNET_DISK_filename_canonicalize (char *fn)
885 {
886   char *idx;
887   char c;
888
889   idx = fn;
890   while (*idx)
891     {
892       c = *idx;
893
894       if (c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' ||
895           c == '"' || c == '<' || c == '>' || c == '|')
896         {
897           *idx = '_';
898         }
899
900       idx++;
901     }
902 }
903
904
905
906 /**
907  * @brief Change owner of a file
908  */
909 int
910 GNUNET_DISK_file_change_owner (const char *filename, const char *user)
911 {
912 #ifndef MINGW
913   struct passwd *pws;
914
915   pws = getpwnam (user);
916   if (pws == NULL)
917     {
918       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
919                   _("Cannot obtain information about user `%s': %s\n"),
920                   user, STRERROR (errno));
921       return GNUNET_SYSERR;
922     }
923   if (0 != chown (filename, pws->pw_uid, pws->pw_gid))
924     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "chown", filename);
925 #endif
926   return GNUNET_OK;
927 }
928
929
930 /**
931  * Lock a part of a file
932  * @param fh file handle
933  * @lockStart absolute position from where to lock
934  * @lockEnd absolute position until where to lock
935  * @return GNUNET_OK on success, GNUNET_SYSERR on error
936  */
937 int
938 GNUNET_DISK_file_lock(struct GNUNET_IO_Handle *fh, off_t lockStart,
939     off_t lockEnd)
940 {
941   if (fh == NULL)
942     {
943       errno = EINVAL;
944       return GNUNET_SYSERR;
945     }
946
947 #ifndef MINGW
948   struct flock fl;
949
950   memset(&fl, 0, sizeof(struct flock));
951   fl.l_type = F_WRLCK;
952   fl.l_whence = SEEK_SET;
953   fl.l_start = lockStart;
954   fl.l_len = lockEnd;
955
956   return fcntl(fh->fd, F_SETLK, &fl) != 0 ? GNUNET_SYSERR : GNUNET_OK;
957 #else
958   if (!LockFile(fh->h, 0, lockStart, 0, lockEnd))
959   {
960     SetErrnoFromWinError(GetLastError());
961     return GNUNET_SYSERR;
962   }
963
964   return GNUNET_OK;
965 #endif
966 }
967
968
969 /**
970  * Open a file
971  * @param fn file name to be opened
972  * @param flags opening flags, a combination of GNUNET_DISK_OPEN_xxx bit flags
973  * @param perm permissions for the newly created file
974  * @return IO handle on success, NULL on error
975  */
976 struct GNUNET_IO_Handle *
977 GNUNET_DISK_file_open (const char *fn, int flags, ...)
978 {
979   char *expfn;
980   struct GNUNET_IO_Handle *ret;
981 #ifdef MINGW
982   DWORD access, disp;
983   HANDLE h;
984 #else
985   int oflags, mode;
986   int fd;
987 #endif
988
989   expfn = GNUNET_STRINGS_filename_expand (fn);
990
991 #ifndef MINGW
992   oflags = 0;
993   if (flags & GNUNET_DISK_OPEN_READ)
994     oflags = O_RDONLY;
995   if (flags & GNUNET_DISK_OPEN_WRITE)
996     oflags |= O_WRONLY;
997   if (flags & GNUNET_DISK_OPEN_FAILIFEXISTS)
998     oflags |= (O_CREAT & O_EXCL);
999   if (flags & GNUNET_DISK_OPEN_TRUNCATE)
1000     oflags |= O_TRUNC;
1001   if (flags & GNUNET_DISK_OPEN_CREATE)
1002     {
1003       int perm;
1004
1005       oflags |= O_CREAT;
1006
1007       va_list arg;
1008       va_start (arg, flags);
1009       perm = va_arg (arg, int);
1010       va_end (arg);
1011
1012       mode = 0;
1013       if (perm & GNUNET_DISK_PERM_USER_READ)
1014         mode = S_IRUSR;
1015       if (perm & GNUNET_DISK_PERM_USER_WRITE)
1016         mode |= S_IWUSR;
1017       if (perm & GNUNET_DISK_PERM_USER_EXEC)
1018         mode |= S_IXUSR;
1019       if (perm & GNUNET_DISK_PERM_GROUP_READ)
1020         mode = S_IRGRP;
1021       if (perm & GNUNET_DISK_PERM_GROUP_WRITE)
1022         mode |= S_IWGRP;
1023       if (perm & GNUNET_DISK_PERM_GROUP_EXEC)
1024         mode |= S_IXGRP;
1025       if (perm & GNUNET_DISK_PERM_OTHER_READ)
1026         mode = S_IROTH;
1027       if (perm & GNUNET_DISK_PERM_OTHER_WRITE)
1028         mode |= S_IWOTH;
1029       if (perm & GNUNET_DISK_PERM_OTHER_EXEC)
1030         mode |= S_IXOTH;
1031     }
1032   if (flags & GNUNET_DISK_OPEN_APPEND)
1033     oflags = O_APPEND;
1034
1035   fd = open (expfn, oflags | O_LARGEFILE, mode);
1036   if (fd == -1)
1037   {
1038     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "open", fn);
1039     return NULL;
1040   }
1041 #else
1042   access = 0;
1043   disp = OPEN_ALWAYS;
1044
1045   if (flags & GNUNET_DISK_OPEN_READ)
1046     access = FILE_READ_DATA;
1047   if (flags & GNUNET_DISK_OPEN_WRITE)
1048     access = FILE_WRITE_DATA;
1049
1050   if (flags & GNUNET_DISK_OPEN_FAILIFEXISTS)
1051     disp = CREATE_NEW;
1052   if (flags & GNUNET_DISK_OPEN_TRUNCATE)
1053     disp = TRUNCATE_EXISTING;
1054   if (flags & GNUNET_DISK_OPEN_CREATE)
1055     disp |= OPEN_ALWAYS;
1056
1057   /* TODO: access priviledges? */
1058   h = CreateFile (expfn, access, FILE_SHARE_DELETE | FILE_SHARE_READ
1059       | FILE_SHARE_WRITE, NULL, disp, FILE_ATTRIBUTE_NORMAL, NULL);
1060   if (h == INVALID_HANDLE_VALUE)
1061   {
1062     SetErrnoFromWinError (GetLastError ());
1063     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "open", fn);
1064     return NULL;
1065   }
1066
1067   if (flags & GNUNET_DISK_OPEN_APPEND)
1068     if (SetFilePointer (h, 0, 0, FILE_END) == INVALID_SET_FILE_POINTER)
1069     {
1070       SetErrnoFromWinError (GetLastError ());
1071       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "SetFilePointer", fn);
1072       CloseHandle (h);
1073       return NULL;
1074     }
1075 #endif
1076
1077   ret = (struct GNUNET_IO_Handle *) GNUNET_malloc(sizeof(struct GNUNET_IO_Handle));
1078 #ifdef MINGW
1079   ret->h = h;
1080 #else
1081   ret->fd = fd;
1082 #endif
1083
1084   return ret;
1085 }
1086
1087 /**
1088  * Close an open file
1089  * @param h file handle
1090  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1091  */
1092 int
1093 GNUNET_DISK_file_close (struct GNUNET_IO_Handle **h)
1094 {
1095   if (*h == NULL)
1096     {
1097       errno = EINVAL;
1098       return GNUNET_SYSERR;
1099     }
1100
1101 #if MINGW
1102   if (!CloseHandle ((*h)->h))
1103   {
1104     SetErrnoFromWinError (GetLastError ());
1105     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "close");
1106     return GNUNET_SYSERR;
1107   }
1108 #else
1109   if (close ((*h)->fd) != 0)
1110   {
1111     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "close");
1112     return GNUNET_SYSERR;
1113   }
1114 #endif
1115
1116   GNUNET_DISK_handle_invalidate (*h);
1117   free(*h);
1118   *h = NULL;
1119
1120   return GNUNET_OK;
1121 }
1122
1123 /**
1124  * Construct full path to a file inside of the private
1125  * directory used by GNUnet.  Also creates the corresponding
1126  * directory.  If the resulting name is supposed to be
1127  * a directory, end the last argument in '/' (or pass
1128  * DIR_SEPARATOR_STR as the last argument before NULL).
1129  *
1130  * @param serviceName name of the service
1131  * @param varargs is NULL-terminated list of
1132  *                path components to append to the
1133  *                private directory name.
1134  * @return the constructed filename
1135  */
1136 char *
1137 GNUNET_DISK_get_home_filename (struct GNUNET_CONFIGURATION_Handle *cfg,
1138                                const char *serviceName, ...)
1139 {
1140   const char *c;
1141   char *pfx;
1142   char *ret;
1143   va_list ap;
1144   unsigned int needed;
1145
1146   if (GNUNET_OK !=
1147       GNUNET_CONFIGURATION_get_value_filename (cfg,
1148                                                serviceName, "HOME", &pfx))
1149     return NULL;
1150   if (pfx == NULL)
1151     {
1152       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1153                   _("No `%s' specified for service `%s' in configuration.\n"),
1154                   "HOME", serviceName);
1155       return NULL;
1156     }
1157   needed = strlen (pfx) + 2;
1158   if ((pfx[strlen (pfx) - 1] != '/') && (pfx[strlen (pfx) - 1] != '\\'))
1159     needed++;
1160   va_start (ap, serviceName);
1161   while (1)
1162     {
1163       c = va_arg (ap, const char *);
1164       if (c == NULL)
1165         break;
1166       needed += strlen (c);
1167       if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\'))
1168         needed++;
1169     }
1170   va_end (ap);
1171   ret = GNUNET_malloc (needed);
1172   strcpy (ret, pfx);
1173   GNUNET_free (pfx);
1174   va_start (ap, serviceName);
1175   while (1)
1176     {
1177       c = va_arg (ap, const char *);
1178       if (c == NULL)
1179         break;
1180       if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\'))
1181         strcat (ret, DIR_SEPARATOR_STR);
1182       strcat (ret, c);
1183     }
1184   va_end (ap);
1185   if ((ret[strlen (ret) - 1] != '/') && (ret[strlen (ret) - 1] != '\\'))
1186     GNUNET_DISK_directory_create_for_file (ret);
1187   else
1188     GNUNET_DISK_directory_create (ret);
1189   return ret;
1190 }
1191
1192 /**
1193  * Map a file into memory
1194  * @param h open file handle
1195  * @param m handle to the new mapping
1196  * @param access access specification, GNUNET_DISK_MAP_xxx
1197  * @param len size of the mapping
1198  * @return pointer to the mapped memory region, NULL on failure
1199  */
1200 void *
1201 GNUNET_DISK_file_map (const struct GNUNET_IO_Handle *h, struct GNUNET_IO_Handle **m,
1202     int access, size_t len)
1203 {
1204   if (h == NULL)
1205     {
1206       errno = EINVAL;
1207       return NULL;
1208     }
1209
1210 #ifdef MINGW
1211   DWORD mapAccess, protect;
1212   void *ret;
1213
1214   if (access & GNUNET_DISK_MAP_READ && access & GNUNET_DISK_MAP_WRITE)
1215     {
1216       protect = PAGE_READWRITE;
1217       mapAccess = FILE_MAP_ALL_ACCESS;
1218     }
1219   else if (access & GNUNET_DISK_MAP_READ)
1220     {
1221       protect = PAGE_READONLY;
1222       mapAccess = FILE_MAP_READ;
1223     }
1224   else if (access & GNUNET_DISK_MAP_WRITE)
1225     {
1226       protect = PAGE_READWRITE;
1227       mapAccess = FILE_MAP_WRITE;
1228     }
1229   else
1230     {
1231       GNUNET_break (0);
1232       return NULL;
1233     }
1234
1235   *m = (struct GNUNET_IO_Handle *) GNUNET_malloc (sizeof (struct GNUNET_IO_Handle));
1236   (*m)->h = CreateFileMapping (h->h, NULL, protect, 0, 0, NULL);
1237   if ((*m)->h == INVALID_HANDLE_VALUE)
1238     {
1239       SetErrnoFromWinError (GetLastError ());
1240       GNUNET_free (*m);
1241       return NULL;
1242     }
1243
1244   ret = MapViewOfFile ((*m)->h, mapAccess, 0, 0, len);
1245   if (!ret)
1246     {
1247       SetErrnoFromWinError (GetLastError ());
1248       CloseHandle ((*m)->h);
1249       GNUNET_free (*m);
1250     }
1251
1252   return ret;
1253 #else
1254   int prot;
1255
1256   prot = 0;
1257   if (access & GNUNET_DISK_MAP_READ)
1258     prot = PROT_READ;
1259   if (access & GNUNET_DISK_MAP_WRITE)
1260     prot |= PROT_WRITE;
1261
1262   return mmap (NULL, len, prot, MAP_SHARED, h->fd, 0);
1263 #endif
1264 }
1265
1266 /**
1267  * Unmap a file
1268  * @param h mapping handle
1269  * @param addr pointer to the mapped memory region
1270  * @param len size of the mapping
1271  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1272  */
1273 int
1274 GNUNET_DISK_file_unmap (struct GNUNET_IO_Handle **h, void *addr, size_t len)
1275 {
1276 #ifdef MINGW
1277   int ret;
1278
1279   if (h == NULL || *h == NULL)
1280     {
1281       errno = EINVAL;
1282       return GNUNET_SYSERR;
1283     }
1284
1285   ret = UnmapViewOfFile (addr) ? GNUNET_OK : GNUNET_SYSERR;
1286   if (ret != GNUNET_OK)
1287     SetErrnoFromWinError (GetLastError ());
1288   if (!CloseHandle ((*h)->h) && ret == GNUNET_OK)
1289     {
1290       ret = GNUNET_SYSERR;
1291       SetErrnoFromWinError (GetLastError ());
1292     }
1293
1294   GNUNET_DISK_handle_invalidate (*h);
1295   GNUNET_free (*h);
1296   h = NULL;
1297
1298   return ret;
1299 #else
1300   int ret;
1301   ret = munmap (addr, len) != -1 ? GNUNET_OK : GNUNET_SYSERR;
1302   GNUNET_DISK_handle_invalidate (*h);
1303   return ret;
1304 #endif
1305 }
1306
1307 /**
1308  * Write file changes to disk
1309  * @param h handle to an open file
1310  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1311  */
1312 int
1313 GNUNET_DISK_file_sync (const struct GNUNET_IO_Handle *h)
1314 {
1315   if (h == NULL)
1316     {
1317       errno = EINVAL;
1318       return GNUNET_SYSERR;
1319     }
1320
1321 #ifdef MINGW
1322   int ret;
1323
1324   ret = FlushFileBuffers (h->h) ? GNUNET_OK : GNUNET_SYSERR;
1325   if (ret != GNUNET_OK)
1326     SetErrnoFromWinError (GetLastError ());
1327   return ret;
1328 #else
1329   return fsync (h->fd) == -1 ? GNUNET_SYSERR : GNUNET_OK;
1330 #endif
1331 }
1332
1333 /* end of disk.c */