"Fossies" - the Fresh Open Source Software Archive

Member "encfs-1.9.5/encfs/BlockFileIO.cpp" (27 Apr 2018, 14091 Bytes) of package /linux/misc/encfs-1.9.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "BlockFileIO.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.9.4_vs_1.9.5.

    1 /*****************************************************************************
    2  * Author:   Valient Gough <vgough@pobox.com>
    3  *
    4  *****************************************************************************
    5  * Copyright (c) 2004, Valient Gough
    6  *
    7  * This program is free software: you can redistribute it and/or modify it
    8  * under the terms of the GNU Lesser General Public License as published by the
    9  * Free Software Foundation, either version 3 of the License, or (at your
   10  * option) any later version.
   11  *
   12  * This program is distributed in the hope that it will be useful, but WITHOUT
   13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
   15  * for more details.
   16  *
   17  * You should have received a copy of the GNU Lesser General Public License
   18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   19  */
   20 
   21 #include "BlockFileIO.h"
   22 
   23 #include <cstring>  // for memset, memcpy, NULL
   24 
   25 #include "Error.h"
   26 #include "FSConfig.h"    // for FSConfigPtr
   27 #include "FileIO.h"      // for IORequest, FileIO
   28 #include "FileUtils.h"   // for EncFS_Opts
   29 #include "MemoryPool.h"  // for MemBlock, release, allocation
   30 
   31 namespace encfs {
   32 
   33 template <typename Type>
   34 inline Type min(Type A, Type B) {
   35   return (B < A) ? B : A;
   36 }
   37 
   38 static void clearCache(IORequest &req, unsigned int blockSize) {
   39   memset(req.data, 0, blockSize);
   40   req.dataLen = 0;
   41 }
   42 
   43 BlockFileIO::BlockFileIO(unsigned int blockSize, const FSConfigPtr &cfg)
   44     : _blockSize(blockSize), _allowHoles(cfg->config->allowHoles) {
   45   CHECK(_blockSize > 1);
   46   _cache.data = new unsigned char[_blockSize];
   47   _noCache = cfg->opts->noCache;
   48 }
   49 
   50 BlockFileIO::~BlockFileIO() {
   51   clearCache(_cache, _blockSize);
   52   delete[] _cache.data;
   53 }
   54 
   55 /**
   56  * Serve a read request for the size of one block or less,
   57  * at block-aligned offsets.
   58  * Always requests full blocks form the lower layer, truncates the
   59  * returned data as neccessary.
   60  */
   61 ssize_t BlockFileIO::cacheReadOneBlock(const IORequest &req) const {
   62   CHECK(req.dataLen <= _blockSize);
   63   CHECK(req.offset % _blockSize == 0);
   64 
   65   /* we can satisfy the request even if _cache.dataLen is too short, because
   66    * we always request a full block during reads. This just means we are
   67    * in the last block of a file, which may be smaller than the blocksize.
   68    * For reverse encryption, the cache must not be used at all, because
   69    * the lower file may have changed behind our back. */
   70   if ((!_noCache) && (req.offset == _cache.offset) && (_cache.dataLen != 0)) {
   71     // satisfy request from cache
   72     size_t len = req.dataLen;
   73     if (_cache.dataLen < len) {
   74       len = _cache.dataLen;  // Don't read past EOF
   75     }
   76     memcpy(req.data, _cache.data, len);
   77     return len;
   78   }
   79   if (_cache.dataLen > 0) {
   80     clearCache(_cache, _blockSize);
   81   }
   82 
   83   // cache results of read -- issue reads for full blocks
   84   IORequest tmp;
   85   tmp.offset = req.offset;
   86   tmp.data = _cache.data;
   87   tmp.dataLen = _blockSize;
   88   ssize_t result = readOneBlock(tmp);
   89   if (result > 0) {
   90     _cache.offset = req.offset;
   91     _cache.dataLen = result;  // the amount we really have
   92     if ((size_t)result > req.dataLen) {
   93       result = req.dataLen;  // only as much as requested
   94     }
   95     memcpy(req.data, _cache.data, result);
   96   }
   97   return result;
   98 }
   99 
  100 ssize_t BlockFileIO::cacheWriteOneBlock(const IORequest &req) {
  101   // Let's point request buffer to our own buffer, as it may be modified by
  102   // encryption : originating process may not like to have its buffer modified
  103   memcpy(_cache.data, req.data, req.dataLen);
  104   IORequest tmp;
  105   tmp.offset = req.offset;
  106   tmp.data = _cache.data;
  107   tmp.dataLen = req.dataLen;
  108   ssize_t res = writeOneBlock(tmp);
  109   if (res < 0) {
  110     clearCache(_cache, _blockSize);
  111   }
  112   else {
  113     // And now we can cache the write buffer from the request
  114     memcpy(_cache.data, req.data, req.dataLen);
  115     _cache.offset = req.offset;
  116     _cache.dataLen = req.dataLen;
  117   }
  118   return res;
  119 }
  120 
  121 /**
  122  * Serve a read request of arbitrary size at an arbitrary offset.
  123  * Stitches together multiple blocks to serve large requests, drops
  124  * data from the front of the first block if the request is not aligned.
  125  * Always requests aligned data of the size of one block or less from the
  126  * lower layer.
  127  * Returns the number of bytes read, or -errno in case of failure.
  128  */
  129 ssize_t BlockFileIO::read(const IORequest &req) const {
  130   CHECK(_blockSize != 0);
  131 
  132   int partialOffset =
  133       req.offset % _blockSize;  // can be int as _blockSize is int
  134   off_t blockNum = req.offset / _blockSize;
  135   ssize_t result = 0;
  136 
  137   if (partialOffset == 0 && req.dataLen <= _blockSize) {
  138     // read completely within a single block -- can be handled as-is by
  139     // readOneBlock().
  140     return cacheReadOneBlock(req);
  141   }
  142   size_t size = req.dataLen;
  143 
  144   // if the request is larger then a block, then request each block
  145   // individually
  146   MemBlock mb;         // in case we need to allocate a temporary block..
  147   IORequest blockReq;  // for requests we may need to make
  148   blockReq.dataLen = _blockSize;
  149   blockReq.data = nullptr;
  150 
  151   unsigned char *out = req.data;
  152   while (size != 0u) {
  153     blockReq.offset = blockNum * _blockSize;
  154 
  155     // if we're reading a full block, then read directly into the
  156     // result buffer instead of using a temporary
  157     if (partialOffset == 0 && size >= _blockSize) {
  158       blockReq.data = out;
  159     } else {
  160       if (mb.data == nullptr) {
  161         mb = MemoryPool::allocate(_blockSize);
  162       }
  163       blockReq.data = mb.data;
  164     }
  165 
  166     ssize_t readSize = cacheReadOneBlock(blockReq);
  167     if (readSize < 0) {
  168       result = readSize;
  169       break;
  170     }
  171     if (readSize <= partialOffset) {
  172       break;  // didn't get enough bytes
  173     }
  174 
  175     size_t cpySize = min((size_t)readSize - (size_t)partialOffset, size);
  176     CHECK(cpySize <= (size_t)readSize);
  177 
  178     // if we read to a temporary buffer, then move the data
  179     if (blockReq.data != out) {
  180       memcpy(out, blockReq.data + partialOffset, cpySize);
  181     }
  182 
  183     result += cpySize;
  184     size -= cpySize;
  185     out += cpySize;
  186     ++blockNum;
  187     partialOffset = 0;
  188 
  189     if ((size_t)readSize < _blockSize) {
  190       break;
  191     }
  192   }
  193 
  194   if (mb.data != nullptr) {
  195     MemoryPool::release(mb);
  196   }
  197 
  198   return result;
  199 }
  200 
  201 /**
  202  * Returns the number of bytes written, or -errno in case of failure.
  203  */
  204 ssize_t BlockFileIO::write(const IORequest &req) {
  205   CHECK(_blockSize != 0);
  206 
  207   off_t fileSize = getSize();
  208   if (fileSize < 0) {
  209     return fileSize;
  210   }
  211 
  212   // where write request begins
  213   off_t blockNum = req.offset / _blockSize;
  214   int partialOffset =
  215       req.offset % _blockSize;  // can be int as _blockSize is int
  216 
  217   // last block of file (for testing write overlaps with file boundary)
  218   off_t lastFileBlock = fileSize / _blockSize;
  219   size_t lastBlockSize = fileSize % _blockSize;
  220 
  221   off_t lastNonEmptyBlock = lastFileBlock;
  222   if (lastBlockSize == 0) {
  223     --lastNonEmptyBlock;
  224   }
  225 
  226   if (req.offset > fileSize) {
  227     // extend file first to fill hole with 0's..
  228     const bool forceWrite = false;
  229     int res = padFile(fileSize, req.offset, forceWrite);
  230     if (res < 0) {
  231       return res;
  232     }
  233   }
  234 
  235   // check against edge cases where we can just let the base class handle the
  236   // request as-is..
  237   if (partialOffset == 0 && req.dataLen <= _blockSize) {
  238     // if writing a full block.. pretty safe..
  239     if (req.dataLen == _blockSize) {
  240       return cacheWriteOneBlock(req);
  241     }
  242 
  243     // if writing a partial block, but at least as much as what is
  244     // already there..
  245     if (blockNum == lastFileBlock && req.dataLen >= lastBlockSize) {
  246       return cacheWriteOneBlock(req);
  247     }
  248   }
  249 
  250   // have to merge data with existing block(s)..
  251   MemBlock mb;
  252 
  253   IORequest blockReq;
  254   blockReq.data = nullptr;
  255   blockReq.dataLen = _blockSize;
  256 
  257   ssize_t res = 0;
  258   size_t size = req.dataLen;
  259   unsigned char *inPtr = req.data;
  260   while (size != 0u) {
  261     blockReq.offset = blockNum * _blockSize;
  262     size_t toCopy = min((size_t)_blockSize - (size_t)partialOffset, size);
  263 
  264     // if writing an entire block, or writing a partial block that requires
  265     // no merging with existing data..
  266     if ((toCopy == _blockSize) ||
  267         (partialOffset == 0 && blockReq.offset + (off_t)toCopy >= fileSize)) {
  268       // write directly from buffer
  269       blockReq.data = inPtr;
  270       blockReq.dataLen = toCopy;
  271     } else {
  272       // need a temporary buffer, since we have to either merge or pad
  273       // the data.
  274       if (mb.data == nullptr) {
  275         mb = MemoryPool::allocate(_blockSize);
  276       }
  277       memset(mb.data, 0, _blockSize);
  278       blockReq.data = mb.data;
  279 
  280       if (blockNum > lastNonEmptyBlock) {
  281         // just pad..
  282         blockReq.dataLen = partialOffset + toCopy;
  283       } else {
  284         // have to merge with existing block data..
  285         blockReq.dataLen = _blockSize;
  286         ssize_t readSize = cacheReadOneBlock(blockReq);
  287         if (readSize < 0) {
  288           res = readSize;
  289           break;
  290         }
  291         blockReq.dataLen = readSize;
  292 
  293         // extend data if necessary..
  294         if (partialOffset + toCopy > blockReq.dataLen) {
  295           blockReq.dataLen = partialOffset + toCopy;
  296         }
  297       }
  298       // merge in the data to be written..
  299       memcpy(blockReq.data + partialOffset, inPtr, toCopy);
  300     }
  301 
  302     // Finally, write the damn thing!
  303     res = cacheWriteOneBlock(blockReq);
  304     if (res < 0) {
  305       break;
  306     }
  307 
  308     // prepare to start all over with the next block..
  309     size -= toCopy;
  310     inPtr += toCopy;
  311     ++blockNum;
  312     partialOffset = 0;
  313   }
  314 
  315   if (mb.data != nullptr) {
  316     MemoryPool::release(mb);
  317   }
  318 
  319   if (res < 0) {
  320     return res;
  321   }
  322   return req.dataLen;
  323 }
  324 
  325 unsigned int BlockFileIO::blockSize() const { return _blockSize; }
  326 
  327 /**
  328  * Returns 0 in case of success, or -errno in case of failure.
  329  */
  330 int BlockFileIO::padFile(off_t oldSize, off_t newSize, bool forceWrite) {
  331   off_t oldLastBlock = oldSize / _blockSize;
  332   off_t newLastBlock = newSize / _blockSize;
  333   int newBlockSize = newSize % _blockSize;  // can be int as _blockSize is int
  334   ssize_t res = 0;
  335 
  336   IORequest req;
  337   MemBlock mb;
  338 
  339   if (oldLastBlock == newLastBlock) {
  340     // when the real write occurs, it will have to read in the existing
  341     // data and pad it anyway, so we won't do it here (unless we're
  342     // forced).
  343     if (forceWrite) {
  344       mb = MemoryPool::allocate(_blockSize);
  345       req.data = mb.data;
  346 
  347       req.offset = oldLastBlock * _blockSize;
  348       req.dataLen = oldSize % _blockSize;
  349       int outSize = newSize % _blockSize;  // outSize > req.dataLen
  350 
  351       if (outSize != 0) {
  352         memset(mb.data, 0, outSize);
  353         if ((res = cacheReadOneBlock(req)) >= 0) {
  354           req.dataLen = outSize;
  355           res = cacheWriteOneBlock(req);
  356         }
  357       }
  358     } else
  359       VLOG(1) << "optimization: not padding last block";
  360   } else {
  361     mb = MemoryPool::allocate(_blockSize);
  362     req.data = mb.data;
  363 
  364     // 1. extend the first block to full length
  365     // 2. write the middle empty blocks
  366     // 3. write the last block
  367 
  368     req.offset = oldLastBlock * _blockSize;
  369     req.dataLen = oldSize % _blockSize;
  370 
  371     // 1. req.dataLen == 0, iff oldSize was already a multiple of blocksize
  372     if (req.dataLen != 0) {
  373       VLOG(1) << "padding block " << oldLastBlock;
  374       memset(mb.data, 0, _blockSize);
  375       if ((res = cacheReadOneBlock(req)) >= 0) {
  376         req.dataLen = _blockSize;  // expand to full block size
  377         res = cacheWriteOneBlock(req);
  378       }
  379       ++oldLastBlock;
  380     }
  381 
  382     // 2, pad zero blocks unless holes are allowed
  383     if (!_allowHoles) {
  384       for (; (res >= 0) && (oldLastBlock != newLastBlock); ++oldLastBlock) {
  385         VLOG(1) << "padding block " << oldLastBlock;
  386         req.offset = oldLastBlock * _blockSize;
  387         req.dataLen = _blockSize;
  388         memset(mb.data, 0, req.dataLen);
  389         res = cacheWriteOneBlock(req);
  390       }
  391     }
  392 
  393     // 3. only necessary if write is forced and block is non 0 length
  394     if ((res >= 0) && forceWrite && (newBlockSize != 0)) {
  395       req.offset = newLastBlock * _blockSize;
  396       req.dataLen = newBlockSize;
  397       memset(mb.data, 0, req.dataLen);
  398       res = cacheWriteOneBlock(req);
  399     }
  400   }
  401 
  402   if (mb.data != nullptr) {
  403     MemoryPool::release(mb);
  404   }
  405 
  406   if (res < 0) {
  407     return res;
  408   }
  409   return 0;
  410 }
  411 
  412 /**
  413  * Returns 0 in case of success, or -errno in case of failure.
  414  */
  415 int BlockFileIO::truncateBase(off_t size, FileIO *base) {
  416   int partialBlock = size % _blockSize;  // can be int as _blockSize is int
  417   int res = 0;
  418 
  419   off_t oldSize = getSize();
  420 
  421   if (size > oldSize) {
  422     // truncate can be used to extend a file as well.  truncate man page
  423     // states that it will pad with 0's.
  424     // do the truncate so that the underlying filesystem can allocate
  425     // the space, and then we'll fill it in padFile..
  426     if (base != nullptr) {
  427       res = base->truncate(size);
  428     }
  429 
  430     const bool forceWrite = true;
  431     if (res == 0) {
  432       res = padFile(oldSize, size, forceWrite);
  433     }
  434   } else if (size == oldSize) {
  435     // the easiest case, but least likely....
  436   } else if (partialBlock != 0) {
  437     // partial block after truncate.  Need to read in the block being
  438     // truncated before the truncate.  Then write it back out afterwards,
  439     // since the encoding will change..
  440     off_t blockNum = size / _blockSize;
  441     MemBlock mb = MemoryPool::allocate(_blockSize);
  442 
  443     IORequest req;
  444     req.offset = blockNum * _blockSize;
  445     req.dataLen = _blockSize;
  446     req.data = mb.data;
  447 
  448     ssize_t readSize = cacheReadOneBlock(req);
  449     if (readSize < 0) {
  450       res = readSize;
  451     }
  452 
  453     else if (base != nullptr) {
  454       // do the truncate
  455       res = base->truncate(size);
  456     }
  457 
  458     // write back out partial block
  459     req.dataLen = partialBlock;
  460     if (res == 0) {
  461       ssize_t writeSize = cacheWriteOneBlock(req);
  462       if (writeSize < 0) {
  463         res = writeSize;
  464       }
  465     }
  466 
  467     MemoryPool::release(mb);
  468   } else {
  469     // truncating on a block bounday.  No need to re-encode the last
  470     // block..
  471     if (base != nullptr) {
  472       res = base->truncate(size);
  473     }
  474   }
  475 
  476   return res;
  477 }
  478 
  479 }  // namespace encfs