LogCabin
Storage/FilesystemUtil.cc
Go to the documentation of this file.
00001 /* Copyright (c) 2011-2012 Stanford University
00002  *
00003  * Permission to use, copy, modify, and distribute this software for any
00004  * purpose with or without fee is hereby granted, provided that the above
00005  * copyright notice and this permission notice appear in all copies.
00006  *
00007  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES
00008  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
00009  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR
00010  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
00011  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
00012  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
00013  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
00014  */
00015 
00016 #include <assert.h>
00017 #include <dirent.h>
00018 #include <errno.h>
00019 #include <fcntl.h>
00020 #include <string.h>
00021 #include <sys/file.h>
00022 #include <sys/mman.h>
00023 #include <sys/stat.h>
00024 #include <sys/uio.h>
00025 #include <unistd.h>
00026 
00027 #include "Core/Debug.h"
00028 #include "Core/StringUtil.h"
00029 #include "Core/Util.h"
00030 #include "Storage/FilesystemUtil.h"
00031 
00032 namespace LogCabin {
00033 namespace Storage {
00034 namespace FilesystemUtil {
00035 
00036 bool skipFsync = false;
00037 
00038 File::File()
00039     : fd(-1)
00040     , path()
00041 {
00042 }
00043 
00044 File::File(File&& other)
00045     : fd(other.fd)
00046     , path(other.path)
00047 {
00048     other.fd = -1;
00049     other.path.clear();
00050 }
00051 
00052 File::File(int fd, std::string path)
00053     : fd(fd)
00054     , path(path)
00055 {
00056 }
00057 
00058 File::~File()
00059 {
00060     close();
00061 }
00062 
00063 File&
00064 File::operator=(File&& other)
00065 {
00066     std::swap(fd, other.fd);
00067     std::swap(path, other.path);
00068     return *this;
00069 }
00070 
00071 void
00072 File::close()
00073 {
00074     if (fd < 0)
00075         return;
00076     if (::close(fd) != 0) {
00077         PANIC("Failed to close file %s: %s",
00078               path.c_str(), strerror(errno));
00079     }
00080     fd = -1;
00081     path.clear();
00082 }
00083 
00084 int
00085 File::release()
00086 {
00087     int ret = fd;
00088     fd = -1;
00089     path.clear();
00090     return ret;
00091 }
00092 
00093 void
00094 allocate(const File& file, uint64_t offset, uint64_t bytes)
00095 {
00096     int errnum = ::posix_fallocate(file.fd,
00097                                    Core::Util::downCast<off_t>(offset),
00098                                    Core::Util::downCast<off_t>(bytes));
00099     if (errnum != 0) {
00100         PANIC("Could not posix_fallocate bytes [%lu, %lu) of %s: %s",
00101               offset, offset + bytes, file.path.c_str(), strerror(errnum));
00102     }
00103 }
00104 
00105 File
00106 dup(const File& file)
00107 {
00108     int newFd = ::dup(file.fd);
00109     if (newFd == -1) {
00110         PANIC("Dup failed on fd %d for path %s: %s",
00111               file.fd, file.path.c_str(), strerror(errno));
00112     }
00113     return File(newFd, file.path);
00114 }
00115 
00116 void
00117 fsync(const File& file)
00118 {
00119     if (skipFsync)
00120         return;
00121     if (::fsync(file.fd) != 0) {
00122         PANIC("Could not fsync %s: %s",
00123               file.path.c_str(), strerror(errno));
00124     }
00125 }
00126 
00127 void
00128 fdatasync(const File& file)
00129 {
00130     if (skipFsync)
00131         return;
00132     if (::fdatasync(file.fd) != 0) {
00133         PANIC("Could not fdatasync %s: %s",
00134               file.path.c_str(), strerror(errno));
00135     }
00136 }
00137 
00138 
00139 void
00140 flock(const File& file, int operation)
00141 {
00142     std::string e = tryFlock(file, operation);
00143     if (!e.empty())
00144         PANIC("%s", e.c_str());
00145 }
00146 
00147 std::string
00148 tryFlock(const File& file, int operation)
00149 {
00150     if (::flock(file.fd, operation) == 0)
00151         return std::string();
00152     int error = errno;
00153     std::string msg = Core::StringUtil::format(
00154         "Could not flock('%s', %s): %s",
00155         file.path.c_str(),
00156         Core::StringUtil::flags(operation,
00157                                 {{LOCK_SH, "LOCK_SH"},
00158                                  {LOCK_EX, "LOCK_EX"},
00159                                  {LOCK_UN, "LOCK_UN"},
00160                                  {LOCK_NB, "LOCK_NB"}}).c_str(),
00161         strerror(error));
00162     if (error == EWOULDBLOCK)
00163         return msg;
00164     else
00165         PANIC("%s", msg.c_str());
00166 }
00167 
00168 uint64_t
00169 getSize(const File& file)
00170 {
00171     struct stat stat;
00172     if (fstat(file.fd, &stat) != 0) {
00173         PANIC("Could not stat %s: %s",
00174               file.path.c_str(), strerror(errno));
00175     }
00176     return Core::Util::downCast<uint64_t>(stat.st_size);
00177 }
00178 
00179 std::vector<std::string>
00180 lsHelper(DIR* dir, const std::string& path)
00181 {
00182     if (dir == NULL) {
00183         PANIC("Could not list contents of %s: %s",
00184               path.c_str(), strerror(errno));
00185     }
00186 
00187     // If dir was opened with fdopendir and was read from previously, this is
00188     // needed to rewind the directory, at least on eglibc v2.13. The unit test
00189     // "ls_RewindDir" shows the exact problem.
00190     rewinddir(dir);
00191 
00192     std::vector<std::string> contents;
00193     while (true) {
00194         struct dirent entry;
00195         struct dirent* entryp;
00196         if (readdir_r(dir, &entry, &entryp) != 0) {
00197             PANIC("readdir(%s) failed: %s",
00198                   path.c_str(), strerror(errno));
00199         }
00200         if (entryp == NULL) // no more entries
00201             break;
00202         const std::string name = entry.d_name;
00203         if (name == "." || name == "..")
00204             continue;
00205         contents.push_back(name);
00206     }
00207 
00208     if (closedir(dir) != 0) {
00209         WARNING("closedir(%s) failed: %s",
00210                 path.c_str(), strerror(errno));
00211     }
00212 
00213     return contents;
00214 }
00215 
00216 std::vector<std::string>
00217 ls(const std::string& path)
00218 {
00219     return lsHelper(opendir(path.c_str()), path);
00220 }
00221 
00222 std::vector<std::string>
00223 ls(const File& dir)
00224 {
00225     return lsHelper(fdopendir(dup(dir).release()), dir.path);
00226 }
00227 
00228 File
00229 openDir(const std::string& path)
00230 {
00231     assert(!path.empty());
00232     int r = mkdir(path.c_str(), 0755);
00233     if (r == 0) {
00234         FilesystemUtil::syncDir(path + "/..");
00235     } else {
00236         if (errno != EEXIST) {
00237             PANIC("Could not create directory %s: %s",
00238                   path.c_str(), strerror(errno));
00239         }
00240     }
00241     // It'd be awesome if one could do O_RDONLY|O_CREAT|O_DIRECTORY here,
00242     // but at least on eglibc v2.13, this combination of flags creates a
00243     // regular file!
00244     int fd = open(path.c_str(), O_RDONLY|O_DIRECTORY);
00245     if (fd == -1) {
00246         PANIC("Could not open %s: %s",
00247               path.c_str(), strerror(errno));
00248     }
00249     return File(fd, path);
00250 }
00251 
00252 File
00253 openDir(const File& dir, const std::string& child)
00254 {
00255     assert(!Core::StringUtil::startsWith(child, "/"));
00256     int r = mkdirat(dir.fd, child.c_str(), 0755);
00257     if (r == 0) {
00258         fsync(dir);
00259     } else {
00260         if (errno != EEXIST) {
00261             PANIC("Could not create directory %s/%s: %s",
00262                   dir.path.c_str(), child.c_str(), strerror(errno));
00263         }
00264     }
00265     // It'd be awesome if one could do O_RDONLY|O_CREAT|O_DIRECTORY here,
00266     // but at least on eglibc v2.13, this combination of flags creates a
00267     // regular file!
00268     int fd = openat(dir.fd, child.c_str(), O_RDONLY|O_DIRECTORY);
00269     if (fd == -1) {
00270         PANIC("Could not open %s/%s: %s",
00271               dir.path.c_str(), child.c_str(), strerror(errno));
00272     }
00273     return File(fd, dir.path + "/" + child);
00274 }
00275 
00276 File
00277 openFile(const File& dir, const std::string& child, int flags)
00278 {
00279     assert(!Core::StringUtil::startsWith(child, "/"));
00280     int fd = openat(dir.fd, child.c_str(), flags, 0644);
00281     if (fd == -1) {
00282         PANIC("Could not open %s/%s: %s",
00283               dir.path.c_str(), child.c_str(), strerror(errno));
00284     }
00285     return File(fd, dir.path + "/" + child);
00286 }
00287 
00288 File
00289 tryOpenFile(const File& dir, const std::string& child, int flags)
00290 {
00291     assert(!Core::StringUtil::startsWith(child, "/"));
00292     int fd = openat(dir.fd, child.c_str(), flags, 0644);
00293     if (fd == -1) {
00294         if (errno == EEXIST || errno == ENOENT)
00295             return File();
00296         PANIC("Could not open %s/%s: %s",
00297               dir.path.c_str(), child.c_str(), strerror(errno));
00298     }
00299     return File(fd, dir.path + "/" + child);
00300 }
00301 
00302 void
00303 remove(const std::string& path)
00304 {
00305     while (true) {
00306         if (::remove(path.c_str()) == 0)
00307             return;
00308         if (errno == ENOENT) {
00309             return;
00310         } else if (errno == EEXIST || errno == ENOTEMPTY) {
00311             std::vector<std::string> children = ls(path);
00312             for (auto it = children.begin(); it != children.end(); ++it)
00313                 remove(path + "/" + *it);
00314             continue;
00315         } else {
00316             PANIC("Could not remove %s: %s", path.c_str(), strerror(errno));
00317         }
00318     }
00319 }
00320 
00321 void
00322 removeFile(const File& dir, const std::string& path)
00323 {
00324     assert(!Core::StringUtil::startsWith(path, "/"));
00325     if (::unlinkat(dir.fd, path.c_str(), 0) == 0)
00326         return;
00327     if (errno == ENOENT)
00328         return;
00329     PANIC("Could not remove %s/%s: %s",
00330           dir.path.c_str(), path.c_str(), strerror(errno));
00331 }
00332 
00333 void
00334 rename(const File& oldDir, const std::string& oldChild,
00335        const File& newDir, const std::string& newChild)
00336 {
00337     assert(!Core::StringUtil::startsWith(oldChild, "/"));
00338     assert(!Core::StringUtil::startsWith(newChild, "/"));
00339     if (::renameat(oldDir.fd, oldChild.c_str(),
00340                    newDir.fd, newChild.c_str()) == 0)
00341         return;
00342     PANIC("Could not rename %s/%s to %s/%s: %s",
00343           oldDir.path.c_str(), oldChild.c_str(),
00344           newDir.path.c_str(), newChild.c_str(),
00345           strerror(errno));
00346 }
00347 
00348 void
00349 syncDir(const std::string& path)
00350 {
00351     if (skipFsync)
00352         return;
00353     int fd = open(path.c_str(), O_RDONLY);
00354     if (fd == -1) {
00355         PANIC("Could not open %s: %s",
00356               path.c_str(), strerror(errno));
00357     }
00358     if (::fsync(fd) != 0) {
00359         PANIC("Could not fsync %s: %s",
00360               path.c_str(), strerror(errno));
00361     }
00362     if (close(fd) != 0) {
00363         WARNING("Failed to close file %s: %s",
00364                 path.c_str(), strerror(errno));
00365     }
00366 }
00367 
00368 void
00369 truncate(const File& file, uint64_t bytes)
00370 {
00371     if (::ftruncate(file.fd, Core::Util::downCast<off_t>(bytes)) != 0) {
00372         PANIC("Could not ftruncate %s: %s",
00373               file.path.c_str(),
00374               strerror(errno));
00375     }
00376 }
00377 
00378 std::string
00379 mkdtemp()
00380 {
00381     char d[] = "/tmp/logcabinXXXXXX";
00382     const char* path = ::mkdtemp(d);
00383     if (path == NULL)
00384         PANIC("Couldn't create temporary directory");
00385     return path;
00386 }
00387 
00388 namespace System {
00389 // This is mocked out in some unit tests.
00390 ssize_t (*writev)(int fildes,
00391                   const struct iovec* iov,
00392                   int iovcnt) = ::writev;
00393 }
00394 
00395 ssize_t
00396 write(int fildes, const void* data, uint64_t dataLen)
00397 {
00398     return write(fildes, {{data, dataLen}});
00399 }
00400 
00401 ssize_t
00402 write(int fildes,
00403        std::initializer_list<std::pair<const void*, uint64_t>> data)
00404 {
00405     using Core::Util::downCast;
00406     size_t totalBytes = 0;
00407     uint64_t iovcnt = data.size();
00408     struct iovec iov[iovcnt];
00409     uint64_t i = 0;
00410     for (auto it = data.begin(); it != data.end(); ++it) {
00411         iov[i].iov_base = const_cast<void*>(it->first);
00412         iov[i].iov_len = it->second;
00413         totalBytes += it->second;
00414         ++i;
00415     }
00416 
00417     size_t bytesRemaining = totalBytes;
00418     while (true) {
00419          ssize_t written = System::writev(fildes, iov, downCast<int>(iovcnt));
00420          if (written == -1) {
00421              if (errno == EINTR)
00422                  continue;
00423              return -1;
00424          }
00425          bytesRemaining = downCast<size_t>(bytesRemaining -
00426                                            downCast<size_t>(written));
00427          if (bytesRemaining == 0)
00428              return downCast<ssize_t>(totalBytes);
00429          for (uint64_t i = 0; i < iovcnt; ++i) {
00430              if (iov[i].iov_len < static_cast<size_t>(written)) {
00431                  written -= iov[i].iov_len;
00432                  iov[i].iov_len = 0;
00433              } else if (iov[i].iov_len >= static_cast<size_t>(written)) {
00434                  iov[i].iov_len = iov[i].iov_len - downCast<size_t>(written);
00435                  iov[i].iov_base = (static_cast<char*>(iov[i].iov_base) +
00436                                     written);
00437                  break;
00438              }
00439          }
00440     }
00441 }
00442 
00443 // class FileContents
00444 
00445 FileContents::FileContents(const File& origFile)
00446     : file(dup(origFile))
00447     , fileLen(getSize(file))
00448     , map(NULL)
00449 {
00450     // len of 0 for empty files results in invalid argument
00451     if (fileLen > 0) {
00452         map = mmap(NULL, fileLen, PROT_READ, MAP_SHARED, file.fd, 0);
00453         if (map == MAP_FAILED) {
00454             PANIC("Could not map %s: %s",
00455                   file.path.c_str(), strerror(errno));
00456         }
00457     }
00458 }
00459 
00460 FileContents::~FileContents()
00461 {
00462     if (map == NULL)
00463         return;
00464     if (munmap(const_cast<void*>(map), fileLen) != 0) {
00465         WARNING("Failed to munmap file %s: %s",
00466                 file.path.c_str(), strerror(errno));
00467     }
00468 }
00469 
00470 void
00471 FileContents::copy(uint64_t offset, void* buf, uint64_t length)
00472 {
00473     if (copyPartial(offset, buf, length) != length) {
00474         PANIC("File %s too short or corrupt",
00475               file.path.c_str());
00476     }
00477 }
00478 
00479 uint64_t
00480 FileContents::copyPartial(uint64_t offset, void* buf, uint64_t maxLength)
00481 {
00482     if (offset >= fileLen)
00483         return 0;
00484     uint64_t length = std::min(fileLen - offset, maxLength);
00485     memcpy(buf, static_cast<const char*>(map) + offset, length);
00486     return length;
00487 }
00488 
00489 const void*
00490 FileContents::getHelper(uint64_t offset, uint64_t length)
00491 {
00492     if (length != 0 && offset + length > fileLen) {
00493         PANIC("File %s too short or corrupt",
00494               file.path.c_str());
00495     }
00496     return static_cast<const char*>(map) + offset;
00497 }
00498 
00499 } // namespace LogCabin::Storage::FilesystemUtil
00500 } // namespace LogCabin::Storage
00501 } // namespace LogCabin
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines