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