//| Copyright: (C) 2020-2024 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwTest.h" #include "cwObject.h" #include "cwMem.h" #include "cwFileSys.h" #include "cwText.h" #include "cwFile.h" #ifdef OS_LINUX #include #endif namespace cw { namespace file { typedef struct file_str { FILE* fp; char* fnStr; rc_t lastRC; } this_t; this_t* _handleToPtr(handle_t h) { return handleToPtr(h); } char* _fileToBuf( handle_t h, unsigned nn, unsigned* bufByteCntPtr ) { errno = 0; unsigned n = byteCount(h); char* buf = nullptr; // if the file size calculation is ok if( errno != 0 ) { cwLogSysError(kOpFailRC,errno,"Invalid file buffer length on '%s'.", cwStringNullGuard(name(h))); goto errLabel; } // allocate the read target buffer if((buf = mem::alloc(n+nn)) == nullptr) { cwLogError(kMemAllocFailRC,"Read buffer allocation failed."); goto errLabel; } // read the file if( read(h,buf,n) != kOkRC ) goto errLabel; // zero memory after the file data memset(buf+n,0,nn); if( bufByteCntPtr != nullptr ) *bufByteCntPtr = n; return buf; errLabel: if( bufByteCntPtr != nullptr ) *bufByteCntPtr = 0; mem::release(buf); return nullptr; } char* _fileFnToBuf( const char* fn, unsigned nn, unsigned* bufByteCntPtr ) { handle_t h; char* buf = nullptr; if( open(h,fn,kReadFl | kBinaryFl) != kOkRC ) goto errLabel; buf = _fileToBuf(h,nn,bufByteCntPtr); errLabel: close(h); return buf; } rc_t _fileGetLine( this_t* p, char* buf, unsigned* bufByteCntPtr ) { // fgets() reads up to n-1 bytes into buf[] if( fgets(buf,*bufByteCntPtr,p->fp) == nullptr ) { // an read error or EOF condition occurred *bufByteCntPtr = 0; if( !feof(p->fp ) ) return p->lastRC = cwLogSysError(kReadFailRC,errno,"File read line failed"); p->lastRC = kEofRC; return kEofRC; } return kOkRC; } } } cw::rc_t cw::file::open( handle_t& hRef, const char* fn, unsigned flags ) { this_t* p = nullptr; char* exp_fn = nullptr; rc_t rc = kOkRC; char mode[] = "/0/0/0"; if((rc = close(hRef)) != kOkRC ) return rc; if( cwIsFlag(flags,kReadFl) ) mode[0] = 'r'; else if( cwIsFlag(flags,kWriteFl) ) mode[0] = 'w'; else if( cwIsFlag(flags,kAppendFl) ) mode[0] = 'a'; else cwLogError(kInvalidArgRC,"File open flags must contain 'kReadFl','kWriteFl', or 'kAppendFl'."); if( cwIsFlag(flags,kUpdateFl) ) mode[1] = '+'; // handle requests to use stdin,stdout,stderr FILE* sfp = nullptr; if( cwIsFlag(flags,kStdoutFl) ) { sfp = stdout; fn = "stdout"; } else if( cwIsFlag(flags,kStderrFl) ) { sfp = stderr; fn = "stderr"; } else if( cwIsFlag(flags,kStdinFl) ) { sfp = stdin; fn = "stdin"; } // verify the filename is not empty if( fn == nullptr || strlen(fn)==0 ) return cwLogError(kInvalidArgRC,"File object allocation failed due to empty file name."); unsigned byteCnt = sizeof(this_t) + strlen(fn) + 1; // create the file object if((p = mem::allocZ(byteCnt)) == nullptr ) return cwLogError(kOpFailRC,"File object allocation failed for file '%s'.",cwStringNullGuard(fn)); // copy in the file name p->fnStr = (char*)(p+1); strcpy(p->fnStr,fn); // if a special file was requestd if( sfp != nullptr ) p->fp = sfp; else { exp_fn = filesys::expandPath(fn); errno = 0; if((p->fp = fopen(exp_fn,mode)) == nullptr ) rc = p->lastRC = cwLogSysError(kOpenFailRC,errno,"File open failed on file:'%s'.",cwStringNullGuard(fn)); mem::release(exp_fn); } if( rc != kOkRC ) mem::release(p); else hRef.set(p); return rc; } cw::rc_t cw::file::close( handle_t& hRef ) { if( isValid(hRef) == false ) return kOkRC; this_t* p = _handleToPtr(hRef); errno = 0; if( p->fp != nullptr ) if( fclose(p->fp) != 0 ) return p->lastRC = cwLogSysError(kCloseFailRC,errno,"File close failed on '%s'.", cwStringNullGuard(p->fnStr)); mem::release(p); hRef.clear(); return kOkRC; } bool cw::file::isValid( handle_t h ) { return h.isValid(); } cw::rc_t cw::file::lastRC( handle_t h ) { this_t* p = _handleToPtr(h); return p->lastRC; } cw::rc_t cw::file::read( handle_t h, void* buf, unsigned bufByteCnt, unsigned* actualByteCntRef ) { rc_t rc = kOkRC; this_t* p = _handleToPtr(h); unsigned actualByteCnt = 0; if( p->lastRC != kOkRC ) return p->lastRC; errno = 0; if(( actualByteCnt = fread(buf,1,bufByteCnt,p->fp)) != bufByteCnt ) { if( feof( p->fp ) != 0 ) rc = p->lastRC = kEofRC; else rc= p->lastRC = cwLogSysError(kReadFailRC,errno,"File read failed on '%s'.", cwStringNullGuard(p->fnStr)); } if( actualByteCntRef != nullptr ) *actualByteCntRef = actualByteCnt; return rc; } cw::rc_t cw::file::write( handle_t h, const void* buf, unsigned bufByteCnt ) { this_t* p = _handleToPtr(h); if( p->lastRC != kOkRC ) return p->lastRC; if( bufByteCnt ) { errno = 0; if( fwrite(buf,bufByteCnt,1,p->fp) != 1 ) return p->lastRC = cwLogSysError(kWriteFailRC,errno,"File write failed on '%s'.", cwStringNullGuard(p->fnStr)); } return kOkRC; } cw::rc_t cw::file::seek( handle_t h, enum seekFlags_t flags, int offsByteCnt ) { this_t* p = _handleToPtr(h); unsigned fileflags = 0; if( cwIsFlag(flags,kBeginFl) ) fileflags = SEEK_SET; else if( cwIsFlag(flags,kCurFl) ) fileflags = SEEK_CUR; else if( cwIsFlag(flags,kEndFl) ) fileflags = SEEK_END; else return cwLogError(kInvalidArgRC,"Invalid file seek flag on '%s'.",cwStringNullGuard(p->fnStr)); errno = 0; if( fseek(p->fp,offsByteCnt,fileflags) != 0 ) return cwLogSysError(kSeekFailRC,errno,"File seek failed on '%s'",cwStringNullGuard(p->fnStr)); // if the seek succeeded then override any previous error state p->lastRC = kOkRC; return kOkRC; } cw::rc_t cw::file::tell( handle_t h, long* offsPtr ) { cwAssert( offsPtr != nullptr ); *offsPtr = -1; this_t* p = _handleToPtr(h); errno = 0; if((*offsPtr = ftell(p->fp)) == -1) return p->lastRC = cwLogSysError(kOpFailRC,errno,"File tell failed on '%s'.", cwStringNullGuard(p->fnStr)); return kOkRC; } bool cw::file::eof( handle_t h ) { return feof( _handleToPtr(h)->fp ) != 0; } unsigned cw::file::byteCount( handle_t h ) { struct stat sr; int f; this_t* p = _handleToPtr(h); const char errMsg[] = "File byte count request failed."; errno = 0; if((f = fileno(p->fp)) == -1) { p->lastRC = cwLogSysError(kInvalidOpRC,errno,"%s because fileno() failed on '%s'.",errMsg,cwStringNullGuard(p->fnStr)); return 0; } if(fstat(f,&sr) == -1) { p->lastRC = cwLogSysError(kInvalidOpRC,errno,"%s because fstat() failed on '%s'.",errMsg,cwStringNullGuard(p->fnStr)); return 0; } return sr.st_size; } cw::rc_t cw::file::byteCountFn( const char* fn, unsigned* fileByteCntPtr ) { cwAssert( fileByteCntPtr != nullptr ); rc_t rc; handle_t h; if((rc = open(h,fn,kReadFl)) != kOkRC ) return rc; if( fileByteCntPtr != nullptr) *fileByteCntPtr = byteCount(h); close(h); return rc; } cw::rc_t cw::file::compare( const char* fn0, const char* fn1, bool& isEqualRef ) { rc_t rc = kOkRC; unsigned bufByteCnt = 2048; handle_t h0; handle_t h1; this_t* p0 = nullptr; this_t* p1 = nullptr; char b0[ bufByteCnt ]; char b1[ bufByteCnt ]; isEqualRef = true; if((rc = open(h0,fn0,kReadFl)) != kOkRC ) goto errLabel; if((rc = open(h1,fn1,kReadFl)) != kOkRC ) goto errLabel; p0 = _handleToPtr(h0); p1 = _handleToPtr(h1); while(1) { size_t n0 = fread(b0,1,bufByteCnt,p0->fp); size_t n1 = fread(b1,1,bufByteCnt,p1->fp); if( n0 != n1 || memcmp(b0,b1,n0) != 0 ) { isEqualRef = false; break; } if( n0 != bufByteCnt || n1 != bufByteCnt ) break; } errLabel: close(h0); close(h1); return rc; } const char* cw::file::name( handle_t h ) { this_t* p = _handleToPtr(h); return p->fnStr; } cw::rc_t cw::file::fnWrite( const char* fn, const void* buf, unsigned bufByteCnt ) { handle_t h; rc_t rc; if((rc = open(h,fn,kWriteFl)) != kOkRC ) goto errLabel; rc = write(h,buf,bufByteCnt); errLabel: close(h); return rc; } cw::rc_t cw::file::copy( const char* srcDir, const char* srcFn, const char* srcExt, const char* dstDir, const char* dstFn, const char* dstExt) { rc_t rc = kOkRC; unsigned byteCnt = 0; char* buf = nullptr; char* srcPathFn = nullptr; char* dstPathFn = nullptr; // form the source path fn if((srcPathFn = filesys::makeFn(srcDir,srcFn,srcExt,nullptr)) == nullptr ) { rc = cwLogError(kOpFailRC,"The soure file name for dir:%s name:%s ext:%s could not be formed.",cwStringNullGuard(srcDir),cwStringNullGuard(srcFn),cwStringNullGuard(srcExt)); goto errLabel; } // form the dest path fn if((dstPathFn = filesys::makeFn(dstDir,dstFn,dstExt,nullptr)) == nullptr ) { rc = cwLogError(kOpFailRC,"The destination file name for dir:%s name:%s ext:%s could not be formed.",cwStringNullGuard(dstDir),cwStringNullGuard(dstFn),cwStringNullGuard(dstExt)); goto errLabel; } // verify that the source exists if( filesys::isFile(srcPathFn) == false ) { rc = cwLogError(kOpenFailRC,"The source file '%s' does not exist.",cwStringNullGuard(srcPathFn)); goto errLabel; } // read the source file into a buffer if((buf = fnToBuf(srcPathFn,&byteCnt)) == nullptr ) rc = cwLogError(kReadFailRC,"Attempt to fill a buffer from '%s' failed.",cwStringNullGuard(srcPathFn)); else { // write the file to the output file if( fnWrite(dstPathFn,buf,byteCnt) != kOkRC ) rc = cwLogError(kWriteFailRC,"An attempt to write a buffer to '%s' failed.",cwStringNullGuard(dstPathFn)); } errLabel: // free the buffer mem::release(buf); mem::release(srcPathFn); mem::release(dstPathFn); return rc; } cw::rc_t cw::file::backup( const char* dir, const char* name, const char* ext, const char* dst0_dir ) { rc_t rc = kOkRC; char* newName = nullptr; char* newFn = nullptr; unsigned n = 0; char* srcFn = nullptr; filesys::pathPart_t* pp = nullptr; const char* dst_dir = nullptr; char* dst_base_dir = nullptr; // expand the destination path if( dst0_dir != nullptr ) { if((dst_base_dir = filesys::expandPath(dst0_dir)) == nullptr ) { rc = cwLogError(kOpFailRC,"The backup dest directory '%s' could not be expanded."); goto errLabel; } dst_dir = dst_base_dir; } // form the name of the backup file to backup if((srcFn = filesys::makeFn(dir,name,ext,nullptr)) == nullptr ) { rc = cwLogError(kOpFailRC,"Backup source file name formation failed."); goto errLabel; } // if the src file does not exist then there is nothing to do if( filesys::isFile(srcFn) == false ) return rc; // break the source file name up into dir/fn/ext. if((pp = filesys::pathParts(srcFn)) == nullptr || pp->fnStr==nullptr) { rc = cwLogError(kOpFailRC,"The file name '%s' could not be parsed into its parts.",cwStringNullGuard(srcFn)); goto errLabel; } if( dst_dir == nullptr ) dst_dir = pp->dirStr; // iterate until a unique file name is found for(n=0; 1; ++n) { mem::release(newFn); // generate a new file name newName = mem::printf(newName,"%s_%i",pp->fnStr,n); // form the new file name into a complete path if((newFn = filesys::makeFn(dst_dir,newName,pp->extStr,nullptr)) == nullptr ) { rc = cwLogError(kOpFailRC,"A backup file name could not be formed for the file '%s'.",cwStringNullGuard(newName)); goto errLabel; } // if the new file name is not already in use ... if( filesys::isFile(newFn) == false ) { // .. then duplicate the file if((rc = copy(srcFn,nullptr,nullptr,newFn,nullptr,nullptr)) != kOkRC ) rc = cwLogError(rc,"The file '%s' could not be duplicated as '%s'.",cwStringNullGuard(srcFn),cwStringNullGuard(newFn)); break; } } errLabel: mem::release(dst_base_dir); mem::release(srcFn); mem::release(newFn); mem::release(newName); mem::release(pp); return rc; } cw::rc_t cw::file::backup( const char* fname, const char* dst_dir ) { rc_t rc = kOkRC; filesys::pathPart_t* pp = nullptr; if((pp = filesys::pathParts(fname)) == nullptr ) { rc = cwLogError(kInvalidArgRC,"The parts (dir,name,ext) of the file name '%s' could not be parsed."); goto errLabel; } rc = backup(pp->dirStr,pp->fnStr,pp->extStr,dst_dir); errLabel: mem::release(pp); return rc; } char* cw::file::toBuf( handle_t h, unsigned* bufByteCntPtr ) { return _fileToBuf(h,0,bufByteCntPtr); } char* cw::file::fnToBuf( const char* fn, unsigned* bufByteCntPtr ) { return _fileFnToBuf(fn,0,bufByteCntPtr); } char* cw::file::toStr( handle_t h, unsigned* bufByteCntPtr ) { return _fileToBuf(h,1,bufByteCntPtr); } char* cw::file::fnToStr( const char* fn, unsigned* bufByteCntPtr ) { return _fileFnToBuf(fn,1,bufByteCntPtr); } cw::rc_t cw::file::lineCount( handle_t h, unsigned* lineCntPtr ) { rc_t rc = kOkRC; this_t* p = _handleToPtr(h); unsigned lineCnt = 0; long offs; int c; cwAssert( lineCntPtr != nullptr ); *lineCntPtr = 0; if((rc = tell(h,&offs)) != kOkRC ) return rc; errno = 0; while(1) { c = fgetc(p->fp); if( c == EOF ) { if( errno ) rc = cwLogSysError(kReadFailRC,errno,"File read char failed on 's'.", cwStringNullGuard(name(h))); else ++lineCnt; // add one in case the last line isn't terminated with a '\n'. break; } // if an end-of-line was encountered if( c == '\n' ) ++lineCnt; } if((rc = seek(h,kBeginFl,offs)) != kOkRC ) return rc; *lineCntPtr = lineCnt; return rc; } cw::rc_t cw::file::getLine( handle_t h, char* buf, unsigned* bufByteCntPtr ) { cwAssert( bufByteCntPtr != nullptr ); this_t* p = _handleToPtr(h); unsigned tn = 128; char t[ tn ]; unsigned on = *bufByteCntPtr; long offs; rc_t rc; // store the current file offset if((rc = tell(h,&offs)) != kOkRC ) return rc; // if no buffer was given then use t[] if( buf == nullptr || *bufByteCntPtr == 0 ) { *bufByteCntPtr = tn; buf = t; } // fill the buffer from the current line if((rc = _fileGetLine(p,buf,bufByteCntPtr)) != kOkRC ) return rc; // get length of the string in the buffer // (this is one less than the count of bytes written to the buffer) unsigned n = strlen(buf); // if the provided buffer was large enough to read the entire string if( on > n+1 ) { //*bufByteCntPtr = n+1; return kOkRC; } // // the provided buffer was not large enough // // m tracks the length of the string unsigned m = n; while( n+1 == *bufByteCntPtr ) { // fill the buffer from the current line if((rc = _fileGetLine(p,buf,bufByteCntPtr)) != kOkRC ) return rc; n = strlen(buf); m += n; } // restore the original file offset if((rc = seek(h,kBeginFl,offs)) != kOkRC ) return rc; // add 1 for /0, 1 for /n and 1 to detect buf-too-short *bufByteCntPtr = m+3; return kBufTooSmallRC; } cw::rc_t cw::file::getLineAuto( handle_t h, char** bufPtrPtr, unsigned* bufByteCntPtr ) { rc_t rc = kOkRC; bool fl = true; char* buf = *bufPtrPtr; *bufPtrPtr = nullptr; while(fl) { fl = false; switch( rc = getLine(h,buf,bufByteCntPtr) ) { case kOkRC: { *bufPtrPtr = buf; } break; case kBufTooSmallRC: buf = mem::resizeZ(buf,*bufByteCntPtr); fl = true; break; default: mem::release(buf); break; } } return rc; } cw::rc_t cw::file::readChar( handle_t h, char* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readUChar( handle_t h, unsigned char* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readShort( handle_t h, short* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readUShort( handle_t h, unsigned short* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readLong( handle_t h, long* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readULong( handle_t h, unsigned long* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readInt( handle_t h, int* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readUInt( handle_t h, unsigned int* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readFloat( handle_t h, float* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readDouble( handle_t h, double* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::readBool( handle_t h, bool* buf, unsigned cnt ) { return read(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeChar( handle_t h, const char* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeUChar( handle_t h, const unsigned char* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeShort( handle_t h, const short* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeUShort( handle_t h, const unsigned short* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeLong( handle_t h, const long* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeULong( handle_t h, const unsigned long* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeInt( handle_t h, const int* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeUInt( handle_t h, const unsigned int* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeFloat( handle_t h, const float* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeDouble( handle_t h, const double* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeBool( handle_t h, const bool* buf, unsigned cnt ) { return write(h,buf,sizeof(buf[0])*cnt); } cw::rc_t cw::file::writeStr( handle_t h, const char* s ) { rc_t rc; unsigned n = textLength(s); if((rc = writeUInt(h,&n,1)) != kOkRC ) return rc; if( n > 0 ) rc = writeChar(h,s,n); return rc; } cw::rc_t cw::file::readStr( handle_t h, char** sRef, unsigned maxCharN ) { unsigned n; rc_t rc; cwAssert(sRef != nullptr ); *sRef = nullptr; if( maxCharN == 0 ) maxCharN = 16384; // read the string length if((rc = readUInt(h,&n,1)) != kOkRC ) return rc; // verify that string isn't too long if( n > maxCharN ) { return cwLogError(kInvalidArgRC,"The stored string is larger than the maximum allowable size of %i.",maxCharN); } // allocate a read buffer char* s = mem::allocZ(n+1); // fill the buffer from the file if((rc = readChar(h,s,n)) != kOkRC ) return rc; s[n] = 0; // terminate the string *sRef = s; return rc; } cw::rc_t cw::file::print( handle_t h, const char* text ) { this_t* p = _handleToPtr(h); errno = 0; if( fputs(text,p->fp) < 0 ) return p->lastRC = cwLogSysError(kOpFailRC,errno,"File print failed on '%s'.", cwStringNullGuard(name(h))); return kOkRC; } cw::rc_t cw::file::vPrintf( handle_t h, const char* fmt, va_list vl ) { this_t* p = _handleToPtr(h); if( vfprintf(p->fp,fmt,vl) < 0 ) return p->lastRC = cwLogSysError(kOpFailRC,errno,"File print failed on '%s'.", cwStringNullGuard(name(h))); return kOkRC; } cw::rc_t cw::file::printf( handle_t h, const char* fmt, ... ) { va_list vl; va_start(vl,fmt); rc_t rc = vPrintf(h,fmt,vl); va_end(vl); return rc; }