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
20static inline bool
21swap_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
34Transaction::Transaction(Volume* volume)
35	:
36	fVolume(volume),
37	fSHA256(NULL),
38	fCheckSum(NULL),
39	fID(-1)
40{
41}
42
43
44Transaction::~Transaction()
45{
46	Abort();
47
48	delete fCheckSum;
49	delete fSHA256;
50}
51
52
53status_t
54Transaction::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
88status_t
89Transaction::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
99status_t
100Transaction::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
158void
159Transaction::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
198status_t
199Transaction::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
224status_t
225Transaction::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
245bool
246Transaction::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
262void
263Transaction::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
275void
276Transaction::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
288status_t
289Transaction::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
329void
330Transaction::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
359Transaction::NodeInfo*
360Transaction::_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
372void
373Transaction::_DeleteNodeInfosAndUnlock(bool failed)
374{
375	while (NodeInfo* info = fNodeInfos.RemoveHead())
376		_DeleteNodeInfoAndUnlock(info, failed);
377}
378
379
380void
381Transaction::_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
398status_t
399Transaction::_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
428status_t
429Transaction::_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
450PostCommitNotification::~PostCommitNotification()
451{
452}
453