// The developer of the original code and/or files is Tripwire, Inc. // Portions created by Tripwire, Inc. are copyright (C) 2000-2019 Tripwire, // Inc. Tripwire is a registered trademark of Tripwire, Inc. All rights // reserved. // // This program is free software. The contents of this file are subject // to the terms of the GNU General Public License as published by the // Free Software Foundation; either version 2 of the License, or (at your // option) any later version. You may redistribute it and/or modify it // only in compliance with the GNU General Public License. // // This program is distributed in the hope that it will be useful. // However, this program is distributed AS-IS WITHOUT ANY // WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS // FOR A PARTICULAR PURPOSE. Please see the GNU General Public License // for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, // USA. // // Nothing in the GNU General Public License or any other license to use // the code or files shall permit you to use Tripwire's trademarks, // service marks, or other intellectual property without Tripwire's // prior written consent. // // If you have any questions, please contact Tripwire, Inc. at either // info@tripwire.org or www.tripwire.org. // // file_unix.cpp : Specific implementation of file operations for Unix. /* On GNU/Hurd, need to define _GNU_SOURCE in order to use O_NOATIME which technically is still a nonstandard extension to open() */ #if IS_HURD # define _GNU_SOURCE #endif #include "core/stdcore.h" #if !IS_UNIX # error Need to be unix to use unixfsservices #endif #include "core/file.h" #include #include #include #include #include #include #if HAVE_SYS_FS_VX_IOCTL_H #include #endif #include "core/debug.h" #include "core/corestrings.h" #include "core/fsservices.h" #include "core/errorutil.h" #if IS_RISCOS #include #endif /////////////////////////////////////////////////////////////////////////// // cFile_i : Insulated implementation for cFile objects. /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// struct cFile_i { cFile_i(); ~cFile_i(); int m_fd; //underlying file descriptor FILE* mpCurrStream; //currently defined file stream TSTRING mFileName; //the name of the file we are currently referencing. uint32_t mFlags; //Flags used to open the file }; //Ctor cFile_i::cFile_i() : m_fd(-1), mpCurrStream(NULL), mFlags(0) { } //Dtor cFile_i::~cFile_i() { if (mpCurrStream != NULL) { fclose(mpCurrStream); mpCurrStream = NULL; #if !CAN_UNLINK_WHILE_OPEN // so unlink after close instead if (mFlags & cFile::OPEN_LOCKED_TEMP) { // unlink this file if (0 != unlink(mFileName.c_str())) { throw(eFileOpen(mFileName, iFSServices::GetInstance()->GetErrString())); } } #endif } } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // cFile () -- Implements file operations /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// cFile::cFile() : mpData(NULL), isWritable(false) { mpData = new cFile_i; } cFile::~cFile() { if (mpData != NULL) { delete mpData; mpData = NULL; } } /////////////////////////////////////////////////////////////////////////////// // Open /////////////////////////////////////////////////////////////////////////////// #if !USES_DEVICE_PATH void cFile::Open(const TSTRING& sFileName, uint32_t flags) { #else void cFile::Open(const TSTRING& sFileNameC, uint32_t flags) { TSTRING sFileName = cDevicePath::AsNative(sFileNameC); #endif mode_t openmode = 0664; if (mpData->mpCurrStream != NULL) Close(); mpData->mFlags = flags; // // set up the open permissions // int perm = 0; TSTRING mode; if (flags & OPEN_WRITE) { perm |= O_RDWR; isWritable = true; mode = _T("rb"); if (flags & OPEN_TRUNCATE) { perm |= O_TRUNC; perm |= O_CREAT; mode = _T("w+b"); } else mode = _T("r+b"); } else { perm |= O_RDONLY; isWritable = false; mode = _T("rb"); } if (flags & OPEN_EXCLUSIVE) { perm |= O_CREAT | O_EXCL; openmode = (mode_t)0600; // Make sure only root can read the file } if (flags & OPEN_CREATE) perm |= O_CREAT; #ifdef O_NONBLOCK if (flags & OPEN_SCANNING) perm |= O_NONBLOCK; #endif #ifdef O_NOATIME if (flags & OPEN_SCANNING) perm |= O_NOATIME; #endif #ifdef O_DIRECT //Only use O_DIRECT for scanning, since cfg/policy/report reads // don't happen w/ a nice round block size. if ((flags & OPEN_DIRECT) && (flags & OPEN_SCANNING)) perm |= O_DIRECT; #endif // // actually open the file // int fh = _topen(sFileName.c_str(), perm, openmode); if (fh == -1) { throw(eFileOpen(sFileName, iFSServices::GetInstance()->GetErrString())); } mpData->m_fd = fh; #if CAN_UNLINK_WHILE_OPEN if (flags & OPEN_LOCKED_TEMP) { // unlink this file if (0 != unlink(sFileName.c_str())) { // we weren't able to unlink file, so close handle and fail close(fh); throw(eFileOpen(sFileName, iFSServices::GetInstance()->GetErrString())); } } #endif // // turn the file handle into a FILE* // mpData->mpCurrStream = _tfdopen(fh, mode.c_str()); mpData->mFileName = sFileName; //Set mFileName to the newly opened file. cFile::Rewind(); #ifdef F_NOCACHE //OSX if ((flags & OPEN_DIRECT) && (flags & OPEN_SCANNING)) fcntl(fh, F_NOCACHE, 1); #endif #if IS_SOLARIS if ((flags & OPEN_DIRECT) && (flags & OPEN_SCANNING)) directio(fh, DIRECTIO_ON); #endif #if HAVE_POSIX_FADVISE if (flags & OPEN_SCANNING && !(flags & OPEN_DIRECT)) { posix_fadvise(fh, 0, 0, POSIX_FADV_SEQUENTIAL); posix_fadvise(fh, 0, 0, POSIX_FADV_NOREUSE); } #elif HAVE_SYS_FS_VX_IOCTL_H if (flags & OPEN_SCANNING) { if (flags & OPEN_DIRECT) ioctl(fh, VX_SETCACHE, VX_DIRECT); else ioctl(fh, VX_SETCACHE, VX_SEQ | VX_NOREUSE); } #endif } /////////////////////////////////////////////////////////////////////////// // Close -- Closes mpCurrStream and sets the pointer to NULL /////////////////////////////////////////////////////////////////////////// void cFile::Close() //throw(eFile) { if (mpData->mpCurrStream != NULL) { #ifdef HAVE_POSIX_FADVISE posix_fadvise(fileno(mpData->mpCurrStream), 0, 0, POSIX_FADV_DONTNEED); #endif fclose(mpData->mpCurrStream); mpData->mpCurrStream = NULL; } mpData->mFileName.empty(); } bool cFile::IsOpen(void) const { return (mpData->mpCurrStream != NULL); } // Autoconf docs say HAVE_FSEEKO applies to both fseeko & ftello #if HAVE_FSEEKO #define tss_fseek fseeko #define tss_ftell ftello #else #define tss_fseek fseek #define tss_ftell ftell #endif /////////////////////////////////////////////////////////////////////////// // Seek -- Positions the read/write offset in mpCurrStream. Returns the // current offset upon completion. Returns 0 if no stream is defined. /////////////////////////////////////////////////////////////////////////// cFile::File_t cFile::Seek(File_t offset, SeekFrom From) const //throw(eFile) { //Check to see if a file as been opened yet... ASSERT(mpData->mpCurrStream != 0); int apiFrom; switch (From) { case cFile::SEEK_BEGIN: apiFrom = SEEK_SET; break; case cFile::SEEK_CURRENT: apiFrom = SEEK_CUR; break; case cFile::SEEK_EOF: apiFrom = SEEK_END; break; default: //An invalid SeekFrom parameter was passed. throw(eInternal(_T("file_unix"))); } // this is a hack to simulate running out of disk space #if 0 static int blowupCount = 1; if (++blowupCount == 1075) { fputs("***** faking seek failure!\n", stderr); //throw eFileSeek(); throw std::bad_alloc(); } fprintf(stderr, "%d\n", blowupCount); #endif if (tss_fseek(mpData->mpCurrStream, offset, apiFrom) != 0) { #ifdef DEBUG cDebug d("cFile::Seek"); d.TraceDebug("Seek failed!\n"); #endif throw eFileSeek(); } return tss_ftell(mpData->mpCurrStream); } /////////////////////////////////////////////////////////////////////////// // Read -- Returns the actual bytes read from mpCurrStream. Returns 0 if // mpCurrStream is undefined. /////////////////////////////////////////////////////////////////////////// cFile::File_t cFile::Read(void* buffer, File_t nBytes) const //throw(eFile) { File_t iBytesRead; // Has a file been opened? ASSERT(mpData->mpCurrStream != NULL); // Is the nBytes parameter 0? If so, return without touching buffer: if (nBytes == 0) return 0; if (mpData->mFlags & OPEN_DIRECT) { iBytesRead = read(mpData->m_fd, buffer, nBytes); if (iBytesRead < 0) { throw eFileRead(mpData->mFileName, iFSServices::GetInstance()->GetErrString()); } } else { iBytesRead = fread(buffer, sizeof(uint8_t), nBytes, mpData->mpCurrStream); if (ferror(mpData->mpCurrStream) != 0) { throw eFileRead(mpData->mFileName, iFSServices::GetInstance()->GetErrString()); } } return iBytesRead; } /////////////////////////////////////////////////////////////////////////// // Write -- Returns the actual number of bytes written to mpCurrStream // Returns 0 if no file has been opened. /////////////////////////////////////////////////////////////////////////// cFile::File_t cFile::Write(const void* buffer, File_t nBytes) //throw(eFile) { File_t actual_count = 0; // Has a file been opened? Is it writable? ASSERT(mpData->mpCurrStream != NULL); ASSERT(isWritable); if ((actual_count = fwrite(buffer, sizeof(uint8_t), nBytes, mpData->mpCurrStream)) < nBytes) throw eFileWrite(mpData->mFileName, iFSServices::GetInstance()->GetErrString()); else return actual_count; } /////////////////////////////////////////////////////////////////////////// // Tell -- Returns the current file offset. Returns 0 if no file has been // opened. /////////////////////////////////////////////////////////////////////////// cFile::File_t cFile::Tell() const { ASSERT(mpData->mpCurrStream != 0); return ftell(mpData->mpCurrStream); } /////////////////////////////////////////////////////////////////////////// // Flush -- Flushes the current stream. /////////////////////////////////////////////////////////////////////////// bool cFile::Flush() //throw(eFile) { if (mpData->mpCurrStream == NULL) throw eFileFlush(mpData->mFileName, iFSServices::GetInstance()->GetErrString()); return (fflush(mpData->mpCurrStream) == 0); } /////////////////////////////////////////////////////////////////////////// // Rewind -- Sets the offset to the beginning of the file. If mpCurrStream // is NULL, this method returns false. If the rewind operation fails, // an exception is thrown. /////////////////////////////////////////////////////////////////////////// void cFile::Rewind() const //throw(eFile) { ASSERT(mpData->mpCurrStream != 0); rewind(mpData->mpCurrStream); if (ftell(mpData->mpCurrStream) != 0) throw(eFileRewind(mpData->mFileName, iFSServices::GetInstance()->GetErrString())); } /////////////////////////////////////////////////////////////////////////// // GetSize -- Returns the size of the current stream, if one has been // opened. If no stream has been opened, returns -1. /////////////////////////////////////////////////////////////////////////// cFile::File_t cFile::GetSize() const { File_t vCurrentOffset = Tell(); //for saving the current offset File_t ret; //Has a file been opened? If not, return -1 if (mpData->mpCurrStream == NULL) return -1; ret = Seek(0, cFile::SEEK_EOF); Seek(vCurrentOffset, cFile::SEEK_BEGIN); //return the offset to it's position prior to GetSize call. return ret; } ///////////////////////////////////////////////////////////////////////// // Truncate ///////////////////////////////////////////////////////////////////////// void cFile::Truncate(File_t offset) // throw(eFile) { ASSERT(mpData->mpCurrStream != 0); ASSERT(isWritable); ftruncate(fileno(mpData->mpCurrStream), offset); if (GetSize() != offset) throw(eFileTrunc(mpData->mFileName, iFSServices::GetInstance()->GetErrString())); } ///////////////////////////////////////////////////////////////////////// // Platform path conversion methods ///////////////////////////////////////////////////////////////////////// bool cDosPath::IsAbsolutePath(const TSTRING& in) { if (in.empty()) return false; if (in[0] == '/') return true; if (in.length() >= 2 && in[1] == ':') return true; return false; } // For paths of type C:\DOS TSTRING cDosPath::AsPosix(const TSTRING& in) { if (in[0] == '/') { return in; } TSTRING out = (cDosPath::IsAbsolutePath(in)) ? ("/dev/" + in) : in; std::replace(out.begin(), out.end(), '\\', '/'); out.erase(std::remove(out.begin(), out.end(), ':'), out.end()); return out; } TSTRING cDosPath::AsNative(const TSTRING& in) { if (in[0] != '/') { return in; } if (in.find("/dev") != 0 || in.length() < 6) return in; TSTRING out = "?:/"; out[0] = in[5]; if (in.length() >= 8) out.append(in.substr(7)); std::replace(out.begin(), out.end(), '/', '\\'); return out; } TSTRING cDosPath::BackupName(const TSTRING& in) { TSTRING out = in; std::string::size_type pos = out.find_last_of("\\"); if (std::string::npos == pos) return in; TSTRING path = in.substr(0, pos); TSTRING name = in.substr(pos, 9); std::replace(name.begin(), name.end(), '.', '_'); path.append(name); return path; } ///////////////////////////////////////////////////////////////////////// bool cArosPath::IsAbsolutePath(const TSTRING& in) { if (in.empty()) return false; if (in[0] == '/') return true; if (in.find(":") != std::string::npos) return true; return false; } // For paths of type DH0:dir/file TSTRING cArosPath::AsPosix(const TSTRING& in) { if (in[0] == '/') { return in; } TSTRING out = IsAbsolutePath(in) ? '/' + in : in; std::replace(out.begin(), out.end(), ':', '/'); return out; } TSTRING cArosPath::AsNative(const TSTRING& in) { if (in[0] != '/') { return in; } std::string::size_type drive = in.find_first_not_of("/"); TSTRING out = (drive != std::string::npos) ? in.substr(drive) : in; TSTRING::size_type t = out.find_first_of('/'); if (t != std::string::npos) out[t] = ':'; else out.append(":"); return out; } ///////////////////////////////////////////////////////////////////////// bool cRiscosPath::IsAbsolutePath(const TSTRING& in) { if (in.empty()) return false; if (in[0] == '/') return true; if (in.find("$") != std::string::npos) return true; return false; } // For paths of type SDFS::Volume.$.dir.file TSTRING cRiscosPath::AsPosix(const TSTRING& in) { #if IS_RISCOS if (in[0] == '/') { return in; } TSTRING out; char* unixified = __unixify(in.c_str(), 0, 0, 0, 0); if (unixified) { out.assign(unixified); free(unixified); return out; } return in; #else return in; #endif } TSTRING cRiscosPath::AsNative(const TSTRING& in) { #if IS_RISCOS if (in[0] != '/') { return in; } TSTRING out; int buf_size = in.length() + 100; // examples pad by 100 std::vector buf(buf_size); __riscosify(in.c_str(), 0, 0, &buf[0], buf_size, 0); if (buf[0]) { out.assign(&buf[0]); return out; } return in; #else return in; #endif } ///////////////////////////////////////////////////////////////////////// bool cRedoxPath::IsAbsolutePath(const TSTRING& in) { if (in.empty()) return false; if (in[0] == '/') return true; if (in.find(":") != std::string::npos) return true; return false; } // For paths of type file:/dir/file TSTRING cRedoxPath::AsPosix(const TSTRING& in) { if (in[0] == '/') { return in; } TSTRING out = IsAbsolutePath(in) ? '/' + in : in; std::string::size_type colon = out.find_first_of(":"); if (colon != std::string::npos) out.erase(colon, 1); return out; } TSTRING cRedoxPath::AsNative(const TSTRING& in) { if (in[0] != '/') { return in; } std::string::size_type drive = in.find_first_not_of("/"); TSTRING out = (drive != std::string::npos) ? in.substr(drive) : in; TSTRING::size_type slash = out.find_first_of('/'); if (slash != std::string::npos) out.insert(slash, ":"); else out.append(":/"); return out; }