#include <errno.h>
+#include <cstdio>
#include <stdexcept>
#include <system_error>
#include "folly/Conv.h"
+#include "folly/FBString.h"
#include "folly/Likely.h"
#include "folly/Portability.h"
namespace folly {
+// Various helpers to throw appropriate std::system_error exceptions from C
+// library errors (returned in errno, as positive return values (many POSIX
+// functions), or as negative return values (Linux syscalls))
+//
+// The *Explicit functions take an explicit value for errno.
+
// Helper to throw std::system_error
-void throwSystemError(int err, const char* msg) FOLLY_NORETURN;
-inline void throwSystemError(int err, const char* msg) {
+void throwSystemErrorExplicit(int err, const char*) FOLLY_NORETURN;
+inline void throwSystemErrorExplicit(int err, const char* msg) {
throw std::system_error(err, std::system_category(), msg);
}
-// Helper to throw std::system_error from errno
-void throwSystemError(const char* msg) FOLLY_NORETURN;
-inline void throwSystemError(const char* msg) {
- throwSystemError(errno, msg);
+template <class... Args>
+void throwSystemErrorExplicit(int, Args&&... args) FOLLY_NORETURN;
+template <class... Args>
+void throwSystemErrorExplicit(int err, Args&&... args) {
+ throwSystemErrorExplicit(
+ err, to<fbstring>(std::forward<Args>(args)...).c_str());
}
// Helper to throw std::system_error from errno and components of a string
template <class... Args>
-void throwSystemError(Args... args) FOLLY_NORETURN;
+void throwSystemError(Args&&... args) FOLLY_NORETURN;
template <class... Args>
-inline void throwSystemError(Args... args) {
- throwSystemError(errno, folly::to<std::string>(args...));
+void throwSystemError(Args&&... args) {
+ throwSystemErrorExplicit(errno, std::forward<Args>(args)...);
}
// Check a Posix return code (0 on success, error number on error), throw
// on error.
-inline void checkPosixError(int err, const char* msg) {
+template <class... Args>
+void checkPosixError(int err, Args&&... args) {
if (UNLIKELY(err != 0)) {
- throwSystemError(err, msg);
+ throwSystemErrorExplicit(err, std::forward<Args>(args)...);
}
}
// Check a Linux kernel-style return code (>= 0 on success, negative error
// number on error), throw on error.
-inline void checkKernelError(ssize_t ret, const char* msg) {
+template <class... Args>
+void checkKernelError(ssize_t ret, Args&&... args) {
if (UNLIKELY(ret < 0)) {
- throwSystemError(-ret, msg);
+ throwSystemErrorExplicit(-ret, std::forward<Args>(args)...);
}
}
// Check a traditional Unix return code (-1 and sets errno on error), throw
// on error.
-inline void checkUnixError(ssize_t ret, const char* msg) {
+template <class... Args>
+void checkUnixError(ssize_t ret, Args&&... args) {
if (UNLIKELY(ret == -1)) {
- throwSystemError(msg);
+ throwSystemError(std::forward<Args>(args)...);
}
}
-inline void checkUnixError(ssize_t ret, int savedErrno, const char* msg) {
+
+template <class... Args>
+void checkUnixErrorExplicit(ssize_t ret, int savedErrno, Args&&... args) {
if (UNLIKELY(ret == -1)) {
- throwSystemError(savedErrno, msg);
+ throwSystemErrorExplicit(savedErrno, std::forward<Args>(args)...);
+ }
+}
+
+// Check the return code from a fopen-style function (returns a non-nullptr
+// FILE* on success, nullptr on error, sets errno). Works with fopen, fdopen,
+// freopen, tmpfile, etc.
+template <class... Args>
+void checkFopenError(FILE* fp, Args&&... args) {
+ if (UNLIKELY(!fp)) {
+ throwSystemError(std::forward<Args>(args)...);
+ }
+}
+
+template <class... Args>
+void checkFopenErrorExplicit(FILE* fp, int savedErrno, Args&&... args) {
+ if (UNLIKELY(!fp)) {
+ throwSystemErrorExplicit(savedErrno, std::forward<Args>(args)...);
}
}
*/
#include "folly/File.h"
+
+#include <sys/file.h>
+#include <fcntl.h>
+#include <unistd.h>
+
#include "folly/Format.h"
+#include "folly/Exception.h"
#include "folly/ScopeGuard.h"
#include <system_error>
File::File(const char* name, int flags, mode_t mode)
: fd_(::open(name, flags, mode))
, ownsFd_(false) {
-
- if (fd_ < 0) {
- throw std::system_error(errno, std::system_category(),
- folly::format("open(\"{}\", {:#o}, 0{:#o}) failed",
- name, flags, mode).str());
+ if (fd_ == -1) {
+ throwSystemError(folly::format("open(\"{}\", {:#o}, 0{:#o}) failed",
+ name, flags, mode).fbstr());
}
ownsFd_ = true;
}
/* static */ File File::temporary() {
// make a temp file with tmpfile(), dup the fd, then return it in a File.
FILE* tmpFile = tmpfile();
- if (!tmpFile) {
- throw std::system_error(errno, std::system_category(), "tmpfile() failed");
- }
+ checkFopenError(tmpFile, "tmpfile() failed");
SCOPE_EXIT { fclose(tmpFile); };
int fd = dup(fileno(tmpFile));
- if (fd < 0) {
- throw std::system_error(errno, std::system_category(), "dup() failed");
- }
+ checkUnixError(fd, "dup() failed");
return File(fd, true);
}
void File::close() {
if (!closeNoThrow()) {
- throw std::system_error(errno, std::system_category(), "close() failed");
+ throwSystemError("close() failed");
}
}
return r == 0;
}
+void File::lock() { doLock(LOCK_EX); }
+bool File::try_lock() { return doTryLock(LOCK_EX); }
+void File::lock_shared() { doLock(LOCK_SH); }
+bool File::try_lock_shared() { return doTryLock(LOCK_SH); }
+
+void File::doLock(int op) {
+ checkUnixError(flock(fd_, op), "flock() failed (lock)");
+}
+
+bool File::doTryLock(int op) {
+ int r = flock(fd_, op | LOCK_NB);
+ // flock returns EWOULDBLOCK if already locked
+ if (r == -1 && errno == EWOULDBLOCK) return false;
+ checkUnixError(r, "flock() failed (try_lock)");
+ return true;
+}
+
+void File::unlock() {
+ checkUnixError(flock(fd_, LOCK_UN), "flock() failed (unlock)");
+}
+void File::unlock_shared() { unlock(); }
+
} // namespace folly
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
+#include <unistd.h>
namespace folly {
File(File&&);
File& operator=(File&&);
+ // FLOCK (INTERPROCESS) LOCKS
+ //
+ // NOTE THAT THESE LOCKS ARE flock() LOCKS. That is, they may only be used
+ // for inter-process synchronization -- an attempt to acquire a second lock
+ // on the same file descriptor from the same process may succeed. Attempting
+ // to acquire a second lock on a different file descriptor for the same file
+ // should fail, but some systems might implement flock() using fcntl() locks,
+ // in which case it will succeed.
+ void lock();
+ bool try_lock();
+ void unlock();
+
+ void lock_shared();
+ bool try_lock_shared();
+ void unlock_shared();
+
private:
+ void doLock(int op);
+ bool doTryLock(int op);
+
// unique
File(const File&) = delete;
File& operator=(const File&) = delete;
void swap(File& a, File& b);
+
} // namespace folly
#endif /* FOLLY_FILE_H_ */
--- /dev/null
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "folly/FileUtil.h"
+
+#include <cerrno>
+
+namespace folly {
+
+int closeNoInt(int fd) {
+ int r = close(fd);
+ // Ignore EINTR. On Linux, close() may only return EINTR after the file
+ // descriptor has been closed, so you must not retry close() on EINTR --
+ // in the best case, you'll get EBADF, and in the worst case, you'll end up
+ // closing a different file (one opened from another thread).
+ //
+ // Interestingly enough, the Single Unix Specification says that the state
+ // of the file descriptor is unspecified if close returns EINTR. In that
+ // case, the safe thing to do is also not to retry close() -- leaking a file
+ // descriptor is probably better than closing the wrong file.
+ if (r == -1 && errno == EINTR) {
+ r = 0;
+ }
+ return r;
+}
+
+namespace {
+
+// Wrap call to f(args) in loop to retry on EINTR
+template<typename F, typename... Args>
+ssize_t wrapNoInt(F f, Args... args) {
+ ssize_t r;
+ do {
+ r = f(args...);
+ } while (r == -1 && errno == EINTR);
+ return r;
+}
+
+} // namespace
+
+ssize_t readNoInt(int fd, void* buf, size_t count) {
+ return wrapNoInt(read, fd, buf, count);
+}
+
+ssize_t preadNoInt(int fd, void* buf, size_t count, off_t offset) {
+ return wrapNoInt(pread, fd, buf, count, offset);
+}
+
+ssize_t readvNoInt(int fd, const struct iovec* iov, int count) {
+ return wrapNoInt(writev, fd, iov, count);
+}
+
+ssize_t writeNoInt(int fd, const void* buf, size_t count) {
+ return wrapNoInt(write, fd, buf, count);
+}
+
+ssize_t pwriteNoInt(int fd, const void* buf, size_t count, off_t offset) {
+ return wrapNoInt(pwrite, fd, buf, count, offset);
+}
+
+ssize_t writevNoInt(int fd, const struct iovec* iov, int count) {
+ return wrapNoInt(writev, fd, iov, count);
+}
+
+ssize_t readFull(int fd, void* buf, size_t count) {
+ char* b = static_cast<char*>(buf);
+ ssize_t totalBytes = 0;
+ ssize_t r;
+ do {
+ r = read(fd, b, count);
+ if (r == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return r;
+ }
+
+ totalBytes += r;
+ b += r;
+ count -= r;
+ } while (r != 0 && count); // 0 means EOF
+
+ return totalBytes;
+}
+
+ssize_t preadFull(int fd, void* buf, size_t count, off_t offset) {
+ char* b = static_cast<char*>(buf);
+ ssize_t totalBytes = 0;
+ ssize_t r;
+ do {
+ r = pread(fd, b, count, offset);
+ if (r == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return r;
+ }
+
+ totalBytes += r;
+ b += r;
+ offset += r;
+ count -= r;
+ } while (r != 0 && count); // 0 means EOF
+
+ return totalBytes;
+}
+
+ssize_t writeFull(int fd, const void* buf, size_t count) {
+ const char* b = static_cast<const char*>(buf);
+ ssize_t totalBytes = 0;
+ ssize_t r;
+ do {
+ r = write(fd, b, count);
+ if (r == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return r;
+ }
+
+ totalBytes += r;
+ b += r;
+ count -= r;
+ } while (r != 0 && count); // 0 means EOF
+
+ return totalBytes;
+}
+
+ssize_t pwriteFull(int fd, const void* buf, size_t count, off_t offset) {
+ const char* b = static_cast<const char*>(buf);
+ ssize_t totalBytes = 0;
+ ssize_t r;
+ do {
+ r = pwrite(fd, b, count, offset);
+ if (r == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return r;
+ }
+
+ totalBytes += r;
+ b += r;
+ offset += r;
+ count -= r;
+ } while (r != 0 && count); // 0 means EOF
+
+ return totalBytes;
+}
+
+} // namespaces
+
--- /dev/null
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_FILEUTIL_H_
+#define FOLLY_FILEUTIL_H_
+
+#include <sys/uio.h>
+#include <unistd.h>
+
+namespace folly {
+
+/**
+ * Convenience wrappers around some commonly used system calls. The *NoInt
+ * wrappers retry on EINTR. The *Full wrappers retry on EINTR and also loop
+ * until all data is written. Note that *Full wrappers weaken the thread
+ * semantics of underlying system calls.
+ */
+int closeNoInt(int fd);
+
+ssize_t readNoInt(int fd, void* buf, size_t n);
+ssize_t preadNoInt(int fd, void* buf, size_t n, off_t offset);
+ssize_t readvNoInt(int fd, const struct iovec* iov, int count);
+
+ssize_t writeNoInt(int fd, const void* buf, size_t n);
+ssize_t pwriteNoInt(int fd, const void* buf, size_t n, off_t offset);
+ssize_t writevNoInt(int fd, const struct iovec* iov, int count);
+
+/**
+ * Wrapper around read() (and pread()) that, in addition to retrying on
+ * EINTR, will loop until all data is read.
+ *
+ * This wrapper is only useful for blocking file descriptors (for non-blocking
+ * file descriptors, you have to be prepared to deal with incomplete reads
+ * anyway), and only exists because POSIX allows read() to return an incomplete
+ * read if interrupted by a signal (instead of returning -1 and setting errno
+ * to EINTR).
+ *
+ * Note that this wrapper weakens the thread safety of read(): the file pointer
+ * is shared between threads, but the system call is atomic. If multiple
+ * threads are reading from a file at the same time, you don't know where your
+ * data came from in the file, but you do know that the returned bytes were
+ * contiguous. You can no longer make this assumption if using readFull().
+ * You should probably use pread() when reading from the same file descriptor
+ * from multiple threads simultaneously, anyway.
+ */
+ssize_t readFull(int fd, void* buf, size_t n);
+ssize_t preadFull(int fd, void* buf, size_t n, off_t offset);
+// TODO(tudorb): add readvFull if needed
+
+/**
+ * Similar to readFull and preadFull above, wrappers around write() and
+ * pwrite() that loop until all data is written.
+ *
+ * Generally, the write() / pwrite() system call may always write fewer bytes
+ * than requested, just like read(). In certain cases (such as when writing to
+ * a pipe), POSIX provides stronger guarantees, but not in the general case.
+ * For example, Linux (even on a 64-bit platform) won't write more than 2GB in
+ * one write() system call.
+ */
+ssize_t writeFull(int fd, const void* buf, size_t n);
+ssize_t pwriteFull(int fd, const void* buf, size_t n, off_t offset);
+// TODO(tudorb): add writevFull if needed
+
+} // namespaces
+
+#endif /* FOLLY_FILEUTIL_H_ */
+
--- /dev/null
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "folly/Exception.h"
+
+#include <cstdio>
+#include <memory>
+
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+namespace folly { namespace test {
+
+#define EXPECT_SYSTEM_ERROR(statement, err, msg) \
+ try { \
+ statement; \
+ ADD_FAILURE() << "Didn't throw"; \
+ } catch (const std::system_error& e) { \
+ std::system_error expected(err, std::system_category(), msg); \
+ EXPECT_STREQ(expected.what(), e.what()); \
+ } catch (...) { \
+ ADD_FAILURE() << "Threw a different type"; \
+ }
+
+
+TEST(ExceptionTest, Simple) {
+ // Make sure errno isn't used when we don't want it to, set it to something
+ // else than what we set when we call Explicit functions
+ errno = ERANGE;
+ EXPECT_SYSTEM_ERROR({throwSystemErrorExplicit(EIO, "hello");},
+ EIO, "hello");
+ errno = ERANGE;
+ EXPECT_SYSTEM_ERROR({throwSystemErrorExplicit(EIO, "hello", " world");},
+ EIO, "hello world");
+ errno = ERANGE;
+ EXPECT_SYSTEM_ERROR({throwSystemError("hello", " world");},
+ ERANGE, "hello world");
+
+ EXPECT_NO_THROW({checkPosixError(0, "hello", " world");});
+ errno = ERANGE;
+ EXPECT_SYSTEM_ERROR({checkPosixError(EIO, "hello", " world");},
+ EIO, "hello world");
+
+ EXPECT_NO_THROW({checkKernelError(0, "hello", " world");});
+ EXPECT_NO_THROW({checkKernelError(EIO, "hello", " world");});
+ errno = ERANGE;
+ EXPECT_SYSTEM_ERROR({checkKernelError(-EIO, "hello", " world");},
+ EIO, "hello world");
+
+ EXPECT_NO_THROW({checkUnixError(0, "hello", " world");});
+ EXPECT_NO_THROW({checkUnixError(1, "hello", " world");});
+ errno = ERANGE;
+ EXPECT_SYSTEM_ERROR({checkUnixError(-1, "hello", " world");},
+ ERANGE, "hello world");
+
+ EXPECT_NO_THROW({checkUnixErrorExplicit(0, EIO, "hello", " world");});
+ EXPECT_NO_THROW({checkUnixErrorExplicit(1, EIO, "hello", " world");});
+ errno = ERANGE;
+ EXPECT_SYSTEM_ERROR({checkUnixErrorExplicit(-1, EIO, "hello", " world");},
+ EIO, "hello world");
+
+ std::shared_ptr<FILE> fp(tmpfile(), fclose);
+ ASSERT_TRUE(fp != nullptr);
+
+ EXPECT_NO_THROW({checkFopenError(fp.get(), "hello", " world");});
+ errno = ERANGE;
+ EXPECT_SYSTEM_ERROR({checkFopenError(nullptr, "hello", " world");},
+ ERANGE, "hello world");
+
+ EXPECT_NO_THROW({checkFopenErrorExplicit(fp.get(), EIO, "hello", " world");});
+ errno = ERANGE;
+ EXPECT_SYSTEM_ERROR({checkFopenErrorExplicit(nullptr, EIO,
+ "hello", " world");},
+ EIO, "hello world");
+}
+
+}} // namespaces
+
+int main(int argc, char *argv[]) {
+ testing::InitGoogleTest(&argc, argv);
+ google::ParseCommandLineFlags(&argc, &argv, true);
+ return RUN_ALL_TESTS();
+}
+
#include "folly/File.h"
+#include <mutex>
+
+#include <boost/thread/locks.hpp>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "folly/Benchmark.h"
#include "folly/String.h"
+#include "folly/Subprocess.h"
+#include "folly/experimental/io/FsUtil.h"
+#include "folly/experimental/TestUtil.h"
using namespace folly;
+using namespace folly::test;
namespace {
void expectWouldBlock(ssize_t r) {
EXPECT_TRUE(false);
}
}
+
+TEST(File, Locks) {
+ typedef std::unique_lock<File> Lock;
+ typedef boost::shared_lock<File> SharedLock;
+
+ // Find out where we are.
+ static constexpr size_t pathLength = 2048;
+ char buf[pathLength + 1];
+ int r = readlink("/proc/self/exe", buf, pathLength);
+ CHECK_ERR(r);
+ buf[r] = '\0';
+
+ fs::path helper(buf);
+ helper.remove_filename();
+ helper /= "file_test_lock_helper";
+
+ TemporaryFile tempFile;
+ File f(tempFile.fd());
+
+ enum LockMode { EXCLUSIVE, SHARED };
+ auto testLock = [&] (LockMode mode, bool expectedSuccess) {
+ auto ret =
+ Subprocess({helper.native(),
+ mode == SHARED ? "-s" : "-x",
+ tempFile.path().native()}).wait();
+ EXPECT_TRUE(ret.exited());
+ if (ret.exited()) {
+ EXPECT_EQ(expectedSuccess ? 0 : 42, ret.exitStatus());
+ }
+ };
+
+ // Make sure nothing breaks and things compile.
+ {
+ Lock lock(f);
+ }
+
+ {
+ SharedLock lock(f);
+ }
+
+ {
+ Lock lock(f, std::defer_lock);
+ EXPECT_TRUE(lock.try_lock());
+ }
+
+ {
+ SharedLock lock(f, boost::defer_lock);
+ EXPECT_TRUE(lock.try_lock());
+ }
+
+ // X blocks X
+ {
+ Lock lock(f);
+ testLock(EXCLUSIVE, false);
+ }
+
+ // X blocks S
+ {
+ Lock lock(f);
+ testLock(SHARED, false);
+ }
+
+ // S blocks X
+ {
+ SharedLock lock(f);
+ testLock(EXCLUSIVE, false);
+ }
+
+ // S does not block S
+ {
+ SharedLock lock(f);
+ testLock(SHARED, true);
+ }
+}
+
--- /dev/null
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+
+#include "folly/File.h"
+
+DEFINE_bool(s, false, "get shared lock");
+DEFINE_bool(x, false, "get exclusive lock");
+
+int main(int argc, char *argv[]) {
+ google::ParseCommandLineFlags(&argc, &argv, true);
+ google::InitGoogleLogging(argv[0]);
+ CHECK_EQ(FLAGS_s + FLAGS_x, 1)
+ << "exactly one of -s and -x must be specified";
+ CHECK_EQ(argc, 2);
+ folly::File f(argv[1], O_RDWR);
+ bool r;
+ if (FLAGS_s) {
+ r = f.try_lock_shared();
+ } else {
+ r = f.try_lock();
+ }
+ return r ? 0 : 42;
+}
TEST(ParentDeathSubprocessTest, ParentDeathSignal) {
// Find out where we are.
static constexpr size_t pathLength = 2048;
- char buf[pathLength];
+ char buf[pathLength + 1];
int r = readlink("/proc/self/exe", buf, pathLength);
CHECK_ERR(r);
buf[r] = '\0';