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