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