1 /*
2 * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7 #include "Transaction.h"
8
9 #include <errno.h>
10
11 #include <algorithm>
12
13 #include <AutoDeleter.h>
14
15 #include "BlockAllocator.h"
16 #include "DebugSupport.h"
17 #include "Volume.h"
18
19
20 static inline bool
swap_if_greater(Node * & a,Node * & b)21 swap_if_greater(Node*& a, Node*& b)
22 {
23 if (a->BlockIndex() <= b->BlockIndex())
24 return false;
25
26 std::swap(a, b);
27 return true;
28 }
29
30
31 // #pragma mark - Transaction
32
33
Transaction(Volume * volume)34 Transaction::Transaction(Volume* volume)
35 :
36 fVolume(volume),
37 fSHA256(NULL),
38 fCheckSum(NULL),
39 fID(-1)
40 {
41 }
42
43
~Transaction()44 Transaction::~Transaction()
45 {
46 Abort();
47
48 delete fCheckSum;
49 delete fSHA256;
50 }
51
52
53 status_t
Start()54 Transaction::Start()
55 {
56 ASSERT(fID < 0);
57
58 status_t error = fBlockInfos.Init();
59 if (error != B_OK)
60 return error;
61
62 if (fSHA256 == NULL) {
63 fSHA256 = new(std::nothrow) SHA256;
64 if (fSHA256 == NULL)
65 return B_NO_MEMORY;
66 }
67
68 if (fCheckSum == NULL) {
69 fCheckSum = new(std::nothrow) checksum_device_ioctl_check_sum;
70 if (fCheckSum == NULL)
71 return B_NO_MEMORY;
72 }
73
74 fVolume->TransactionStarted();
75
76 fID = cache_start_transaction(fVolume->BlockCache());
77 if (fID < 0) {
78 fVolume->TransactionFinished();
79 return fID;
80 }
81
82 fOldFreeBlockCount = fVolume->GetBlockAllocator()->FreeBlocks();
83
84 return B_OK;
85 }
86
87
88 status_t
StartAndAddNode(Node * node,uint32 flags)89 Transaction::StartAndAddNode(Node* node, uint32 flags)
90 {
91 status_t error = Start();
92 if (error != B_OK)
93 return error;
94
95 return AddNode(node, flags);
96 }
97
98
99 status_t
Commit(const PostCommitNotification * notification1,const PostCommitNotification * notification2,const PostCommitNotification * notification3)100 Transaction::Commit(const PostCommitNotification* notification1,
101 const PostCommitNotification* notification2,
102 const PostCommitNotification* notification3)
103 {
104 ASSERT(fID >= 0);
105
106 // flush the nodes
107 for (NodeInfoList::Iterator it = fNodeInfos.GetIterator();
108 NodeInfo* info = it.Next();) {
109 status_t error = info->node->Flush(*this);
110 if (error != B_OK) {
111 Abort();
112 return error;
113 }
114 }
115
116 // Make sure the previous transaction is on disk. This is not particularly
117 // performance friendly, but prevents race conditions between us setting
118 // the new block check sums and the block writer deciding to write back the
119 // old block data.
120 status_t error = block_cache_sync(fVolume->BlockCache());
121 if (error != B_OK) {
122 Abort();
123 return error;
124 }
125
126 // compute the new block check sums
127 error = _UpdateBlockCheckSums();
128 if (error != B_OK) {
129 Abort();
130 return error;
131 }
132
133 // commit the cache transaction
134 error = cache_end_transaction(fVolume->BlockCache(), fID, NULL, NULL);
135 if (error != B_OK) {
136 Abort();
137 return error;
138 }
139
140 // send notifications
141 if (notification1 != NULL)
142 notification1->NotifyPostCommit();
143 if (notification2 != NULL)
144 notification2->NotifyPostCommit();
145 if (notification3 != NULL)
146 notification3->NotifyPostCommit();
147
148 // clean up
149 _DeleteNodeInfosAndUnlock(false);
150
151 fVolume->TransactionFinished();
152 fID = -1;
153
154 return B_OK;
155 }
156
157
158 void
Abort()159 Transaction::Abort()
160 {
161 if (fID < 0)
162 return;
163
164 // abort the cache transaction
165 cache_abort_transaction(fVolume->BlockCache(), fID);
166
167 // revert the nodes
168 for (NodeInfoList::Iterator it = fNodeInfos.GetIterator();
169 NodeInfo* info = it.Next();) {
170 info->node->RevertNodeData(info->oldNodeData);
171 }
172
173 // revert the block check sums
174 _RevertBlockCheckSums();
175
176 // clean up
177
178 // delete the node infos
179 _DeleteNodeInfosAndUnlock(true);
180
181 // delete the block infos
182 BlockInfo* blockInfo = fBlockInfos.Clear(true);
183 while (blockInfo != NULL) {
184 BlockInfo* nextInfo = blockInfo->hashNext;
185 block_cache_put(fVolume->BlockCache(),
186 blockInfo->indexAndCheckSum.blockIndex);
187 delete nextInfo;
188 blockInfo = nextInfo;
189 }
190
191 fVolume->GetBlockAllocator()->ResetFreeBlocks(fOldFreeBlockCount);
192
193 fVolume->TransactionFinished();
194 fID = -1;
195 }
196
197
198 status_t
AddNode(Node * node,uint32 flags)199 Transaction::AddNode(Node* node, uint32 flags)
200 {
201 ASSERT(fID >= 0);
202
203 NodeInfo* info = _GetNodeInfo(node);
204 if (info != NULL)
205 return B_OK;
206
207 info = new(std::nothrow) NodeInfo;
208 if (info == NULL)
209 return B_NO_MEMORY;
210
211 if ((flags & TRANSACTION_NODE_ALREADY_LOCKED) == 0)
212 node->WriteLock();
213
214 info->node = node;
215 info->oldNodeData = node->NodeData();
216 info->flags = flags;
217
218 fNodeInfos.Add(info);
219
220 return B_OK;
221 }
222
223
224 status_t
AddNodes(Node * node1,Node * node2,Node * node3)225 Transaction::AddNodes(Node* node1, Node* node2, Node* node3)
226 {
227 ASSERT(fID >= 0);
228
229 // sort the nodes
230 swap_if_greater(node1, node2);
231 if (node3 != NULL && swap_if_greater(node2, node3))
232 swap_if_greater(node1, node2);
233
234 // add them
235 status_t error = AddNode(node1);
236 if (error == B_OK)
237 error = AddNode(node2);
238 if (error == B_OK && node3 != NULL)
239 AddNode(node3);
240
241 return error;
242 }
243
244
245 bool
RemoveNode(Node * node)246 Transaction::RemoveNode(Node* node)
247 {
248 ASSERT(fID >= 0);
249
250 NodeInfo* info = _GetNodeInfo(node);
251 if (info == NULL)
252 return false;
253
254 fNodeInfos.Remove(info);
255
256 _DeleteNodeInfoAndUnlock(info, false);
257
258 return true;
259 }
260
261
262 void
UpdateNodeFlags(Node * node,uint32 flags)263 Transaction::UpdateNodeFlags(Node* node, uint32 flags)
264 {
265 ASSERT(fID >= 0);
266
267 NodeInfo* info = _GetNodeInfo(node);
268 if (info == NULL)
269 return;
270
271 info->flags = flags;
272 }
273
274
275 void
KeepNode(Node * node)276 Transaction::KeepNode(Node* node)
277 {
278 ASSERT(fID >= 0);
279
280 NodeInfo* info = _GetNodeInfo(node);
281 if (info == NULL)
282 return;
283
284 info->flags &= ~(uint32)TRANSACTION_DELETE_NODE;
285 }
286
287
288 status_t
RegisterBlock(uint64 blockIndex)289 Transaction::RegisterBlock(uint64 blockIndex)
290 {
291 ASSERT(fID >= 0);
292
293 // look it up -- maybe it's already registered
294 BlockInfo* info = fBlockInfos.Lookup(blockIndex);
295 if (info != NULL) {
296 info->refCount++;
297 return B_OK;
298 }
299
300 // nope, create a new one
301 info = new(std::nothrow) BlockInfo;
302 if (info == NULL)
303 RETURN_ERROR(B_NO_MEMORY);
304 ObjectDeleter<BlockInfo> infoDeleter(info);
305
306 info->indexAndCheckSum.blockIndex = blockIndex;
307 info->refCount = 1;
308 info->dirty = false;
309
310 // get the old check sum
311 if (ioctl(fVolume->FD(), CHECKSUM_DEVICE_IOCTL_GET_CHECK_SUM,
312 &info->indexAndCheckSum, sizeof(info->indexAndCheckSum)) < 0) {
313 RETURN_ERROR(errno);
314 }
315
316 // get the data (we're fine with read-only)
317 info->data = block_cache_get(fVolume->BlockCache(), blockIndex);
318 if (info->data == NULL) {
319 delete info;
320 RETURN_ERROR(B_ERROR);
321 }
322
323 fBlockInfos.Insert(infoDeleter.Detach());
324
325 return B_OK;
326 }
327
328
329 void
PutBlock(uint64 blockIndex,const void * data)330 Transaction::PutBlock(uint64 blockIndex, const void* data)
331 {
332 ASSERT(fID >= 0);
333
334 BlockInfo* info = fBlockInfos.Lookup(blockIndex);
335 if (info == NULL) {
336 panic("checksumfs: Transaction::PutBlock(): unknown block %" B_PRIu64,
337 blockIndex);
338 return;
339 }
340
341 if (info->refCount == 0) {
342 panic("checksumfs: Unbalanced Transaction::PutBlock(): for block %"
343 B_PRIu64, blockIndex);
344 return;
345 }
346
347 info->dirty |= data != NULL;
348
349 if (--info->refCount == 0 && !info->dirty) {
350 // block wasn't got successfully -- remove the info
351 fBlockInfos.Remove(info);
352 block_cache_put(fVolume->BlockCache(),
353 info->indexAndCheckSum.blockIndex);
354 delete info;
355 }
356 }
357
358
359 Transaction::NodeInfo*
_GetNodeInfo(Node * node) const360 Transaction::_GetNodeInfo(Node* node) const
361 {
362 for (NodeInfoList::ConstIterator it = fNodeInfos.GetIterator();
363 NodeInfo* info = it.Next();) {
364 if (node == info->node)
365 return info;
366 }
367
368 return NULL;
369 }
370
371
372 void
_DeleteNodeInfosAndUnlock(bool failed)373 Transaction::_DeleteNodeInfosAndUnlock(bool failed)
374 {
375 while (NodeInfo* info = fNodeInfos.RemoveHead())
376 _DeleteNodeInfoAndUnlock(info, failed);
377 }
378
379
380 void
_DeleteNodeInfoAndUnlock(NodeInfo * info,bool failed)381 Transaction::_DeleteNodeInfoAndUnlock(NodeInfo* info, bool failed)
382 {
383 if (failed) {
384 if ((info->flags & TRANSACTION_REMOVE_NODE_ON_ERROR) != 0)
385 fVolume->RemoveNode(info->node);
386 else if ((info->flags & TRANSACTION_UNREMOVE_NODE_ON_ERROR) != 0)
387 fVolume->UnremoveNode(info->node);
388 }
389
390 if ((info->flags & TRANSACTION_DELETE_NODE) != 0)
391 delete info->node;
392 else if ((info->flags & TRANSACTION_KEEP_NODE_LOCKED) == 0)
393 info->node->WriteUnlock();
394 delete info;
395 }
396
397
398 status_t
_UpdateBlockCheckSums()399 Transaction::_UpdateBlockCheckSums()
400 {
401 for (BlockInfoTable::Iterator it = fBlockInfos.GetIterator();
402 BlockInfo* info = it.Next();) {
403 if (info->refCount > 0) {
404 panic("checksumfs: Transaction::Commit(): block %" B_PRIu64
405 " still referenced", info->indexAndCheckSum.blockIndex);
406 }
407
408 if (!info->dirty)
409 continue;
410
411 // compute the check sum
412 fSHA256->Init();
413 fSHA256->Update(info->data, B_PAGE_SIZE);
414 fCheckSum->blockIndex = info->indexAndCheckSum.blockIndex;
415 fCheckSum->checkSum = fSHA256->Digest();
416
417 // set it
418 if (ioctl(fVolume->FD(), CHECKSUM_DEVICE_IOCTL_SET_CHECK_SUM, fCheckSum,
419 sizeof(*fCheckSum)) < 0) {
420 return errno;
421 }
422 }
423
424 return B_OK;
425 }
426
427
428 status_t
_RevertBlockCheckSums()429 Transaction::_RevertBlockCheckSums()
430 {
431 for (BlockInfoTable::Iterator it = fBlockInfos.GetIterator();
432 BlockInfo* info = it.Next();) {
433 if (!info->dirty)
434 continue;
435
436 // set the old check sum
437 if (ioctl(fVolume->FD(), CHECKSUM_DEVICE_IOCTL_SET_CHECK_SUM,
438 &info->indexAndCheckSum, sizeof(info->indexAndCheckSum)) < 0) {
439 return errno;
440 }
441 }
442
443 return B_OK;
444 }
445
446
447 // #pragma mark - PostCommitNotification
448
449
~PostCommitNotification()450 PostCommitNotification::~PostCommitNotification()
451 {
452 }
453