eliminated spurious estra leading '/'
[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, 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), 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   PathToComponents(cwd_buf, CList);
354   CList.push_back(Path);
355
356   return ComponentsToAbsolutePath(s_PathMakeCanonical(CList, 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 std::string
434 Kumu::PathJoin(const std::string& Path1, const std::string& Path2, char separator)
435 {
436   return Path1 + separator + Path2;
437 }
438
439 //
440 std::string
441 Kumu::PathJoin(const std::string& Path1, const std::string& Path2, const std::string& Path3, char separator)
442 {
443   return Path1 + separator + Path2 + separator + Path3;
444 }
445
446 //
447 std::string
448 Kumu::PathJoin(const std::string& Path1, const std::string& Path2,
449                const std::string& Path3, const std::string& Path4, char separator)
450 {
451   return Path1 + separator + Path2 + separator + Path3 + separator + Path4;
452 }
453
454 //
455 Kumu::PathList_t&
456 Kumu::FindInPaths(const IPathMatch& Pattern, const Kumu::PathList_t& SearchPaths,
457                   Kumu::PathList_t& FoundPaths, bool one_shot, char separator)
458 {
459   PathList_t::const_iterator si;
460   for ( si = SearchPaths.begin(); si != SearchPaths.end(); si++ )
461     {
462       FindInPath(Pattern, *si, FoundPaths, one_shot, separator);
463
464       if ( one_shot && ! FoundPaths.empty() )
465         break;
466     }
467
468   return FoundPaths;
469 }
470
471 //
472 Kumu::PathList_t&
473 Kumu::FindInPath(const IPathMatch& Pattern, const std::string& SearchDir,
474                   Kumu::PathList_t& FoundPaths, bool one_shot, char separator)
475 {
476   char name_buf[MaxFilePath];
477   DirScanner Dir;
478
479   if ( KM_SUCCESS(Dir.Open(SearchDir.c_str())) )
480     {
481       while ( KM_SUCCESS(Dir.GetNext(name_buf)) )
482         {
483           if ( name_buf[0] == '.' ) continue; // no hidden files
484           std::string tmp_path = SearchDir + separator + name_buf;
485
486           if ( PathIsDirectory(tmp_path.c_str()) )
487             FindInPath(Pattern, tmp_path, FoundPaths, one_shot, separator);
488           
489           else if ( Pattern.Match(name_buf) )
490             {
491               FoundPaths.push_back(SearchDir + separator + name_buf);
492               if ( one_shot )
493                 break;
494             }
495         }
496     }
497
498   return FoundPaths;
499 }
500
501
502 #ifndef KM_WIN32
503
504 //
505 Kumu::PathMatchRegex::PathMatchRegex(const std::string& s)
506 {
507   int result = regcomp(&m_regex, s.c_str(), REG_NOSUB); // (REG_EXTENDED|REG_NOSUB|REG_NEWLINE));
508
509   if ( result )
510     {
511       char buf[128];
512       regerror(result, &m_regex, buf, 128);
513       DefaultLogSink().Error("PathMatchRegex: %s\n", buf);
514       regfree(&m_regex);
515     }
516 }
517
518 Kumu::PathMatchRegex::PathMatchRegex(const PathMatchRegex& rhs) : IPathMatch() {
519   m_regex = rhs.m_regex;
520 }
521
522 Kumu::PathMatchRegex::~PathMatchRegex() {
523   regfree(&m_regex);
524 }
525
526 bool
527 Kumu::PathMatchRegex::Match(const std::string& s) const {
528   return ( regexec(&m_regex, s.c_str(), 0, 0, 0) == 0 );
529 }
530
531
532
533 //
534 Kumu::PathMatchGlob::PathMatchGlob(const std::string& glob)
535 {
536   std::string regex; // convert glob to regex
537
538   for ( const char* p = glob.c_str(); *p != 0; p++ )
539     {
540       switch (*p)
541         {
542         case '.':  regex += "\\.";  break;
543         case '*':  regex += ".*";   break;
544         case '?':  regex += ".?";   break;
545         default:   regex += *p;
546         }
547     }
548   regex += '$';
549
550   int result = regcomp(&m_regex, regex.c_str(), REG_NOSUB);
551
552   if ( result )
553     {
554       char buf[128];
555       regerror(result, &m_regex, buf, 128);
556       DefaultLogSink().Error("PathMatchRegex: %s\n", buf);
557       regfree(&m_regex);
558     }
559 }
560
561 Kumu::PathMatchGlob::PathMatchGlob(const PathMatchGlob& rhs) : IPathMatch() {
562   m_regex = rhs.m_regex;
563 }
564
565 Kumu::PathMatchGlob::~PathMatchGlob() {
566   regfree(&m_regex);
567 }
568
569 bool
570 Kumu::PathMatchGlob::Match(const std::string& s) const {
571   return ( regexec(&m_regex, s.c_str(), 0, 0, 0) == 0 );
572 }
573
574 #endif
575
576 //------------------------------------------------------------------------------------------
577 // portable aspects of the file classes
578
579 const int IOVecMaxEntries = 32; // we never use more that 3, but that number seems somehow small...
580
581 //
582 class Kumu::FileWriter::h__iovec
583 {
584 public:
585   int            m_Count;
586   struct iovec   m_iovec[IOVecMaxEntries];
587   h__iovec() : m_Count(0) {}
588 };
589
590
591
592 //
593 Kumu::fsize_t
594 Kumu::FileReader::Size() const
595 {
596 #ifdef KM_WIN32
597   return FileSize(m_Filename.c_str());
598 #else
599   fstat_t info;
600
601   if ( KM_SUCCESS(do_fstat(m_Handle, &info)) )
602     {
603       if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
604         return(info.st_size);
605     }
606 #endif
607
608   return 0;
609 }
610
611 // these are declared here instead of in the header file
612 // because we have a mem_ptr that is managing a hidden class
613 Kumu::FileWriter::FileWriter() {}
614 Kumu::FileWriter::~FileWriter() {}
615
616 //
617 Kumu::Result_t
618 Kumu::FileWriter::Writev(const byte_t* buf, ui32_t buf_len)
619 {
620   assert( ! m_IOVec.empty() );
621   register h__iovec* iov = m_IOVec;
622   KM_TEST_NULL_L(buf);
623
624   if ( iov->m_Count >= IOVecMaxEntries )
625     {
626       DefaultLogSink().Error("The iovec is full! Only %u entries allowed before a flush.\n",
627                              IOVecMaxEntries);
628       return RESULT_WRITEFAIL;
629     }
630
631   iov->m_iovec[iov->m_Count].iov_base = (char*)buf; // stupid iovec uses char*
632   iov->m_iovec[iov->m_Count].iov_len = buf_len;
633   iov->m_Count++;
634
635   return RESULT_OK;
636 }
637
638
639 #ifdef KM_WIN32
640 //------------------------------------------------------------------------------------------
641 //
642
643 Kumu::Result_t
644 Kumu::FileReader::OpenRead(const char* filename) const
645 {
646   KM_TEST_NULL_STR_L(filename);
647   const_cast<FileReader*>(this)->m_Filename = filename;
648   
649   // suppress popup window on error
650   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
651
652   const_cast<FileReader*>(this)->m_Handle = ::CreateFile(filename,
653                           (GENERIC_READ),                // open for reading
654                           FILE_SHARE_READ,               // share for reading
655                           NULL,                          // no security
656                           OPEN_EXISTING,                 // read
657                           FILE_ATTRIBUTE_NORMAL,         // normal file
658                           NULL                           // no template file
659                           );
660
661   ::SetErrorMode(prev);
662
663   return ( m_Handle == INVALID_HANDLE_VALUE ) ?
664     Kumu::RESULT_FILEOPEN : Kumu::RESULT_OK;
665 }
666
667 //
668 Kumu::Result_t
669 Kumu::FileReader::Close() const
670 {
671   if ( m_Handle == INVALID_HANDLE_VALUE )
672     return Kumu::RESULT_FILEOPEN;
673
674   // suppress popup window on error
675   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
676   BOOL result = ::CloseHandle(m_Handle);
677   ::SetErrorMode(prev);
678   const_cast<FileReader*>(this)->m_Handle = INVALID_HANDLE_VALUE;
679
680   return ( result == 0 ) ? Kumu::RESULT_FAIL : Kumu::RESULT_OK;
681 }
682
683 //
684 Kumu::Result_t
685 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
686 {
687   if ( m_Handle == INVALID_HANDLE_VALUE )
688     return Kumu::RESULT_STATE;
689
690   LARGE_INTEGER in;
691   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
692   in.QuadPart = position;
693   in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, whence);
694   HRESULT LastError = GetLastError();
695   ::SetErrorMode(prev);
696
697   if ( (LastError != NO_ERROR
698         && (in.LowPart == INVALID_SET_FILE_POINTER
699             || in.LowPart == ERROR_NEGATIVE_SEEK )) )
700     return Kumu::RESULT_READFAIL;
701   
702   return Kumu::RESULT_OK;
703 }
704
705 //
706 Kumu::Result_t
707 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
708 {
709   KM_TEST_NULL_L(pos);
710
711   if ( m_Handle == INVALID_HANDLE_VALUE )
712     return Kumu::RESULT_FILEOPEN;
713
714   LARGE_INTEGER in;
715   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
716   in.QuadPart = (__int64)0;
717   in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, FILE_CURRENT);
718   HRESULT LastError = GetLastError();
719   ::SetErrorMode(prev);
720
721   if ( (LastError != NO_ERROR
722         && (in.LowPart == INVALID_SET_FILE_POINTER
723             || in.LowPart == ERROR_NEGATIVE_SEEK )) )
724     return Kumu::RESULT_READFAIL;
725
726   *pos = (Kumu::fpos_t)in.QuadPart;
727   return Kumu::RESULT_OK;
728 }
729
730 //
731 Kumu::Result_t
732 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
733 {
734   KM_TEST_NULL_L(buf);
735   Result_t result = Kumu::RESULT_OK;
736   DWORD    tmp_count;
737   ui32_t tmp_int;
738
739   if ( read_count == 0 )
740     read_count = &tmp_int;
741
742   *read_count = 0;
743
744   if ( m_Handle == INVALID_HANDLE_VALUE )
745     return Kumu::RESULT_FILEOPEN;
746   
747   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
748   if ( ::ReadFile(m_Handle, buf, buf_len, &tmp_count, NULL) == 0 )
749     result = Kumu::RESULT_READFAIL;
750
751   ::SetErrorMode(prev);
752
753   if ( tmp_count == 0 ) /* EOF */
754     result = Kumu::RESULT_ENDOFFILE;
755
756   if ( KM_SUCCESS(result) )
757     *read_count = tmp_count;
758
759   return result;
760 }
761
762
763
764 //------------------------------------------------------------------------------------------
765 //
766
767 //
768 Kumu::Result_t
769 Kumu::FileWriter::OpenWrite(const char* filename)
770 {
771   KM_TEST_NULL_STR_L(filename);
772   m_Filename = filename;
773   
774   // suppress popup window on error
775   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
776
777   m_Handle = ::CreateFile(filename,
778                           (GENERIC_WRITE|GENERIC_READ),  // open for reading
779                           FILE_SHARE_READ,               // share for reading
780                           NULL,                          // no security
781                           CREATE_ALWAYS,                 // overwrite (beware!)
782                           FILE_ATTRIBUTE_NORMAL,         // normal file
783                           NULL                           // no template file
784                           );
785
786   ::SetErrorMode(prev);
787
788   if ( m_Handle == INVALID_HANDLE_VALUE )
789     return Kumu::RESULT_FILEOPEN;
790   
791   m_IOVec = new h__iovec;
792   return Kumu::RESULT_OK;
793 }
794
795 //
796 Kumu::Result_t
797 Kumu::FileWriter::Writev(ui32_t* bytes_written)
798 {
799   assert( ! m_IOVec.empty() );
800   register h__iovec* iov = m_IOVec;
801   ui32_t tmp_int;
802
803   if ( bytes_written == 0 )
804     bytes_written = &tmp_int;
805
806   if ( m_Handle == INVALID_HANDLE_VALUE )
807     return Kumu::RESULT_STATE;
808
809   *bytes_written = 0;
810   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
811   Result_t result = Kumu::RESULT_OK;
812
813   // AFAIK, there is no writev() equivalent in the win32 API
814   for ( register int i = 0; i < iov->m_Count; i++ )
815     {
816       ui32_t tmp_count = 0;
817       BOOL wr_result = ::WriteFile(m_Handle,
818                                    iov->m_iovec[i].iov_base,
819                                    iov->m_iovec[i].iov_len,
820                                    (DWORD*)&tmp_count,
821                                    NULL);
822
823       if ( wr_result == 0 || tmp_count != iov->m_iovec[i].iov_len)
824         {
825           result = Kumu::RESULT_WRITEFAIL;
826           break;
827         }
828
829       *bytes_written += tmp_count;
830     }
831
832   ::SetErrorMode(prev);
833   iov->m_Count = 0; // error nor not, all is lost
834
835   return result;
836 }
837
838 //
839 Kumu::Result_t
840 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
841 {
842   KM_TEST_NULL_L(buf);
843   ui32_t tmp_int;
844
845   if ( bytes_written == 0 )
846     bytes_written = &tmp_int;
847
848   if ( m_Handle == INVALID_HANDLE_VALUE )
849     return Kumu::RESULT_STATE;
850
851   // suppress popup window on error
852   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
853   BOOL result = ::WriteFile(m_Handle, buf, buf_len, (DWORD*)bytes_written, NULL);
854   ::SetErrorMode(prev);
855
856   if ( result == 0 || *bytes_written != buf_len )
857     return Kumu::RESULT_WRITEFAIL;
858
859   return Kumu::RESULT_OK;
860 }
861
862 #else // KM_WIN32
863 //------------------------------------------------------------------------------------------
864 // POSIX
865
866 //
867 Kumu::Result_t
868 Kumu::FileReader::OpenRead(const char* filename) const
869 {
870   KM_TEST_NULL_STR_L(filename);
871   const_cast<FileReader*>(this)->m_Filename = filename;
872   const_cast<FileReader*>(this)->m_Handle = open(filename, O_RDONLY, 0);
873   return ( m_Handle == -1L ) ? RESULT_FILEOPEN : RESULT_OK;
874 }
875
876 //
877 Kumu::Result_t
878 Kumu::FileReader::Close() const
879 {
880   if ( m_Handle == -1L )
881     return RESULT_FILEOPEN;
882
883   close(m_Handle);
884   const_cast<FileReader*>(this)->m_Handle = -1L;
885   return RESULT_OK;
886 }
887
888 //
889 Kumu::Result_t
890 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
891 {
892   if ( m_Handle == -1L )
893     return RESULT_FILEOPEN;
894
895   if ( lseek(m_Handle, position, whence) == -1L )
896     return RESULT_BADSEEK;
897
898   return RESULT_OK;
899 }
900
901 //
902 Kumu::Result_t
903 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
904 {
905   KM_TEST_NULL_L(pos);
906
907   if ( m_Handle == -1L )
908     return RESULT_FILEOPEN;
909
910   Kumu::fpos_t tmp_pos;
911
912   if (  (tmp_pos = lseek(m_Handle, 0, SEEK_CUR)) == -1 )
913     return RESULT_READFAIL;
914
915   *pos = tmp_pos;
916   return RESULT_OK;
917 }
918
919 //
920 Kumu::Result_t
921 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
922 {
923   KM_TEST_NULL_L(buf);
924   i32_t  tmp_count = 0;
925   ui32_t tmp_int = 0;
926
927   if ( read_count == 0 )
928     read_count = &tmp_int;
929
930   *read_count = 0;
931
932   if ( m_Handle == -1L )
933     return RESULT_FILEOPEN;
934
935   if ( (tmp_count = read(m_Handle, buf, buf_len)) == -1L )
936     return RESULT_READFAIL;
937
938   *read_count = tmp_count;
939   return (tmp_count == 0 ? RESULT_ENDOFFILE : RESULT_OK);
940 }
941
942
943 //------------------------------------------------------------------------------------------
944 //
945
946 //
947 Kumu::Result_t
948 Kumu::FileWriter::OpenWrite(const char* filename)
949 {
950   KM_TEST_NULL_STR_L(filename);
951   m_Filename = filename;
952   m_Handle = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0664);
953
954   if ( m_Handle == -1L )
955     {
956       DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
957       return RESULT_FILEOPEN;
958     }
959
960   m_IOVec = new h__iovec;
961   return RESULT_OK;
962 }
963
964 //
965 Kumu::Result_t
966 Kumu::FileWriter::OpenModify(const char* filename)
967 {
968   KM_TEST_NULL_STR_L(filename);
969   m_Filename = filename;
970   m_Handle = open(filename, O_RDWR|O_CREAT, 0664);
971
972   if ( m_Handle == -1L )
973     {
974       DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
975       return RESULT_FILEOPEN;
976     }
977
978   m_IOVec = new h__iovec;
979   return RESULT_OK;
980 }
981
982 //
983 Kumu::Result_t
984 Kumu::FileWriter::Writev(ui32_t* bytes_written)
985 {
986   assert( ! m_IOVec.empty() );
987   register h__iovec* iov = m_IOVec;
988   ui32_t tmp_int;
989
990   if ( bytes_written == 0 )
991     bytes_written = &tmp_int;
992
993   if ( m_Handle == -1L )
994     return RESULT_STATE;
995
996   int total_size = 0;
997   for ( int i = 0; i < iov->m_Count; i++ )
998     total_size += iov->m_iovec[i].iov_len;
999
1000   int write_size = writev(m_Handle, iov->m_iovec, iov->m_Count);
1001   
1002   if ( write_size == -1L || write_size != total_size )
1003     return RESULT_WRITEFAIL;
1004
1005   iov->m_Count = 0;
1006   *bytes_written = write_size;  
1007   return RESULT_OK;
1008 }
1009
1010 //
1011 Kumu::Result_t
1012 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
1013 {
1014   KM_TEST_NULL_L(buf);
1015   ui32_t tmp_int;
1016
1017   if ( bytes_written == 0 )
1018     bytes_written = &tmp_int;
1019
1020   if ( m_Handle == -1L )
1021     return RESULT_STATE;
1022
1023   int write_size = write(m_Handle, buf, buf_len);
1024
1025   if ( write_size == -1L || (ui32_t)write_size != buf_len )
1026     return RESULT_WRITEFAIL;
1027
1028   *bytes_written = write_size;
1029   return RESULT_OK;
1030 }
1031
1032
1033 #endif // KM_WIN32
1034
1035 //------------------------------------------------------------------------------------------
1036
1037
1038 //
1039 Kumu::Result_t
1040 Kumu::ReadFileIntoString(const char* filename, std::string& outString, ui32_t max_size)
1041 {
1042   fsize_t    fsize = 0;
1043   ui32_t     read_size = 0;
1044   FileReader File;
1045   ByteString ReadBuf;
1046
1047   KM_TEST_NULL_STR_L(filename);
1048
1049   Result_t result = File.OpenRead(filename);
1050
1051   if ( KM_SUCCESS(result) )
1052     {
1053       fsize = File.Size();
1054
1055       if ( fsize > (Kumu::fpos_t)max_size )
1056         {
1057           DefaultLogSink().Error("%s: exceeds available buffer size (%u)\n", filename, max_size);
1058           return RESULT_ALLOC;
1059         }
1060
1061       if ( fsize == 0 )
1062         {
1063           DefaultLogSink().Error("%s: zero file size\n", filename);
1064           return RESULT_READFAIL;
1065         }
1066
1067       result = ReadBuf.Capacity((ui32_t)fsize);
1068     }
1069
1070   if ( KM_SUCCESS(result) )
1071     result = File.Read(ReadBuf.Data(), ReadBuf.Capacity(), &read_size);
1072
1073   if ( KM_SUCCESS(result) )
1074     outString.assign((const char*)ReadBuf.RoData(), read_size);
1075
1076   return result;
1077 }
1078
1079
1080 //
1081 Kumu::Result_t
1082 Kumu::WriteStringIntoFile(const char* filename, const std::string& inString)
1083 {
1084   FileWriter File;
1085   ui32_t write_count = 0;
1086   KM_TEST_NULL_STR_L(filename);
1087
1088   Result_t result = File.OpenWrite(filename);
1089
1090   if ( KM_SUCCESS(result) )
1091     result = File.Write((byte_t*)inString.c_str(), inString.length(), &write_count);
1092
1093   return result;
1094 }
1095
1096 //------------------------------------------------------------------------------------------
1097
1098
1099 //
1100 Kumu::Result_t
1101 Kumu::ReadFileIntoObject(const std::string& Filename, Kumu::IArchive& Object, ui32_t max_size)
1102 {
1103   ByteString Buffer;
1104   ui32_t file_size = static_cast<ui32_t>(FileSize(Filename));
1105   Result_t result = Buffer.Capacity(file_size);
1106
1107   if ( KM_SUCCESS(result) )
1108     {
1109       ui32_t read_count = 0;
1110       FileWriter Reader;
1111
1112       result = Reader.OpenRead(Filename.c_str());
1113
1114       if ( KM_SUCCESS(result) )
1115         result = Reader.Read(Buffer.Data(), file_size, &read_count);
1116     
1117       if ( KM_SUCCESS(result) )
1118         {
1119           assert(file_size == read_count);
1120           Buffer.Length(read_count);
1121           MemIOReader MemReader(&Buffer);
1122           result = Object.Unarchive(&MemReader) ? RESULT_OK : RESULT_READFAIL;
1123         }
1124     }
1125
1126   return result;
1127 }
1128
1129 //
1130 Kumu::Result_t
1131 Kumu::WriteObjectIntoFile(const Kumu::IArchive& Object, const std::string& Filename)
1132 {
1133   ByteString Buffer;
1134   Result_t result = Buffer.Capacity(Object.ArchiveLength());
1135
1136   if ( KM_SUCCESS(result) )
1137     {
1138       ui32_t write_count = 0;
1139       FileWriter Writer;
1140       MemIOWriter MemWriter(&Buffer);
1141
1142       result = Object.Archive(&MemWriter) ? RESULT_OK : RESULT_WRITEFAIL;
1143
1144       if ( KM_SUCCESS(result) )
1145         {
1146           Buffer.Length(MemWriter.Length());
1147           result = Writer.OpenWrite(Filename.c_str());
1148         }
1149
1150       if ( KM_SUCCESS(result) )
1151         result = Writer.Write(Buffer.RoData(), Buffer.Length(), &write_count);
1152     }
1153
1154   return result;
1155 }
1156
1157 //------------------------------------------------------------------------------------------
1158 //
1159
1160 //
1161 Result_t
1162 Kumu::ReadFileIntoBuffer(const std::string& Filename, Kumu::ByteString& Buffer, ui32_t max_size)
1163 {
1164   ui32_t file_size = FileSize(Filename);
1165   Result_t result = Buffer.Capacity(file_size);
1166
1167   if ( KM_SUCCESS(result) )
1168     {
1169       ui32_t read_count = 0;
1170       FileWriter Reader;
1171
1172       result = Reader.OpenRead(Filename.c_str());
1173
1174       if ( KM_SUCCESS(result) )
1175         result = Reader.Read(Buffer.Data(), file_size, &read_count);
1176     
1177       if ( KM_SUCCESS(result) )
1178         {
1179           if ( file_size != read_count) 
1180             return RESULT_READFAIL;
1181
1182           Buffer.Length(read_count);
1183         }
1184     }
1185   
1186   return result;
1187 }
1188
1189 //
1190 Result_t
1191 Kumu::WriteBufferIntoFile(const Kumu::ByteString& Buffer, const std::string& Filename)
1192 {
1193   ui32_t write_count = 0;
1194   FileWriter Writer;
1195
1196   Result_t result = Writer.OpenWrite(Filename.c_str());
1197
1198   if ( KM_SUCCESS(result) )
1199     result = Writer.Write(Buffer.RoData(), Buffer.Length(), &write_count);
1200
1201   if ( KM_SUCCESS(result) && Buffer.Length() != write_count) 
1202     return RESULT_WRITEFAIL;
1203
1204   return result;
1205 }
1206
1207 //------------------------------------------------------------------------------------------
1208 //
1209
1210
1211 // Win32 directory scanner
1212 //
1213 #ifdef KM_WIN32
1214
1215 //
1216 Kumu::DirScanner::DirScanner(void) : m_Handle(-1) {}
1217
1218 //
1219 //
1220 Result_t
1221 Kumu::DirScanner::Open(const char* filename)
1222 {
1223   KM_TEST_NULL_STR_L(filename);
1224
1225   // we need to append a '*' to read the entire directory
1226   ui32_t fn_len = strlen(filename); 
1227   char* tmp_file = (char*)malloc(fn_len + 8);
1228
1229   if ( tmp_file == 0 )
1230     return RESULT_ALLOC;
1231
1232   strcpy(tmp_file, filename);
1233   char* p = &tmp_file[fn_len] - 1;
1234
1235   if ( *p != '/' && *p != '\\' )
1236     {
1237       p++;
1238       *p++ = '/';
1239     }
1240
1241   *p++ = '*';
1242   *p = 0;
1243   // whew...
1244
1245   m_Handle = _findfirsti64(tmp_file, &m_FileInfo);
1246   Result_t result = RESULT_OK;
1247
1248   if ( m_Handle == -1 )
1249     result = RESULT_NOT_FOUND;
1250
1251   return result;
1252 }
1253
1254
1255 //
1256 //
1257 Result_t
1258 Kumu::DirScanner::Close()
1259 {
1260   if ( m_Handle == -1 )
1261     return RESULT_FILEOPEN;
1262
1263   if ( _findclose((long)m_Handle) == -1 )
1264     return RESULT_FAIL;
1265
1266   m_Handle = -1;
1267   return RESULT_OK;
1268 }
1269
1270
1271 // This sets filename param to the same per-instance buffer every time, so
1272 // the value will change on the next call
1273 Result_t
1274 Kumu::DirScanner::GetNext(char* filename)
1275 {
1276   KM_TEST_NULL_L(filename);
1277
1278   if ( m_Handle == -1 )
1279     return RESULT_FILEOPEN;
1280
1281   if ( m_FileInfo.name[0] == '\0' )
1282     return RESULT_ENDOFFILE;
1283
1284   strncpy(filename, m_FileInfo.name, MaxFilePath);
1285   Result_t result = RESULT_OK;
1286
1287   if ( _findnexti64((long)m_Handle, &m_FileInfo) == -1 )
1288     {
1289       m_FileInfo.name[0] = '\0';
1290           
1291       if ( errno != ENOENT )
1292         result = RESULT_FAIL;
1293     }
1294
1295   return result;
1296 }
1297
1298
1299 #else // KM_WIN32
1300
1301 // POSIX directory scanner
1302
1303 //
1304 Kumu::DirScanner::DirScanner(void) : m_Handle(NULL) {}
1305
1306 //
1307 Result_t
1308 Kumu::DirScanner::Open(const char* filename)
1309 {
1310   KM_TEST_NULL_STR_L(filename);
1311
1312   Result_t result = RESULT_OK;
1313
1314   if ( ( m_Handle = opendir(filename) ) == NULL )
1315     {
1316       switch ( errno )
1317         {
1318         case ENOENT:
1319         case ENOTDIR:
1320           result = RESULT_NOTAFILE;
1321         case EACCES:
1322           result = RESULT_NO_PERM;
1323         case ELOOP:
1324         case ENAMETOOLONG:
1325           result = RESULT_PARAM;
1326         case EMFILE:
1327         case ENFILE:
1328           result = RESULT_STATE;
1329         default:
1330           DefaultLogSink().Error("DirScanner::Open(%s): %s\n", filename, strerror(errno));
1331           result = RESULT_FAIL;
1332         }
1333     }
1334
1335   return result;
1336 }
1337
1338
1339 //
1340 Result_t
1341 Kumu::DirScanner::Close()
1342 {
1343   if ( m_Handle == NULL )
1344     return RESULT_FILEOPEN;
1345
1346   if ( closedir(m_Handle) == -1 ) {
1347     switch ( errno )
1348       {
1349       case EBADF:
1350       case EINTR:
1351         return RESULT_STATE;
1352       default:
1353         DefaultLogSink().Error("DirScanner::Close(): %s\n", strerror(errno));
1354         return RESULT_FAIL;
1355       }
1356   }
1357
1358   m_Handle = NULL;
1359   return RESULT_OK;
1360 }
1361
1362
1363 //
1364 Result_t
1365 Kumu::DirScanner::GetNext(char* filename)
1366 {
1367   KM_TEST_NULL_L(filename);
1368
1369   if ( m_Handle == NULL )
1370     return RESULT_FILEOPEN;
1371
1372   struct dirent* entry;
1373
1374   for (;;)
1375     {
1376       if ( ( entry = readdir(m_Handle)) == NULL )
1377         return RESULT_ENDOFFILE;
1378
1379       break;
1380     }
1381
1382   strncpy(filename, entry->d_name, MaxFilePath);
1383   return RESULT_OK;
1384 }
1385
1386
1387 #endif // KM_WIN32
1388
1389
1390 //------------------------------------------------------------------------------------------
1391
1392 //
1393 // Attention Windows users: make sure to use the proper separator character
1394 // with these functions.
1395 //
1396
1397 // given a path string, create any missing directories so that PathIsDirectory(Path) is true.
1398 //
1399 Result_t
1400 Kumu::CreateDirectoriesInPath(const std::string& Path)
1401 {
1402   bool abs = PathIsAbsolute(Path);
1403   PathCompList_t PathComps, TmpPathComps;
1404
1405   PathToComponents(Path, PathComps);
1406
1407   while ( ! PathComps.empty() )
1408     {
1409       TmpPathComps.push_back(PathComps.front());
1410       PathComps.pop_front();
1411       std::string tmp_path = abs ? ComponentsToAbsolutePath(TmpPathComps) : ComponentsToPath(TmpPathComps);
1412
1413       if ( ! PathIsDirectory(tmp_path) )
1414         {
1415 #ifdef KM_WIN32
1416           if ( mkdir(tmp_path.c_str()) != 0 )
1417 #else // KM_WIN32
1418           if ( mkdir(tmp_path.c_str(), 0775) != 0 )
1419 #endif // KM_WIN32
1420             {
1421               DefaultLogSink().Error("CreateDirectoriesInPath mkdir %s: %s\n",
1422                                      tmp_path.c_str(), strerror(errno));
1423               return RESULT_DIR_CREATE;
1424             }
1425         }
1426     }
1427
1428   return RESULT_OK;
1429 }
1430
1431
1432 //
1433 Result_t
1434 Kumu::DeleteFile(const std::string& filename)
1435 {
1436   if ( unlink(filename.c_str()) == 0 )
1437     return RESULT_OK;
1438
1439   switch ( errno )
1440     {
1441     case ENOENT:
1442     case ENOTDIR: return RESULT_NOTAFILE;
1443
1444     case EROFS:
1445     case EBUSY:
1446     case EACCES:
1447     case EPERM:   return RESULT_NO_PERM;
1448     }
1449
1450   DefaultLogSink().Error("DeleteFile %s: %s\n", filename.c_str(), strerror(errno));
1451   return RESULT_FAIL;
1452 }
1453
1454 //
1455 Result_t
1456 h__DeletePath(const std::string& pathname)
1457 {
1458   fprintf(stderr, "h__DeletePath %s\n", pathname.c_str());
1459   Result_t result = RESULT_OK;
1460
1461   if ( ! PathIsDirectory(pathname) )
1462     {
1463       result = DeleteFile(pathname);
1464     }
1465   else
1466     {
1467       {
1468         DirScanner TestDir;
1469         char       next_file[Kumu::MaxFilePath];
1470         result = TestDir.Open(pathname.c_str());
1471
1472         while ( KM_SUCCESS(result) && KM_SUCCESS(TestDir.GetNext(next_file)) )
1473           {
1474             if ( next_file[0] == '.' )
1475               {
1476                 if ( next_file[1] ==  0 )
1477                   continue; // don't delete 'this'
1478                 
1479                 if ( next_file[1] == '.' && next_file[2] ==  0 )
1480                   continue; // don't delete 'this' parent
1481               }
1482
1483             result = h__DeletePath(pathname + std::string("/") + next_file);
1484           }
1485       }
1486
1487       if ( rmdir(pathname.c_str()) != 0 )
1488         {
1489           switch ( errno )
1490             {
1491             case ENOENT:
1492             case ENOTDIR:
1493               result = RESULT_NOTAFILE;
1494               break;
1495
1496             case EROFS:
1497             case EBUSY:
1498             case EACCES:
1499             case EPERM:
1500               result = RESULT_NO_PERM;
1501               break;
1502
1503             default:
1504               DefaultLogSink().Error("DeletePath %s: %s\n", pathname.c_str(), strerror(errno));
1505               result = RESULT_FAIL;
1506             }
1507         }
1508     }
1509
1510   return result;
1511 }
1512
1513 //
1514 Result_t
1515 Kumu::DeletePath(const std::string& pathname)
1516 {
1517   std::string c_pathname = PathMakeAbsolute(PathMakeCanonical(pathname));
1518   DefaultLogSink().Debug("DeletePath (%s) c(%s)\n", pathname.c_str(), c_pathname.c_str());
1519   return h__DeletePath(c_pathname);
1520 }
1521
1522
1523 //------------------------------------------------------------------------------------------
1524 //
1525
1526
1527 Result_t
1528 Kumu::FreeSpaceForPath(const std::string& path, Kumu::fsize_t& free_space, Kumu::fsize_t& total_space)
1529 {
1530 #ifdef KM_WIN32
1531         ULARGE_INTEGER lTotalNumberOfBytes;
1532         ULARGE_INTEGER lTotalNumberOfFreeBytes;
1533
1534         BOOL fResult = ::GetDiskFreeSpaceEx(path.c_str(), NULL, &lTotalNumberOfBytes, &lTotalNumberOfFreeBytes);
1535         if (fResult) {
1536       free_space = static_cast<Kumu::fsize_t>(lTotalNumberOfFreeBytes.QuadPart);
1537       total_space = static_cast<Kumu::fsize_t>(lTotalNumberOfBytes.QuadPart);
1538       return RESULT_OK;
1539         }
1540         HRESULT LastError = ::GetLastError();
1541
1542         DefaultLogSink().Error("FreeSpaceForPath GetDiskFreeSpaceEx %s: %lu\n", path.c_str(), ::GetLastError());
1543         return RESULT_FAIL;
1544 #else // KM_WIN32
1545   struct statfs s;
1546
1547   if ( statfs(path.c_str(), &s) == 0 )
1548     {
1549       if ( s.f_blocks < 1 )
1550         {
1551           DefaultLogSink().Error("File system %s has impossible size: %ld\n",
1552                                  path.c_str(), s.f_blocks);
1553           return RESULT_FAIL;
1554         }
1555
1556       free_space = (Kumu::fsize_t)s.f_bsize * (Kumu::fsize_t)s.f_bavail;
1557       total_space = (Kumu::fsize_t)s.f_bsize * (Kumu::fsize_t)s.f_blocks;
1558       return RESULT_OK;
1559     }
1560
1561   switch ( errno )
1562     {
1563     case ENOENT:
1564     case ENOTDIR: return RESULT_NOTAFILE;
1565     case EACCES:  return RESULT_NO_PERM;
1566     }
1567
1568   DefaultLogSink().Error("FreeSpaceForPath statfs %s: %s\n", path.c_str(), strerror(errno));
1569   return RESULT_FAIL;
1570 #endif // KM_WIN32
1571
1572
1573
1574 //
1575 // end KM_fileio.cpp
1576 //