Try to fix incorrect non-Latin handling on Win32 filenames.
[libdcp.git] / asdcplib / src / KM_fileio.cpp
1 /*
2 Copyright (c) 2004-2011, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27   /*! \file    KM_fileio.cpp
28     \version $Id: KM_fileio.cpp,v 1.31 2011/03/08 19:03:47 jhurst Exp $
29     \brief   portable file i/o
30   */
31
32 #include <KM_fileio.h>
33 #include <KM_log.h>
34 #include <fcntl.h>
35 #include <sstream>
36 #include <iomanip>
37
38 #include <assert.h>
39
40 #ifdef KM_WIN32
41 #include <direct.h>
42 #else
43 #define _getcwd getcwd
44 #define _unlink unlink
45 #define _rmdir rmdir
46 #endif
47
48 using namespace Kumu;
49
50 #ifdef KM_WIN32
51 typedef struct _stati64 fstat_t;
52 #define S_IFLNK 0
53
54 // win32 has WriteFileGather() and ReadFileScatter() but they
55 // demand page alignment and page sizing, making them unsuitable
56 // for use with arbitrary buffer sizes.
57 struct iovec {
58   char* iov_base; // stupid iovec uses char*
59   int   iov_len;
60 };
61 #else
62 # if defined(__linux__)
63 #   include <sys/statfs.h>
64 # else
65 #  include <sys/param.h>
66 #  include <sys/mount.h>
67 # endif
68
69 #include <sys/stat.h>
70 #include <sys/uio.h>
71 typedef struct stat     fstat_t;
72 #endif
73
74 //
75 static void
76 split(const std::string& str, char separator, std::list<std::string>& components)
77 {
78   const char* pstr = str.c_str();
79   const char* r = strchr(pstr, separator);
80
81   while ( r != 0 )
82     {
83       assert(r >= pstr);
84       if ( r > pstr )
85         {
86           std::string tmp_str;
87           tmp_str.assign(pstr, (r - pstr));
88           components.push_back(tmp_str);
89         }
90
91       pstr = r + 1;
92       r = strchr(pstr, separator);
93     }
94
95   if( strlen(pstr) > 0 )
96     components.push_back(std::string(pstr));
97 }
98
99
100 //
101 static Kumu::Result_t
102 do_stat(const char* path, fstat_t* stat_info)
103 {
104   KM_TEST_NULL_STR_L(path);
105   KM_TEST_NULL_L(stat_info);
106
107   Kumu::Result_t result = Kumu::RESULT_OK;
108
109 #ifdef KM_WIN32
110   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
111
112   if ( _stati64(path, stat_info) == (__int64)-1 )
113     result = Kumu::RESULT_FILEOPEN;
114
115   ::SetErrorMode( prev );
116 #else
117   if ( stat(path, stat_info) == -1L )
118     result = Kumu::RESULT_FILEOPEN;
119
120   if ( (stat_info->st_mode & (S_IFREG|S_IFLNK|S_IFDIR)) == 0 )
121     result = Kumu::RESULT_FILEOPEN;
122 #endif
123
124   return result;
125 }
126
127 #ifndef KM_WIN32
128
129 //
130 static Kumu::Result_t
131 do_fstat(FileHandle handle, fstat_t* stat_info)
132 {
133   KM_TEST_NULL_L(stat_info);
134
135   Kumu::Result_t result = Kumu::RESULT_OK;
136
137   if ( fstat(handle, stat_info) == -1L )
138     result = Kumu::RESULT_FILEOPEN;
139
140   if ( (stat_info->st_mode & (S_IFREG|S_IFLNK|S_IFDIR)) == 0 )
141     result = Kumu::RESULT_FILEOPEN;
142
143   return result;
144 }
145
146 #endif
147
148
149 //
150 bool
151 Kumu::PathExists(const std::string& pathname)
152 {
153   if ( pathname.empty() )
154     return false;
155
156   fstat_t info;
157
158   if ( KM_SUCCESS(do_stat(pathname.c_str(), &info)) )
159     return true;
160
161   return false;
162 }
163
164 //
165 bool
166 Kumu::PathIsFile(const std::string& pathname)
167 {
168   if ( pathname.empty() )
169     return false;
170
171   fstat_t info;
172
173   if ( KM_SUCCESS(do_stat(pathname.c_str(), &info)) )
174     {
175       if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
176         return true;
177     }
178
179   return false;
180 }
181
182
183 //
184 bool
185 Kumu::PathIsDirectory(const std::string& pathname)
186 {
187   if ( pathname.empty() )
188     return false;
189
190   fstat_t info;
191
192   if ( KM_SUCCESS(do_stat(pathname.c_str(), &info)) )
193     {
194       if ( info.st_mode & S_IFDIR )
195         return true;
196     }
197
198   return false;
199 }
200
201 //
202 Kumu::fsize_t
203 Kumu::FileSize(const std::string& pathname)
204 {
205   if ( pathname.empty() )
206     return 0;
207
208   fstat_t info;
209
210   if ( KM_SUCCESS(do_stat(pathname.c_str(), &info)) )
211     {
212       if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
213         return(info.st_size);
214     }
215
216   return 0;
217 }
218
219 //
220 static PathCompList_t&
221 s_PathMakeCanonical(PathCompList_t& CList, bool is_absolute)
222 {
223   PathCompList_t::iterator ci, ri; // component and removal iterators
224
225   for ( ci = CList.begin(); ci != CList.end(); ci++ )
226     {
227       if ( *ci == "." && ( CList.size() > 1 || is_absolute ) )
228         {
229           ri = ci++;
230           CList.erase(ri);
231         }
232       else if ( *ci == ".." && ci != CList.begin() )
233         {
234           ri = ci;
235           ri--;
236               
237           if ( *ri != ".." )
238             {
239               CList.erase(ri);
240               ri = ci++;
241               CList.erase(ri);
242             }
243         }
244     }
245
246   return CList;
247 }
248
249 //
250 std::string
251 Kumu::PathMakeCanonical(const std::string& Path, char separator)
252 {
253   PathCompList_t CList;
254   bool is_absolute = PathIsAbsolute(Path, separator);
255   s_PathMakeCanonical(PathToComponents(Path, CList, separator), is_absolute);
256
257   if ( is_absolute )
258     return ComponentsToAbsolutePath(CList, separator);
259
260   return ComponentsToPath(CList, separator);
261 }
262
263 //
264 bool
265 Kumu::PathsAreEquivalent(const std::string& lhs, const std::string& rhs)
266 {
267   return PathMakeCanonical(lhs) == PathMakeCanonical(rhs);
268 }
269
270 //
271 Kumu::PathCompList_t&
272 Kumu::PathToComponents(const std::string& Path, PathCompList_t& CList, char separator)
273 {
274   split(Path, separator, CList);
275   return CList;
276 }
277
278 //
279 std::string
280 Kumu::ComponentsToPath(const PathCompList_t& CList, char separator)
281 {
282   if ( CList.empty() )
283     return "";
284
285   PathCompList_t::const_iterator ci = CList.begin();
286   std::string out_path = *ci;
287
288   for ( ci++; ci != CList.end(); ci++ )
289     out_path += separator + *ci;
290
291   return out_path;
292 }
293
294 //
295 std::string
296 Kumu::ComponentsToAbsolutePath(const PathCompList_t& CList, char separator)
297 {
298   std::string out_path;
299
300   if ( CList.empty() )
301     out_path = separator;
302   else
303     {
304       PathCompList_t::const_iterator ci;
305
306       for ( ci = CList.begin(); ci != CList.end(); ci++ )
307         out_path += separator + *ci;
308     }
309
310   return out_path;
311 }
312
313 //
314 bool
315 Kumu::PathHasComponents(const std::string& Path, char separator)
316 {
317   if ( strchr(Path.c_str(), separator) == 0 )
318     return false;
319
320   return true;
321 }
322
323 //
324 bool
325 Kumu::PathIsAbsolute(const std::string& Path, char separator)
326 {
327   if ( Path.empty() )
328     return false;
329
330   if ( Path[0] == separator)
331     return true;
332
333   return false;
334 }
335
336 //
337 std::string
338 Kumu::PathMakeAbsolute(const std::string& Path, char separator)
339 {
340   if ( Path.empty() )
341     {
342       std::string out_path;
343       out_path = separator;
344       return out_path;
345     }
346
347   if ( PathIsAbsolute(Path, separator) )
348     return Path;
349
350   char cwd_buf [MaxFilePath];
351   if ( _getcwd(cwd_buf, MaxFilePath) == 0 )
352     {
353       DefaultLogSink().Error("Error retrieving current working directory.");
354       return "";
355     }
356
357   PathCompList_t CList;
358   PathToComponents(cwd_buf, CList);
359   CList.push_back(Path);
360
361   return ComponentsToAbsolutePath(s_PathMakeCanonical(CList, true), separator);
362 }
363
364 //
365 std::string
366 Kumu::PathMakeLocal(const std::string& Path, const std::string& Parent)
367 {
368   size_t pos = Path.find(Parent);
369
370   if ( pos == 0 ) // Parent found at offset 0
371     return Path.substr(Parent.size()+1);
372
373   return Path;
374 }
375
376 //
377 std::string
378 Kumu::PathBasename(const std::string& Path, char separator)
379 {
380   PathCompList_t CList;
381   PathToComponents(Path, CList, separator);
382
383   if ( CList.empty() )
384     return "";
385
386   return CList.back();
387 }
388
389 //
390 std::string
391 Kumu::PathDirname(const std::string& Path, char separator)
392 {
393   PathCompList_t CList;
394   bool is_absolute = PathIsAbsolute(Path, separator);
395   PathToComponents(Path, CList, separator);
396
397   if ( CList.empty() )
398     return is_absolute ? "/" : "";
399
400   CList.pop_back();
401
402   if ( is_absolute )
403     return ComponentsToAbsolutePath(CList, separator);
404
405   return ComponentsToPath(CList, separator);
406 }
407
408 //
409 std::string
410 Kumu::PathGetExtension(const std::string& Path)
411 {
412   std::string Basename = PathBasename(Path);
413   const char* p = strrchr(Basename.c_str(), '.'); 
414
415   if ( p++ == 0 )
416     return "";
417
418   return p;
419 }
420
421 //
422 std::string
423 Kumu::PathSetExtension(const std::string& Path, const std::string& Extension) // empty extension removes
424 {
425   std::string Basename = PathBasename(Path);
426   const char* p = strrchr(Basename.c_str(), '.'); 
427
428   if ( p != 0 )
429     Basename = Basename.substr(0, p - Basename.c_str());
430
431   if ( Extension.empty() )
432     return Basename;
433
434   return Basename + "." + Extension;
435 }
436
437 //
438 std::string
439 Kumu::PathJoin(const std::string& Path1, const std::string& Path2, char separator)
440 {
441   return Path1 + separator + Path2;
442 }
443
444 //
445 std::string
446 Kumu::PathJoin(const std::string& Path1, const std::string& Path2, const std::string& Path3, char separator)
447 {
448   return Path1 + separator + Path2 + separator + Path3;
449 }
450
451 //
452 std::string
453 Kumu::PathJoin(const std::string& Path1, const std::string& Path2,
454                const std::string& Path3, const std::string& Path4, char separator)
455 {
456   return Path1 + separator + Path2 + separator + Path3 + separator + Path4;
457 }
458
459 //
460 Kumu::PathList_t&
461 Kumu::FindInPaths(const IPathMatch& Pattern, const Kumu::PathList_t& SearchPaths,
462                   Kumu::PathList_t& FoundPaths, bool one_shot, char separator)
463 {
464   PathList_t::const_iterator si;
465   for ( si = SearchPaths.begin(); si != SearchPaths.end(); si++ )
466     {
467       FindInPath(Pattern, *si, FoundPaths, one_shot, separator);
468
469       if ( one_shot && ! FoundPaths.empty() )
470         break;
471     }
472
473   return FoundPaths;
474 }
475
476 //
477 Kumu::PathList_t&
478 Kumu::FindInPath(const IPathMatch& Pattern, const std::string& SearchDir,
479                   Kumu::PathList_t& FoundPaths, bool one_shot, char separator)
480 {
481   char name_buf[MaxFilePath];
482   DirScanner Dir;
483
484   if ( KM_SUCCESS(Dir.Open(SearchDir.c_str())) )
485     {
486       while ( KM_SUCCESS(Dir.GetNext(name_buf)) )
487         {
488           if ( name_buf[0] == '.' ) continue; // no hidden files
489           std::string tmp_path = SearchDir + separator + name_buf;
490
491           if ( PathIsDirectory(tmp_path.c_str()) )
492             FindInPath(Pattern, tmp_path, FoundPaths, one_shot, separator);
493           
494           else if ( Pattern.Match(name_buf) )
495             {
496               FoundPaths.push_back(SearchDir + separator + name_buf);
497               if ( one_shot )
498                 break;
499             }
500         }
501     }
502
503   return FoundPaths;
504 }
505
506
507 #ifndef KM_WIN32
508
509 //
510 Kumu::PathMatchRegex::PathMatchRegex(const std::string& s)
511 {
512   int result = regcomp(&m_regex, s.c_str(), REG_NOSUB); // (REG_EXTENDED|REG_NOSUB|REG_NEWLINE));
513
514   if ( result )
515     {
516       char buf[128];
517       regerror(result, &m_regex, buf, 128);
518       DefaultLogSink().Error("PathMatchRegex: %s\n", buf);
519       regfree(&m_regex);
520     }
521 }
522
523 Kumu::PathMatchRegex::PathMatchRegex(const PathMatchRegex& rhs) : IPathMatch() {
524   m_regex = rhs.m_regex;
525 }
526
527 Kumu::PathMatchRegex::~PathMatchRegex() {
528   regfree(&m_regex);
529 }
530
531 bool
532 Kumu::PathMatchRegex::Match(const std::string& s) const {
533   return ( regexec(&m_regex, s.c_str(), 0, 0, 0) == 0 );
534 }
535
536
537
538 //
539 Kumu::PathMatchGlob::PathMatchGlob(const std::string& glob)
540 {
541   std::string regex; // convert glob to regex
542
543   for ( const char* p = glob.c_str(); *p != 0; p++ )
544     {
545       switch (*p)
546         {
547         case '.':  regex += "\\.";  break;
548         case '*':  regex += ".*";   break;
549         case '?':  regex += ".?";   break;
550         default:   regex += *p;
551         }
552     }
553   regex += '$';
554
555   int result = regcomp(&m_regex, regex.c_str(), REG_NOSUB);
556
557   if ( result )
558     {
559       char buf[128];
560       regerror(result, &m_regex, buf, 128);
561       DefaultLogSink().Error("PathMatchRegex: %s\n", buf);
562       regfree(&m_regex);
563     }
564 }
565
566 Kumu::PathMatchGlob::PathMatchGlob(const PathMatchGlob& rhs) : IPathMatch() {
567   m_regex = rhs.m_regex;
568 }
569
570 Kumu::PathMatchGlob::~PathMatchGlob() {
571   regfree(&m_regex);
572 }
573
574 bool
575 Kumu::PathMatchGlob::Match(const std::string& s) const {
576   return ( regexec(&m_regex, s.c_str(), 0, 0, 0) == 0 );
577 }
578
579 #endif
580
581 //------------------------------------------------------------------------------------------
582 // portable aspects of the file classes
583
584 const int IOVecMaxEntries = 32; // we never use more that 3, but that number seems somehow small...
585
586 //
587 class Kumu::FileWriter::h__iovec
588 {
589 public:
590   int            m_Count;
591   struct iovec   m_iovec[IOVecMaxEntries];
592   h__iovec() : m_Count(0) {}
593 };
594
595
596
597 //
598 Kumu::fsize_t
599 Kumu::FileReader::Size() const
600 {
601 #ifdef KM_WIN32
602   return FileSize(m_Filename.c_str());
603 #else
604   fstat_t info;
605
606   if ( KM_SUCCESS(do_fstat(m_Handle, &info)) )
607     {
608       if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
609         return(info.st_size);
610     }
611 #endif
612
613   return 0;
614 }
615
616 // these are declared here instead of in the header file
617 // because we have a mem_ptr that is managing a hidden class
618 Kumu::FileWriter::FileWriter()
619         : m_Hashing (false)
620 {}
621
622 Kumu::FileWriter::~FileWriter() {}
623
624 //
625 Kumu::Result_t
626 Kumu::FileWriter::Writev(const byte_t* buf, ui32_t buf_len)
627 {
628   assert( ! m_IOVec.empty() );
629   register h__iovec* iov = m_IOVec;
630   KM_TEST_NULL_L(buf);
631
632   if ( iov->m_Count >= IOVecMaxEntries )
633     {
634       DefaultLogSink().Error("The iovec is full! Only %u entries allowed before a flush.\n",
635                              IOVecMaxEntries);
636       return RESULT_WRITEFAIL;
637     }
638
639   iov->m_iovec[iov->m_Count].iov_base = (char*)buf; // stupid iovec uses char*
640   iov->m_iovec[iov->m_Count].iov_len = buf_len;
641   iov->m_Count++;
642
643   return RESULT_OK;
644 }
645
646 void
647 Kumu::FileWriter::StartHashing()
648 {
649         m_Hashing = true;
650         MD5_Init (&m_MD5Context);
651 }
652
653 void
654 Kumu::FileWriter::MaybeHash(void const * data, int size)
655 {
656         if (m_Hashing) {
657                 MD5_Update (&m_MD5Context, data, size);
658         }
659 }
660
661 std::string
662 Kumu::FileWriter::StopHashing()
663 {
664         m_Hashing = false;
665         
666         unsigned char digest[MD5_DIGEST_LENGTH];
667         MD5_Final (digest, &m_MD5Context);
668
669         std::stringstream s;
670         for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
671                 s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
672         }
673
674         return s.str ();
675 }
676
677
678 #ifdef KM_WIN32
679 //------------------------------------------------------------------------------------------
680 //
681
682 /** @param filename File name (UTF-8 encoded) */
683 Kumu::Result_t
684 Kumu::FileReader::OpenRead(const char* filename) const
685 {
686   KM_TEST_NULL_STR_L(filename);
687   const_cast<FileReader*>(this)->m_Filename = filename;
688   
689   // suppress popup window on error
690   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
691
692   int const wn = MultiByteToWideChar (CP_UTF8, 0, filename, -1, 0, 0);
693   whar_t* buffer = new wchar_t[wn];
694   if (MultiByteToWideChar (CP_UTF8, 0, filename, -1, buffer, wn)) {
695           delete[] buffer;
696           return Kumu::RESULT_FAIL;
697   }
698
699   const_cast<FileReader*>(this)->m_Handle = ::CreateFileW(buffer,
700                           (GENERIC_READ),                // open for reading
701                           FILE_SHARE_READ,               // share for reading
702                           NULL,                          // no security
703                           OPEN_EXISTING,                 // read
704                           FILE_ATTRIBUTE_NORMAL,         // normal file
705                           NULL                           // no template file
706                           );
707
708   delete[] buffer;
709           
710   ::SetErrorMode(prev);
711
712   return ( m_Handle == INVALID_HANDLE_VALUE ) ?
713     Kumu::RESULT_FILEOPEN : Kumu::RESULT_OK;
714 }
715
716 //
717 Kumu::Result_t
718 Kumu::FileReader::Close() const
719 {
720   if ( m_Handle == INVALID_HANDLE_VALUE )
721     return Kumu::RESULT_FILEOPEN;
722
723   // suppress popup window on error
724   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
725   BOOL result = ::CloseHandle(m_Handle);
726   ::SetErrorMode(prev);
727   const_cast<FileReader*>(this)->m_Handle = INVALID_HANDLE_VALUE;
728
729   return ( result == 0 ) ? Kumu::RESULT_FAIL : Kumu::RESULT_OK;
730 }
731
732 //
733 Kumu::Result_t
734 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
735 {
736   if ( m_Handle == INVALID_HANDLE_VALUE )
737     return Kumu::RESULT_STATE;
738
739   LARGE_INTEGER in;
740   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
741   in.QuadPart = position;
742   in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, whence);
743   HRESULT LastError = GetLastError();
744   ::SetErrorMode(prev);
745
746   if ( (LastError != NO_ERROR
747         && (in.LowPart == INVALID_SET_FILE_POINTER
748             || in.LowPart == ERROR_NEGATIVE_SEEK )) )
749     return Kumu::RESULT_READFAIL;
750   
751   return Kumu::RESULT_OK;
752 }
753
754 //
755 Kumu::Result_t
756 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
757 {
758   KM_TEST_NULL_L(pos);
759
760   if ( m_Handle == INVALID_HANDLE_VALUE )
761     return Kumu::RESULT_FILEOPEN;
762
763   LARGE_INTEGER in;
764   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
765   in.QuadPart = (__int64)0;
766   in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, FILE_CURRENT);
767   HRESULT LastError = GetLastError();
768   ::SetErrorMode(prev);
769
770   if ( (LastError != NO_ERROR
771         && (in.LowPart == INVALID_SET_FILE_POINTER
772             || in.LowPart == ERROR_NEGATIVE_SEEK )) )
773     return Kumu::RESULT_READFAIL;
774
775   *pos = (Kumu::fpos_t)in.QuadPart;
776   return Kumu::RESULT_OK;
777 }
778
779 //
780 Kumu::Result_t
781 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
782 {
783   KM_TEST_NULL_L(buf);
784   Result_t result = Kumu::RESULT_OK;
785   DWORD    tmp_count;
786   ui32_t tmp_int;
787
788   if ( read_count == 0 )
789     read_count = &tmp_int;
790
791   *read_count = 0;
792
793   if ( m_Handle == INVALID_HANDLE_VALUE )
794     return Kumu::RESULT_FILEOPEN;
795   
796   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
797   if ( ::ReadFile(m_Handle, buf, buf_len, &tmp_count, NULL) == 0 )
798     result = Kumu::RESULT_READFAIL;
799
800   ::SetErrorMode(prev);
801
802   if ( tmp_count == 0 ) /* EOF */
803     result = Kumu::RESULT_ENDOFFILE;
804
805   if ( KM_SUCCESS(result) )
806     *read_count = tmp_count;
807
808   return result;
809 }
810
811
812
813 //------------------------------------------------------------------------------------------
814 //
815
816 /** @param filename File name (UTF-8 encoded) */
817 Kumu::Result_t
818 Kumu::FileWriter::OpenWrite(const char* filename)
819 {
820   KM_TEST_NULL_STR_L(filename);
821   m_Filename = filename;
822   
823   // suppress popup window on error
824   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
825
826   int const wn = MultiByteToWideChar (CP_UTF8, 0, filename, -1, 0, 0);
827   whar_t* buffer = new wchar_t[wn];
828   if (MultiByteToWideChar (CP_UTF8, 0, filename, -1, buffer, wn)) {
829           delete[] buffer;
830           return Kumu::RESULT_FAIL;
831   }
832
833   m_Handle = ::CreateFileW(buffer,
834                           (GENERIC_WRITE|GENERIC_READ),  // open for reading
835                           FILE_SHARE_READ,               // share for reading
836                           NULL,                          // no security
837                           CREATE_ALWAYS,                 // overwrite (beware!)
838                           FILE_ATTRIBUTE_NORMAL,         // normal file
839                           NULL                           // no template file
840                           );
841
842   delete[] buffer;
843   
844   ::SetErrorMode(prev);
845
846   if ( m_Handle == INVALID_HANDLE_VALUE )
847     return Kumu::RESULT_FILEOPEN;
848   
849   m_IOVec = new h__iovec;
850   return Kumu::RESULT_OK;
851 }
852
853 /** @param filename File name (UTF-8 encoded) */
854 Kumu::Result_t
855 Kumu::FileWriter::OpenModify(const char* filename)
856 {
857   KM_TEST_NULL_STR_L(filename);
858   m_Filename = filename;
859   
860   // suppress popup window on error
861   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
862
863   int const wn = MultiByteToWideChar (CP_UTF8, 0, filename, -1, 0, 0);
864   whar_t* buffer = new wchar_t[wn];
865   if (MultiByteToWideChar (CP_UTF8, 0, filename, -1, buffer, wn)) {
866           delete[] buffer;
867           return Kumu::RESULT_FAIL;
868   }
869
870   m_Handle = ::CreateFileW(buffer,
871                           (GENERIC_WRITE|GENERIC_READ),  // open for reading
872                           FILE_SHARE_READ,               // share for reading
873                           NULL,                          // no security
874                           OPEN_ALWAYS,                   // don't truncate existing
875                           FILE_ATTRIBUTE_NORMAL,         // normal file
876                           NULL                           // no template file
877                           );
878
879   delete[] buffer;
880   
881   ::SetErrorMode(prev);
882
883   if ( m_Handle == INVALID_HANDLE_VALUE )
884     return Kumu::RESULT_FILEOPEN;
885   
886   m_IOVec = new h__iovec;
887   return Kumu::RESULT_OK;
888 }
889
890 //
891 Kumu::Result_t
892 Kumu::FileWriter::Writev(ui32_t* bytes_written)
893 {
894   assert( ! m_IOVec.empty() );
895   register h__iovec* iov = m_IOVec;
896   ui32_t tmp_int;
897
898   if ( bytes_written == 0 )
899     bytes_written = &tmp_int;
900
901   if ( m_Handle == INVALID_HANDLE_VALUE )
902     return Kumu::RESULT_STATE;
903
904   *bytes_written = 0;
905   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
906   Result_t result = Kumu::RESULT_OK;
907
908   // AFAIK, there is no writev() equivalent in the win32 API
909   for ( register int i = 0; i < iov->m_Count; i++ )
910     {
911       ui32_t tmp_count = 0;
912       BOOL wr_result = ::WriteFile(m_Handle,
913                                    iov->m_iovec[i].iov_base,
914                                    iov->m_iovec[i].iov_len,
915                                    (DWORD*)&tmp_count,
916                                    NULL);
917
918       if ( wr_result == 0 || tmp_count != iov->m_iovec[i].iov_len)
919         {
920           result = Kumu::RESULT_WRITEFAIL;
921           break;
922         }
923
924       MaybeHash (iov->m_iovec[i].iov_base, iov->m_iovec[i].iov_len);
925       *bytes_written += tmp_count;
926     }
927
928   ::SetErrorMode(prev);
929   iov->m_Count = 0; // error nor not, all is lost
930
931   return result;
932 }
933
934 //
935 Kumu::Result_t
936 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
937 {
938   KM_TEST_NULL_L(buf);
939   ui32_t tmp_int;
940
941   if ( bytes_written == 0 )
942     bytes_written = &tmp_int;
943
944   if ( m_Handle == INVALID_HANDLE_VALUE )
945     return Kumu::RESULT_STATE;
946
947   // suppress popup window on error
948   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
949   BOOL result = ::WriteFile(m_Handle, buf, buf_len, (DWORD*)bytes_written, NULL);
950   ::SetErrorMode(prev);
951
952   if ( result == 0 || *bytes_written != buf_len )
953     return Kumu::RESULT_WRITEFAIL;
954
955   MaybeHash (buf, buf_len);
956   
957   return Kumu::RESULT_OK;
958 }
959
960 #else // KM_WIN32
961 //------------------------------------------------------------------------------------------
962 // POSIX
963
964 //
965 Kumu::Result_t
966 Kumu::FileReader::OpenRead(const char* filename) const
967 {
968   KM_TEST_NULL_STR_L(filename);
969   const_cast<FileReader*>(this)->m_Filename = filename;
970   const_cast<FileReader*>(this)->m_Handle = open(filename, O_RDONLY, 0);
971   return ( m_Handle == -1L ) ? RESULT_FILEOPEN : RESULT_OK;
972 }
973
974 //
975 Kumu::Result_t
976 Kumu::FileReader::Close() const
977 {
978   if ( m_Handle == -1L )
979     return RESULT_FILEOPEN;
980
981   close(m_Handle);
982   const_cast<FileReader*>(this)->m_Handle = -1L;
983   return RESULT_OK;
984 }
985
986 //
987 Kumu::Result_t
988 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
989 {
990   if ( m_Handle == -1L )
991     return RESULT_FILEOPEN;
992
993   if ( lseek(m_Handle, position, whence) == -1L )
994     return RESULT_BADSEEK;
995
996   return RESULT_OK;
997 }
998
999 //
1000 Kumu::Result_t
1001 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
1002 {
1003   KM_TEST_NULL_L(pos);
1004
1005   if ( m_Handle == -1L )
1006     return RESULT_FILEOPEN;
1007
1008   Kumu::fpos_t tmp_pos;
1009
1010   if (  (tmp_pos = lseek(m_Handle, 0, SEEK_CUR)) == -1 )
1011     return RESULT_READFAIL;
1012
1013   *pos = tmp_pos;
1014   return RESULT_OK;
1015 }
1016
1017 //
1018 Kumu::Result_t
1019 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
1020 {
1021   KM_TEST_NULL_L(buf);
1022   i32_t  tmp_count = 0;
1023   ui32_t tmp_int = 0;
1024
1025   if ( read_count == 0 )
1026     read_count = &tmp_int;
1027
1028   *read_count = 0;
1029
1030   if ( m_Handle == -1L )
1031     return RESULT_FILEOPEN;
1032
1033   if ( (tmp_count = read(m_Handle, buf, buf_len)) == -1L )
1034     return RESULT_READFAIL;
1035
1036   *read_count = tmp_count;
1037   return (tmp_count == 0 ? RESULT_ENDOFFILE : RESULT_OK);
1038 }
1039
1040
1041 //------------------------------------------------------------------------------------------
1042 //
1043
1044 //
1045 Kumu::Result_t
1046 Kumu::FileWriter::OpenWrite(const char* filename)
1047 {
1048   KM_TEST_NULL_STR_L(filename);
1049   m_Filename = filename;
1050   m_Handle = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0664);
1051
1052   if ( m_Handle == -1L )
1053     {
1054       DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
1055       return RESULT_FILEOPEN;
1056     }
1057
1058   m_IOVec = new h__iovec;
1059   return RESULT_OK;
1060 }
1061
1062 //
1063 Kumu::Result_t
1064 Kumu::FileWriter::OpenModify(const char* filename)
1065 {
1066   KM_TEST_NULL_STR_L(filename);
1067   m_Filename = filename;
1068   m_Handle = open(filename, O_RDWR|O_CREAT, 0664);
1069
1070   if ( m_Handle == -1L )
1071     {
1072       DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
1073       return RESULT_FILEOPEN;
1074     }
1075
1076   m_IOVec = new h__iovec;
1077   return RESULT_OK;
1078 }
1079
1080 //
1081 Kumu::Result_t
1082 Kumu::FileWriter::Writev(ui32_t* bytes_written)
1083 {
1084   assert( ! m_IOVec.empty() );
1085   register h__iovec* iov = m_IOVec;
1086   ui32_t tmp_int;
1087
1088   if ( bytes_written == 0 )
1089     bytes_written = &tmp_int;
1090
1091   if ( m_Handle == -1L )
1092     return RESULT_STATE;
1093
1094   int total_size = 0;
1095   for ( int i = 0; i < iov->m_Count; i++ )
1096     total_size += iov->m_iovec[i].iov_len;
1097
1098   int write_size = writev(m_Handle, iov->m_iovec, iov->m_Count);
1099   
1100   if ( write_size == -1L || write_size != total_size )
1101     return RESULT_WRITEFAIL;
1102
1103   for (int i = 0; i < iov->m_Count; ++i) {
1104           MaybeHash (iov->m_iovec[i].iov_base, iov->m_iovec[i].iov_len);
1105   }
1106
1107   iov->m_Count = 0;
1108   *bytes_written = write_size;  
1109   return RESULT_OK;
1110 }
1111
1112 //
1113 Kumu::Result_t
1114 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
1115 {
1116   KM_TEST_NULL_L(buf);
1117   ui32_t tmp_int;
1118
1119   if ( bytes_written == 0 )
1120     bytes_written = &tmp_int;
1121
1122   if ( m_Handle == -1L )
1123     return RESULT_STATE;
1124
1125   int write_size = write(m_Handle, buf, buf_len);
1126   MaybeHash (buf, buf_len);
1127
1128   if ( write_size == -1L || (ui32_t)write_size != buf_len )
1129     return RESULT_WRITEFAIL;
1130
1131   *bytes_written = write_size;
1132   return RESULT_OK;
1133 }
1134
1135
1136 #endif
1137
1138 //------------------------------------------------------------------------------------------
1139
1140
1141 //
1142 Kumu::Result_t
1143 Kumu::ReadFileIntoString(const char* filename, std::string& outString, ui32_t max_size)
1144 {
1145   fsize_t    fsize = 0;
1146   ui32_t     read_size = 0;
1147   FileReader File;
1148   ByteString ReadBuf;
1149
1150   KM_TEST_NULL_STR_L(filename);
1151
1152   Result_t result = File.OpenRead(filename);
1153
1154   if ( KM_SUCCESS(result) )
1155     {
1156       fsize = File.Size();
1157
1158       if ( fsize > (Kumu::fpos_t)max_size )
1159         {
1160           DefaultLogSink().Error("%s: exceeds available buffer size (%u)\n", filename, max_size);
1161           return RESULT_ALLOC;
1162         }
1163
1164       if ( fsize == 0 )
1165         {
1166           DefaultLogSink().Error("%s: zero file size\n", filename);
1167           return RESULT_READFAIL;
1168         }
1169
1170       result = ReadBuf.Capacity((ui32_t)fsize);
1171     }
1172
1173   if ( KM_SUCCESS(result) )
1174     result = File.Read(ReadBuf.Data(), ReadBuf.Capacity(), &read_size);
1175
1176   if ( KM_SUCCESS(result) )
1177     outString.assign((const char*)ReadBuf.RoData(), read_size);
1178
1179   return result;
1180 }
1181
1182
1183 //
1184 Kumu::Result_t
1185 Kumu::WriteStringIntoFile(const char* filename, const std::string& inString)
1186 {
1187   FileWriter File;
1188   ui32_t write_count = 0;
1189   KM_TEST_NULL_STR_L(filename);
1190
1191   Result_t result = File.OpenWrite(filename);
1192
1193   if ( KM_SUCCESS(result) )
1194     result = File.Write((byte_t*)inString.c_str(), inString.length(), &write_count);
1195
1196   return result;
1197 }
1198
1199 //------------------------------------------------------------------------------------------
1200
1201
1202 //
1203 Kumu::Result_t
1204 Kumu::ReadFileIntoObject(const std::string& Filename, Kumu::IArchive& Object, ui32_t)
1205 {
1206   ByteString Buffer;
1207   ui32_t file_size = static_cast<ui32_t>(FileSize(Filename));
1208   Result_t result = Buffer.Capacity(file_size);
1209
1210   if ( KM_SUCCESS(result) )
1211     {
1212       ui32_t read_count = 0;
1213       FileWriter Reader;
1214
1215       result = Reader.OpenRead(Filename.c_str());
1216
1217       if ( KM_SUCCESS(result) )
1218         result = Reader.Read(Buffer.Data(), file_size, &read_count);
1219     
1220       if ( KM_SUCCESS(result) )
1221         {
1222           assert(file_size == read_count);
1223           Buffer.Length(read_count);
1224           MemIOReader MemReader(&Buffer);
1225           result = Object.Unarchive(&MemReader) ? RESULT_OK : RESULT_READFAIL;
1226         }
1227     }
1228
1229   return result;
1230 }
1231
1232 //
1233 Kumu::Result_t
1234 Kumu::WriteObjectIntoFile(const Kumu::IArchive& Object, const std::string& Filename)
1235 {
1236   ByteString Buffer;
1237   Result_t result = Buffer.Capacity(Object.ArchiveLength());
1238
1239   if ( KM_SUCCESS(result) )
1240     {
1241       ui32_t write_count = 0;
1242       FileWriter Writer;
1243       MemIOWriter MemWriter(&Buffer);
1244
1245       result = Object.Archive(&MemWriter) ? RESULT_OK : RESULT_WRITEFAIL;
1246
1247       if ( KM_SUCCESS(result) )
1248         {
1249           Buffer.Length(MemWriter.Length());
1250           result = Writer.OpenWrite(Filename.c_str());
1251         }
1252
1253       if ( KM_SUCCESS(result) )
1254         result = Writer.Write(Buffer.RoData(), Buffer.Length(), &write_count);
1255     }
1256
1257   return result;
1258 }
1259
1260 //------------------------------------------------------------------------------------------
1261 //
1262
1263 //
1264 Result_t
1265 Kumu::ReadFileIntoBuffer(const std::string& Filename, Kumu::ByteString& Buffer, ui32_t)
1266 {
1267   ui32_t file_size = FileSize(Filename);
1268   Result_t result = Buffer.Capacity(file_size);
1269
1270   if ( KM_SUCCESS(result) )
1271     {
1272       ui32_t read_count = 0;
1273       FileWriter Reader;
1274
1275       result = Reader.OpenRead(Filename.c_str());
1276
1277       if ( KM_SUCCESS(result) )
1278         result = Reader.Read(Buffer.Data(), file_size, &read_count);
1279     
1280       if ( KM_SUCCESS(result) )
1281         {
1282           if ( file_size != read_count) 
1283             return RESULT_READFAIL;
1284
1285           Buffer.Length(read_count);
1286         }
1287     }
1288   
1289   return result;
1290 }
1291
1292 //
1293 Result_t
1294 Kumu::WriteBufferIntoFile(const Kumu::ByteString& Buffer, const std::string& Filename)
1295 {
1296   ui32_t write_count = 0;
1297   FileWriter Writer;
1298
1299   Result_t result = Writer.OpenWrite(Filename.c_str());
1300
1301   if ( KM_SUCCESS(result) )
1302     result = Writer.Write(Buffer.RoData(), Buffer.Length(), &write_count);
1303
1304   if ( KM_SUCCESS(result) && Buffer.Length() != write_count) 
1305     return RESULT_WRITEFAIL;
1306
1307   return result;
1308 }
1309
1310 //------------------------------------------------------------------------------------------
1311 //
1312
1313 Kumu::DirScanner::DirScanner()
1314 {
1315
1316 }
1317
1318 Result_t
1319 Kumu::DirScanner::Open (const char* filename)
1320 {
1321         KM_TEST_NULL_L (filename);
1322
1323         if (!boost::filesystem::is_directory(filename)) {
1324                 return RESULT_NOT_FOUND;
1325         }
1326         
1327         _iterator = boost::filesystem::directory_iterator (filename);
1328         return RESULT_OK;
1329 }
1330
1331 Result_t
1332 Kumu::DirScanner::GetNext (char* filename)
1333 {
1334         KM_TEST_NULL_L (filename);
1335         
1336         if (_iterator == boost::filesystem::directory_iterator()) {
1337                 return RESULT_ENDOFFILE;
1338         }
1339
1340 #if BOOST_FILESYSTEM_VERSION == 3       
1341         std::string f = boost::filesystem::path(*_iterator).filename().generic_string();
1342 #else
1343         std::string f = boost::filesystem::path(*_iterator).filename();
1344 #endif  
1345         strncpy (filename, f.c_str(), MaxFilePath);
1346         ++_iterator;
1347         return RESULT_OK;
1348 }
1349
1350 //------------------------------------------------------------------------------------------
1351
1352 //
1353 // Attention Windows users: make sure to use the proper separator character
1354 // with these functions.
1355 //
1356
1357 // given a path string, create any missing directories so that PathIsDirectory(Path) is true.
1358 //
1359 Result_t
1360 Kumu::CreateDirectoriesInPath(const std::string& Path)
1361 {
1362   bool abs = PathIsAbsolute(Path);
1363   PathCompList_t PathComps, TmpPathComps;
1364
1365   PathToComponents(Path, PathComps);
1366
1367   while ( ! PathComps.empty() )
1368     {
1369       TmpPathComps.push_back(PathComps.front());
1370       PathComps.pop_front();
1371       std::string tmp_path = abs ? ComponentsToAbsolutePath(TmpPathComps) : ComponentsToPath(TmpPathComps);
1372
1373       if ( ! PathIsDirectory(tmp_path) )
1374         {
1375 #ifdef KM_WIN32
1376           if ( _mkdir(tmp_path.c_str()) != 0 )
1377 #else // KM_WIN32
1378           if ( mkdir(tmp_path.c_str(), 0775) != 0 )
1379 #endif // KM_WIN32
1380             {
1381               DefaultLogSink().Error("CreateDirectoriesInPath mkdir %s: %s\n",
1382                                      tmp_path.c_str(), strerror(errno));
1383               return RESULT_DIR_CREATE;
1384             }
1385         }
1386     }
1387
1388   return RESULT_OK;
1389 }
1390
1391
1392 //
1393 Result_t
1394 Kumu::DeleteFile(const std::string& filename)
1395 {
1396   if ( _unlink(filename.c_str()) == 0 )
1397     return RESULT_OK;
1398
1399   switch ( errno )
1400     {
1401     case ENOENT:
1402     case ENOTDIR: return RESULT_NOTAFILE;
1403
1404     case EROFS:
1405     case EBUSY:
1406     case EACCES:
1407     case EPERM:   return RESULT_NO_PERM;
1408     }
1409
1410   DefaultLogSink().Error("DeleteFile %s: %s\n", filename.c_str(), strerror(errno));
1411   return RESULT_FAIL;
1412 }
1413
1414 //
1415 Result_t
1416 h__DeletePath(const std::string& pathname)
1417 {
1418   if ( pathname.empty() )
1419     return RESULT_NULL_STR;
1420
1421   Result_t result = RESULT_OK;
1422
1423   if ( ! PathIsDirectory(pathname) )
1424     {
1425       result = DeleteFile(pathname);
1426     }
1427   else
1428     {
1429       {
1430         DirScanner TestDir;
1431         char       next_file[Kumu::MaxFilePath];
1432         result = TestDir.Open(pathname.c_str());
1433
1434         while ( KM_SUCCESS(result) && KM_SUCCESS(TestDir.GetNext(next_file)) )
1435           {
1436             if ( next_file[0] == '.' )
1437               {
1438                 if ( next_file[1] ==  0 )
1439                   continue; // don't delete 'this'
1440                 
1441                 if ( next_file[1] == '.' && next_file[2] ==  0 )
1442                   continue; // don't delete 'this' parent
1443               }
1444
1445             result = h__DeletePath(pathname + std::string("/") + next_file);
1446           }
1447       }
1448
1449       if ( _rmdir(pathname.c_str()) != 0 )
1450         {
1451           switch ( errno )
1452             {
1453             case ENOENT:
1454             case ENOTDIR:
1455               result = RESULT_NOTAFILE;
1456               break;
1457
1458             case EROFS:
1459             case EBUSY:
1460             case EACCES:
1461             case EPERM:
1462               result = RESULT_NO_PERM;
1463               break;
1464
1465             default:
1466               DefaultLogSink().Error("DeletePath %s: %s\n", pathname.c_str(), strerror(errno));
1467               result = RESULT_FAIL;
1468             }
1469         }
1470     }
1471
1472   return result;
1473 }
1474
1475 //
1476 Result_t
1477 Kumu::DeletePath(const std::string& pathname)
1478 {
1479   std::string c_pathname = PathMakeAbsolute(PathMakeCanonical(pathname));
1480   DefaultLogSink().Debug("DeletePath (%s) c(%s)\n", pathname.c_str(), c_pathname.c_str());
1481   return h__DeletePath(c_pathname);
1482 }
1483
1484
1485 //------------------------------------------------------------------------------------------
1486 //
1487
1488
1489 Result_t
1490 Kumu::FreeSpaceForPath(const std::string& path, Kumu::fsize_t& free_space, Kumu::fsize_t& total_space)
1491 {
1492 #ifdef KM_WIN32
1493         ULARGE_INTEGER lTotalNumberOfBytes;
1494         ULARGE_INTEGER lTotalNumberOfFreeBytes;
1495
1496         BOOL fResult = ::GetDiskFreeSpaceExA(path.c_str(), NULL, &lTotalNumberOfBytes, &lTotalNumberOfFreeBytes);
1497         if (fResult) {
1498       free_space = static_cast<Kumu::fsize_t>(lTotalNumberOfFreeBytes.QuadPart);
1499       total_space = static_cast<Kumu::fsize_t>(lTotalNumberOfBytes.QuadPart);
1500       return RESULT_OK;
1501         }
1502         HRESULT LastError = ::GetLastError();
1503
1504         DefaultLogSink().Error("FreeSpaceForPath GetDiskFreeSpaceEx %s: %lu\n", path.c_str(), ::GetLastError());
1505         return RESULT_FAIL;
1506 #else // KM_WIN32
1507   struct statfs s;
1508
1509   if ( statfs(path.c_str(), &s) == 0 )
1510     {
1511       if ( s.f_blocks < 1 )
1512         {
1513           DefaultLogSink().Error("File system %s has impossible size: %ld\n",
1514                                  path.c_str(), s.f_blocks);
1515           return RESULT_FAIL;
1516         }
1517
1518       free_space = (Kumu::fsize_t)s.f_bsize * (Kumu::fsize_t)s.f_bavail;
1519       total_space = (Kumu::fsize_t)s.f_bsize * (Kumu::fsize_t)s.f_blocks;
1520       return RESULT_OK;
1521     }
1522
1523   switch ( errno )
1524     {
1525     case ENOENT:
1526     case ENOTDIR: return RESULT_NOTAFILE;
1527     case EACCES:  return RESULT_NO_PERM;
1528     }
1529
1530   DefaultLogSink().Error("FreeSpaceForPath statfs %s: %s\n", path.c_str(), strerror(errno));
1531   return RESULT_FAIL;
1532 #endif // KM_WIN32
1533
1534
1535
1536 //
1537 // end KM_fileio.cpp
1538 //