2 Copyright (c) 2004-2006, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
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.
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.
27 /*! \file KM_fileio.cpp
29 \brief portable file i/o
32 #include <KM_fileio.h>
40 typedef struct _stati64 fstat_t;
44 // win32 has WriteFileGather() and ReadFileScatter() but they
45 // demand page alignment and page sizing, making them unsuitable
46 // for use with arbitrary buffer sizes.
48 char* iov_base; // stupid iovec uses char*
53 typedef struct stat fstat_t;
58 do_stat(const char* path, fstat_t* stat_info)
60 KM_TEST_NULL_STR(path);
61 KM_TEST_NULL(stat_info);
63 Kumu::Result_t result = Kumu::RESULT_OK;
66 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
68 if ( _stati64(path, stat_info) == (__int64)-1 )
69 result = Kumu::RESULT_FILEOPEN;
71 ::SetErrorMode( prev );
73 if ( stat(path, stat_info) == -1L )
74 result = Kumu::RESULT_FILEOPEN;
76 if ( stat_info->st_mode & (S_IFREG|S_IFLNK|S_IFDIR) == 0 )
77 result = Kumu::RESULT_FILEOPEN;
87 do_fstat(HANDLE handle, fstat_t* stat_info)
89 KM_TEST_NULL(stat_info);
91 Kumu::Result_t result = Kumu::RESULT_OK;
93 if ( fstat(handle, stat_info) == -1L )
94 result = Kumu::RESULT_FILEOPEN;
96 if ( stat_info->st_mode & (S_IFREG|S_IFLNK|S_IFDIR) == 0 )
97 result = Kumu::RESULT_FILEOPEN;
107 Kumu::PathIsFile(const char* pathname)
112 if ( KM_SUCCESS(do_stat(pathname, &info)) )
114 if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
124 Kumu::PathIsDirectory(const char* pathname)
129 if ( KM_SUCCESS(do_stat(pathname, &info)) )
131 if ( info.st_mode & S_IFDIR )
141 Kumu::FileSize(const char* pathname)
146 if ( KM_SUCCESS(do_stat(pathname, &info)) )
148 if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
149 return(info.st_size);
155 //------------------------------------------------------------------------------------------
156 // portable aspects of the file classes
158 const int IOVecMaxEntries = 32; // we never use more that 3, but that number seems somehow small...
161 class Kumu::FileWriter::h__iovec
165 struct iovec m_iovec[IOVecMaxEntries];
166 h__iovec() : m_Count(0) {}
173 Kumu::FileReader::Size() const
176 return FileSize(m_Filename.c_str());
180 if ( KM_SUCCESS(do_fstat(m_Handle, &info)) )
182 if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
183 return(info.st_size);
190 // these are declared here instead of in the header file
191 // because we have a mem_ptr that is managing a hidden class
192 Kumu::FileWriter::FileWriter() {}
193 Kumu::FileWriter::~FileWriter() {}
197 Kumu::FileWriter::Writev(const byte_t* buf, ui32_t buf_len)
199 assert( ! m_IOVec.empty() );
200 register h__iovec* iov = m_IOVec;
203 if ( iov->m_Count >= IOVecMaxEntries )
205 DefaultLogSink().Error("The iovec is full! Only %u entries allowed before a flush.\n",
210 iov->m_iovec[iov->m_Count].iov_base = (char*)buf; // stupid iovec uses char*
211 iov->m_iovec[iov->m_Count].iov_len = buf_len;
219 //------------------------------------------------------------------------------------------
223 Kumu::FileReader::OpenRead(const char* filename) const
225 KM_TEST_NULL_STR(filename);
226 const_cast<FileReader*>(this)->m_Filename = filename;
228 // suppress popup window on error
229 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
231 const_cast<FileReader*>(this)->m_Handle = ::CreateFile(filename,
232 (GENERIC_READ), // open for reading
233 FILE_SHARE_READ, // share for reading
235 OPEN_EXISTING, // read
236 FILE_ATTRIBUTE_NORMAL, // normal file
237 NULL // no template file
240 ::SetErrorMode(prev);
242 return ( m_Handle == INVALID_HANDLE_VALUE ) ?
243 Kumu::RESULT_FILEOPEN : Kumu::RESULT_OK;
248 Kumu::FileReader::Close() const
250 if ( m_Handle == INVALID_HANDLE_VALUE )
251 return Kumu::RESULT_FILEOPEN;
253 // suppress popup window on error
254 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
255 BOOL result = ::CloseHandle(m_Handle);
256 ::SetErrorMode(prev);
257 const_cast<FileReader*>(this)->m_Handle = INVALID_HANDLE_VALUE;
259 return ( result == 0 ) ? Kumu::RESULT_FAIL : Kumu::RESULT_OK;
264 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
266 if ( m_Handle == INVALID_HANDLE_VALUE )
267 return Kumu::RESULT_STATE;
270 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
271 in.QuadPart = position;
272 in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, whence);
273 HRESULT LastError = GetLastError();
274 ::SetErrorMode(prev);
276 if ( (LastError != NO_ERROR
277 && (in.LowPart == INVALID_SET_FILE_POINTER
278 || in.LowPart == ERROR_NEGATIVE_SEEK )) )
279 return Kumu::RESULT_READFAIL;
281 return Kumu::RESULT_OK;
286 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
290 if ( m_Handle == (HANDLE)-1L )
291 return Kumu::RESULT_FILEOPEN;
294 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
295 in.QuadPart = (__int64)0;
296 in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, FILE_CURRENT);
297 HRESULT LastError = GetLastError();
298 ::SetErrorMode(prev);
300 if ( (LastError != NO_ERROR
301 && (in.LowPart == INVALID_SET_FILE_POINTER
302 || in.LowPart == ERROR_NEGATIVE_SEEK )) )
303 return Kumu::RESULT_READFAIL;
305 *pos = (Kumu::fpos_t)in.QuadPart;
306 return Kumu::RESULT_OK;
311 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
314 Result_t result = Kumu::RESULT_OK;
318 if ( read_count == 0 )
319 read_count = &tmp_int;
323 if ( m_Handle == INVALID_HANDLE_VALUE )
324 return Kumu::RESULT_FILEOPEN;
326 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
327 if ( ::ReadFile(m_Handle, buf, buf_len, &tmp_count, NULL) == 0 )
328 result = Kumu::RESULT_READFAIL;
330 ::SetErrorMode(prev);
332 if ( tmp_count == 0 ) /* EOF */
333 result = Kumu::RESULT_ENDOFFILE;
335 if ( KM_SUCCESS(result) )
336 *read_count = tmp_count;
343 //------------------------------------------------------------------------------------------
348 Kumu::FileWriter::OpenWrite(const char* filename)
350 KM_TEST_NULL_STR(filename);
351 m_Filename = filename;
353 // suppress popup window on error
354 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
356 m_Handle = ::CreateFile(filename,
357 (GENERIC_WRITE|GENERIC_READ), // open for reading
358 FILE_SHARE_READ, // share for reading
360 CREATE_ALWAYS, // overwrite (beware!)
361 FILE_ATTRIBUTE_NORMAL, // normal file
362 NULL // no template file
365 ::SetErrorMode(prev);
367 if ( m_Handle == INVALID_HANDLE_VALUE )
368 return Kumu::RESULT_FILEOPEN;
370 m_IOVec = new h__iovec;
371 return Kumu::RESULT_OK;
376 Kumu::FileWriter::Writev(ui32_t* bytes_written)
378 assert( ! m_IOVec.empty() );
379 register h__iovec* iov = m_IOVec;
382 if ( bytes_written == 0 )
383 bytes_written = &tmp_int;
385 if ( m_Handle == INVALID_HANDLE_VALUE )
386 return Kumu::RESULT_STATE;
389 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
390 Result_t result = Kumu::RESULT_OK;
392 // AFAIK, there is no writev() equivalent in the win32 API
393 for ( register int i = 0; i < iov->m_Count; i++ )
395 ui32_t tmp_count = 0;
396 BOOL wr_result = ::WriteFile(m_Handle,
397 iov->m_iovec[i].iov_base,
398 iov->m_iovec[i].iov_len,
402 if ( wr_result == 0 )
404 result = Kumu::RESULT_WRITEFAIL;
408 assert(iov->m_iovec[i].iov_len == tmp_count);
409 *bytes_written += tmp_count;
412 ::SetErrorMode(prev);
413 iov->m_Count = 0; // error nor not, all is lost
420 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
425 if ( bytes_written == 0 )
426 bytes_written = &tmp_int;
428 if ( m_Handle == INVALID_HANDLE_VALUE )
429 return Kumu::RESULT_STATE;
431 // suppress popup window on error
432 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
433 BOOL result = ::WriteFile(m_Handle, buf, buf_len, (DWORD*)bytes_written, NULL);
434 ::SetErrorMode(prev);
436 return ( result == 0 ) ? Kumu::RESULT_WRITEFAIL : Kumu::RESULT_OK;
440 //------------------------------------------------------------------------------------------
445 Kumu::FileReader::OpenRead(const char* filename) const
447 KM_TEST_NULL_STR(filename);
448 const_cast<FileReader*>(this)->m_Filename = filename;
449 const_cast<FileReader*>(this)->m_Handle = open(filename, O_RDONLY, 0);
450 return ( m_Handle == -1L ) ? RESULT_FILEOPEN : RESULT_OK;
455 Kumu::FileReader::Close() const
457 if ( m_Handle == -1L )
458 return RESULT_FILEOPEN;
461 const_cast<FileReader*>(this)->m_Handle = -1L;
467 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
469 if ( m_Handle == -1L )
470 return RESULT_FILEOPEN;
472 if ( lseek(m_Handle, position, whence) == -1L )
473 return RESULT_BADSEEK;
480 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
484 if ( m_Handle == -1L )
485 return RESULT_FILEOPEN;
487 Kumu::fpos_t tmp_pos;
489 if ( (tmp_pos = lseek(m_Handle, 0, SEEK_CUR)) == -1 )
490 return RESULT_READFAIL;
498 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
504 if ( read_count == 0 )
505 read_count = &tmp_int;
509 if ( m_Handle == -1L )
510 return RESULT_FILEOPEN;
512 if ( (tmp_count = read(m_Handle, buf, buf_len)) == -1L )
513 return RESULT_READFAIL;
515 *read_count = tmp_count;
516 return (tmp_count == 0 ? RESULT_ENDOFFILE : RESULT_OK);
520 //------------------------------------------------------------------------------------------
525 Kumu::FileWriter::OpenWrite(const char* filename)
527 KM_TEST_NULL_STR(filename);
528 m_Filename = filename;
529 m_Handle = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0664);
531 if ( m_Handle == -1L )
533 DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
534 return RESULT_FILEOPEN;
537 m_IOVec = new h__iovec;
543 Kumu::FileWriter::OpenModify(const char* filename)
545 KM_TEST_NULL_STR(filename);
546 m_Filename = filename;
547 m_Handle = open(filename, O_RDWR|O_CREAT, 0664);
549 if ( m_Handle == -1L )
551 DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
552 return RESULT_FILEOPEN;
555 m_IOVec = new h__iovec;
561 Kumu::FileWriter::Writev(ui32_t* bytes_written)
563 assert( ! m_IOVec.empty() );
564 register h__iovec* iov = m_IOVec;
567 if ( bytes_written == 0 )
568 bytes_written = &tmp_int;
570 if ( m_Handle == -1L )
573 int read_size = writev(m_Handle, iov->m_iovec, iov->m_Count);
575 if ( read_size == -1L )
576 return RESULT_WRITEFAIL;
579 *bytes_written = read_size;
585 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
590 if ( bytes_written == 0 )
591 bytes_written = &tmp_int;
596 if ( m_Handle == -1L )
599 int read_size = write(m_Handle, buf, buf_len);
601 if ( read_size == -1L )
602 return RESULT_WRITEFAIL;
604 *bytes_written = read_size;
611 //------------------------------------------------------------------------------------------
616 Kumu::ReadFileIntoString(const char* filename, std::string& outString, ui32_t max_size)
619 ui32_t read_size = 0;
623 KM_TEST_NULL_STR(filename);
625 Result_t result = File.OpenRead(filename);
627 if ( KM_SUCCESS(result) )
631 if ( fsize > (Kumu::fpos_t)max_size )
633 DefaultLogSink().Error("%s: exceeds available buffer size (%u)", filename, max_size);
637 result = ReadBuf.Capacity((ui32_t)fsize);
640 if ( KM_SUCCESS(result) )
641 result = File.Read(ReadBuf.Data(), ReadBuf.Capacity(), &read_size);
643 if ( KM_SUCCESS(result) )
644 outString.assign((const char*)ReadBuf.RoData(), read_size);
652 Kumu::WriteStringIntoFile(const char* filename, const std::string& inString)
655 ui32_t write_count = 0;
656 KM_TEST_NULL_STR(filename);
658 Result_t result = File.OpenWrite(filename);
660 if ( KM_SUCCESS(result) )
661 result = File.Write((byte_t*)inString.c_str(), inString.length(), &write_count);
663 if ( KM_SUCCESS(result) && write_count != inString.length() )
664 return RESULT_WRITEFAIL;
670 //------------------------------------------------------------------------------------------
673 // Win32 directory scanner
680 Kumu::DirScanner::Open(const char* filename)
682 KM_TEST_NULL_STR(filename);
684 // we need to append a '*' to read the entire directory
685 ui32_t fn_len = strlen(filename);
686 char* tmp_file = (char*)malloc(fn_len + 8);
691 strcpy(tmp_file, filename);
692 char* p = &tmp_file[fn_len] - 1;
694 if ( *p != '/' && *p != '\\' )
704 m_Handle = _findfirsti64(tmp_file, &m_FileInfo);
705 Result_t result = RESULT_OK;
707 if ( m_Handle == -1 )
708 result = RESULT_NOT_FOUND;
717 Kumu::DirScanner::Close()
719 if ( m_Handle == -1 )
720 return RESULT_FILEOPEN;
722 if ( _findclose((long)m_Handle) == -1 )
730 // This sets filename param to the same per-instance buffer every time, so
731 // the value will change on the next call
733 Kumu::DirScanner::GetNext(char* filename)
735 KM_TEST_NULL(filename);
737 if ( m_Handle == -1 )
738 return RESULT_FILEOPEN;
740 if ( m_FileInfo.name[0] == '\0' )
741 return RESULT_ENDOFFILE;
743 strncpy(filename, m_FileInfo.name, MaxFilePath);
744 Result_t result = RESULT_OK;
746 if ( _findnexti64((long)m_Handle, &m_FileInfo) == -1 )
748 m_FileInfo.name[0] = '\0';
750 if ( errno != ENOENT )
751 result = RESULT_FAIL;
760 // POSIX directory scanner
764 Kumu::DirScanner::Open(const char* filename)
766 KM_TEST_NULL_STR(filename);
768 Result_t result = RESULT_OK;
770 if ( ( m_Handle = opendir(filename) ) == NULL )
772 if ( errno == ENOENT )
773 result = RESULT_ENDOFFILE;
776 result = RESULT_FAIL;
785 Kumu::DirScanner::Close()
787 if ( m_Handle == NULL )
788 return RESULT_FILEOPEN;
790 if ( closedir(m_Handle) == -1 )
800 Kumu::DirScanner::GetNext(char* filename)
802 KM_TEST_NULL(filename);
804 if ( m_Handle == NULL )
805 return RESULT_FILEOPEN;
807 struct dirent* entry;
811 if ( ( entry = readdir(m_Handle)) == NULL )
812 return RESULT_ENDOFFILE;
817 strncpy(filename, entry->d_name, MaxFilePath);