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