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_L(path);
61 KM_TEST_NULL_L(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_L(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)
109 if ( pathname == 0 || *pathname == 0 )
114 if ( KM_SUCCESS(do_stat(pathname, &info)) )
116 if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
126 Kumu::PathIsDirectory(const char* pathname)
128 if ( pathname == 0 || *pathname == 0 )
133 if ( KM_SUCCESS(do_stat(pathname, &info)) )
135 if ( info.st_mode & S_IFDIR )
145 Kumu::FileSize(const char* pathname)
147 if ( pathname == 0 || *pathname == 0 )
152 if ( KM_SUCCESS(do_stat(pathname, &info)) )
154 if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
155 return(info.st_size);
161 //------------------------------------------------------------------------------------------
162 // portable aspects of the file classes
164 const int IOVecMaxEntries = 32; // we never use more that 3, but that number seems somehow small...
167 class Kumu::FileWriter::h__iovec
171 struct iovec m_iovec[IOVecMaxEntries];
172 h__iovec() : m_Count(0) {}
179 Kumu::FileReader::Size() const
182 return FileSize(m_Filename.c_str());
186 if ( KM_SUCCESS(do_fstat(m_Handle, &info)) )
188 if ( info.st_mode & ( S_IFREG|S_IFLNK ) )
189 return(info.st_size);
196 // these are declared here instead of in the header file
197 // because we have a mem_ptr that is managing a hidden class
198 Kumu::FileWriter::FileWriter() {}
199 Kumu::FileWriter::~FileWriter() {}
203 Kumu::FileWriter::Writev(const byte_t* buf, ui32_t buf_len)
205 assert( ! m_IOVec.empty() );
206 register h__iovec* iov = m_IOVec;
209 if ( iov->m_Count >= IOVecMaxEntries )
211 DefaultLogSink().Error("The iovec is full! Only %u entries allowed before a flush.\n",
213 return RESULT_WRITEFAIL;
216 iov->m_iovec[iov->m_Count].iov_base = (char*)buf; // stupid iovec uses char*
217 iov->m_iovec[iov->m_Count].iov_len = buf_len;
225 //------------------------------------------------------------------------------------------
229 Kumu::FileReader::OpenRead(const char* filename) const
231 KM_TEST_NULL_STR_L(filename);
232 const_cast<FileReader*>(this)->m_Filename = filename;
234 // suppress popup window on error
235 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
237 const_cast<FileReader*>(this)->m_Handle = ::CreateFile(filename,
238 (GENERIC_READ), // open for reading
239 FILE_SHARE_READ, // share for reading
241 OPEN_EXISTING, // read
242 FILE_ATTRIBUTE_NORMAL, // normal file
243 NULL // no template file
246 ::SetErrorMode(prev);
248 return ( m_Handle == INVALID_HANDLE_VALUE ) ?
249 Kumu::RESULT_FILEOPEN : Kumu::RESULT_OK;
254 Kumu::FileReader::Close() const
256 if ( m_Handle == INVALID_HANDLE_VALUE )
257 return Kumu::RESULT_FILEOPEN;
259 // suppress popup window on error
260 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
261 BOOL result = ::CloseHandle(m_Handle);
262 ::SetErrorMode(prev);
263 const_cast<FileReader*>(this)->m_Handle = INVALID_HANDLE_VALUE;
265 return ( result == 0 ) ? Kumu::RESULT_FAIL : Kumu::RESULT_OK;
270 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
272 if ( m_Handle == INVALID_HANDLE_VALUE )
273 return Kumu::RESULT_STATE;
276 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
277 in.QuadPart = position;
278 in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, whence);
279 HRESULT LastError = GetLastError();
280 ::SetErrorMode(prev);
282 if ( (LastError != NO_ERROR
283 && (in.LowPart == INVALID_SET_FILE_POINTER
284 || in.LowPart == ERROR_NEGATIVE_SEEK )) )
285 return Kumu::RESULT_READFAIL;
287 return Kumu::RESULT_OK;
292 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
296 if ( m_Handle == (HANDLE)-1L )
297 return Kumu::RESULT_FILEOPEN;
300 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
301 in.QuadPart = (__int64)0;
302 in.LowPart = ::SetFilePointer(m_Handle, in.LowPart, &in.HighPart, FILE_CURRENT);
303 HRESULT LastError = GetLastError();
304 ::SetErrorMode(prev);
306 if ( (LastError != NO_ERROR
307 && (in.LowPart == INVALID_SET_FILE_POINTER
308 || in.LowPart == ERROR_NEGATIVE_SEEK )) )
309 return Kumu::RESULT_READFAIL;
311 *pos = (Kumu::fpos_t)in.QuadPart;
312 return Kumu::RESULT_OK;
317 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
320 Result_t result = Kumu::RESULT_OK;
324 if ( read_count == 0 )
325 read_count = &tmp_int;
329 if ( m_Handle == INVALID_HANDLE_VALUE )
330 return Kumu::RESULT_FILEOPEN;
332 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
333 if ( ::ReadFile(m_Handle, buf, buf_len, &tmp_count, NULL) == 0 )
334 result = Kumu::RESULT_READFAIL;
336 ::SetErrorMode(prev);
338 if ( tmp_count == 0 ) /* EOF */
339 result = Kumu::RESULT_ENDOFFILE;
341 if ( KM_SUCCESS(result) )
342 *read_count = tmp_count;
349 //------------------------------------------------------------------------------------------
354 Kumu::FileWriter::OpenWrite(const char* filename)
356 KM_TEST_NULL_STR_L(filename);
357 m_Filename = filename;
359 // suppress popup window on error
360 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
362 m_Handle = ::CreateFile(filename,
363 (GENERIC_WRITE|GENERIC_READ), // open for reading
364 FILE_SHARE_READ, // share for reading
366 CREATE_ALWAYS, // overwrite (beware!)
367 FILE_ATTRIBUTE_NORMAL, // normal file
368 NULL // no template file
371 ::SetErrorMode(prev);
373 if ( m_Handle == INVALID_HANDLE_VALUE )
374 return Kumu::RESULT_FILEOPEN;
376 m_IOVec = new h__iovec;
377 return Kumu::RESULT_OK;
382 Kumu::FileWriter::Writev(ui32_t* bytes_written)
384 assert( ! m_IOVec.empty() );
385 register h__iovec* iov = m_IOVec;
388 if ( bytes_written == 0 )
389 bytes_written = &tmp_int;
391 if ( m_Handle == INVALID_HANDLE_VALUE )
392 return Kumu::RESULT_STATE;
395 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
396 Result_t result = Kumu::RESULT_OK;
398 // AFAIK, there is no writev() equivalent in the win32 API
399 for ( register int i = 0; i < iov->m_Count; i++ )
401 ui32_t tmp_count = 0;
402 BOOL wr_result = ::WriteFile(m_Handle,
403 iov->m_iovec[i].iov_base,
404 iov->m_iovec[i].iov_len,
408 if ( wr_result == 0 || tmp_count != iov->m_iovec[i].iov_len)
410 result = Kumu::RESULT_WRITEFAIL;
414 *bytes_written += tmp_count;
417 ::SetErrorMode(prev);
418 iov->m_Count = 0; // error nor not, all is lost
425 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
430 if ( bytes_written == 0 )
431 bytes_written = &tmp_int;
433 if ( m_Handle == INVALID_HANDLE_VALUE )
434 return Kumu::RESULT_STATE;
436 // suppress popup window on error
437 UINT prev = ::SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
438 BOOL result = ::WriteFile(m_Handle, buf, buf_len, (DWORD*)bytes_written, NULL);
439 ::SetErrorMode(prev);
441 if ( result == 0 || bytes_written != buf_len )
442 return Kumu::RESULT_WRITEFAIL;
444 return Kumu::RESULT_OK;
448 //------------------------------------------------------------------------------------------
453 Kumu::FileReader::OpenRead(const char* filename) const
455 KM_TEST_NULL_STR_L(filename);
456 const_cast<FileReader*>(this)->m_Filename = filename;
457 const_cast<FileReader*>(this)->m_Handle = open(filename, O_RDONLY, 0);
458 return ( m_Handle == -1L ) ? RESULT_FILEOPEN : RESULT_OK;
463 Kumu::FileReader::Close() const
465 if ( m_Handle == -1L )
466 return RESULT_FILEOPEN;
469 const_cast<FileReader*>(this)->m_Handle = -1L;
475 Kumu::FileReader::Seek(Kumu::fpos_t position, SeekPos_t whence) const
477 if ( m_Handle == -1L )
478 return RESULT_FILEOPEN;
480 if ( lseek(m_Handle, position, whence) == -1L )
481 return RESULT_BADSEEK;
488 Kumu::FileReader::Tell(Kumu::fpos_t* pos) const
492 if ( m_Handle == -1L )
493 return RESULT_FILEOPEN;
495 Kumu::fpos_t tmp_pos;
497 if ( (tmp_pos = lseek(m_Handle, 0, SEEK_CUR)) == -1 )
498 return RESULT_READFAIL;
506 Kumu::FileReader::Read(byte_t* buf, ui32_t buf_len, ui32_t* read_count) const
512 if ( read_count == 0 )
513 read_count = &tmp_int;
517 if ( m_Handle == -1L )
518 return RESULT_FILEOPEN;
520 if ( (tmp_count = read(m_Handle, buf, buf_len)) == -1L )
521 return RESULT_READFAIL;
523 *read_count = tmp_count;
524 return (tmp_count == 0 ? RESULT_ENDOFFILE : RESULT_OK);
528 //------------------------------------------------------------------------------------------
533 Kumu::FileWriter::OpenWrite(const char* filename)
535 KM_TEST_NULL_STR_L(filename);
536 m_Filename = filename;
537 m_Handle = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0664);
539 if ( m_Handle == -1L )
541 DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
542 return RESULT_FILEOPEN;
545 m_IOVec = new h__iovec;
551 Kumu::FileWriter::OpenModify(const char* filename)
553 KM_TEST_NULL_STR_L(filename);
554 m_Filename = filename;
555 m_Handle = open(filename, O_RDWR|O_CREAT, 0664);
557 if ( m_Handle == -1L )
559 DefaultLogSink().Error("Error opening file %s: %s\n", filename, strerror(errno));
560 return RESULT_FILEOPEN;
563 m_IOVec = new h__iovec;
569 Kumu::FileWriter::Writev(ui32_t* bytes_written)
571 assert( ! m_IOVec.empty() );
572 register h__iovec* iov = m_IOVec;
575 if ( bytes_written == 0 )
576 bytes_written = &tmp_int;
578 if ( m_Handle == -1L )
582 for ( int i = 0; i < iov->m_Count; i++ )
583 total_size += iov->m_iovec[i].iov_len;
585 int write_size = writev(m_Handle, iov->m_iovec, iov->m_Count);
587 if ( write_size == -1L || write_size != total_size )
588 return RESULT_WRITEFAIL;
591 *bytes_written = write_size;
597 Kumu::FileWriter::Write(const byte_t* buf, ui32_t buf_len, ui32_t* bytes_written)
602 if ( bytes_written == 0 )
603 bytes_written = &tmp_int;
605 if ( m_Handle == -1L )
608 int write_size = write(m_Handle, buf, buf_len);
610 if ( write_size == -1L || (ui32_t)write_size != buf_len )
611 return RESULT_WRITEFAIL;
613 *bytes_written = write_size;
620 //------------------------------------------------------------------------------------------
625 Kumu::ReadFileIntoString(const char* filename, std::string& outString, ui32_t max_size)
628 ui32_t read_size = 0;
632 KM_TEST_NULL_STR_L(filename);
634 Result_t result = File.OpenRead(filename);
636 if ( KM_SUCCESS(result) )
640 if ( fsize > (Kumu::fpos_t)max_size )
642 DefaultLogSink().Error("%s: exceeds available buffer size (%u)\n", filename, max_size);
648 DefaultLogSink().Error("%s: zero file size\n", filename);
649 return RESULT_READFAIL;
652 result = ReadBuf.Capacity((ui32_t)fsize);
655 if ( KM_SUCCESS(result) )
656 result = File.Read(ReadBuf.Data(), ReadBuf.Capacity(), &read_size);
658 if ( KM_SUCCESS(result) )
659 outString.assign((const char*)ReadBuf.RoData(), read_size);
667 Kumu::WriteStringIntoFile(const char* filename, const std::string& inString)
670 ui32_t write_count = 0;
671 KM_TEST_NULL_STR_L(filename);
673 Result_t result = File.OpenWrite(filename);
675 if ( KM_SUCCESS(result) )
676 result = File.Write((byte_t*)inString.c_str(), inString.length(), &write_count);
682 //------------------------------------------------------------------------------------------
685 // Win32 directory scanner
692 Kumu::DirScanner::Open(const char* filename)
694 KM_TEST_NULL_STR_L(filename);
696 // we need to append a '*' to read the entire directory
697 ui32_t fn_len = strlen(filename);
698 char* tmp_file = (char*)malloc(fn_len + 8);
703 strcpy(tmp_file, filename);
704 char* p = &tmp_file[fn_len] - 1;
706 if ( *p != '/' && *p != '\\' )
716 m_Handle = _findfirsti64(tmp_file, &m_FileInfo);
717 Result_t result = RESULT_OK;
719 if ( m_Handle == -1 )
720 result = RESULT_NOT_FOUND;
729 Kumu::DirScanner::Close()
731 if ( m_Handle == -1 )
732 return RESULT_FILEOPEN;
734 if ( _findclose((long)m_Handle) == -1 )
742 // This sets filename param to the same per-instance buffer every time, so
743 // the value will change on the next call
745 Kumu::DirScanner::GetNext(char* filename)
747 KM_TEST_NULL_L(filename);
749 if ( m_Handle == -1 )
750 return RESULT_FILEOPEN;
752 if ( m_FileInfo.name[0] == '\0' )
753 return RESULT_ENDOFFILE;
755 strncpy(filename, m_FileInfo.name, MaxFilePath);
756 Result_t result = RESULT_OK;
758 if ( _findnexti64((long)m_Handle, &m_FileInfo) == -1 )
760 m_FileInfo.name[0] = '\0';
762 if ( errno != ENOENT )
763 result = RESULT_FAIL;
772 // POSIX directory scanner
776 Kumu::DirScanner::Open(const char* filename)
778 KM_TEST_NULL_STR_L(filename);
780 Result_t result = RESULT_OK;
782 if ( ( m_Handle = opendir(filename) ) == NULL )
784 if ( errno == ENOENT )
785 result = RESULT_ENDOFFILE;
788 result = RESULT_FAIL;
797 Kumu::DirScanner::Close()
799 if ( m_Handle == NULL )
800 return RESULT_FILEOPEN;
802 if ( closedir(m_Handle) == -1 )
812 Kumu::DirScanner::GetNext(char* filename)
814 KM_TEST_NULL_L(filename);
816 if ( m_Handle == NULL )
817 return RESULT_FILEOPEN;
819 struct dirent* entry;
823 if ( ( entry = readdir(m_Handle)) == NULL )
824 return RESULT_ENDOFFILE;
829 strncpy(filename, entry->d_name, MaxFilePath);