// ***************************************************************** -*- C++ -*- /* * Copyright (C) 2004-2007 Andreas Huggel * * This program is part of the Exiv2 distribution. * * This program is free software; you can redistribute it and/or * modify it under 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. */ /* File: basicio.cpp Version: $Rev$ Author(s): Brad Schick (brad) History: 04-Dec-04, brad: created */ // ***************************************************************************** #include "rcsid.hpp" EXIV2_RCSID("@(#) $Id$") // ***************************************************************************** // included header files #ifdef _MSC_VER # include "exv_msvc.h" #else # include "exv_conf.h" #endif #include "basicio.hpp" #include "futils.hpp" #include "types.hpp" #include "error.hpp" // + standard includes #include #include #include #include #include // for remove, rename #include // for alloc, realloc, free #include // for stat, chmod #include // for stat, chmod #ifdef EXV_HAVE_SYS_MMAN_H # include // for mmap and munmap #endif #ifdef EXV_HAVE_PROCESS_H # include #endif #ifdef EXV_HAVE_UNISTD_H # include // for getpid, stat #endif #if defined WIN32 && !defined __CYGWIN__ # include #endif // ***************************************************************************** // class member definitions namespace Exiv2 { FileIo::FileIo(const std::string& path) : path_(path), fp_(0), opMode_(opSeek), pMappedArea_(0), mappedLength_(0), isMalloced_(false) { } FileIo::~FileIo() { munmap(); close(); } void FileIo::munmap() { if (pMappedArea_ != 0) { #if defined EXV_HAVE_MMAP && defined EXV_HAVE_MUNMAP if (::munmap(pMappedArea_, mappedLength_) != 0) { throw Error(2, path_, strError(), "munmap"); } #else if (isMalloced_) { delete[] pMappedArea_; isMalloced_ = false; } #endif } pMappedArea_ = 0; mappedLength_ = 0; } const byte* FileIo::mmap() { assert(fp_ != 0); munmap(); mappedLength_ = size(); #if defined EXV_HAVE_MMAP && defined EXV_HAVE_MUNMAP void* rc = ::mmap(0, mappedLength_, PROT_READ, MAP_SHARED, fileno(fp_), 0); if (MAP_FAILED == rc) { throw Error(2, path_, strError(), "mmap"); } pMappedArea_ = static_cast(rc); #else // Workaround for platforms without mmap: Read the file into memory DataBuf buf(static_cast(mappedLength_)); read(buf.pData_, buf.size_); if (error() || eof()) throw Error(2, path_, strError(), "FileIo::mmap"); pMappedArea_ = buf.release().first; isMalloced_ = true; #endif return pMappedArea_; } BasicIo::AutoPtr FileIo::temporary() const { BasicIo::AutoPtr basicIo; struct stat buf; int ret = stat(path_.c_str(), &buf); // If file is > 1MB then use a file, otherwise use memory buffer if (ret != 0 || buf.st_size > 1048576) { pid_t pid = ::getpid(); std::string tmpname = path_ + toString(pid); std::auto_ptr fileIo(new FileIo(tmpname)); if (fileIo->open("w+b") != 0) { throw Error(10, path_, "w+b", strError()); } basicIo = fileIo; } else { basicIo.reset(new MemIo); } return basicIo; } int FileIo::switchMode(OpMode opMode) { assert(fp_ != 0); if (opMode_ == opMode) return 0; OpMode oldOpMode = opMode_; opMode_ = opMode; bool reopen = true; switch(opMode) { case opRead: // Flush if current mode allows reading, else reopen (in mode "r+b" // as in this case we know that we can write to the file) if (openMode_[0] == 'r' || openMode_[1] == '+') reopen = false; break; case opWrite: // Flush if current mode allows writing, else reopen if (openMode_[0] != 'r' || openMode_[1] == '+') reopen = false; break; case opSeek: reopen = false; break; } if (!reopen) { // Don't do anything when switching _from_ opSeek mode; we // flush when switching _to_ opSeek. if (oldOpMode == opSeek) return 0; // Flush. On msvcrt fflush does not do the job std::fseek(fp_, 0, SEEK_CUR); return 0; } // Reopen the file long offset = std::ftell(fp_); if (offset == -1) return -1; if (open("r+b") != 0) return 1; return std::fseek(fp_, offset, SEEK_SET); } long FileIo::write(const byte* data, long wcount) { assert(fp_ != 0); if (switchMode(opWrite) != 0) return 0; return (long)std::fwrite(data, 1, wcount, fp_); } long FileIo::write(BasicIo& src) { assert(fp_ != 0); if (static_cast(this) == &src) return 0; if (!src.isopen()) return 0; if (switchMode(opWrite) != 0) return 0; byte buf[4096]; long readCount = 0; long writeCount = 0; long writeTotal = 0; while ((readCount = src.read(buf, sizeof(buf)))) { writeTotal += writeCount = (long)std::fwrite(buf, 1, readCount, fp_); if (writeCount != readCount) { // try to reset back to where write stopped src.seek(writeCount-readCount, BasicIo::cur); break; } } return writeTotal; } void FileIo::transfer(BasicIo& src) { const bool wasOpen = (fp_ != 0); const std::string lastMode(openMode_); FileIo *fileIo = dynamic_cast(&src); if (fileIo) { // Optimization if src is another instance of FileIo fileIo->close(); // Check if the file can be written to, if it already exists if (open("w+b") != 0) { // Remove the (temporary) file std::remove(fileIo->path_.c_str()); throw Error(10, path_, "w+b", strError()); } close(); struct stat buf; if (::stat(path_.c_str(), &buf) == -1) { throw Error(2, path_, strError(), "stat"); } // MSVCRT rename that does not overwrite existing files if (fileExists(path_) && std::remove(path_.c_str()) != 0) { throw Error(2, path_, strError(), "std::remove"); } if (std::rename(fileIo->path_.c_str(), path_.c_str()) == -1) { throw Error(17, fileIo->path_, path_, strError()); } std::remove(fileIo->path_.c_str()); // Set original file permissions if (::chmod(path_.c_str(), buf.st_mode) == -1) { throw Error(2, fileIo->path_, strError(), "chmod"); } } else { // Generic handling, reopen both to reset to start if (open("w+b") != 0) { throw Error(10, path_, "w+b", strError()); } if (src.open() != 0) { throw Error(9, src.path(), strError()); } write(src); src.close(); } if (wasOpen) { if (open(lastMode) != 0) { throw Error(10, path_, lastMode, strError()); } } else close(); if (error() || src.error()) throw Error(18, path_, strError()); } int FileIo::putb(byte data) { assert(fp_ != 0); if (switchMode(opWrite) != 0) return EOF; return putc(data, fp_); } int FileIo::seek(long offset, Position pos) { assert(fp_ != 0); int fileSeek = 0; switch (pos) { case BasicIo::cur: fileSeek = SEEK_CUR; break; case BasicIo::beg: fileSeek = SEEK_SET; break; case BasicIo::end: fileSeek = SEEK_END; break; } if (switchMode(opSeek) != 0) return 1; return std::fseek(fp_, offset, fileSeek); } long FileIo::tell() const { assert(fp_ != 0); return std::ftell(fp_); } long FileIo::size() const { // Flush and commit only if the file is open for writing if (fp_ != 0 && (openMode_[0] != 'r' || openMode_[1] == '+')) { std::fflush(fp_); #if defined WIN32 && !defined __CYGWIN__ // This is required on msvcrt before stat after writing to a file _commit(_fileno(fp_)); #endif } struct stat buf; int ret = ::stat(path_.c_str(), &buf); if (ret != 0) return -1; return buf.st_size; } int FileIo::open() { // Default open is in read-only binary mode return open("rb"); } int FileIo::open(const std::string& mode) { if (fp_ != 0) { std::fclose(fp_); } openMode_ = mode; opMode_ = opSeek; fp_ = std::fopen(path_.c_str(), mode.c_str()); if (!fp_) return 1; return 0; } bool FileIo::isopen() const { return fp_ != 0; } int FileIo::close() { if (fp_ != 0) { std::fclose(fp_); fp_= 0; } return 0; } DataBuf FileIo::read(long rcount) { assert(fp_ != 0); DataBuf buf(rcount); long readCount = read(buf.pData_, buf.size_); buf.size_ = readCount; return buf; } long FileIo::read(byte* buf, long rcount) { assert(fp_ != 0); if (switchMode(opRead) != 0) return 0; return (long)std::fread(buf, 1, rcount, fp_); } int FileIo::getb() { assert(fp_ != 0); if (switchMode(opRead) != 0) return EOF; return getc(fp_); } int FileIo::error() const { return fp_ != 0 ? ferror(fp_) : 0; } bool FileIo::eof() const { assert(fp_ != 0); return feof(fp_) != 0; } std::string FileIo::path() const { return path_; } MemIo::MemIo() : data_(0), idx_(0), size_(0), sizeAlloced_(0), isMalloced_(false), eof_(false) { } MemIo::MemIo(const byte* data, long size) : data_(const_cast(data)), idx_(0), size_(size), sizeAlloced_(0), isMalloced_(false), eof_(false) { } MemIo::~MemIo() { if (isMalloced_) { std::free(data_); } } BasicIo::AutoPtr MemIo::temporary() const { return BasicIo::AutoPtr(new MemIo); } void MemIo::reserve(long wcount) { long need = wcount + idx_; if (!isMalloced_) { // Minimum size for 1st block is 32kB long size = std::max(32768 * (1 + need / 32768), size_); byte* data = (byte*)std::malloc(size); std::memcpy(data, data_, size_); data_ = data; sizeAlloced_ = size; isMalloced_ = true; } if (need > size_) { if (need > sizeAlloced_) { // Allocate in blocks of 32kB long want = 32768 * (1 + need / 32768); data_ = (byte*)std::realloc(data_, want); sizeAlloced_ = want; isMalloced_ = true; } size_ = need; } } long MemIo::write(const byte* data, long wcount) { reserve(wcount); assert(isMalloced_); std::memcpy(&data_[idx_], data, wcount); idx_ += wcount; return wcount; } void MemIo::transfer(BasicIo& src) { MemIo *memIo = dynamic_cast(&src); if (memIo) { // Optimization if src is another instance of MemIo if (true == isMalloced_) { std::free(data_); } idx_ = 0; data_ = memIo->data_; size_ = memIo->size_; isMalloced_ = memIo->isMalloced_; memIo->idx_ = 0; memIo->data_ = 0; memIo->size_ = 0; memIo->isMalloced_ = false; } else { // Generic reopen to reset position to start if (src.open() != 0) { throw Error(9, src.path(), strError()); } idx_ = 0; write(src); src.close(); } if (error() || src.error()) throw Error(19, strError()); } long MemIo::write(BasicIo& src) { if (static_cast(this) == &src) return 0; if (!src.isopen()) return 0; byte buf[4096]; long readCount = 0; long writeTotal = 0; while ((readCount = src.read(buf, sizeof(buf)))) { write(buf, readCount); writeTotal += readCount; } return writeTotal; } int MemIo::putb(byte data) { reserve(1); assert(isMalloced_); data_[idx_++] = data; return data; } int MemIo::seek(long offset, Position pos) { long newIdx = 0; switch (pos) { case BasicIo::cur: newIdx = idx_ + offset; break; case BasicIo::beg: newIdx = offset; break; case BasicIo::end: newIdx = size_ + offset; break; } if (newIdx < 0 || newIdx > size_) return 1; idx_ = newIdx; eof_ = false; return 0; } long MemIo::tell() const { return idx_; } long MemIo::size() const { return size_; } int MemIo::open() { idx_ = 0; eof_ = false; return 0; } bool MemIo::isopen() const { return true; } int MemIo::close() { return 0; } DataBuf MemIo::read(long rcount) { DataBuf buf(rcount); long readCount = read(buf.pData_, buf.size_); buf.size_ = readCount; return buf; } long MemIo::read(byte* buf, long rcount) { long avail = size_ - idx_; long allow = std::min(rcount, avail); std::memcpy(buf, &data_[idx_], allow); idx_ += allow; if (rcount > avail) eof_ = true; return allow; } int MemIo::getb() { if (idx_ == size_) { eof_ = true; return EOF; } return data_[idx_++]; } int MemIo::error() const { return 0; } bool MemIo::eof() const { return eof_; } std::string MemIo::path() const { return "MemIo"; } // ************************************************************************* // free functions DataBuf readFile(const std::string& path) { FileIo file(path); if (file.open("rb") != 0) { throw Error(10, path, "rb", strError()); } struct stat st; if (0 != ::stat(path.c_str(), &st)) { throw Error(2, path, strError(), "stat"); } DataBuf buf(st.st_size); long len = file.read(buf.pData_, buf.size_); if (len != buf.size_) { throw Error(2, path, strError(), "FileIo::read"); } return buf; } long writeFile(const DataBuf& buf, const std::string& path) { FileIo file(path); if (file.open("wb") != 0) { throw Error(10, path, "wb", strError()); } return file.write(buf.pData_, buf.size_); } } // namespace Exiv2