Try to fix non-trivial filename character encoding on Windows.
[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   wchar_t buffer[1024];
693   if (MultiByteToWideChar (CP_UTF8, MB_PRECOMPOSED, filename, -1, buffer, 1024)) {
694           return Kumu::RESULT_FAIL;
695   }
696
697   const_cast<FileReader*>(this)->m_Handle = ::CreateFileW(buffer,
698                           (GENERIC_READ),                // open for reading
699                           FILE_SHARE_READ,               // share for reading
700                           NULL,                          // no security
701                           OPEN_EXISTING,                 // read
702                           FILE_ATTRIBUTE_NORMAL,         // normal file
703                           NULL                           // no template file
704                           );
705
706   ::SetErrorMode(prev);
707
708   return ( m_Handle == INVALID_HANDLE_VALUE ) ?
709     Kumu::RESULT_FILEOPEN : Kumu::RESULT_OK;
710 }
711
712 //
713 Kumu::Result_t
714 Kumu::FileReader::Close() const
715 {
716   if ( m_Handle == INVALID_HANDLE_VALUE )
717     return Kumu::RESULT_FILEOPEN;
718
719   // suppress popup window on error
720   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
721   BOOL result = ::CloseHandle(m_Handle);
722   ::SetErrorMode(prev);
723   const_cast<FileReader*>(this)->m_Handle = INVALID_HANDLE_VALUE;
724
725   return ( result == 0 ) ? Kumu::RESULT_FAIL : Kumu::RESULT_OK;
726 }
727
728 //
729 Kumu::Result_t
730 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
731 {
732   if ( m_Handle == INVALID_HANDLE_VALUE )
733     return Kumu::RESULT_STATE;
734
735   LARGE_INTEGER in;
736   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
737   in.QuadPart = position;
738   in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, whence);
739   HRESULT LastError = GetLastError();
740   ::SetErrorMode(prev);
741
742   if ( (LastError != NO_ERROR
743         && (in.LowPart == INVALID_SET_FILE_POINTER
744             || in.LowPart == ERROR_NEGATIVE_SEEK )) )
745     return Kumu::RESULT_READFAIL;
746   
747   return Kumu::RESULT_OK;
748 }
749
750 //
751 Kumu::Result_t
752 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
753 {
754   KM_TEST_NULL_L(pos);
755
756   if ( m_Handle == INVALID_HANDLE_VALUE )
757     return Kumu::RESULT_FILEOPEN;
758
759   LARGE_INTEGER in;
760   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
761   in.QuadPart = (__int64)0;
762   in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, FILE_CURRENT);
763   HRESULT LastError = GetLastError();
764   ::SetErrorMode(prev);
765
766   if ( (LastError != NO_ERROR
767         && (in.LowPart == INVALID_SET_FILE_POINTER
768             || in.LowPart == ERROR_NEGATIVE_SEEK )) )
769     return Kumu::RESULT_READFAIL;
770
771   *pos = (Kumu::fpos_t)in.QuadPart;
772   return Kumu::RESULT_OK;
773 }
774
775 //
776 Kumu::Result_t
777 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
778 {
779   KM_TEST_NULL_L(buf);
780   Result_t result = Kumu::RESULT_OK;
781   DWORD    tmp_count;
782   ui32_t tmp_int;
783
784   if ( read_count == 0 )
785     read_count = &tmp_int;
786
787   *read_count = 0;
788
789   if ( m_Handle == INVALID_HANDLE_VALUE )
790     return Kumu::RESULT_FILEOPEN;
791   
792   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
793   if ( ::ReadFile(m_Handle, buf, buf_len, &tmp_count, NULL) == 0 )
794     result = Kumu::RESULT_READFAIL;
795
796   ::SetErrorMode(prev);
797
798   if ( tmp_count == 0 ) /* EOF */
799     result = Kumu::RESULT_ENDOFFILE;
800
801   if ( KM_SUCCESS(result) )
802     *read_count = tmp_count;
803
804   return result;
805 }
806
807
808
809 //------------------------------------------------------------------------------------------
810 //
811
812 /** @param filename File name (UTF-8 encoded) */
813 Kumu::Result_t
814 Kumu::FileWriter::OpenWrite(const char* filename)
815 {
816   KM_TEST_NULL_STR_L(filename);
817   m_Filename = filename;
818   
819   // suppress popup window on error
820   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
821
822   wchar_t buffer[1024];
823   if (MultiByteToWideChar (CP_UTF8, MB_PRECOMPOSED, filename, -1, buffer, 1024)) {
824           return Kumu::RESULT_FAIL;
825   }
826
827   m_Handle = ::CreateFileW(buffer,
828                           (GENERIC_WRITE|GENERIC_READ),  // open for reading
829                           FILE_SHARE_READ,               // share for reading
830                           NULL,                          // no security
831                           CREATE_ALWAYS,                 // overwrite (beware!)
832                           FILE_ATTRIBUTE_NORMAL,         // normal file
833                           NULL                           // no template file
834                           );
835
836   ::SetErrorMode(prev);
837
838   if ( m_Handle == INVALID_HANDLE_VALUE )
839     return Kumu::RESULT_FILEOPEN;
840   
841   m_IOVec = new h__iovec;
842   return Kumu::RESULT_OK;
843 }
844
845 /** @param filename File name (UTF-8 encoded) */
846 Kumu::Result_t
847 Kumu::FileWriter::OpenModify(const char* filename)
848 {
849   KM_TEST_NULL_STR_L(filename);
850   m_Filename = filename;
851   
852   // suppress popup window on error
853   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
854
855   wchar_t buffer[1024];
856   if (MultiByteToWideChar (CP_UTF8, MB_PRECOMPOSED, filename, -1, buffer, 1024)) {
857           return Kumu::RESULT_FAIL;
858   }
859
860   m_Handle = ::CreateFileW(buffer,
861                           (GENERIC_WRITE|GENERIC_READ),  // open for reading
862                           FILE_SHARE_READ,               // share for reading
863                           NULL,                          // no security
864                           OPEN_ALWAYS,                   // don't truncate existing
865                           FILE_ATTRIBUTE_NORMAL,         // normal file
866                           NULL                           // no template file
867                           );
868
869   ::SetErrorMode(prev);
870
871   if ( m_Handle == INVALID_HANDLE_VALUE )
872     return Kumu::RESULT_FILEOPEN;
873   
874   m_IOVec = new h__iovec;
875   return Kumu::RESULT_OK;
876 }
877
878 //
879 Kumu::Result_t
880 Kumu::FileWriter::Writev(ui32_t* bytes_written)
881 {
882   assert( ! m_IOVec.empty() );
883   register h__iovec* iov = m_IOVec;
884   ui32_t tmp_int;
885
886   if ( bytes_written == 0 )
887     bytes_written = &tmp_int;
888
889   if ( m_Handle == INVALID_HANDLE_VALUE )
890     return Kumu::RESULT_STATE;
891
892   *bytes_written = 0;
893   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
894   Result_t result = Kumu::RESULT_OK;
895
896   // AFAIK, there is no writev() equivalent in the win32 API
897   for ( register int i = 0; i < iov->m_Count; i++ )
898     {
899       ui32_t tmp_count = 0;
900       BOOL wr_result = ::WriteFile(m_Handle,
901                                    iov->m_iovec[i].iov_base,
902                                    iov->m_iovec[i].iov_len,
903                                    (DWORD*)&tmp_count,
904                                    NULL);
905
906       if ( wr_result == 0 || tmp_count != iov->m_iovec[i].iov_len)
907         {
908           result = Kumu::RESULT_WRITEFAIL;
909           break;
910         }
911
912       MaybeHash (iov->m_iovec[i].iov_base, iov->m_iovec[i].iov_len);
913       *bytes_written += tmp_count;
914     }
915
916   ::SetErrorMode(prev);
917   iov->m_Count = 0; // error nor not, all is lost
918
919   return result;
920 }
921
922 //
923 Kumu::Result_t
924 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
925 {
926   KM_TEST_NULL_L(buf);
927   ui32_t tmp_int;
928
929   if ( bytes_written == 0 )
930     bytes_written = &tmp_int;
931
932   if ( m_Handle == INVALID_HANDLE_VALUE )
933     return Kumu::RESULT_STATE;
934
935   // suppress popup window on error
936   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
937   BOOL result = ::WriteFile(m_Handle, buf, buf_len, (DWORD*)bytes_written, NULL);
938   ::SetErrorMode(prev);
939
940   if ( result == 0 || *bytes_written != buf_len )
941     return Kumu::RESULT_WRITEFAIL;
942
943   MaybeHash (buf, buf_len);
944   
945   return Kumu::RESULT_OK;
946 }
947
948 #else // KM_WIN32
949 //------------------------------------------------------------------------------------------
950 // POSIX
951
952 //
953 Kumu::Result_t
954 Kumu::FileReader::OpenRead(const char* filename) const
955 {
956   KM_TEST_NULL_STR_L(filename);
957   const_cast<FileReader*>(this)->m_Filename = filename;
958   const_cast<FileReader*>(this)->m_Handle = open(filename, O_RDONLY, 0);
959   return ( m_Handle == -1L ) ? RESULT_FILEOPEN : RESULT_OK;
960 }
961
962 //
963 Kumu::Result_t
964 Kumu::FileReader::Close() const
965 {
966   if ( m_Handle == -1L )
967     return RESULT_FILEOPEN;
968
969   close(m_Handle);
970   const_cast<FileReader*>(this)->m_Handle = -1L;
971   return RESULT_OK;
972 }
973
974 //
975 Kumu::Result_t
976 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
977 {
978   if ( m_Handle == -1L )
979     return RESULT_FILEOPEN;
980
981   if ( lseek(m_Handle, position, whence) == -1L )
982     return RESULT_BADSEEK;
983
984   return RESULT_OK;
985 }
986
987 //
988 Kumu::Result_t
989 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
990 {
991   KM_TEST_NULL_L(pos);
992
993   if ( m_Handle == -1L )
994     return RESULT_FILEOPEN;
995
996   Kumu::fpos_t tmp_pos;
997
998   if (  (tmp_pos = lseek(m_Handle, 0, SEEK_CUR)) == -1 )
999     return RESULT_READFAIL;
1000
1001   *pos = tmp_pos;
1002   return RESULT_OK;
1003 }
1004
1005 //
1006 Kumu::Result_t
1007 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
1008 {
1009   KM_TEST_NULL_L(buf);
1010   i32_t  tmp_count = 0;
1011   ui32_t tmp_int = 0;
1012
1013   if ( read_count == 0 )
1014     read_count = &tmp_int;
1015
1016   *read_count = 0;
1017
1018   if ( m_Handle == -1L )
1019     return RESULT_FILEOPEN;
1020
1021   if ( (tmp_count = read(m_Handle, buf, buf_len)) == -1L )
1022     return RESULT_READFAIL;
1023
1024   *read_count = tmp_count;
1025   return (tmp_count == 0 ? RESULT_ENDOFFILE : RESULT_OK);
1026 }
1027
1028
1029 //------------------------------------------------------------------------------------------
1030 //
1031
1032 //
1033 Kumu::Result_t
1034 Kumu::FileWriter::OpenWrite(const char* filename)
1035 {
1036   KM_TEST_NULL_STR_L(filename);
1037   m_Filename = filename;
1038   m_Handle = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0664);
1039
1040   if ( m_Handle == -1L )
1041     {
1042       DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
1043       return RESULT_FILEOPEN;
1044     }
1045
1046   m_IOVec = new h__iovec;
1047   return RESULT_OK;
1048 }
1049
1050 //
1051 Kumu::Result_t
1052 Kumu::FileWriter::OpenModify(const char* filename)
1053 {
1054   KM_TEST_NULL_STR_L(filename);
1055   m_Filename = filename;
1056   m_Handle = open(filename, O_RDWR|O_CREAT, 0664);
1057
1058   if ( m_Handle == -1L )
1059     {
1060       DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
1061       return RESULT_FILEOPEN;
1062     }
1063
1064   m_IOVec = new h__iovec;
1065   return RESULT_OK;
1066 }
1067
1068 //
1069 Kumu::Result_t
1070 Kumu::FileWriter::Writev(ui32_t* bytes_written)
1071 {
1072   assert( ! m_IOVec.empty() );
1073   register h__iovec* iov = m_IOVec;
1074   ui32_t tmp_int;
1075
1076   if ( bytes_written == 0 )
1077     bytes_written = &tmp_int;
1078
1079   if ( m_Handle == -1L )
1080     return RESULT_STATE;
1081
1082   int total_size = 0;
1083   for ( int i = 0; i < iov->m_Count; i++ )
1084     total_size += iov->m_iovec[i].iov_len;
1085
1086   int write_size = writev(m_Handle, iov->m_iovec, iov->m_Count);
1087   
1088   if ( write_size == -1L || write_size != total_size )
1089     return RESULT_WRITEFAIL;
1090
1091   for (int i = 0; i < iov->m_Count; ++i) {
1092           MaybeHash (iov->m_iovec[i].iov_base, iov->m_iovec[i].iov_len);
1093   }
1094
1095   iov->m_Count = 0;
1096   *bytes_written = write_size;  
1097   return RESULT_OK;
1098 }
1099
1100 //
1101 Kumu::Result_t
1102 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
1103 {
1104   KM_TEST_NULL_L(buf);
1105   ui32_t tmp_int;
1106
1107   if ( bytes_written == 0 )
1108     bytes_written = &tmp_int;
1109
1110   if ( m_Handle == -1L )
1111     return RESULT_STATE;
1112
1113   int write_size = write(m_Handle, buf, buf_len);
1114   MaybeHash (buf, buf_len);
1115
1116   if ( write_size == -1L || (ui32_t)write_size != buf_len )
1117     return RESULT_WRITEFAIL;
1118
1119   *bytes_written = write_size;
1120   return RESULT_OK;
1121 }
1122
1123
1124 #endif
1125
1126 //------------------------------------------------------------------------------------------
1127
1128
1129 //
1130 Kumu::Result_t
1131 Kumu::ReadFileIntoString(const char* filename, std::string& outString, ui32_t max_size)
1132 {
1133   fsize_t    fsize = 0;
1134   ui32_t     read_size = 0;
1135   FileReader File;
1136   ByteString ReadBuf;
1137
1138   KM_TEST_NULL_STR_L(filename);
1139
1140   Result_t result = File.OpenRead(filename);
1141
1142   if ( KM_SUCCESS(result) )
1143     {
1144       fsize = File.Size();
1145
1146       if ( fsize > (Kumu::fpos_t)max_size )
1147         {
1148           DefaultLogSink().Error("%s: exceeds available buffer size (%u)\n", filename, max_size);
1149           return RESULT_ALLOC;
1150         }
1151
1152       if ( fsize == 0 )
1153         {
1154           DefaultLogSink().Error("%s: zero file size\n", filename);
1155           return RESULT_READFAIL;
1156         }
1157
1158       result = ReadBuf.Capacity((ui32_t)fsize);
1159     }
1160
1161   if ( KM_SUCCESS(result) )
1162     result = File.Read(ReadBuf.Data(), ReadBuf.Capacity(), &read_size);
1163
1164   if ( KM_SUCCESS(result) )
1165     outString.assign((const char*)ReadBuf.RoData(), read_size);
1166
1167   return result;
1168 }
1169
1170
1171 //
1172 Kumu::Result_t
1173 Kumu::WriteStringIntoFile(const char* filename, const std::string& inString)
1174 {
1175   FileWriter File;
1176   ui32_t write_count = 0;
1177   KM_TEST_NULL_STR_L(filename);
1178
1179   Result_t result = File.OpenWrite(filename);
1180
1181   if ( KM_SUCCESS(result) )
1182     result = File.Write((byte_t*)inString.c_str(), inString.length(), &write_count);
1183
1184   return result;
1185 }
1186
1187 //------------------------------------------------------------------------------------------
1188
1189
1190 //
1191 Kumu::Result_t
1192 Kumu::ReadFileIntoObject(const std::string& Filename, Kumu::IArchive& Object, ui32_t)
1193 {
1194   ByteString Buffer;
1195   ui32_t file_size = static_cast<ui32_t>(FileSize(Filename));
1196   Result_t result = Buffer.Capacity(file_size);
1197
1198   if ( KM_SUCCESS(result) )
1199     {
1200       ui32_t read_count = 0;
1201       FileWriter Reader;
1202
1203       result = Reader.OpenRead(Filename.c_str());
1204
1205       if ( KM_SUCCESS(result) )
1206         result = Reader.Read(Buffer.Data(), file_size, &read_count);
1207     
1208       if ( KM_SUCCESS(result) )
1209         {
1210           assert(file_size == read_count);
1211           Buffer.Length(read_count);
1212           MemIOReader MemReader(&Buffer);
1213           result = Object.Unarchive(&MemReader) ? RESULT_OK : RESULT_READFAIL;
1214         }
1215     }
1216
1217   return result;
1218 }
1219
1220 //
1221 Kumu::Result_t
1222 Kumu::WriteObjectIntoFile(const Kumu::IArchive& Object, const std::string& Filename)
1223 {
1224   ByteString Buffer;
1225   Result_t result = Buffer.Capacity(Object.ArchiveLength());
1226
1227   if ( KM_SUCCESS(result) )
1228     {
1229       ui32_t write_count = 0;
1230       FileWriter Writer;
1231       MemIOWriter MemWriter(&Buffer);
1232
1233       result = Object.Archive(&MemWriter) ? RESULT_OK : RESULT_WRITEFAIL;
1234
1235       if ( KM_SUCCESS(result) )
1236         {
1237           Buffer.Length(MemWriter.Length());
1238           result = Writer.OpenWrite(Filename.c_str());
1239         }
1240
1241       if ( KM_SUCCESS(result) )
1242         result = Writer.Write(Buffer.RoData(), Buffer.Length(), &write_count);
1243     }
1244
1245   return result;
1246 }
1247
1248 //------------------------------------------------------------------------------------------
1249 //
1250
1251 //
1252 Result_t
1253 Kumu::ReadFileIntoBuffer(const std::string& Filename, Kumu::ByteString& Buffer, ui32_t)
1254 {
1255   ui32_t file_size = FileSize(Filename);
1256   Result_t result = Buffer.Capacity(file_size);
1257
1258   if ( KM_SUCCESS(result) )
1259     {
1260       ui32_t read_count = 0;
1261       FileWriter Reader;
1262
1263       result = Reader.OpenRead(Filename.c_str());
1264
1265       if ( KM_SUCCESS(result) )
1266         result = Reader.Read(Buffer.Data(), file_size, &read_count);
1267     
1268       if ( KM_SUCCESS(result) )
1269         {
1270           if ( file_size != read_count) 
1271             return RESULT_READFAIL;
1272
1273           Buffer.Length(read_count);
1274         }
1275     }
1276   
1277   return result;
1278 }
1279
1280 //
1281 Result_t
1282 Kumu::WriteBufferIntoFile(const Kumu::ByteString& Buffer, const std::string& Filename)
1283 {
1284   ui32_t write_count = 0;
1285   FileWriter Writer;
1286
1287   Result_t result = Writer.OpenWrite(Filename.c_str());
1288
1289   if ( KM_SUCCESS(result) )
1290     result = Writer.Write(Buffer.RoData(), Buffer.Length(), &write_count);
1291
1292   if ( KM_SUCCESS(result) && Buffer.Length() != write_count) 
1293     return RESULT_WRITEFAIL;
1294
1295   return result;
1296 }
1297
1298 //------------------------------------------------------------------------------------------
1299 //
1300
1301 Kumu::DirScanner::DirScanner()
1302 {
1303
1304 }
1305
1306 Result_t
1307 Kumu::DirScanner::Open (const char* filename)
1308 {
1309         KM_TEST_NULL_L (filename);
1310
1311         if (!boost::filesystem::is_directory(filename)) {
1312                 return RESULT_NOT_FOUND;
1313         }
1314         
1315         _iterator = boost::filesystem::directory_iterator (filename);
1316         return RESULT_OK;
1317 }
1318
1319 Result_t
1320 Kumu::DirScanner::GetNext (char* filename)
1321 {
1322         KM_TEST_NULL_L (filename);
1323         
1324         if (_iterator == boost::filesystem::directory_iterator()) {
1325                 return RESULT_ENDOFFILE;
1326         }
1327
1328 #if BOOST_FILESYSTEM_VERSION == 3       
1329         std::string f = boost::filesystem::path(*_iterator).filename().generic_string();
1330 #else
1331         std::string f = boost::filesystem::path(*_iterator).filename();
1332 #endif  
1333         strncpy (filename, f.c_str(), MaxFilePath);
1334         ++_iterator;
1335         return RESULT_OK;
1336 }
1337
1338 //------------------------------------------------------------------------------------------
1339
1340 //
1341 // Attention Windows users: make sure to use the proper separator character
1342 // with these functions.
1343 //
1344
1345 // given a path string, create any missing directories so that PathIsDirectory(Path) is true.
1346 //
1347 Result_t
1348 Kumu::CreateDirectoriesInPath(const std::string& Path)
1349 {
1350   bool abs = PathIsAbsolute(Path);
1351   PathCompList_t PathComps, TmpPathComps;
1352
1353   PathToComponents(Path, PathComps);
1354
1355   while ( ! PathComps.empty() )
1356     {
1357       TmpPathComps.push_back(PathComps.front());
1358       PathComps.pop_front();
1359       std::string tmp_path = abs ? ComponentsToAbsolutePath(TmpPathComps) : ComponentsToPath(TmpPathComps);
1360
1361       if ( ! PathIsDirectory(tmp_path) )
1362         {
1363 #ifdef KM_WIN32
1364           if ( _mkdir(tmp_path.c_str()) != 0 )
1365 #else // KM_WIN32
1366           if ( mkdir(tmp_path.c_str(), 0775) != 0 )
1367 #endif // KM_WIN32
1368             {
1369               DefaultLogSink().Error("CreateDirectoriesInPath mkdir %s: %s\n",
1370                                      tmp_path.c_str(), strerror(errno));
1371               return RESULT_DIR_CREATE;
1372             }
1373         }
1374     }
1375
1376   return RESULT_OK;
1377 }
1378
1379
1380 //
1381 Result_t
1382 Kumu::DeleteFile(const std::string& filename)
1383 {
1384   if ( _unlink(filename.c_str()) == 0 )
1385     return RESULT_OK;
1386
1387   switch ( errno )
1388     {
1389     case ENOENT:
1390     case ENOTDIR: return RESULT_NOTAFILE;
1391
1392     case EROFS:
1393     case EBUSY:
1394     case EACCES:
1395     case EPERM:   return RESULT_NO_PERM;
1396     }
1397
1398   DefaultLogSink().Error("DeleteFile %s: %s\n", filename.c_str(), strerror(errno));
1399   return RESULT_FAIL;
1400 }
1401
1402 //
1403 Result_t
1404 h__DeletePath(const std::string& pathname)
1405 {
1406   if ( pathname.empty() )
1407     return RESULT_NULL_STR;
1408
1409   Result_t result = RESULT_OK;
1410
1411   if ( ! PathIsDirectory(pathname) )
1412     {
1413       result = DeleteFile(pathname);
1414     }
1415   else
1416     {
1417       {
1418         DirScanner TestDir;
1419         char       next_file[Kumu::MaxFilePath];
1420         result = TestDir.Open(pathname.c_str());
1421
1422         while ( KM_SUCCESS(result) && KM_SUCCESS(TestDir.GetNext(next_file)) )
1423           {
1424             if ( next_file[0] == '.' )
1425               {
1426                 if ( next_file[1] ==  0 )
1427                   continue; // don't delete 'this'
1428                 
1429                 if ( next_file[1] == '.' && next_file[2] ==  0 )
1430                   continue; // don't delete 'this' parent
1431               }
1432
1433             result = h__DeletePath(pathname + std::string("/") + next_file);
1434           }
1435       }
1436
1437       if ( _rmdir(pathname.c_str()) != 0 )
1438         {
1439           switch ( errno )
1440             {
1441             case ENOENT:
1442             case ENOTDIR:
1443               result = RESULT_NOTAFILE;
1444               break;
1445
1446             case EROFS:
1447             case EBUSY:
1448             case EACCES:
1449             case EPERM:
1450               result = RESULT_NO_PERM;
1451               break;
1452
1453             default:
1454               DefaultLogSink().Error("DeletePath %s: %s\n", pathname.c_str(), strerror(errno));
1455               result = RESULT_FAIL;
1456             }
1457         }
1458     }
1459
1460   return result;
1461 }
1462
1463 //
1464 Result_t
1465 Kumu::DeletePath(const std::string& pathname)
1466 {
1467   std::string c_pathname = PathMakeAbsolute(PathMakeCanonical(pathname));
1468   DefaultLogSink().Debug("DeletePath (%s) c(%s)\n", pathname.c_str(), c_pathname.c_str());
1469   return h__DeletePath(c_pathname);
1470 }
1471
1472
1473 //------------------------------------------------------------------------------------------
1474 //
1475
1476
1477 Result_t
1478 Kumu::FreeSpaceForPath(const std::string& path, Kumu::fsize_t& free_space, Kumu::fsize_t& total_space)
1479 {
1480 #ifdef KM_WIN32
1481         ULARGE_INTEGER lTotalNumberOfBytes;
1482         ULARGE_INTEGER lTotalNumberOfFreeBytes;
1483
1484         BOOL fResult = ::GetDiskFreeSpaceExA(path.c_str(), NULL, &lTotalNumberOfBytes, &lTotalNumberOfFreeBytes);
1485         if (fResult) {
1486       free_space = static_cast<Kumu::fsize_t>(lTotalNumberOfFreeBytes.QuadPart);
1487       total_space = static_cast<Kumu::fsize_t>(lTotalNumberOfBytes.QuadPart);
1488       return RESULT_OK;
1489         }
1490         HRESULT LastError = ::GetLastError();
1491
1492         DefaultLogSink().Error("FreeSpaceForPath GetDiskFreeSpaceEx %s: %lu\n", path.c_str(), ::GetLastError());
1493         return RESULT_FAIL;
1494 #else // KM_WIN32
1495   struct statfs s;
1496
1497   if ( statfs(path.c_str(), &s) == 0 )
1498     {
1499       if ( s.f_blocks < 1 )
1500         {
1501           DefaultLogSink().Error("File system %s has impossible size: %ld\n",
1502                                  path.c_str(), s.f_blocks);
1503           return RESULT_FAIL;
1504         }
1505
1506       free_space = (Kumu::fsize_t)s.f_bsize * (Kumu::fsize_t)s.f_bavail;
1507       total_space = (Kumu::fsize_t)s.f_bsize * (Kumu::fsize_t)s.f_blocks;
1508       return RESULT_OK;
1509     }
1510
1511   switch ( errno )
1512     {
1513     case ENOENT:
1514     case ENOTDIR: return RESULT_NOTAFILE;
1515     case EACCES:  return RESULT_NO_PERM;
1516     }
1517
1518   DefaultLogSink().Error("FreeSpaceForPath statfs %s: %s\n", path.c_str(), strerror(errno));
1519   return RESULT_FAIL;
1520 #endif // KM_WIN32
1521
1522
1523
1524 //
1525 // end KM_fileio.cpp
1526 //