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