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