"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