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