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