LogCabin
|
00001 /* Copyright (c) 2012 Stanford University 00002 * Copyright (c) 2014 Diego Ongaro 00003 * 00004 * Permission to use, copy, modify, and distribute this software for any 00005 * purpose with or without fee is hereby granted, provided that the above 00006 * copyright notice and this permission notice appear in all copies. 00007 * 00008 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 00009 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 00010 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 00011 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 00012 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 00013 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 00014 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 00015 */ 00016 00017 #include <cassert> 00018 00019 #include "build/Protocol/ServerStats.pb.h" 00020 #include "build/Tree/Snapshot.pb.h" 00021 #include "Core/Debug.h" 00022 #include "Core/StringUtil.h" 00023 #include "Tree/Tree.h" 00024 00025 namespace LogCabin { 00026 namespace Tree { 00027 00028 using Core::StringUtil::format; 00029 using namespace Internal; // NOLINT 00030 00031 ////////// enum Status ////////// 00032 00033 std::ostream& 00034 operator<<(std::ostream& os, Status status) 00035 { 00036 switch (status) { 00037 case Status::OK: 00038 os << "Status::OK"; 00039 break; 00040 case Status::INVALID_ARGUMENT: 00041 os << "Status::INVALID_ARGUMENT"; 00042 break; 00043 case Status::LOOKUP_ERROR: 00044 os << "Status::LOOKUP_ERROR"; 00045 break; 00046 case Status::TYPE_ERROR: 00047 os << "Status::TYPE_ERROR"; 00048 break; 00049 case Status::CONDITION_NOT_MET: 00050 os << "Status::CONDITION_NOT_MET"; 00051 break; 00052 } 00053 return os; 00054 } 00055 00056 ////////// struct Result ////////// 00057 00058 Result::Result() 00059 : status(Status::OK) 00060 , error() 00061 { 00062 } 00063 00064 namespace Internal { 00065 00066 ////////// class File ////////// 00067 00068 File::File() 00069 : contents() 00070 { 00071 } 00072 00073 void 00074 File::dumpSnapshot(Core::ProtoBuf::OutputStream& stream) const 00075 { 00076 Snapshot::File file; 00077 file.set_contents(contents); 00078 stream.writeMessage(file); 00079 } 00080 00081 void 00082 File::loadSnapshot(Core::ProtoBuf::InputStream& stream) 00083 { 00084 Snapshot::File node; 00085 std::string error = stream.readMessage(node); 00086 if (!error.empty()) { 00087 PANIC("Couldn't read snapshot: %s", error.c_str()); 00088 } 00089 contents = node.contents(); 00090 } 00091 00092 ////////// class Directory ////////// 00093 00094 Directory::Directory() 00095 : directories() 00096 , files() 00097 { 00098 } 00099 00100 std::vector<std::string> 00101 Directory::getChildren() const 00102 { 00103 std::vector<std::string> children; 00104 for (auto it = directories.begin(); it != directories.end(); ++it) 00105 children.push_back(it->first + "/"); 00106 for (auto it = files.begin(); it != files.end(); ++it) 00107 children.push_back(it->first); 00108 return children; 00109 } 00110 00111 Directory* 00112 Directory::lookupDirectory(const std::string& name) 00113 { 00114 return const_cast<Directory*>( 00115 const_cast<const Directory*>(this)->lookupDirectory(name)); 00116 } 00117 00118 const Directory* 00119 Directory::lookupDirectory(const std::string& name) const 00120 { 00121 assert(!name.empty()); 00122 assert(!Core::StringUtil::endsWith(name, "/")); 00123 auto it = directories.find(name); 00124 if (it == directories.end()) 00125 return NULL; 00126 return &it->second; 00127 } 00128 00129 00130 Directory* 00131 Directory::makeDirectory(const std::string& name) 00132 { 00133 assert(!name.empty()); 00134 assert(!Core::StringUtil::endsWith(name, "/")); 00135 if (lookupFile(name) != NULL) 00136 return NULL; 00137 return &directories[name]; 00138 } 00139 00140 void 00141 Directory::removeDirectory(const std::string& name) 00142 { 00143 assert(!name.empty()); 00144 assert(!Core::StringUtil::endsWith(name, "/")); 00145 directories.erase(name); 00146 } 00147 00148 File* 00149 Directory::lookupFile(const std::string& name) 00150 { 00151 return const_cast<File*>( 00152 const_cast<const Directory*>(this)->lookupFile(name)); 00153 } 00154 00155 const File* 00156 Directory::lookupFile(const std::string& name) const 00157 { 00158 assert(!name.empty()); 00159 assert(!Core::StringUtil::endsWith(name, "/")); 00160 auto it = files.find(name); 00161 if (it == files.end()) 00162 return NULL; 00163 return &it->second; 00164 } 00165 00166 File* 00167 Directory::makeFile(const std::string& name) 00168 { 00169 assert(!name.empty()); 00170 assert(!Core::StringUtil::endsWith(name, "/")); 00171 if (lookupDirectory(name) != NULL) 00172 return NULL; 00173 return &files[name]; 00174 } 00175 00176 bool 00177 Directory::removeFile(const std::string& name) 00178 { 00179 assert(!name.empty()); 00180 assert(!Core::StringUtil::endsWith(name, "/")); 00181 return (files.erase(name) > 0); 00182 } 00183 00184 void 00185 Directory::dumpSnapshot(Core::ProtoBuf::OutputStream& stream) const 00186 { 00187 // create protobuf of this dir, listing all children 00188 Snapshot::Directory dir; 00189 for (auto it = directories.begin(); it != directories.end(); ++it) 00190 dir.add_directories(it->first); 00191 for (auto it = files.begin(); it != files.end(); ++it) 00192 dir.add_files(it->first); 00193 00194 // write dir into stream 00195 stream.writeMessage(dir); 00196 00197 // dump children in the same order 00198 for (auto it = directories.begin(); it != directories.end(); ++it) 00199 it->second.dumpSnapshot(stream); 00200 for (auto it = files.begin(); it != files.end(); ++it) 00201 it->second.dumpSnapshot(stream); 00202 } 00203 00204 void 00205 Directory::loadSnapshot(Core::ProtoBuf::InputStream& stream) 00206 { 00207 Snapshot::Directory dir; 00208 std::string error = stream.readMessage(dir); 00209 if (!error.empty()) { 00210 PANIC("Couldn't read snapshot: %s", error.c_str()); 00211 } 00212 for (auto it = dir.directories().begin(); 00213 it != dir.directories().end(); 00214 ++it) { 00215 directories[*it].loadSnapshot(stream); 00216 } 00217 for (auto it = dir.files().begin(); 00218 it != dir.files().end(); 00219 ++it) { 00220 files[*it].loadSnapshot(stream); 00221 } 00222 } 00223 00224 ////////// class Path ////////// 00225 00226 Path::Path(const std::string& symbolic) 00227 : result() 00228 , symbolic(symbolic) 00229 , parents() 00230 , target() 00231 { 00232 if (!Core::StringUtil::startsWith(symbolic, "/")) { 00233 result.status = Status::INVALID_ARGUMENT; 00234 result.error = format("'%s' is not a valid path", 00235 symbolic.c_str()); 00236 return; 00237 } 00238 00239 // Add /root prefix (see docs for Tree::superRoot) 00240 parents.push_back("root"); 00241 00242 // Split the path into a list of parent components and a target. 00243 std::string word; 00244 for (auto it = symbolic.begin(); it != symbolic.end(); ++it) { 00245 if (*it == '/') { 00246 if (!word.empty()) { 00247 parents.push_back(word); 00248 word.clear(); 00249 } 00250 } else { 00251 word += *it; 00252 } 00253 } 00254 if (!word.empty()) 00255 parents.push_back(word); 00256 target = parents.back(); 00257 parents.pop_back(); 00258 } 00259 00260 std::string 00261 Path::parentsThrough(std::vector<std::string>::const_iterator end) const 00262 { 00263 auto it = parents.begin(); 00264 ++it; // skip "root" 00265 ++end; // end was inclusive, now exclusive 00266 if (it == end) 00267 return "/"; 00268 std::string ret; 00269 do { 00270 ret += "/" + *it; 00271 ++it; 00272 } while (it != end); 00273 return ret; 00274 } 00275 00276 } // LogCabin::Tree::Internal 00277 00278 ////////// class Tree ////////// 00279 00280 Tree::Tree() 00281 : superRoot() 00282 , numConditionsChecked(0) 00283 , numConditionsFailed(0) 00284 , numMakeDirectoryAttempted(0) 00285 , numMakeDirectorySuccess(0) 00286 , numListDirectoryAttempted(0) 00287 , numListDirectorySuccess(0) 00288 , numRemoveDirectoryAttempted(0) 00289 , numRemoveDirectoryParentNotFound(0) 00290 , numRemoveDirectoryTargetNotFound(0) 00291 , numRemoveDirectoryDone(0) 00292 , numRemoveDirectorySuccess(0) 00293 , numWriteAttempted(0) 00294 , numWriteSuccess(0) 00295 , numReadAttempted(0) 00296 , numReadSuccess(0) 00297 , numRemoveFileAttempted(0) 00298 , numRemoveFileParentNotFound(0) 00299 , numRemoveFileTargetNotFound(0) 00300 , numRemoveFileDone(0) 00301 , numRemoveFileSuccess(0) 00302 { 00303 // Create the root directory so that users don't have to explicitly 00304 // call makeDirectory("/"). 00305 superRoot.makeDirectory("root"); 00306 } 00307 00308 Result 00309 Tree::normalLookup(const Path& path, Directory** parent) 00310 { 00311 return normalLookup(path, 00312 const_cast<const Directory**>(parent)); 00313 } 00314 00315 Result 00316 Tree::normalLookup(const Path& path, const Directory** parent) const 00317 { 00318 *parent = NULL; 00319 Result result; 00320 const Directory* current = &superRoot; 00321 for (auto it = path.parents.begin(); it != path.parents.end(); ++it) { 00322 const Directory* next = current->lookupDirectory(*it); 00323 if (next == NULL) { 00324 if (current->lookupFile(*it) == NULL) { 00325 result.status = Status::LOOKUP_ERROR; 00326 result.error = format("Parent %s of %s does not exist", 00327 path.parentsThrough(it).c_str(), 00328 path.symbolic.c_str()); 00329 } else { 00330 result.status = Status::TYPE_ERROR; 00331 result.error = format("Parent %s of %s is a file", 00332 path.parentsThrough(it).c_str(), 00333 path.symbolic.c_str()); 00334 } 00335 return result; 00336 } 00337 current = next; 00338 } 00339 *parent = current; 00340 return result; 00341 } 00342 00343 Result 00344 Tree::mkdirLookup(const Path& path, Directory** parent) 00345 { 00346 *parent = NULL; 00347 Result result; 00348 Directory* current = &superRoot; 00349 for (auto it = path.parents.begin(); it != path.parents.end(); ++it) { 00350 Directory* next = current->makeDirectory(*it); 00351 if (next == NULL) { 00352 result.status = Status::TYPE_ERROR; 00353 result.error = format("Parent %s of %s is a file", 00354 path.parentsThrough(it).c_str(), 00355 path.symbolic.c_str()); 00356 return result; 00357 } 00358 current = next; 00359 } 00360 *parent = current; 00361 return result; 00362 } 00363 00364 void 00365 Tree::dumpSnapshot(Core::ProtoBuf::OutputStream& stream) const 00366 { 00367 superRoot.dumpSnapshot(stream); 00368 } 00369 00370 /** 00371 * Load the tree from the given stream. 00372 */ 00373 void 00374 Tree::loadSnapshot(Core::ProtoBuf::InputStream& stream) 00375 { 00376 superRoot = Directory(); 00377 superRoot.loadSnapshot(stream); 00378 } 00379 00380 00381 Result 00382 Tree::checkCondition(const std::string& path, 00383 const std::string& contents) const 00384 { 00385 ++numConditionsChecked; 00386 std::string actualContents; 00387 Result readResult = read(path, actualContents); 00388 if (readResult.status == Status::OK) { 00389 if (contents == actualContents) { 00390 return Result(); 00391 } else { 00392 Result result; 00393 result.status = Status::CONDITION_NOT_MET; 00394 result.error = format("Path '%s' has value '%s', not '%s' as " 00395 "required", 00396 path.c_str(), 00397 actualContents.c_str(), 00398 contents.c_str()); 00399 ++numConditionsFailed; 00400 return result; 00401 } 00402 } 00403 if (readResult.status == Status::LOOKUP_ERROR && contents.empty()) { 00404 return Result(); 00405 } 00406 Result result; 00407 result.status = Status::CONDITION_NOT_MET; 00408 result.error = format("Could not read value at path '%s': %s", 00409 path.c_str(), readResult.error.c_str()); 00410 ++numConditionsFailed; 00411 return result; 00412 } 00413 00414 Result 00415 Tree::makeDirectory(const std::string& symbolicPath) 00416 { 00417 ++numMakeDirectoryAttempted; 00418 Path path(symbolicPath); 00419 if (path.result.status != Status::OK) 00420 return path.result; 00421 Directory* parent; 00422 Result result = mkdirLookup(path, &parent); 00423 if (result.status != Status::OK) 00424 return result; 00425 if (parent->makeDirectory(path.target) == NULL) { 00426 result.status = Status::TYPE_ERROR; 00427 result.error = format("%s already exists but is a file", 00428 path.symbolic.c_str()); 00429 return result; 00430 } 00431 ++numMakeDirectorySuccess; 00432 return result; 00433 } 00434 00435 Result 00436 Tree::listDirectory(const std::string& symbolicPath, 00437 std::vector<std::string>& children) const 00438 { 00439 ++numListDirectoryAttempted; 00440 children.clear(); 00441 Path path(symbolicPath); 00442 if (path.result.status != Status::OK) 00443 return path.result; 00444 const Directory* parent; 00445 Result result = normalLookup(path, &parent); 00446 if (result.status != Status::OK) 00447 return result; 00448 const Directory* targetDir = parent->lookupDirectory(path.target); 00449 if (targetDir == NULL) { 00450 if (parent->lookupFile(path.target) == NULL) { 00451 result.status = Status::LOOKUP_ERROR; 00452 result.error = format("%s does not exist", 00453 path.symbolic.c_str()); 00454 } else { 00455 result.status = Status::TYPE_ERROR; 00456 result.error = format("%s is a file", 00457 path.symbolic.c_str()); 00458 } 00459 return result; 00460 } 00461 children = targetDir->getChildren(); 00462 ++numListDirectorySuccess; 00463 return result; 00464 } 00465 00466 Result 00467 Tree::removeDirectory(const std::string& symbolicPath) 00468 { 00469 ++numRemoveDirectoryAttempted; 00470 Path path(symbolicPath); 00471 if (path.result.status != Status::OK) 00472 return path.result; 00473 Directory* parent; 00474 Result result = normalLookup(path, &parent); 00475 if (result.status == Status::LOOKUP_ERROR) { 00476 // no parent, already done 00477 ++numRemoveDirectoryParentNotFound; 00478 ++numRemoveDirectorySuccess; 00479 return Result(); 00480 } 00481 if (result.status != Status::OK) 00482 return result; 00483 Directory* targetDir = parent->lookupDirectory(path.target); 00484 if (targetDir == NULL) { 00485 if (parent->lookupFile(path.target)) { 00486 result.status = Status::TYPE_ERROR; 00487 result.error = format("%s is a file", 00488 path.symbolic.c_str()); 00489 return result; 00490 } else { 00491 // target does not exist, already done 00492 ++numRemoveDirectoryTargetNotFound; 00493 ++numRemoveDirectorySuccess; 00494 return result; 00495 } 00496 } 00497 parent->removeDirectory(path.target); 00498 if (parent == &superRoot) { // removeDirectory("/") 00499 // If the caller is trying to remove the root directory, we remove the 00500 // contents but not the directory itself. The easiest way to do this 00501 // is to drop but then recreate the directory. 00502 parent->makeDirectory(path.target); 00503 } 00504 ++numRemoveDirectoryDone; 00505 ++numRemoveDirectorySuccess; 00506 return result; 00507 } 00508 00509 Result 00510 Tree::write(const std::string& symbolicPath, const std::string& contents) 00511 { 00512 ++numWriteAttempted; 00513 Path path(symbolicPath); 00514 if (path.result.status != Status::OK) 00515 return path.result; 00516 Directory* parent; 00517 Result result = normalLookup(path, &parent); 00518 if (result.status != Status::OK) 00519 return result; 00520 File* targetFile = parent->makeFile(path.target); 00521 if (targetFile == NULL) { 00522 result.status = Status::TYPE_ERROR; 00523 result.error = format("%s is a directory", 00524 path.symbolic.c_str()); 00525 return result; 00526 } 00527 targetFile->contents = contents; 00528 ++numWriteSuccess; 00529 return result; 00530 } 00531 00532 Result 00533 Tree::read(const std::string& symbolicPath, std::string& contents) const 00534 { 00535 ++numReadAttempted; 00536 contents.clear(); 00537 Path path(symbolicPath); 00538 if (path.result.status != Status::OK) 00539 return path.result; 00540 const Directory* parent; 00541 Result result = normalLookup(path, &parent); 00542 if (result.status != Status::OK) 00543 return result; 00544 const File* targetFile = parent->lookupFile(path.target); 00545 if (targetFile == NULL) { 00546 if (parent->lookupDirectory(path.target) != NULL) { 00547 result.status = Status::TYPE_ERROR; 00548 result.error = format("%s is a directory", 00549 path.symbolic.c_str()); 00550 } else { 00551 result.status = Status::LOOKUP_ERROR; 00552 result.error = format("%s does not exist", 00553 path.symbolic.c_str()); 00554 } 00555 return result; 00556 } 00557 contents = targetFile->contents; 00558 ++numReadSuccess; 00559 return result; 00560 } 00561 00562 Result 00563 Tree::removeFile(const std::string& symbolicPath) 00564 { 00565 ++numRemoveFileAttempted; 00566 Path path(symbolicPath); 00567 if (path.result.status != Status::OK) 00568 return path.result; 00569 Directory* parent; 00570 Result result = normalLookup(path, &parent); 00571 if (result.status == Status::LOOKUP_ERROR) { 00572 // no parent, already done 00573 ++numRemoveFileParentNotFound; 00574 ++numRemoveFileSuccess; 00575 return Result(); 00576 } 00577 if (result.status != Status::OK) 00578 return result; 00579 if (parent->lookupDirectory(path.target) != NULL) { 00580 result.status = Status::TYPE_ERROR; 00581 result.error = format("%s is a directory", 00582 path.symbolic.c_str()); 00583 return result; 00584 } 00585 if (parent->removeFile(path.target)) 00586 ++numRemoveFileDone; 00587 else 00588 ++numRemoveFileTargetNotFound; 00589 ++numRemoveFileSuccess; 00590 return result; 00591 } 00592 00593 void 00594 Tree::updateServerStats(Protocol::ServerStats::Tree& tstats) const 00595 { 00596 tstats.set_num_conditions_checked( 00597 numConditionsChecked); 00598 tstats.set_num_conditions_failed( 00599 numConditionsFailed); 00600 tstats.set_num_make_directory_attempted( 00601 numMakeDirectoryAttempted); 00602 tstats.set_num_make_directory_success( 00603 numMakeDirectorySuccess); 00604 tstats.set_num_list_directory_attempted( 00605 numListDirectoryAttempted); 00606 tstats.set_num_list_directory_success( 00607 numListDirectorySuccess); 00608 tstats.set_num_remove_directory_attempted( 00609 numRemoveDirectoryAttempted); 00610 tstats.set_num_remove_directory_parent_not_found( 00611 numRemoveDirectoryParentNotFound); 00612 tstats.set_num_remove_directory_target_not_found( 00613 numRemoveDirectoryTargetNotFound); 00614 tstats.set_num_remove_directory_done( 00615 numRemoveDirectoryDone); 00616 tstats.set_num_remove_directory_success( 00617 numRemoveDirectorySuccess); 00618 tstats.set_num_write_attempted( 00619 numWriteAttempted); 00620 tstats.set_num_write_success( 00621 numWriteSuccess); 00622 tstats.set_num_read_attempted( 00623 numReadAttempted); 00624 tstats.set_num_read_success( 00625 numReadSuccess); 00626 tstats.set_num_remove_file_attempted( 00627 numRemoveFileAttempted); 00628 tstats.set_num_remove_file_parent_not_found( 00629 numRemoveFileParentNotFound); 00630 tstats.set_num_remove_file_target_not_found( 00631 numRemoveFileTargetNotFound); 00632 tstats.set_num_remove_file_done( 00633 numRemoveFileDone); 00634 tstats.set_num_remove_file_success( 00635 numRemoveFileSuccess); 00636 } 00637 00638 } // namespace LogCabin::Tree 00639 } // namespace LogCabin