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