/***************************************************************************** * Author: Valient Gough * ***************************************************************************** * Copyright (c) 2004, Valient Gough * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation, either version 3 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "fs/BlockFileIO.h" #include "base/Error.h" #include "base/i18n.h" #include "cipher/MemoryPool.h" #include "fs/fsconfig.pb.h" #include #include namespace encfs { template inline Type min(Type A, Type B) { return (B < A) ? B : A; } static void clearCache(IORequest &req, int blockSize) { memset(req.data, 0, blockSize); req.dataLen = 0; } BlockFileIO::BlockFileIO(int blockSize, const FSConfigPtr &cfg) : _blockSize(blockSize), _allowHoles(cfg->config->allow_holes()) { rAssert(_blockSize > 1); _cache.data = new unsigned char[_blockSize]; } BlockFileIO::~BlockFileIO() { clearCache(_cache, _blockSize); delete[] _cache.data; } ssize_t BlockFileIO::cacheReadOneBlock(const IORequest &req) const { // we can satisfy the request even if _cache.dataLen is too short, because // we always request a full block during reads.. if ((req.offset == _cache.offset) && (_cache.dataLen != 0)) { // satisfy request from cache int len = req.dataLen; if (_cache.dataLen < len) len = _cache.dataLen; memcpy(req.data, _cache.data, len); return len; } else { if (_cache.dataLen > 0) clearCache(_cache, _blockSize); // cache results of read -- issue reads for full blocks IORequest tmp; tmp.offset = req.offset; tmp.data = _cache.data; tmp.dataLen = _blockSize; ssize_t result = readOneBlock(tmp); if (result > 0) { _cache.offset = req.offset; _cache.dataLen = result; // the amount we really have if (result > req.dataLen) result = req.dataLen; // only as much as requested memcpy(req.data, _cache.data, result); } return result; } } bool BlockFileIO::cacheWriteOneBlock(const IORequest &req) { // cache results of write (before pass-thru, because it may be modified // in-place) memcpy(_cache.data, req.data, req.dataLen); _cache.offset = req.offset; _cache.dataLen = req.dataLen; bool ok = writeOneBlock(req); if (!ok) clearCache(_cache, _blockSize); return ok; } ssize_t BlockFileIO::read(const IORequest &req) const { rAssert(_blockSize != 0); int partialOffset = req.offset % _blockSize; off_t blockNum = req.offset / _blockSize; if (partialOffset == 0 && req.dataLen <= _blockSize) { // read completely within a single block -- can be handled as-is by // readOneBloc(). return cacheReadOneBlock(req); } else { ssize_t result = 0; size_t size = req.dataLen; // if the request is larger then a block, then request each block // individually MemBlock mb; // in case we need to allocate a temporary block.. IORequest blockReq; // for requests we may need to make blockReq.dataLen = _blockSize; blockReq.data = NULL; unsigned char *out = req.data; while (size) { blockReq.offset = blockNum * _blockSize; // if we're reading a full block, then read directly into the // result buffer instead of using a temporary if (partialOffset == 0 && size >= (size_t)_blockSize) blockReq.data = out; else { if (!mb.data) mb.allocate(_blockSize); blockReq.data = mb.data; } ssize_t readSize = cacheReadOneBlock(blockReq); if (readSize <= partialOffset) break; // didn't get enough bytes int cpySize = min((size_t)(readSize - partialOffset), size); rAssert(cpySize <= readSize); // if we read to a temporary buffer, then move the data if (blockReq.data != out) memcpy(out, blockReq.data + partialOffset, cpySize); result += cpySize; size -= cpySize; out += cpySize; ++blockNum; partialOffset = 0; if (readSize < _blockSize) break; } return result; } } bool BlockFileIO::write(const IORequest &req) { rAssert(_blockSize != 0); off_t fileSize = getSize(); // where write request begins off_t blockNum = req.offset / _blockSize; int partialOffset = req.offset % _blockSize; // last block of file (for testing write overlaps with file boundary) off_t lastFileBlock = fileSize / _blockSize; ssize_t lastBlockSize = fileSize % _blockSize; off_t lastNonEmptyBlock = lastFileBlock; if (lastBlockSize == 0) --lastNonEmptyBlock; if (req.offset > fileSize) { // extend file first to fill hole with 0's.. const bool forceWrite = false; padFile(fileSize, req.offset, forceWrite); } // check against edge cases where we can just let the base class handle the // request as-is.. if (partialOffset == 0 && req.dataLen <= _blockSize) { // if writing a full block.. pretty safe.. if (req.dataLen == _blockSize) return cacheWriteOneBlock(req); // if writing a partial block, but at least as much as what is // already there.. if (blockNum == lastFileBlock && req.dataLen >= lastBlockSize) return cacheWriteOneBlock(req); } // have to merge data with existing block(s).. MemBlock mb; IORequest blockReq; blockReq.data = NULL; blockReq.dataLen = _blockSize; bool ok = true; size_t size = req.dataLen; unsigned char *inPtr = req.data; while (size) { blockReq.offset = blockNum * _blockSize; int toCopy = min((size_t)(_blockSize - partialOffset), size); // if writing an entire block, or writing a partial block that requires // no merging with existing data.. if ((toCopy == _blockSize) || (partialOffset == 0 && blockReq.offset + toCopy >= fileSize)) { // write directly from buffer blockReq.data = inPtr; blockReq.dataLen = toCopy; } else { // need a temporary buffer, since we have to either merge or pad // the data. if (!mb.data) mb.allocate(_blockSize); memset(mb.data, 0, _blockSize); blockReq.data = mb.data; if (blockNum > lastNonEmptyBlock) { // just pad.. blockReq.dataLen = toCopy + partialOffset; } else { // have to merge with existing block data.. blockReq.dataLen = _blockSize; blockReq.dataLen = cacheReadOneBlock(blockReq); // extend data if necessary.. if (partialOffset + toCopy > blockReq.dataLen) blockReq.dataLen = partialOffset + toCopy; } // merge in the data to be written.. memcpy(blockReq.data + partialOffset, inPtr, toCopy); } // Finally, write the damn thing! if (!cacheWriteOneBlock(blockReq)) { ok = false; break; } // prepare to start all over with the next block.. size -= toCopy; inPtr += toCopy; ++blockNum; partialOffset = 0; } return ok; } int BlockFileIO::blockSize() const { return _blockSize; } void BlockFileIO::padFile(off_t oldSize, off_t newSize, bool forceWrite) { off_t oldLastBlock = oldSize / _blockSize; off_t newLastBlock = newSize / _blockSize; int lastBlockSize = newSize % _blockSize; IORequest req; MemBlock mb; if (oldLastBlock == newLastBlock) { // when the real write occurs, it will have to read in the existing // data and pad it anyway, so we won't do it here (unless we're // forced). if (forceWrite) { mb.allocate(_blockSize); req.data = mb.data; req.offset = oldLastBlock * _blockSize; req.dataLen = oldSize % _blockSize; int outSize = newSize % _blockSize; // outSize > req.dataLen if (outSize) { memset(mb.data, 0, outSize); cacheReadOneBlock(req); req.dataLen = outSize; cacheWriteOneBlock(req); } } else VLOG(1) << "optimization: not padding last block"; } else { mb.allocate(_blockSize); req.data = mb.data; // 1. extend the first block to full length // 2. write the middle empty blocks // 3. write the last block req.offset = oldLastBlock * _blockSize; req.dataLen = oldSize % _blockSize; // 1. req.dataLen == 0, iff oldSize was already a multiple of blocksize if (req.dataLen != 0) { VLOG(1) << "padding block " << oldLastBlock; memset(mb.data, 0, _blockSize); cacheReadOneBlock(req); req.dataLen = _blockSize; // expand to full block size cacheWriteOneBlock(req); ++oldLastBlock; } // 2, pad zero blocks unless holes are allowed if (!_allowHoles) { for (; oldLastBlock != newLastBlock; ++oldLastBlock) { VLOG(1) << "padding block " << oldLastBlock; req.offset = oldLastBlock * _blockSize; req.dataLen = _blockSize; memset(mb.data, 0, req.dataLen); cacheWriteOneBlock(req); } } // 3. only necessary if write is forced and block is non 0 length if (forceWrite && lastBlockSize) { req.offset = newLastBlock * _blockSize; req.dataLen = lastBlockSize; memset(mb.data, 0, req.dataLen); cacheWriteOneBlock(req); } } } int BlockFileIO::blockTruncate(off_t size, FileIO *base) { rAssert(size >= 0); int partialBlock = size % _blockSize; int res = 0; off_t oldSize = getSize(); if (size > oldSize) { // truncate can be used to extend a file as well. truncate man page // states that it will pad with 0's. // do the truncate so that the underlying filesystem can allocate // the space, and then we'll fill it in padFile.. if (base) base->truncate(size); const bool forceWrite = true; padFile(oldSize, size, forceWrite); } else if (size == oldSize) { // the easiest case, but least likely.... } else if (partialBlock) { // partial block after truncate. Need to read in the block being // truncated before the truncate. Then write it back out afterwards, // since the encoding will change.. off_t blockNum = size / _blockSize; MemBlock mb; mb.allocate(_blockSize); IORequest req; req.offset = blockNum * _blockSize; req.dataLen = _blockSize; req.data = mb.data; ssize_t rdSz = cacheReadOneBlock(req); // do the truncate if (base) res = base->truncate(size); // write back out partial block req.dataLen = partialBlock; bool wrRes = cacheWriteOneBlock(req); if ((rdSz < 0) || (!wrRes)) { LOG(ERROR) << "truncate failure: read size " << rdSz << ", partial block of " << partialBlock; } } else { // truncating on a block bounday. No need to re-encode the last // block.. if (base) res = base->truncate(size); } return res; } } // namespace encfs