xref: /haiku/src/tests/system/kernel/file_corruption/fs/Transaction.cpp (revision a738ca2c55bd08da2593587ccf7f8cb6cc7a75a0)
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