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>
min(Type A,Type B)34 inline Type min(Type A, Type B) {
35   return (B < A) ? B : A;
36 }
37 
clearCache(IORequest & req,unsigned int blockSize)38 static void clearCache(IORequest &req, unsigned int blockSize) {
39   memset(req.data, 0, blockSize);
40   req.dataLen = 0;
41 }
42 
BlockFileIO(unsigned int blockSize,const FSConfigPtr & cfg)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 
~BlockFileIO()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  */
cacheReadOneBlock(const IORequest & req) const61 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 
cacheWriteOneBlock(const IORequest & req)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  */
read(const IORequest & req) const129 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  */
write(const IORequest & req)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 
blockSize() const325 unsigned int BlockFileIO::blockSize() const { return _blockSize; }
326 
327 /**
328  * Returns 0 in case of success, or -errno in case of failure.
329  */
padFile(off_t oldSize,off_t newSize,bool forceWrite)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  */
truncateBase(off_t size,FileIO * base)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
480