LogCabin
|
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