OR
[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 #include "disk.h"
35
36
37 /**
38  * Block size for IO for copying files.
39  */
40 #define COPY_BLK_SIZE 65536
41
42
43
44 #if defined(LINUX) || defined(CYGWIN)
45 #include <sys/vfs.h>
46 #else
47 #if defined(SOMEBSD) || defined(DARWIN)
48 #include <sys/param.h>
49 #include <sys/mount.h>
50 #else
51 #ifdef SOLARIS
52 #include <sys/types.h>
53 #include <sys/statvfs.h>
54 #else
55 #ifdef MINGW
56 #define         _IFMT           0170000 /* type of file */
57 #define         _IFLNK          0120000 /* symbolic link */
58 #define  S_ISLNK(m)     (((m)&_IFMT) == _IFLNK)
59 #else
60 #error PORT-ME: need to port statfs (how much space is left on the drive?)
61 #endif
62 #endif
63 #endif
64 #endif
65
66 #if !defined(SOMEBSD) && !defined(DARWIN) && !defined(WINDOWS)
67 #include <wordexp.h>
68 #endif
69 #if LINUX
70 #include <sys/statvfs.h>
71 #endif
72
73
74 /**
75  * Handle used to manage a pipe.
76  */
77 struct GNUNET_DISK_PipeHandle
78 {
79   /**
80    * File descriptors for the pipe.
81    */
82   struct GNUNET_DISK_FileHandle *fd[2];
83 };
84
85
86 /**
87  * Closure for the recursion to determine the file size
88  * of a directory.
89  */
90 struct GetFileSizeData
91 {
92   /**
93    * Set to the total file size.
94    */
95   uint64_t total;
96
97   /**
98    * GNUNET_YES if symbolic links should be included.
99    */
100   int include_sym_links;
101 };
102
103
104 /**
105  * Iterate over all files in the given directory and 
106  * accumulate their size.
107  *
108  * @param cls closure of type "struct GetFileSizeData"
109  * @param fn current filename we are looking at
110  * @return GNUNET_SYSERR on serious errors, otherwise GNUNET_OK
111  */
112 static int
113 getSizeRec (void *cls, const char *fn)
114 {
115   struct GetFileSizeData *gfsd = cls;
116 #ifdef HAVE_STAT64
117   struct stat64 buf;
118 #else
119   struct stat buf;
120 #endif
121
122 #ifdef HAVE_STAT64
123   if (0 != STAT64 (fn, &buf))
124     {
125       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat64", fn);
126       return GNUNET_SYSERR;
127     }
128 #else
129   if (0 != STAT (fn, &buf))
130     {
131       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", fn);
132       return GNUNET_SYSERR;
133     }
134 #endif
135   if ((!S_ISLNK (buf.st_mode)) || (gfsd->include_sym_links == GNUNET_YES))
136     gfsd->total += buf.st_size;
137   if ((S_ISDIR (buf.st_mode)) &&
138       (0 == ACCESS (fn, X_OK)) &&
139       ((!S_ISLNK (buf.st_mode)) || (gfsd->include_sym_links == GNUNET_YES)))
140     {
141       if (GNUNET_SYSERR == GNUNET_DISK_directory_scan (fn, &getSizeRec, gfsd))
142         return GNUNET_SYSERR;
143     }
144   return GNUNET_OK;
145 }
146
147
148 /**
149  * Checks whether a handle is invalid
150  *
151  * @param h handle to check
152  * @return GNUNET_YES if invalid, GNUNET_NO if valid
153  */
154 int
155 GNUNET_DISK_handle_invalid (const struct GNUNET_DISK_FileHandle *h)
156 {
157 #ifdef MINGW
158   return ((!h) || (h->h == INVALID_HANDLE_VALUE)) ? GNUNET_YES : GNUNET_NO;
159 #else
160   return ((!h) || (h->fd == -1)) ? GNUNET_YES : GNUNET_NO;
161 #endif
162 }
163
164
165 /**
166  * Move the read/write pointer in a file
167  *
168  * @param h handle of an open file
169  * @param offset position to move to
170  * @param whence specification to which position the offset parameter relates to
171  * @return the new position on success, GNUNET_SYSERR otherwise
172  */
173 off_t
174 GNUNET_DISK_file_seek (const struct GNUNET_DISK_FileHandle * h, off_t offset,
175                        enum GNUNET_DISK_Seek whence)
176 {
177   if (h == NULL)
178     {
179       errno = EINVAL;
180       return GNUNET_SYSERR;
181     }
182
183 #ifdef MINGW
184   DWORD ret;
185   static DWORD t[] = {[GNUNET_DISK_SEEK_SET] = FILE_BEGIN,
186     [GNUNET_DISK_SEEK_CUR] = FILE_CURRENT,[GNUNET_DISK_SEEK_END] = FILE_END
187   };
188
189   ret = SetFilePointer (h->h, offset, NULL, t[whence]);
190   if (ret == INVALID_SET_FILE_POINTER)
191     {
192       SetErrnoFromWinError (GetLastError ());
193       return GNUNET_SYSERR;
194     }
195   return ret;
196 #else
197   static int t[] = {[GNUNET_DISK_SEEK_SET] = SEEK_SET,
198     [GNUNET_DISK_SEEK_CUR] = SEEK_CUR,[GNUNET_DISK_SEEK_END] = SEEK_END
199   };
200
201   return lseek (h->fd, offset, t[whence]);
202 #endif
203 }
204
205
206 /**
207  * Get the size of the file (or directory) of the given file (in
208  * bytes).
209  *
210  * @param filename name of the file or directory
211  * @param size set to the size of the file (or,
212  *             in the case of directories, the sum
213  *             of all sizes of files in the directory)
214  * @param includeSymLinks should symbolic links be
215  *        included?
216  * @return GNUNET_SYSERR on error, GNUNET_OK on success
217  */
218 int
219 GNUNET_DISK_file_size (const char *filename,
220                        uint64_t * size, int includeSymLinks)
221 {
222   struct GetFileSizeData gfsd;
223   int ret;
224
225   GNUNET_assert (size != NULL);
226   gfsd.total = 0;
227   gfsd.include_sym_links = includeSymLinks;
228   ret = getSizeRec (&gfsd, filename);
229   *size = gfsd.total;
230   return ret;
231 }
232
233
234 /**
235  * Obtain some unique identifiers for the given file
236  * that can be used to identify it in the local system.
237  * This function is used between GNUnet processes to
238  * quickly check if two files with the same absolute path
239  * are actually identical.  The two processes represent
240  * the same peer but may communicate over the network
241  * (and the file may be on an NFS volume).  This function
242  * may not be supported on all operating systems.
243  *
244  * @param filename name of the file
245  * @param dev set to the device ID
246  * @param ino set to the inode ID
247  * @return GNUNET_OK on success
248  */
249 int
250 GNUNET_DISK_file_get_identifiers (const char *filename,
251                                   uint32_t * dev, uint64_t * ino)
252 {
253 #if LINUX
254   struct stat sbuf;
255   struct statvfs fbuf;
256
257   if ((0 == stat (filename, &sbuf)) && (0 == statvfs (filename, &fbuf)))
258     {
259       *dev = (uint32_t) fbuf.f_fsid;
260       *ino = (uint64_t) sbuf.st_ino;
261       return GNUNET_OK;
262     }
263 #endif
264   return GNUNET_SYSERR;
265 }
266
267
268 /**
269  * Create an (empty) temporary file on disk.
270  * 
271  * @param t component to use for the name;
272  *        does NOT contain "XXXXXX" or "/tmp/".
273  * @return NULL on error, otherwise name of fresh
274  *         file on disk in directory for temporary files
275  */
276 char *
277 GNUNET_DISK_mktemp (const char *t)
278 {
279   const char *tmpdir;
280   int fd;
281   char *tmpl;
282   char *fn;
283
284   tmpdir = getenv ("TMPDIR");
285   tmpdir = tmpdir ? tmpdir : "/tmp";
286
287   GNUNET_asprintf (&tmpl, "%s/%s%s", tmpdir, t, "XXXXXX");
288 #ifdef MINGW
289   fn = (char *) GNUNET_malloc (MAX_PATH + 1);
290   if (ERROR_SUCCESS != plibc_conv_to_win_path (tmpl, fn))
291     {
292       GNUNET_free (fn);
293       GNUNET_free (tmpl);
294       return NULL;
295     }
296   GNUNET_free (tmpl);
297 #else
298   fn = tmpl;
299 #endif
300   fd = mkstemp (fn);
301   if (fd == -1)
302     {
303       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "mkstemp", fn);
304       GNUNET_free (fn);
305       return NULL;
306     }
307   if (0 != CLOSE (fd))
308     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "close", fn);
309   return fn;
310 }
311
312
313 /**
314  * Get the number of blocks that are left on the partition that
315  * contains the given file (for normal users).
316  *
317  * @param part a file on the partition to check
318  * @return -1 on errors, otherwise the number of free blocks
319  */
320 long
321 GNUNET_DISK_get_blocks_available (const char *part)
322 {
323 #ifdef SOLARIS
324   struct statvfs buf;
325
326   if (0 != statvfs (part, &buf))
327     {
328       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "statfs", part);
329       return -1;
330     }
331   return buf.f_bavail;
332 #elif MINGW
333   DWORD dwDummy;
334   DWORD dwBlocks;
335   char szDrive[4];
336   char *path;
337
338   path = GNUNET_STRINGS_filename_expand (part);
339   memcpy (szDrive, path, 3);
340   GNUNET_free (path);
341   szDrive[3] = 0;
342   if (!GetDiskFreeSpace (szDrive, &dwDummy, &dwDummy, &dwBlocks, &dwDummy))
343     {
344       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
345                   _("`%s' failed for drive `%s': %u\n"),
346                   "GetDiskFreeSpace", szDrive, GetLastError ());
347
348       return -1;
349     }
350   return dwBlocks;
351 #else
352   struct statfs s;
353   if (0 != statfs (part, &s))
354     {
355       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "statfs", part);
356       return -1;
357     }
358   return s.f_bavail;
359 #endif
360 }
361
362
363 /**
364  * Test if "fil" is a directory.
365  * Will not print an error message if the directory
366  * does not exist.  Will log errors if GNUNET_SYSERR is
367  * returned (i.e., a file exists with the same name).
368  *
369  * @param fil filename to test
370  * @return GNUNET_YES if yes, GNUNET_NO if not, GNUNET_SYSERR if it
371  *   does not exist
372  */
373 int
374 GNUNET_DISK_directory_test (const char *fil)
375 {
376   struct stat filestat;
377   int ret;
378
379   ret = STAT (fil, &filestat);
380   if (ret != 0)
381     {
382       if (errno != ENOENT)
383         {
384           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", fil);
385           return GNUNET_SYSERR;
386         }
387       return GNUNET_NO;
388     }
389   if (!S_ISDIR (filestat.st_mode))
390     return GNUNET_NO;
391   if (ACCESS (fil, R_OK | X_OK) < 0)
392     {
393       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "access", fil);
394       return GNUNET_SYSERR;
395     }
396   return GNUNET_YES;
397 }
398
399 /**
400  * Check that fil corresponds to a filename
401  * (of a file that exists and that is not a directory).
402  *
403  * @param fil filename to check
404  * @return GNUNET_YES if yes, GNUNET_NO if not a file, GNUNET_SYSERR if something
405  * else (will print an error message in that case, too).
406  */
407 int
408 GNUNET_DISK_file_test (const char *fil)
409 {
410   struct stat filestat;
411   int ret;
412   char *rdir;
413
414   rdir = GNUNET_STRINGS_filename_expand (fil);
415   if (rdir == NULL)
416     return GNUNET_SYSERR;
417
418   ret = STAT (rdir, &filestat);
419   if (ret != 0)
420     {
421       if (errno != ENOENT)
422         {
423           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", rdir);
424           GNUNET_free (rdir);
425           return GNUNET_SYSERR;
426         }
427       GNUNET_free (rdir);
428       return GNUNET_NO;
429     }
430   if (!S_ISREG (filestat.st_mode))
431     {
432       GNUNET_free (rdir);
433       return GNUNET_NO;
434     }
435   if (ACCESS (rdir, R_OK) < 0)
436     {
437       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "access", rdir);
438       GNUNET_free (rdir);
439       return GNUNET_SYSERR;
440     }
441   GNUNET_free (rdir);
442   return GNUNET_YES;
443 }
444
445
446 /**
447  * Implementation of "mkdir -p"
448  * @param dir the directory to create
449  * @returns GNUNET_OK on success, GNUNET_SYSERR on failure
450  */
451 int
452 GNUNET_DISK_directory_create (const char *dir)
453 {
454   char *rdir;
455   int len;
456   int pos;
457   int ret = GNUNET_OK;
458
459   rdir = GNUNET_STRINGS_filename_expand (dir);
460   if (rdir == NULL)
461     return GNUNET_SYSERR;
462
463   len = strlen (rdir);
464 #ifndef MINGW
465   pos = 1;                      /* skip heading '/' */
466 #else
467   /* Local or Network path? */
468   if (strncmp (rdir, "\\\\", 2) == 0)
469     {
470       pos = 2;
471       while (rdir[pos])
472         {
473           if (rdir[pos] == '\\')
474             {
475               pos++;
476               break;
477             }
478           pos++;
479         }
480     }
481   else
482     {
483       pos = 3;                  /* strlen("C:\\") */
484     }
485 #endif
486   while (pos <= len)
487     {
488       if ((rdir[pos] == DIR_SEPARATOR) || (pos == len))
489         {
490           rdir[pos] = '\0';
491           ret = GNUNET_DISK_directory_test (rdir);
492           if (ret == GNUNET_SYSERR)
493             {
494               GNUNET_free (rdir);
495               return GNUNET_SYSERR;
496             }
497           if (ret == GNUNET_NO)
498             {
499 #ifndef MINGW
500               ret = mkdir (rdir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);  /* 755 */
501 #else
502               ret = mkdir (rdir);
503 #endif
504               if ((ret != 0) && (errno != EEXIST))
505                 {
506                   GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "mkdir",
507                                             rdir);
508                   GNUNET_free (rdir);
509                   return GNUNET_SYSERR;
510                 }
511             }
512           rdir[pos] = DIR_SEPARATOR;
513         }
514       pos++;
515     }
516   GNUNET_free (rdir);
517   return GNUNET_OK;
518 }
519
520
521 /**
522  * Create the directory structure for storing
523  * a file.
524  *
525  * @param filename name of a file in the directory
526  * @returns GNUNET_OK on success,
527  *          GNUNET_SYSERR on failure,
528  *          GNUNET_NO if the directory
529  *          exists but is not writeable for us
530  */
531 int
532 GNUNET_DISK_directory_create_for_file (const char *filename)
533 {
534   char *rdir;
535   int len;
536   int ret;
537
538   rdir = GNUNET_STRINGS_filename_expand (filename);
539   if (rdir == NULL)
540     return GNUNET_SYSERR;
541   len = strlen (rdir);
542   while ((len > 0) && (rdir[len] != DIR_SEPARATOR))
543     len--;
544   rdir[len] = '\0';
545   ret = GNUNET_DISK_directory_create (rdir);
546   if ((ret == GNUNET_OK) && (0 != ACCESS (rdir, W_OK)))
547     ret = GNUNET_NO;
548   GNUNET_free (rdir);
549   return ret;
550 }
551
552
553 /**
554  * Read the contents of a binary file into a buffer.
555  * @param h handle to an open file
556  * @param result the buffer to write the result to
557  * @param len the maximum number of bytes to read
558  * @return the number of bytes read on success, GNUNET_SYSERR on failure
559  */
560 ssize_t
561 GNUNET_DISK_file_read (const struct GNUNET_DISK_FileHandle * h, void *result,
562                        size_t len)
563 {
564   if (h == NULL)
565     {
566       errno = EINVAL;
567       return GNUNET_SYSERR;
568     }
569
570 #ifdef MINGW
571   DWORD bytesRead;
572
573   if (!ReadFile (h->h, result, len, &bytesRead, NULL))
574     {
575       SetErrnoFromWinError (GetLastError ());
576       return GNUNET_SYSERR;
577     }
578   return bytesRead;
579 #else
580   return read (h->fd, result, len);
581 #endif
582 }
583
584
585 /**
586  * Read the contents of a binary file into a buffer.
587  *
588  * @param fn file name
589  * @param result the buffer to write the result to
590  * @param len the maximum number of bytes to read
591  * @return number of bytes read, GNUNET_SYSERR on failure
592  */
593 ssize_t
594 GNUNET_DISK_fn_read (const char *fn, void *result, size_t len)
595 {
596   struct GNUNET_DISK_FileHandle *fh;
597   ssize_t ret;
598
599   fh = GNUNET_DISK_file_open (fn, GNUNET_DISK_OPEN_READ,
600                               GNUNET_DISK_PERM_NONE);
601   if (!fh)
602     return GNUNET_SYSERR;
603   ret = GNUNET_DISK_file_read (fh, result, len);
604   GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fh));
605
606   return ret;
607 }
608
609
610 /**
611  * Write a buffer to a file.
612  * @param h handle to open file
613  * @param buffer the data to write
614  * @param n number of bytes to write
615  * @return number of bytes written on success, GNUNET_SYSERR on error
616  */
617 ssize_t
618 GNUNET_DISK_file_write (const struct GNUNET_DISK_FileHandle * h,
619                         const void *buffer, size_t n)
620 {
621   if (h == NULL)
622     {
623       errno = EINVAL;
624       return GNUNET_SYSERR;
625     }
626
627 #ifdef MINGW
628   DWORD bytesWritten;
629
630   if (!WriteFile (h->h, buffer, n, &bytesWritten, NULL))
631     {
632       SetErrnoFromWinError (GetLastError ());
633       return GNUNET_SYSERR;
634     }
635   return bytesWritten;
636 #else
637   return write (h->fd, buffer, n);
638 #endif
639 }
640
641 /**
642  * Write a buffer to a file.  If the file is longer than the
643  * number of bytes that will be written, it will be truncated.
644  *
645  * @param fn file name
646  * @param buffer the data to write
647  * @param n number of bytes to write
648  * @param mode file permissions 
649  * @return number of bytes written on success, GNUNET_SYSERR on error
650  */
651 ssize_t
652 GNUNET_DISK_fn_write (const char *fn, const void *buffer,
653                       size_t n, enum GNUNET_DISK_AccessPermissions mode)
654 {
655   struct GNUNET_DISK_FileHandle *fh;
656   ssize_t ret;
657
658   fh = GNUNET_DISK_file_open (fn,
659                               GNUNET_DISK_OPEN_WRITE
660                               | GNUNET_DISK_OPEN_TRUNCATE
661                               | GNUNET_DISK_OPEN_CREATE, mode);
662   if (!fh)
663     return GNUNET_SYSERR;
664   ret = GNUNET_DISK_file_write (fh, buffer, n);
665   GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fh));
666   return ret;
667 }
668
669 /**
670  * Scan a directory for files. 
671  *
672  * @param dirName the name of the directory
673  * @param callback the method to call for each file,
674  *        can be NULL, in that case, we only count
675  * @param callback_cls closure for callback
676  * @return the number of files found, GNUNET_SYSERR on error or
677  *         ieration aborted by callback returning GNUNET_SYSERR
678  */
679 int
680 GNUNET_DISK_directory_scan (const char *dirName,
681                             GNUNET_FileNameCallback callback,
682                             void *callback_cls)
683 {
684   DIR *dinfo;
685   struct dirent *finfo;
686   struct stat istat;
687   int count = 0;
688   char *name;
689   char *dname;
690   unsigned int name_len;
691   unsigned int n_size;
692
693   GNUNET_assert (dirName != NULL);
694   dname = GNUNET_STRINGS_filename_expand (dirName);
695   while ((strlen (dname) > 0) && (dname[strlen (dname) - 1] == DIR_SEPARATOR))
696     dname[strlen (dname) - 1] = '\0';
697   if (0 != STAT (dname, &istat))
698     {
699       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", dname);
700       GNUNET_free (dname);
701       return GNUNET_SYSERR;
702     }
703   if (!S_ISDIR (istat.st_mode))
704     {
705       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
706                   _("Expected `%s' to be a directory!\n"), dirName);
707       GNUNET_free (dname);
708       return GNUNET_SYSERR;
709     }
710   errno = 0;
711   dinfo = OPENDIR (dname);
712   if ((errno == EACCES) || (dinfo == NULL))
713     {
714       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "opendir", dname);
715       if (dinfo != NULL)
716         closedir (dinfo);
717       GNUNET_free (dname);
718       return GNUNET_SYSERR;
719     }
720   name_len = 256;
721   n_size = strlen (dname) + name_len + 2;
722   name = GNUNET_malloc (n_size);
723   while ((finfo = readdir (dinfo)) != NULL)
724     {
725       if ((0 == strcmp (finfo->d_name, ".")) ||
726           (0 == strcmp (finfo->d_name, "..")))
727         continue;
728       if (callback != NULL)
729         {
730           if (name_len < strlen (finfo->d_name))
731             {
732               GNUNET_free (name);
733               name_len = strlen (finfo->d_name);
734               n_size = strlen (dname) + name_len + 2;
735               name = GNUNET_malloc (n_size);
736             }
737           /* dname can end in "/" only if dname == "/";
738              if dname does not end in "/", we need to add
739              a "/" (otherwise, we must not!) */
740           GNUNET_snprintf (name,
741                            n_size,
742                            "%s%s%s",
743                            dname,
744                            (strcmp (dname, DIR_SEPARATOR_STR) ==
745                             0) ? "" : DIR_SEPARATOR_STR, finfo->d_name);
746           if (GNUNET_OK != callback (callback_cls, name))
747             {
748               closedir (dinfo);
749               GNUNET_free (name);
750               GNUNET_free (dname);
751               return GNUNET_SYSERR;
752             }
753         }
754       count++;
755     }
756   closedir (dinfo);
757   GNUNET_free (name);
758   GNUNET_free (dname);
759   return count;
760 }
761
762
763 /**
764  * Opaque handle used for iterating over a directory.
765  */
766 struct GNUNET_DISK_DirectoryIterator
767 {
768   /**
769    * Our scheduler.
770    */
771   struct GNUNET_SCHEDULER_Handle *sched;
772
773   /**
774    * Function to call on directory entries.
775    */
776   GNUNET_DISK_DirectoryIteratorCallback callback;
777
778   /**
779    * Closure for callback.
780    */
781   void *callback_cls;
782
783   /**
784    * Reference to directory.
785    */
786   DIR *directory;
787
788   /**
789    * Directory name.
790    */
791   char *dirname;
792
793   /**
794    * Next filename to process.
795    */
796   char *next_name;
797
798   /**
799    * Our priority.
800    */
801   enum GNUNET_SCHEDULER_Priority priority;
802
803 };
804
805
806 /**
807  * Task used by the directory iterator.
808  */
809 static void
810 directory_iterator_task (void *cls,
811                          const struct GNUNET_SCHEDULER_TaskContext *tc)
812 {
813   struct GNUNET_DISK_DirectoryIterator *iter = cls;
814   char *name;
815
816   name = iter->next_name;
817   GNUNET_assert (name != NULL);
818   iter->next_name = NULL;
819   iter->callback (iter->callback_cls, iter, name, iter->dirname);
820   GNUNET_free (name);
821 }
822
823
824 /**
825  * This function must be called during the DiskIteratorCallback
826  * (exactly once) to schedule the task to process the next
827  * filename in the directory (if there is one).
828  *
829  * @param iter opaque handle for the iterator
830  * @param can set to GNUNET_YES to terminate the iteration early
831  * @return GNUNET_YES if iteration will continue,
832  *         GNUNET_NO if this was the last entry (and iteration is complete),
833  *         GNUNET_SYSERR if abort was YES
834  */
835 int
836 GNUNET_DISK_directory_iterator_next (struct GNUNET_DISK_DirectoryIterator
837                                      *iter, int can)
838 {
839   struct dirent *finfo;
840
841   GNUNET_assert (iter->next_name == NULL);
842   if (can == GNUNET_YES)
843     {
844       closedir (iter->directory);
845       GNUNET_free (iter->dirname);
846       GNUNET_free (iter);
847       return GNUNET_SYSERR;
848     }
849   while (NULL != (finfo = readdir (iter->directory)))
850     {
851       if ((0 == strcmp (finfo->d_name, ".")) ||
852           (0 == strcmp (finfo->d_name, "..")))
853         continue;
854       GNUNET_asprintf (&iter->next_name,
855                        "%s%s%s",
856                        iter->dirname, DIR_SEPARATOR_STR, finfo->d_name);
857       break;
858     }
859   if (finfo == NULL)
860     {
861       GNUNET_DISK_directory_iterator_next (iter, GNUNET_YES);
862       return GNUNET_NO;
863     }
864   GNUNET_SCHEDULER_add_with_priority (iter->sched,
865                                       iter->priority,
866                                       &directory_iterator_task, iter);
867   return GNUNET_YES;
868 }
869
870
871 /**
872  * Scan a directory for files using the scheduler to run a task for
873  * each entry.  The name of the directory must be expanded first (!).
874  * If a scheduler does not need to be used, GNUNET_DISK_directory_scan
875  * may provide a simpler API.
876  *
877  * @param sched scheduler to use
878  * @param prio priority to use
879  * @param dirName the name of the directory
880  * @param callback the method to call for each file
881  * @param callback_cls closure for callback
882  */
883 void
884 GNUNET_DISK_directory_iterator_start (struct GNUNET_SCHEDULER_Handle *sched,
885                                       enum GNUNET_SCHEDULER_Priority prio,
886                                       const char *dirName,
887                                       GNUNET_DISK_DirectoryIteratorCallback
888                                       callback, void *callback_cls)
889 {
890   struct GNUNET_DISK_DirectoryIterator *di;
891
892   di = GNUNET_malloc (sizeof (struct GNUNET_DISK_DirectoryIterator));
893   di->sched = sched;
894   di->callback = callback;
895   di->callback_cls = callback_cls;
896   di->directory = OPENDIR (dirName);
897   di->dirname = GNUNET_strdup (dirName);
898   di->priority = prio;
899   GNUNET_DISK_directory_iterator_next (di, GNUNET_NO);
900 }
901
902
903 /**
904  * Function that removes the given directory by calling
905  * "GNUNET_DISK_directory_remove".
906  *
907  * @param unused not used
908  * @param fn directory to remove
909  * @return GNUNET_OK
910  */
911 static int
912 remove_helper (void *unused, const char *fn)
913 {
914   (void) GNUNET_DISK_directory_remove (fn);
915   return GNUNET_OK;
916 }
917
918
919 /**
920  * Remove all files in a directory (rm -rf). Call with
921  * caution.
922  *
923  *
924  * @param fileName the file to remove
925  * @return GNUNET_OK on success, GNUNET_SYSERR on error
926  */
927 int
928 GNUNET_DISK_directory_remove (const char *fileName)
929 {
930   struct stat istat;
931
932   if (0 != LSTAT (fileName, &istat))
933     return GNUNET_NO;           /* file may not exist... */
934   if (UNLINK (fileName) == 0)
935     return GNUNET_OK;
936   if ((errno != EISDIR) &&
937       /* EISDIR is not sufficient in all cases, e.g.
938          sticky /tmp directory may result in EPERM on BSD.
939          So we also explicitly check "isDirectory" */
940       (GNUNET_YES != GNUNET_DISK_directory_test (fileName)))
941     {
942       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "rmdir", fileName);
943       return GNUNET_SYSERR;
944     }
945   if (GNUNET_SYSERR ==
946       GNUNET_DISK_directory_scan (fileName, &remove_helper, NULL))
947     return GNUNET_SYSERR;
948   if (0 != RMDIR (fileName))
949     {
950       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "rmdir", fileName);
951       return GNUNET_SYSERR;
952     }
953   return GNUNET_OK;
954 }
955
956
957 /**
958  * Copy a file.
959  *
960  * @param src file to copy
961  * @param dst destination file name
962  * @return GNUNET_OK on success, GNUNET_SYSERR on error
963  */
964 int
965 GNUNET_DISK_file_copy (const char *src, const char *dst)
966 {
967   char *buf;
968   uint64_t pos;
969   uint64_t size;
970   size_t len;
971   struct GNUNET_DISK_FileHandle *in;
972   struct GNUNET_DISK_FileHandle *out;
973
974   if (GNUNET_OK != GNUNET_DISK_file_size (src, &size, GNUNET_YES))
975     return GNUNET_SYSERR;
976   pos = 0;
977   in = GNUNET_DISK_file_open (src, GNUNET_DISK_OPEN_READ,
978                               GNUNET_DISK_PERM_NONE);
979   if (!in)
980     return GNUNET_SYSERR;
981   out = GNUNET_DISK_file_open (dst, GNUNET_DISK_OPEN_WRITE
982                                | GNUNET_DISK_OPEN_CREATE |
983                                GNUNET_DISK_OPEN_FAILIFEXISTS,
984                                GNUNET_DISK_PERM_USER_READ |
985                                GNUNET_DISK_PERM_USER_WRITE |
986                                GNUNET_DISK_PERM_GROUP_READ |
987                                GNUNET_DISK_PERM_GROUP_WRITE);
988   if (!out)
989     {
990       GNUNET_DISK_file_close (in);
991       return GNUNET_SYSERR;
992     }
993   buf = GNUNET_malloc (COPY_BLK_SIZE);
994   while (pos < size)
995     {
996       len = COPY_BLK_SIZE;
997       if (len > size - pos)
998         len = size - pos;
999       if (len != GNUNET_DISK_file_read (in, buf, len))
1000         goto FAIL;
1001       if (len != GNUNET_DISK_file_write (out, buf, len))
1002         goto FAIL;
1003       pos += len;
1004     }
1005   GNUNET_free (buf);
1006   GNUNET_DISK_file_close (in);
1007   GNUNET_DISK_file_close (out);
1008   return GNUNET_OK;
1009 FAIL:
1010   GNUNET_free (buf);
1011   GNUNET_DISK_file_close (in);
1012   GNUNET_DISK_file_close (out);
1013   return GNUNET_SYSERR;
1014 }
1015
1016
1017 /**
1018  * @brief Removes special characters as ':' from a filename.
1019  * @param fn the filename to canonicalize
1020  */
1021 void
1022 GNUNET_DISK_filename_canonicalize (char *fn)
1023 {
1024   char *idx;
1025   char c;
1026
1027   idx = fn;
1028   while (*idx)
1029     {
1030       c = *idx;
1031
1032       if (c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' ||
1033           c == '"' || c == '<' || c == '>' || c == '|')
1034         {
1035           *idx = '_';
1036         }
1037
1038       idx++;
1039     }
1040 }
1041
1042
1043
1044 /**
1045  * @brief Change owner of a file
1046  *
1047  * @param filename name of file to change the owner of
1048  * @param user name of the new owner
1049  * @return GNUNET_OK on success, GNUNET_SYSERR on failure
1050  */
1051 int
1052 GNUNET_DISK_file_change_owner (const char *filename, const char *user)
1053 {
1054 #ifndef MINGW
1055   struct passwd *pws;
1056
1057   pws = getpwnam (user);
1058   if (pws == NULL)
1059     {
1060       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1061                   _("Cannot obtain information about user `%s': %s\n"),
1062                   user, STRERROR (errno));
1063       return GNUNET_SYSERR;
1064     }
1065   if (0 != chown (filename, pws->pw_uid, pws->pw_gid))
1066     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "chown", filename);
1067 #endif
1068   return GNUNET_OK;
1069 }
1070
1071
1072 /**
1073  * Lock a part of a file
1074  * @param fh file handle
1075  * @param lockStart absolute position from where to lock
1076  * @param lockEnd absolute position until where to lock
1077  * @param excl GNUNET_YES for an exclusive lock
1078  * @return GNUNET_OK on success, GNUNET_SYSERR on error
1079  */
1080 int
1081 GNUNET_DISK_file_lock (struct GNUNET_DISK_FileHandle *fh, off_t lockStart,
1082                        off_t lockEnd, int excl)
1083 {
1084   if (fh == NULL)
1085     {
1086       errno = EINVAL;
1087       return GNUNET_SYSERR;
1088     }
1089
1090 #ifndef MINGW
1091   struct flock fl;
1092
1093   memset (&fl, 0, sizeof (struct flock));
1094   fl.l_type = excl ? F_WRLCK : F_RDLCK;
1095   fl.l_whence = SEEK_SET;
1096   fl.l_start = lockStart;
1097   fl.l_len = lockEnd;
1098
1099   return fcntl (fh->fd, F_SETLK, &fl) != 0 ? GNUNET_SYSERR : GNUNET_OK;
1100 #else
1101   OVERLAPPED o;
1102
1103   memset (&o, 0, sizeof (OVERLAPPED));
1104   o.Offset = lockStart;
1105
1106   if (!LockFileEx (fh->h, (excl ? LOCKFILE_EXCLUSIVE_LOCK : 0)
1107                    | LOCKFILE_FAIL_IMMEDIATELY, 0, lockEnd - lockStart, 0,
1108                    &o))
1109     {
1110       SetErrnoFromWinError (GetLastError ());
1111       return GNUNET_SYSERR;
1112     }
1113
1114   return GNUNET_OK;
1115 #endif
1116 }
1117
1118
1119 /**
1120  * Unlock a part of a file
1121  * @param fh file handle
1122  * @param unlockStart absolute position from where to unlock
1123  * @param unlockEnd absolute position until where to unlock
1124  * @return GNUNET_OK on success, GNUNET_SYSERR on error
1125  */
1126 int
1127 GNUNET_DISK_file_unlock (struct GNUNET_DISK_FileHandle *fh, off_t unlockStart,
1128                          off_t unlockEnd)
1129 {
1130   if (fh == NULL)
1131     {
1132       errno = EINVAL;
1133       return GNUNET_SYSERR;
1134     }
1135
1136 #ifndef MINGW
1137   struct flock fl;
1138
1139   memset (&fl, 0, sizeof (struct flock));
1140   fl.l_type = F_UNLCK;
1141   fl.l_whence = SEEK_SET;
1142   fl.l_start = unlockStart;
1143   fl.l_len = unlockEnd;
1144
1145   return fcntl (fh->fd, F_SETLK, &fl) != 0 ? GNUNET_SYSERR : GNUNET_OK;
1146 #else
1147   OVERLAPPED o;
1148
1149   memset (&o, 0, sizeof (OVERLAPPED));
1150   o.Offset = unlockStart;
1151
1152   if (!UnlockFileEx (fh->h, 0, unlockEnd - unlockStart, 0, &o))
1153     {
1154       SetErrnoFromWinError (GetLastError ());
1155       return GNUNET_SYSERR;
1156     }
1157
1158   return GNUNET_OK;
1159 #endif
1160 }
1161
1162
1163 /**
1164  * Open a file.  Note that the access permissions will only be
1165  * used if a new file is created and if the underlying operating
1166  * system supports the given permissions.
1167  *
1168  * @param fn file name to be opened
1169  * @param flags opening flags, a combination of GNUNET_DISK_OPEN_xxx bit flags
1170  * @param perm permissions for the newly created file, use
1171  *             GNUNET_DISK_PERM_USER_NONE if a file could not be created by this
1172  *             call (because of flags)
1173  * @return IO handle on success, NULL on error
1174  */
1175 struct GNUNET_DISK_FileHandle *
1176 GNUNET_DISK_file_open (const char *fn,
1177                        enum GNUNET_DISK_OpenFlags flags,
1178                        enum GNUNET_DISK_AccessPermissions perm)
1179 {
1180   char *expfn;
1181   struct GNUNET_DISK_FileHandle *ret;
1182 #ifdef MINGW
1183   DWORD access;
1184   DWORD disp;
1185   HANDLE h;
1186 #else
1187   int oflags;
1188   int mode;
1189   int fd;
1190 #endif
1191
1192   expfn = GNUNET_STRINGS_filename_expand (fn);
1193   if (NULL == expfn)
1194     return NULL;
1195
1196 #ifndef MINGW
1197   mode = 0;
1198   if (GNUNET_DISK_OPEN_READWRITE == (flags & GNUNET_DISK_OPEN_READWRITE))
1199     oflags = O_RDWR;            /* note: O_RDWR is NOT always O_RDONLY | O_WRONLY */
1200   else if (flags & GNUNET_DISK_OPEN_READ)
1201     oflags = O_RDONLY;
1202   else if (flags & GNUNET_DISK_OPEN_WRITE)
1203     oflags = O_WRONLY;
1204   else
1205     {
1206       GNUNET_break (0);
1207       GNUNET_free (expfn);
1208       return NULL;
1209     }
1210   if (flags & GNUNET_DISK_OPEN_FAILIFEXISTS)
1211     oflags |= (O_CREAT | O_EXCL);
1212   if (flags & GNUNET_DISK_OPEN_TRUNCATE)
1213     oflags |= O_TRUNC;
1214   if (flags & GNUNET_DISK_OPEN_APPEND)
1215     oflags |= O_APPEND;
1216   if (flags & GNUNET_DISK_OPEN_CREATE)
1217     {
1218       oflags |= O_CREAT;
1219       if (perm & GNUNET_DISK_PERM_USER_READ)
1220         mode |= S_IRUSR;
1221       if (perm & GNUNET_DISK_PERM_USER_WRITE)
1222         mode |= S_IWUSR;
1223       if (perm & GNUNET_DISK_PERM_USER_EXEC)
1224         mode |= S_IXUSR;
1225       if (perm & GNUNET_DISK_PERM_GROUP_READ)
1226         mode |= S_IRGRP;
1227       if (perm & GNUNET_DISK_PERM_GROUP_WRITE)
1228         mode |= S_IWGRP;
1229       if (perm & GNUNET_DISK_PERM_GROUP_EXEC)
1230         mode |= S_IXGRP;
1231       if (perm & GNUNET_DISK_PERM_OTHER_READ)
1232         mode |= S_IROTH;
1233       if (perm & GNUNET_DISK_PERM_OTHER_WRITE)
1234         mode |= S_IWOTH;
1235       if (perm & GNUNET_DISK_PERM_OTHER_EXEC)
1236         mode |= S_IXOTH;
1237     }
1238
1239   fd = open (expfn, oflags | O_LARGEFILE, mode);
1240   if (fd == -1)
1241     {
1242       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "open", expfn);
1243       GNUNET_free (expfn);
1244       return NULL;
1245     }
1246 #else
1247   access = 0;
1248   disp = OPEN_ALWAYS;
1249
1250   if (GNUNET_DISK_OPEN_READWRITE == (flags & GNUNET_DISK_OPEN_READWRITE))
1251     access = FILE_READ_DATA | FILE_WRITE_DATA;
1252   else if (flags & GNUNET_DISK_OPEN_READ)
1253     access = FILE_READ_DATA;
1254   else if (flags & GNUNET_DISK_OPEN_WRITE)
1255     access = FILE_WRITE_DATA;
1256
1257   if (flags & GNUNET_DISK_OPEN_FAILIFEXISTS)
1258     {
1259       disp = CREATE_NEW;
1260     }
1261   else if (flags & GNUNET_DISK_OPEN_CREATE)
1262     {
1263       if (flags & GNUNET_DISK_OPEN_TRUNCATE)
1264         disp = CREATE_ALWAYS;
1265       else
1266         disp = OPEN_ALWAYS;
1267     }
1268   else if (flags & GNUNET_DISK_OPEN_TRUNCATE)
1269     {
1270       disp = TRUNCATE_EXISTING;
1271     }
1272   else
1273     {
1274       disp = OPEN_EXISTING;
1275     }
1276
1277   /* TODO: access priviledges? */
1278   h = CreateFile (expfn, access, FILE_SHARE_DELETE | FILE_SHARE_READ
1279                   | FILE_SHARE_WRITE, NULL, disp, FILE_ATTRIBUTE_NORMAL,
1280                   NULL);
1281   if (h == INVALID_HANDLE_VALUE)
1282     {
1283       SetErrnoFromWinError (GetLastError ());
1284       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "open", expfn);
1285       GNUNET_free (expfn);
1286       return NULL;
1287     }
1288
1289   if (flags & GNUNET_DISK_OPEN_APPEND)
1290     if (SetFilePointer (h, 0, 0, FILE_END) == INVALID_SET_FILE_POINTER)
1291       {
1292         SetErrnoFromWinError (GetLastError ());
1293         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "SetFilePointer",
1294                                   expfn);
1295         CloseHandle (h);
1296         GNUNET_free (expfn);
1297         return NULL;
1298       }
1299 #endif
1300
1301   ret = GNUNET_malloc (sizeof (struct GNUNET_DISK_FileHandle));
1302 #ifdef MINGW
1303   ret->h = h;
1304 #else
1305   ret->fd = fd;
1306 #endif
1307   GNUNET_free (expfn);
1308   return ret;
1309 }
1310
1311
1312 /**
1313  * Close an open file
1314  * @param h file handle
1315  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1316  */
1317 int
1318 GNUNET_DISK_file_close (struct GNUNET_DISK_FileHandle *h)
1319 {
1320   if (h == NULL)
1321     {
1322       errno = EINVAL;
1323       return GNUNET_SYSERR;
1324     }
1325
1326 #if MINGW
1327   if (!CloseHandle (h->h))
1328     {
1329       SetErrnoFromWinError (GetLastError ());
1330       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "close");
1331       GNUNET_free (h);
1332       return GNUNET_SYSERR;
1333     }
1334 #else
1335   if (close (h->fd) != 0)
1336     {
1337       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "close");
1338       GNUNET_free (h);
1339       return GNUNET_SYSERR;
1340     }
1341 #endif
1342   GNUNET_free (h);
1343   return GNUNET_OK;
1344 }
1345
1346
1347 /**
1348  * Construct full path to a file inside of the private
1349  * directory used by GNUnet.  Also creates the corresponding
1350  * directory.  If the resulting name is supposed to be
1351  * a directory, end the last argument in '/' (or pass
1352  * DIR_SEPARATOR_STR as the last argument before NULL).
1353  *
1354  * @param cfg configuration to use (determines HOME)
1355  * @param serviceName name of the service
1356  * @param ... is NULL-terminated list of
1357  *                path components to append to the
1358  *                private directory name.
1359  * @return the constructed filename
1360  */
1361 char *
1362 GNUNET_DISK_get_home_filename (const struct GNUNET_CONFIGURATION_Handle *cfg,
1363                                const char *serviceName, ...)
1364 {
1365   const char *c;
1366   char *pfx;
1367   char *ret;
1368   va_list ap;
1369   unsigned int needed;
1370
1371   if (GNUNET_OK !=
1372       GNUNET_CONFIGURATION_get_value_filename (cfg,
1373                                                serviceName, "HOME", &pfx))
1374     return NULL;
1375   if (pfx == NULL)
1376     {
1377       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1378                   _("No `%s' specified for service `%s' in configuration.\n"),
1379                   "HOME", serviceName);
1380       return NULL;
1381     }
1382   needed = strlen (pfx) + 2;
1383   if ((pfx[strlen (pfx) - 1] != '/') && (pfx[strlen (pfx) - 1] != '\\'))
1384     needed++;
1385   va_start (ap, serviceName);
1386   while (1)
1387     {
1388       c = va_arg (ap, const char *);
1389       if (c == NULL)
1390         break;
1391       needed += strlen (c);
1392       if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\'))
1393         needed++;
1394     }
1395   va_end (ap);
1396   ret = GNUNET_malloc (needed);
1397   strcpy (ret, pfx);
1398   GNUNET_free (pfx);
1399   va_start (ap, serviceName);
1400   while (1)
1401     {
1402       c = va_arg (ap, const char *);
1403       if (c == NULL)
1404         break;
1405       if ((c[strlen (c) - 1] != '/') && (c[strlen (c) - 1] != '\\'))
1406         strcat (ret, DIR_SEPARATOR_STR);
1407       strcat (ret, c);
1408     }
1409   va_end (ap);
1410   if ((ret[strlen (ret) - 1] != '/') && (ret[strlen (ret) - 1] != '\\'))
1411     GNUNET_DISK_directory_create_for_file (ret);
1412   else
1413     GNUNET_DISK_directory_create (ret);
1414   return ret;
1415 }
1416
1417
1418 /**
1419  * Handle for a memory-mapping operation.
1420  */
1421 struct GNUNET_DISK_MapHandle
1422 {
1423 #ifdef MINGW
1424   /**
1425    * Underlying OS handle.
1426    */
1427   HANDLE h;
1428 #else
1429   /**
1430    * Address where the map is in memory.
1431    */
1432   void *addr;
1433
1434   /**
1435    * Number of bytes mapped.
1436    */
1437   size_t len;
1438 #endif
1439 };
1440
1441
1442 #ifndef MAP_FAILED
1443 #define MAP_FAILED ((void *) -1)
1444 #endif
1445
1446 /**
1447  * Map a file into memory
1448  *
1449  * @param h open file handle
1450  * @param m handle to the new mapping
1451  * @param access access specification, GNUNET_DISK_MAP_TYPE_xxx
1452  * @param len size of the mapping
1453  * @return pointer to the mapped memory region, NULL on failure
1454  */
1455 void *
1456 GNUNET_DISK_file_map (const struct GNUNET_DISK_FileHandle *h,
1457                       struct GNUNET_DISK_MapHandle **m,
1458                       enum GNUNET_DISK_MapType access, size_t len)
1459 {
1460   if (h == NULL)
1461     {
1462       errno = EINVAL;
1463       return NULL;
1464     }
1465
1466 #ifdef MINGW
1467   DWORD mapAccess, protect;
1468   void *ret;
1469
1470   if ((access & GNUNET_DISK_MAP_TYPE_READ) &&
1471       (access & GNUNET_DISK_MAP_TYPE_WRITE))
1472     {
1473       protect = PAGE_READWRITE;
1474       mapAccess = FILE_MAP_ALL_ACCESS;
1475     }
1476   else if (access & GNUNET_DISK_MAP_TYPE_READ)
1477     {
1478       protect = PAGE_READONLY;
1479       mapAccess = FILE_MAP_READ;
1480     }
1481   else if (access & GNUNET_DISK_MAP_TYPE_WRITE)
1482     {
1483       protect = PAGE_READWRITE;
1484       mapAccess = FILE_MAP_WRITE;
1485     }
1486   else
1487     {
1488       GNUNET_break (0);
1489       return NULL;
1490     }
1491
1492   *m = GNUNET_malloc (sizeof (struct GNUNET_DISK_MapHandle));
1493   (*m)->h = CreateFileMapping (h->h, NULL, protect, 0, 0, NULL);
1494   if ((*m)->h == INVALID_HANDLE_VALUE)
1495     {
1496       SetErrnoFromWinError (GetLastError ());
1497       GNUNET_free (*m);
1498       return NULL;
1499     }
1500
1501   ret = MapViewOfFile ((*m)->h, mapAccess, 0, 0, len);
1502   if (!ret)
1503     {
1504       SetErrnoFromWinError (GetLastError ());
1505       CloseHandle ((*m)->h);
1506       GNUNET_free (*m);
1507     }
1508
1509   return ret;
1510 #else
1511   int prot;
1512   int ec;
1513
1514   prot = 0;
1515   if (access & GNUNET_DISK_MAP_TYPE_READ)
1516     prot = PROT_READ;
1517   if (access & GNUNET_DISK_MAP_TYPE_WRITE)
1518     prot |= PROT_WRITE;
1519   *m = GNUNET_malloc (sizeof (struct GNUNET_DISK_MapHandle));
1520   (*m)->addr = mmap (NULL, len, prot, MAP_SHARED, h->fd, 0);
1521   GNUNET_assert (NULL != (*m)->addr);
1522   if (MAP_FAILED == (*m)->addr)
1523     {    
1524       ec = errno;
1525       GNUNET_free (*m);
1526       errno = ec;
1527       return NULL;
1528     }
1529   (*m)->len = len;
1530   return (*m)->addr;
1531 #endif
1532 }
1533
1534 /**
1535  * Unmap a file
1536  * @param h mapping handle
1537  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1538  */
1539 int
1540 GNUNET_DISK_file_unmap (struct GNUNET_DISK_MapHandle *h)
1541 {
1542   int ret;
1543   if (h == NULL)
1544     {
1545       errno = EINVAL;
1546       return GNUNET_SYSERR;
1547     }
1548
1549 #ifdef MINGW
1550   ret = UnmapViewOfFile (h->h) ? GNUNET_OK : GNUNET_SYSERR;
1551   if (ret != GNUNET_OK)
1552     SetErrnoFromWinError (GetLastError ());
1553   if (!CloseHandle (h->h) && (ret == GNUNET_OK))
1554     {
1555       ret = GNUNET_SYSERR;
1556       SetErrnoFromWinError (GetLastError ());
1557     }
1558 #else
1559   ret = munmap (h->addr, h->len) != -1 ? GNUNET_OK : GNUNET_SYSERR;
1560 #endif
1561   GNUNET_free (h);
1562   return ret;
1563 }
1564
1565
1566 /**
1567  * Write file changes to disk
1568  * @param h handle to an open file
1569  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1570  */
1571 int
1572 GNUNET_DISK_file_sync (const struct GNUNET_DISK_FileHandle *h)
1573 {
1574   if (h == NULL)
1575     {
1576       errno = EINVAL;
1577       return GNUNET_SYSERR;
1578     }
1579
1580 #ifdef MINGW
1581   int ret;
1582
1583   ret = FlushFileBuffers (h->h) ? GNUNET_OK : GNUNET_SYSERR;
1584   if (ret != GNUNET_OK)
1585     SetErrnoFromWinError (GetLastError ());
1586   return ret;
1587 #elif defined(FREEBSD) || defined(OPENBSD) || defined(DARWIN)
1588   return fsync (h->fd) == -1 ? GNUNET_SYSERR : GNUNET_OK;
1589 #else
1590   return fdatasync (h->fd) == -1 ? GNUNET_SYSERR : GNUNET_OK;
1591 #endif
1592 }
1593
1594 /**
1595  * Creates an interprocess channel
1596  *
1597  * @param blocking creates an asynchronous pipe if set to GNUNET_NO
1598  * @return handle to the new pipe, NULL on error
1599  */
1600 struct GNUNET_DISK_PipeHandle *
1601 GNUNET_DISK_pipe (int blocking)
1602 {
1603   struct GNUNET_DISK_PipeHandle *p;
1604   struct GNUNET_DISK_FileHandle *fds;
1605
1606   p =
1607     GNUNET_malloc (sizeof (struct GNUNET_DISK_PipeHandle) +
1608                    2 * sizeof (struct GNUNET_DISK_FileHandle));
1609   fds = (struct GNUNET_DISK_FileHandle *) &p[1];
1610   p->fd[0] = &fds[0];
1611   p->fd[1] = &fds[1];
1612 #ifndef MINGW
1613   int fd[2];
1614   int ret;
1615   int flags;
1616   int eno;
1617
1618   ret = pipe (fd);
1619   if (ret == -1)
1620     {
1621       eno = errno;
1622       GNUNET_free (p);
1623       errno = eno;
1624       return NULL;
1625     }
1626   p->fd[0]->fd = fd[0];
1627   p->fd[1]->fd = fd[1];
1628   if (!blocking)
1629     {
1630       flags = fcntl (fd[0], F_GETFL);
1631       flags |= O_NONBLOCK;
1632       ret = fcntl (fd[0], F_SETFL, flags);
1633       if (ret != -1)
1634         {
1635           flags = fcntl (fd[1], F_GETFL);
1636           flags |= O_NONBLOCK;
1637           ret = fcntl (fd[1], F_SETFL, flags);
1638         }
1639       if (ret == -1)
1640         {
1641           eno = errno;
1642           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fcntl");
1643           GNUNET_break (0 == close (p->fd[0]->fd));
1644           GNUNET_break (0 == close (p->fd[1]->fd));
1645           GNUNET_free (p);
1646           errno = eno;
1647           return NULL;
1648         }
1649     }
1650 #else
1651   BOOL ret;
1652
1653   ret = CreatePipe (&p->fd[0]->h, &p->fd[1]->h, NULL, 0);
1654   if (!ret)
1655     {
1656       GNUNET_free (p);
1657       SetErrnoFromWinError (GetLastError ());
1658       return NULL;
1659     }
1660   if (!blocking)
1661     {
1662       DWORD mode;
1663
1664       mode = PIPE_NOWAIT;
1665       SetNamedPipeHandleState (p->fd[0]->h, &mode, NULL, NULL);
1666       SetNamedPipeHandleState (p->fd[1]->h, &mode, NULL, NULL);
1667       /* this always fails on Windows 95, so we don't care about error handling */
1668     }
1669 #endif
1670   return p;
1671 }
1672
1673
1674 /**
1675  * Closes an interprocess channel
1676  *
1677  * @param p pipe to close
1678  * @param end which end of the pipe to close
1679  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1680  */
1681 int
1682 GNUNET_DISK_pipe_close_end (struct GNUNET_DISK_PipeHandle *p,
1683                             enum GNUNET_DISK_PipeEnd end)
1684 {
1685   int ret = GNUNET_OK;
1686   int save;
1687
1688   /* FIXME: What can we safely set HANDLE to once we've closed an end, NULL? */
1689 #ifdef MINGW
1690   if (end == GNUNET_DISK_PIPE_END_READ)
1691     {
1692       if (!CloseHandle (p->fd[0]->h))
1693         {
1694           SetErrnoFromWinError (GetLastError ());
1695           ret = GNUNET_SYSERR;
1696         }
1697     }
1698   else if (end == GNUNET_DISK_PIPE_END_WRITE)
1699     {
1700       if (!CloseHandle (p->fd[1]->h))
1701         {
1702           SetErrnoFromWinError (GetLastError ());
1703           ret = GNUNET_SYSERR;
1704         }
1705     }
1706   save = errno;
1707 #else
1708   save = 0;
1709   if (end == GNUNET_DISK_PIPE_END_READ)
1710     {
1711       if (0 != close (p->fd[0]->fd))
1712         {
1713           ret = GNUNET_SYSERR;
1714           save = errno;
1715         }
1716       p->fd[0]->fd = -1;
1717     }
1718   else if (end == GNUNET_DISK_PIPE_END_WRITE)
1719     {
1720       if (0 != close (p->fd[1]->fd))
1721         {
1722           ret = GNUNET_SYSERR;
1723           save = errno;
1724         }
1725       p->fd[1]->fd = -1;
1726     }
1727 #endif
1728   errno = save;
1729   return ret;
1730 }
1731
1732 /**
1733  * Closes an interprocess channel
1734  *
1735  * @param p pipe to close
1736  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1737  */
1738 int
1739 GNUNET_DISK_pipe_close (struct GNUNET_DISK_PipeHandle *p)
1740 {
1741   int ret = GNUNET_OK;
1742   int save;
1743
1744 #ifdef MINGW
1745   if (!CloseHandle (p->fd[0]->h))
1746     {
1747       SetErrnoFromWinError (GetLastError ());
1748       ret = GNUNET_SYSERR;
1749     }
1750   if (!CloseHandle (p->fd[1]->h))
1751     {
1752       SetErrnoFromWinError (GetLastError ());
1753       ret = GNUNET_SYSERR;
1754     }
1755   save = errno;
1756 #else
1757   save = 0;
1758   if (p->fd[0]->fd != -1)
1759     {
1760       if (0 != close (p->fd[0]->fd))
1761         {
1762           ret = GNUNET_SYSERR;
1763           save = errno;
1764         }
1765     }
1766
1767   if (p->fd[1]->fd != -1)
1768     {
1769       if (0 != close (p->fd[1]->fd))
1770         {
1771           ret = GNUNET_SYSERR;
1772           save = errno;
1773         }
1774     }
1775 #endif
1776   GNUNET_free (p);
1777   errno = save;
1778   return ret;
1779 }
1780
1781
1782 /**
1783  * Get the handle to a particular pipe end
1784  *
1785  * @param p pipe
1786  * @param n end to access
1787  * @return handle for the respective end
1788  */
1789 const struct GNUNET_DISK_FileHandle *
1790 GNUNET_DISK_pipe_handle (const struct GNUNET_DISK_PipeHandle *p,
1791                          enum GNUNET_DISK_PipeEnd n)
1792 {
1793   switch (n)
1794     {
1795     case GNUNET_DISK_PIPE_END_READ:
1796     case GNUNET_DISK_PIPE_END_WRITE:
1797       return p->fd[n];
1798     default:
1799       GNUNET_break (0);
1800       return NULL;
1801     }
1802 }
1803
1804
1805 /**
1806  * Retrieve OS file handle
1807  * @internal
1808  * @param fh GNUnet file descriptor
1809  * @param dst destination buffer
1810  * @param dst_len length of dst
1811  * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
1812  */
1813 int
1814 GNUNET_DISK_internal_file_handle_ (const struct GNUNET_DISK_FileHandle *fh,
1815                                    void *dst, size_t dst_len)
1816 {
1817 #ifdef MINGW
1818   if (dst_len < sizeof (HANDLE))
1819     return GNUNET_SYSERR;
1820   *((HANDLE *) dst) = fh->h;
1821 #else
1822   if (dst_len < sizeof (int))
1823     return GNUNET_SYSERR;
1824   *((int *) dst) = fh->fd;
1825 #endif
1826
1827   return GNUNET_OK;
1828 }
1829
1830 /* end of disk.c */