ginormo merge-back with Kumu, SMPTE MIC key and MPEG parser fix
[asdcplib.git] / src / KM_fileio.cpp
1 /*
2 Copyright (c) 2004-2006, 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 #include <assert.h>
36
37 using namespace Kumu;
38
39 #ifdef KM_WIN32
40 typedef struct _stati64 fstat_t;
41
42 // AFAIK, there is no iovec equivalent in the win32 API
43 struct iovec {
44   char* iov_base; // stupid iovec uses char*
45   int   iov_len;
46 };
47 #else
48 #include <sys/uio.h>
49 typedef struct stat     fstat_t;
50 #endif
51
52 //
53 static Kumu::Result_t
54 do_stat(const char* path, fstat_t* stat_info)
55 {
56   KM_TEST_NULL_STR(path);
57   KM_TEST_NULL(stat_info);
58
59   Kumu::Result_t result = Kumu::RESULT_OK;
60
61 #ifdef KM_WIN32
62   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
63
64   if ( _stati64(path, stat_info) == (__int64)-1 )
65     result = Kumu::RESULT_FILEOPEN;
66
67   ::SetErrorMode( prev );
68 #else
69   if ( stat(path, stat_info) == -1L )
70     result = Kumu::RESULT_FILEOPEN;
71
72   if ( stat_info->st_mode & (S_IFREG|S_IFLNK|S_IFDIR) == 0 )
73     result = Kumu::RESULT_FILEOPEN;
74 #endif
75
76   return result;
77 }
78
79 #ifndef KM_WIN32
80
81 //
82 static Kumu::Result_t
83 do_fstat(HANDLE handle, fstat_t* stat_info)
84 {
85   KM_TEST_NULL(stat_info);
86
87   Kumu::Result_t result = Kumu::RESULT_OK;
88
89   if ( fstat(handle, stat_info) == -1L )
90     result = Kumu::RESULT_FILEOPEN;
91
92   if ( stat_info->st_mode & (S_IFREG|S_IFLNK|S_IFDIR) == 0 )
93     result = Kumu::RESULT_FILEOPEN;
94
95   return result;
96 }
97
98 #endif
99
100
101 //
102 bool
103 Kumu::PathIsFile(const char* pathname)
104 {
105   assert(pathname);
106   fstat_t info;
107
108   if ( KM_SUCCESS(do_stat(pathname, &info)) )
109     {
110       if ( info.st_mode & S_IFREG )
111         return true;
112     }
113
114   return false;
115 }
116
117
118 //
119 bool
120 Kumu::PathIsDirectory(const char* pathname)
121 {
122   assert(pathname);
123   fstat_t info;
124
125   if ( KM_SUCCESS(do_stat(pathname, &info)) )
126     {
127       if ( info.st_mode & S_IFDIR )
128         return true;
129     }
130
131   return false;
132 }
133
134
135 //
136 Kumu::fsize_t
137 Kumu::FileSize(const char* pathname)
138 {
139   assert(pathname);
140   fstat_t info;
141
142   if ( KM_SUCCESS(do_stat(pathname, &info)) )
143     {
144       if ( info.st_mode & S_IFREG )
145         return(info.st_size);
146     }
147
148   return 0;
149 }
150
151 //------------------------------------------------------------------------------------------
152 // portable aspects of the file classes
153
154 const int IOVecMaxEntries = 32; // we never use more that 3, but that number seems somehow small...
155
156 //
157 class Kumu::FileWriter::h__iovec
158 {
159 public:
160   int            m_Count;
161   struct iovec   m_iovec[IOVecMaxEntries];
162   h__iovec() : m_Count(0) {}
163 };
164
165
166
167 //
168 Kumu::fsize_t
169 Kumu::FileReader::Size() const
170 {
171 #ifdef KM_WIN32
172   return FileSize(m_Filename.c_str());
173 #else
174   fstat_t info;
175
176   if ( KM_SUCCESS(do_fstat(m_Handle, &info)) )
177     {
178       if ( info.st_mode & S_IFREG )
179         return(info.st_size);
180     }
181 #endif
182
183   return 0;
184 }
185
186 // these are declared here instead of in the header file
187 // because we have a mem_ptr that is managing a hidden class
188 Kumu::FileWriter::FileWriter() {}
189 Kumu::FileWriter::~FileWriter() {}
190
191 //
192 Kumu::Result_t
193 Kumu::FileWriter::Writev(const byte_t* buf, ui32_t buf_len)
194 {
195   assert( ! m_IOVec.empty() );
196   register h__iovec* iov = m_IOVec;
197   KM_TEST_NULL(buf);
198
199   if ( iov->m_Count >= IOVecMaxEntries )
200     {
201       DefaultLogSink().Error("The iovec is full! Only %lu entries allowed before a flush.\n",
202                              IOVecMaxEntries);
203       return RESULT_FAIL;
204     }
205
206   iov->m_iovec[iov->m_Count].iov_base = (char*)buf; // stupid iovec uses char*
207   iov->m_iovec[iov->m_Count].iov_len = buf_len;
208   iov->m_Count++;
209
210   return RESULT_OK;
211 }
212
213
214 #ifdef KM_WIN32
215 //------------------------------------------------------------------------------------------
216 //
217
218 Kumu::Result_t
219 Kumu::FileReader::OpenRead(const char* filename) const
220 {
221   KM_TEST_NULL_STR(filename);
222   const_cast<FileReader*>(this)->m_Filename = filename;
223   
224   // suppress popup window on error
225   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
226
227   const_cast<FileReader*>(this)->m_Handle = ::CreateFile(filename,
228                           (GENERIC_READ),                // open for reading
229                           FILE_SHARE_READ,               // share for reading
230                           NULL,                          // no security
231                           OPEN_EXISTING,                 // read
232                           FILE_ATTRIBUTE_NORMAL,         // normal file
233                           NULL                           // no template file
234                           );
235
236   ::SetErrorMode(prev);
237
238   return ( m_Handle == INVALID_HANDLE_VALUE ) ?
239     Kumu::RESULT_FILEOPEN : Kumu::RESULT_OK;
240 }
241
242 //
243 Kumu::Result_t
244 Kumu::FileReader::Close() const
245 {
246   if ( m_Handle == INVALID_HANDLE_VALUE )
247     return Kumu::RESULT_FILEOPEN;
248
249   // suppress popup window on error
250   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
251   BOOL result = ::CloseHandle(m_Handle);
252   ::SetErrorMode(prev);
253   const_cast<FileReader*>(this)->m_Handle = INVALID_HANDLE_VALUE;
254
255   return ( result == 0 ) ? Kumu::RESULT_FAIL : Kumu::RESULT_OK;
256 }
257
258 //
259 Kumu::Result_t
260 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
261 {
262   if ( m_Handle == INVALID_HANDLE_VALUE )
263     return Kumu::RESULT_STATE;
264
265   LARGE_INTEGER in;
266   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
267   in.QuadPart = position;
268   in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, whence);
269   HRESULT LastError = GetLastError();
270   ::SetErrorMode(prev);
271
272   if ( (LastError != NO_ERROR
273         && (in.LowPart == INVALID_SET_FILE_POINTER
274             || in.LowPart == ERROR_NEGATIVE_SEEK )) )
275     return Kumu::RESULT_READFAIL;
276   
277   return Kumu::RESULT_OK;
278 }
279
280 //
281 Kumu::Result_t
282 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
283 {
284   KM_TEST_NULL(pos);
285
286   if ( m_Handle == (HANDLE)-1L )
287     return Kumu::RESULT_FILEOPEN;
288
289   LARGE_INTEGER in;
290   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
291   in.QuadPart = (__int64)0;
292   in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, FILE_CURRENT);
293   HRESULT LastError = GetLastError();
294   ::SetErrorMode(prev);
295
296   if ( (LastError != NO_ERROR
297         && (in.LowPart == INVALID_SET_FILE_POINTER
298             || in.LowPart == ERROR_NEGATIVE_SEEK )) )
299     return Kumu::RESULT_READFAIL;
300
301   *pos = (Kumu::fpos_t)in.QuadPart;
302   return Kumu::RESULT_OK;
303 }
304
305 //
306 Kumu::Result_t
307 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
308 {
309   KM_TEST_NULL(buf);
310   Result_t result = Kumu::RESULT_OK;
311   DWORD    tmp_count;
312   ui32_t tmp_int;
313
314   if ( read_count == 0 )
315     read_count = &tmp_int;
316
317   *read_count = 0;
318
319   if ( m_Handle == INVALID_HANDLE_VALUE )
320     return Kumu::RESULT_FILEOPEN;
321   
322   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
323   if ( ::ReadFile(m_Handle, buf, buf_len, &tmp_count, NULL) == 0 )
324     result = Kumu::RESULT_READFAIL;
325
326   ::SetErrorMode(prev);
327
328   if ( tmp_count == 0 ) /* EOF */
329     result = Kumu::RESULT_ENDOFFILE;
330
331   if ( KM_SUCCESS(result) )
332     *read_count = tmp_count;
333
334   return result;
335 }
336
337
338
339 //------------------------------------------------------------------------------------------
340 //
341
342 //
343 Kumu::Result_t
344 Kumu::FileWriter::OpenWrite(const char* filename)
345 {
346   KM_TEST_NULL_STR(filename);
347   m_Filename = filename;
348   
349   // suppress popup window on error
350   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
351
352   m_Handle = ::CreateFile(filename,
353                           (GENERIC_WRITE|GENERIC_READ),  // open for reading
354                           FILE_SHARE_READ,               // share for reading
355                           NULL,                          // no security
356                           CREATE_ALWAYS,                 // overwrite (beware!)
357                           FILE_ATTRIBUTE_NORMAL,         // normal file
358                           NULL                           // no template file
359                           );
360
361   ::SetErrorMode(prev);
362
363   if ( m_Handle == INVALID_HANDLE_VALUE )
364     return Kumu::RESULT_FILEOPEN;
365   
366   m_IOVec = new h__iovec;
367   return Kumu::RESULT_OK;
368 }
369
370 //
371 Kumu::Result_t
372 Kumu::FileWriter::Writev(ui32_t* bytes_written)
373 {
374   assert( ! m_IOVec.empty() );
375   register h__iovec* iov = m_IOVec;
376   ui32_t tmp_int;
377
378   if ( bytes_written == 0 )
379     bytes_written = &tmp_int;
380
381   if ( m_Handle == INVALID_HANDLE_VALUE )
382     return Kumu::RESULT_STATE;
383
384   *bytes_written = 0;
385   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
386   Result_t result = Kumu::RESULT_OK;
387
388   // AFAIK, there is no writev() equivalent in the win32 API
389   for ( register int i = 0; i < iov->m_Count; i++ )
390     {
391       ui32_t tmp_count = 0;
392       BOOL wr_result = ::WriteFile(m_Handle,
393                                    iov->m_iovec[i].iov_base,
394                                    iov->m_iovec[i].iov_len,
395                                    (DWORD*)&tmp_count,
396                                    NULL);
397
398       if ( wr_result == 0 )
399         {
400           result = Kumu::RESULT_WRITEFAIL;
401           break;
402         }
403
404       assert(iov->m_iovec[i].iov_len == tmp_count);
405       *bytes_written += tmp_count;
406     }
407
408   ::SetErrorMode(prev);
409   iov->m_Count = 0; // error nor not, all is lost
410
411   return result;
412 }
413
414 //
415 Kumu::Result_t
416 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
417 {
418   KM_TEST_NULL(buf);
419   ui32_t tmp_int;
420
421   if ( bytes_written == 0 )
422     bytes_written = &tmp_int;
423
424   if ( m_Handle == INVALID_HANDLE_VALUE )
425     return Kumu::RESULT_STATE;
426
427   // suppress popup window on error
428   UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
429   BOOL result = ::WriteFile(m_Handle, buf, buf_len, (DWORD*)bytes_written, NULL);
430   ::SetErrorMode(prev);
431
432   return ( result == 0 ) ? Kumu::RESULT_WRITEFAIL : Kumu::RESULT_OK;
433 }
434
435 #else // KM_WIN32
436 //------------------------------------------------------------------------------------------
437 // POSIX
438
439 //
440 Kumu::Result_t
441 Kumu::FileReader::OpenRead(const char* filename) const
442 {
443   KM_TEST_NULL_STR(filename);
444   const_cast<FileReader*>(this)->m_Filename = filename;
445   const_cast<FileReader*>(this)->m_Handle = open(filename, O_RDONLY, 0);
446   return ( m_Handle == -1L ) ? RESULT_FILEOPEN : RESULT_OK;
447 }
448
449 //
450 Kumu::Result_t
451 Kumu::FileReader::Close() const
452 {
453   if ( m_Handle == -1L )
454     return RESULT_FILEOPEN;
455
456   close(m_Handle);
457   const_cast<FileReader*>(this)->m_Handle = -1L;
458   return RESULT_OK;
459 }
460
461 //
462 Kumu::Result_t
463 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
464 {
465   if ( m_Handle == -1L )
466     return RESULT_FILEOPEN;
467
468   if ( lseek(m_Handle, position, whence) == -1L )
469     return RESULT_BADSEEK;
470
471   return RESULT_OK;
472 }
473
474 //
475 Kumu::Result_t
476 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
477 {
478   KM_TEST_NULL(pos);
479
480   if ( m_Handle == -1L )
481     return RESULT_FILEOPEN;
482
483   Kumu::fpos_t tmp_pos;
484
485   if (  (tmp_pos = lseek(m_Handle, 0, SEEK_CUR)) == -1 )
486     return RESULT_READFAIL;
487
488   *pos = tmp_pos;
489   return RESULT_OK;
490 }
491
492 //
493 Kumu::Result_t
494 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
495 {
496   KM_TEST_NULL(buf);
497   i32_t  tmp_count = 0;
498   ui32_t tmp_int = 0;
499
500   if ( read_count == 0 )
501     read_count = &tmp_int;
502
503   *read_count = 0;
504
505   if ( m_Handle == -1L )
506     return RESULT_FILEOPEN;
507
508   if ( (tmp_count = read(m_Handle, buf, buf_len)) == -1L )
509     return RESULT_READFAIL;
510
511   *read_count = tmp_count;
512   return (tmp_count == 0 ? RESULT_ENDOFFILE : RESULT_OK);
513 }
514
515
516 //------------------------------------------------------------------------------------------
517 //
518
519 //
520 Kumu::Result_t
521 Kumu::FileWriter::OpenWrite(const char* filename)
522 {
523   KM_TEST_NULL_STR(filename);
524   m_Filename = filename;
525   m_Handle = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0644);
526
527   if ( m_Handle == -1L )
528     {
529       DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
530       return RESULT_FILEOPEN;
531     }
532
533   m_IOVec = new h__iovec;
534   return RESULT_OK;
535 }
536
537 //
538 Kumu::Result_t
539 Kumu::FileWriter::OpenModify(const char* filename)
540 {
541   KM_TEST_NULL_STR(filename);
542   m_Filename = filename;
543   m_Handle = open(filename, O_RDWR|O_CREAT, 0644);
544
545   if ( m_Handle == -1L )
546     {
547       DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
548       return RESULT_FILEOPEN;
549     }
550
551   m_IOVec = new h__iovec;
552   return RESULT_OK;
553 }
554
555 //
556 Kumu::Result_t
557 Kumu::FileWriter::Writev(ui32_t* bytes_written)
558 {
559   assert( ! m_IOVec.empty() );
560   register h__iovec* iov = m_IOVec;
561   ui32_t tmp_int;
562
563   if ( bytes_written == 0 )
564     bytes_written = &tmp_int;
565
566   if ( m_Handle == -1L )
567     return RESULT_STATE;
568
569   int read_size = writev(m_Handle, iov->m_iovec, iov->m_Count);
570   
571   if ( read_size == -1L )
572     return RESULT_WRITEFAIL;
573
574   iov->m_Count = 0;
575   *bytes_written = read_size;  
576   return RESULT_OK;
577 }
578
579 //
580 Kumu::Result_t
581 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
582 {
583   KM_TEST_NULL(buf);
584   ui32_t tmp_int;
585
586   if ( bytes_written == 0 )
587     bytes_written = &tmp_int;
588
589   // TODO: flush iovec
590
591
592   if ( m_Handle == -1L )
593     return RESULT_STATE;
594
595   int read_size = write(m_Handle, buf, buf_len);
596   
597   if ( read_size == -1L )
598     return RESULT_WRITEFAIL;
599
600   *bytes_written = read_size;  
601   return RESULT_OK;
602 }
603
604
605 #endif // KM_WIN32
606
607 //------------------------------------------------------------------------------------------
608
609
610 //
611 Kumu::Result_t
612 Kumu::ReadFileIntoString(const char* filename, std::string& outString, ui32_t max_size)
613 {
614   fsize_t    fsize = 0;
615   ui32_t     read_size;
616   FileReader File;
617   ByteString ReadBuf;
618
619   KM_TEST_NULL_STR(filename);
620
621   Result_t result = File.OpenRead(filename);
622
623   if ( KM_SUCCESS(result) )
624     {
625       fsize = File.Size();
626
627       if ( fsize > max_size )
628         return RESULT_ALLOC;
629
630       result = ReadBuf.Capacity((ui32_t)fsize);
631     }
632
633   if ( KM_SUCCESS(result) )
634     result = File.Read(ReadBuf.Data(), ReadBuf.Capacity(), &read_size);
635
636   if ( KM_SUCCESS(result) )
637     outString.assign((const char*)ReadBuf.RoData(), read_size);
638
639   return result;
640 }
641
642
643 //------------------------------------------------------------------------------------------
644 //
645
646 // Win32 directory scanner
647 //
648 #ifdef KM_WIN32
649
650 //
651 //
652 Result_t
653 Kumu::DirScanner::Open(const char* filename)
654 {
655   KM_TEST_NULL_STR(filename);
656
657   // we need to append a '*' to read the entire directory
658   ui32_t fn_len = strlen(filename); 
659   char* tmp_file = (char*)malloc(fn_len + 8);
660
661   if ( tmp_file == 0 )
662     return RESULT_ALLOC;
663
664   strcpy(tmp_file, filename);
665   char* p = &tmp_file[fn_len] - 1;
666
667   if ( *p != '/' && *p != '\\' )
668     {
669       p++;
670       *p++ = '/';
671     }
672
673   *p++ = '*';
674   *p = 0;
675   // whew...
676
677   m_Handle = _findfirsti64(tmp_file, &m_FileInfo);
678   Result_t result = RESULT_OK;
679
680   if ( m_Handle == -1 )
681     result = RESULT_NOT_FOUND;
682
683   return result;
684 }
685
686
687 //
688 //
689 Result_t
690 Kumu::DirScanner::Close()
691 {
692   if ( m_Handle == -1 )
693     return RESULT_FILEOPEN;
694
695   if ( _findclose((long)m_Handle) == -1 )
696     return RESULT_FAIL;
697
698   m_Handle = -1;
699   return RESULT_OK;
700 }
701
702
703 // This sets filename param to the same per-instance buffer every time, so
704 // the value will change on the next call
705 Result_t
706 Kumu::DirScanner::GetNext(char* filename)
707 {
708   KM_TEST_NULL(filename);
709
710   if ( m_Handle == -1 )
711     return RESULT_FILEOPEN;
712
713   if ( m_FileInfo.name[0] == '\0' )
714     return RESULT_ENDOFFILE;
715
716   strncpy(filename, m_FileInfo.name, MaxFilePath);
717   Result_t result = RESULT_OK;
718
719   if ( _findnexti64((long)m_Handle, &m_FileInfo) == -1 )
720     {
721       m_FileInfo.name[0] = '\0';
722           
723       if ( errno != ENOENT )
724         result = RESULT_FAIL;
725     }
726
727   return result;
728 }
729
730
731 #else // KM_WIN32
732
733 // POSIX directory scanner
734
735 //
736 Result_t
737 Kumu::DirScanner::Open(const char* filename)
738 {
739   KM_TEST_NULL_STR(filename);
740
741   Result_t result = RESULT_OK;
742
743   if ( ( m_Handle = opendir(filename) ) == NULL )
744     {
745       if ( errno == ENOENT )
746         result = RESULT_ENDOFFILE;
747
748       else
749         result = RESULT_FAIL;
750     }
751
752   return result;
753 }
754
755
756 //
757 Result_t
758 Kumu::DirScanner::Close()
759 {
760   if ( m_Handle == NULL )
761     return RESULT_FILEOPEN;
762
763   if ( closedir(m_Handle) == -1 )
764     return RESULT_FAIL;
765
766   m_Handle = NULL;
767   return RESULT_OK;
768 }
769
770
771 //
772 Result_t
773 Kumu::DirScanner::GetNext(char* filename)
774 {
775   KM_TEST_NULL(filename);
776
777   if ( m_Handle == NULL )
778     return RESULT_FILEOPEN;
779
780   struct dirent* entry;
781
782   for (;;)
783     {
784       if ( ( entry = readdir(m_Handle)) == NULL )
785         return RESULT_ENDOFFILE;
786
787       break;
788     }
789
790   strncpy(filename, entry->d_name, MaxFilePath);
791   return RESULT_OK;
792 }
793
794
795 #endif // KM_WIN32
796
797
798
799 //
800 // end KM_fileio.cpp
801 //