LogCabin
Tree/Tree.cc
Go to the documentation of this file.
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
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines