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