blob: 7a465e6fbdce98aedb23dd8bc16b1e9c63073561 [file] [log] [blame]
// Copyright 2011 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lib/scoped_fd.h"
#include "absl/strings/string_view.h"
#include "base/compiler_specific.h"
#include "glog/logging.h"
#include "lib/fileflag.h"
#ifdef _WIN32
#include "base/socket_helper_win.h"
#include "lib/path_resolver.h"
#endif
#ifndef _WIN32
# include <errno.h>
# include <fcntl.h>
# include <poll.h>
# include <sys/socket.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
#endif
namespace devtools_goma {
#ifndef _WIN32
static ScopedFd::FileDescriptor kInvalidFd = -1;
#else
static ScopedFd::FileDescriptor kInvalidFd = INVALID_HANDLE_VALUE;
#endif
ScopedFd::ScopedFd()
: fd_(kInvalidFd) {
}
ScopedFd::ScopedFd(FileDescriptor fd)
: fd_(fd) {
if (valid())
SetCloseOnExec();
}
ScopedFd::~ScopedFd() {
Close();
}
/* static */
ScopedFd::FileDescriptor ScopedFd::OpenForRead(const string& filename) {
#ifndef _WIN32
return open(filename.c_str(), O_RDONLY);
#else
// On Windows, the length of path is 256. When compiling NaCl untrusted code,
// the length of path often exceeds 256. Usually it contains '..', so let's
// clean it.
const string& resolved = PathResolver::ResolvePath(filename);
return CreateFileA(resolved.c_str(), GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
#endif
}
/* static */
ScopedFd::FileDescriptor ScopedFd::OpenForAppend(
const string& filename, int mode) {
#ifndef _WIN32
return open(filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, mode);
#else
UNREFERENCED_PARAMETER(mode);
// TODO: translate mode to file attribute.
const string& resolved = PathResolver::ResolvePath(filename);
HANDLE h = CreateFileA(resolved.c_str(),
FILE_APPEND_DATA,
FILE_SHARE_WRITE,
nullptr,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (h == INVALID_HANDLE_VALUE) {
LOG_SYSRESULT(GetLastError());
LOG(ERROR) << "OpenForAppend failed: filename=" << filename;
}
return h;
#endif
}
/* static */
ScopedFd::FileDescriptor ScopedFd::OpenForRewrite(const string& filename) {
#ifndef _WIN32
return open(filename.c_str(), O_RDWR);
#else
const string& resolved = PathResolver::ResolvePath(filename);
HANDLE h = CreateFileA(resolved.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (h == INVALID_HANDLE_VALUE) {
LOG_SYSRESULT(GetLastError());
LOG(ERROR) << "OpenForRewrite failed: filename=" << filename;
}
return h;
#endif
}
ScopedFd::FileDescriptor ScopedFd::Create(
const string& filename, int mode) {
#ifndef _WIN32
return open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
#else
UNREFERENCED_PARAMETER(mode);
// TODO: translate mode to file attribute.
const string& resolved = PathResolver::ResolvePath(filename);
HANDLE h = CreateFileA(resolved.c_str(),
GENERIC_WRITE,
FILE_SHARE_WRITE,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (h == INVALID_HANDLE_VALUE) {
LOG_SYSRESULT(GetLastError());
LOG(ERROR) << "Create failed: filename=" << filename;
}
return h;
#endif
}
ScopedFd::FileDescriptor ScopedFd::CreateExclusive(
const string& filename, int mode) {
#ifndef _WIN32
return open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode);
#else
UNREFERENCED_PARAMETER(mode);
// TODO: translate mode to file attribute.
// If the file exists, CreateFile with dwCreationDisposition == CREATE_NEW
// will fail.
// See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
const string& resolved = PathResolver::ResolvePath(filename);
HANDLE h = CreateFileA(resolved.c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (h == INVALID_HANDLE_VALUE) {
LOG_SYSRESULT(GetLastError());
LOG(ERROR) << "CreateExclusive failed: filename=" << filename;
}
return h;
#endif
}
ScopedFd::FileDescriptor ScopedFd::OpenNull() {
#ifndef _WIN32
return open("/dev/null", O_RDWR, 0600);
#else
// To allow child process to continue using NUL, bInheritHandle should be set.
SECURITY_ATTRIBUTES secattr;
secattr.nLength = sizeof(secattr);
secattr.lpSecurityDescriptor = nullptr;
secattr.bInheritHandle = TRUE;
// NUL is something like Unix /dev/null on Windows.
// http://stackoverflow.com/questions/438092/how-to-open-a-nul-file
// http://blogs.msdn.com/b/oldnewthing/archive/2003/10/22/55388.aspx
return CreateFileA("NUL", GENERIC_WRITE, 0, &secattr, OPEN_EXISTING, 0,
nullptr);
#endif
}
bool ScopedFd::valid() const {
#ifndef _WIN32
return fd_ >= 0;
#else
if (fd_ == nullptr || fd_ == kInvalidFd)
return false;
return true;
#endif
}
void ScopedFd::SetCloseOnExec() const {
#ifndef _WIN32
SetFileDescriptorFlag(fd_, FD_CLOEXEC);
#endif
}
ssize_t ScopedFd::Read(void* ptr, size_t len) const {
#ifndef _WIN32
ssize_t r = 0;
while ((r = read(fd_, ptr, len)) < 0) {
if (errno != EINTR) break;
}
return r;
#else
DWORD bytes_read = 0;
if (!ReadFile(fd_, ptr, len, &bytes_read, nullptr)) {
LOG_SYSRESULT(GetLastError());
return -1;
}
return bytes_read;
#endif
}
ssize_t ScopedFd::Write(const void* ptr, size_t len) const {
#ifndef _WIN32
ssize_t r = 0;
while ((r = write(fd_, ptr, len)) < 0) {
if (errno != EINTR) break;
}
return r;
#else
DWORD bytes_written = 0;
if (!WriteFile(fd_, ptr, len, &bytes_written, nullptr)) {
LOG_SYSRESULT(GetLastError());
return -1;
}
return bytes_written;
#endif
}
off_t ScopedFd::Seek(off_t offset, Whence whence) const {
#ifndef _WIN32
return lseek(fd_, offset, whence);
#else
// TODO: use lpDistanceToMoveHigh for high order 32bits of 64bits?
DWORD r = SetFilePointer(fd_, offset, nullptr, whence);
if (r == INVALID_SET_FILE_POINTER) {
DWORD err = GetLastError();
if (err != NO_ERROR) {
LOG_SYSRESULT(err);
return static_cast<off_t>(-1);
}
// maybe, seek success.
}
return r;
#endif
}
bool ScopedFd::GetFileSize(size_t* file_size) const {
*file_size = 0;
#ifndef _WIN32
struct stat st;
if (fstat(fd_, &st) != 0)
return false;
*file_size = st.st_size;
return true;
#else
DWORD size = ::GetFileSize(fd_, nullptr);
if (size == INVALID_FILE_SIZE) {
LOG_SYSRESULT(GetLastError());
return false;
}
*file_size = size;
return true;
#endif
}
void ScopedFd::reset(ScopedFd::FileDescriptor fd) {
Close();
fd_ = fd;
#ifndef _WIN32
if (fd >= 0) {
SetCloseOnExec();
}
#endif
}
ScopedFd::FileDescriptor ScopedFd::release() {
FileDescriptor fd = fd_;
fd_ = kInvalidFd;
return fd;
}
bool ScopedFd::Close() {
if (valid()) {
#ifndef _WIN32
return close(release()) == 0;
#else
return CloseHandle(release()) == TRUE;
#endif
}
return true;
}
ScopedSocket::~ScopedSocket() {
Close();
}
bool ScopedSocket::SetCloseOnExec() const {
#ifndef _WIN32
return SetFileDescriptorFlag(fd_, FD_CLOEXEC) == 0;
#else
return true;
#endif
}
bool ScopedSocket::SetNonBlocking() const {
#ifndef _WIN32
return SetFileStatusFlag(fd_, O_NONBLOCK) == 0;
#else
unsigned long non_blocking = 1;
return ioctlsocket(fd_, FIONBIO, &non_blocking) != SOCKET_ERROR;
#endif
}
bool ScopedSocket::SetReuseAddr() const {
int yes = 1;
#ifndef _WIN32
return setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == 0;
#else
return setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR,
(const char*)&yes, sizeof(yes)) == 0;
#endif
}
void ScopedSocket::reset(int fd) {
Close();
fd_ = fd;
}
ssize_t ScopedSocket::Read(void* ptr, size_t len) const {
#ifndef _WIN32
return read(fd_, ptr, len);
#else
return recv(fd_, (char*)ptr, len, 0);
#endif
}
ssize_t ScopedSocket::Write(const void* ptr, size_t len) const {
#ifndef _WIN32
return write(fd_, ptr, len);
#else
return send(fd_, (char*)ptr, len, 0);
#endif
}
bool ScopedSocket::Close() {
if (valid()) {
#ifndef _WIN32
return close(release()) == 0;
#else
return closesocket(release()) == 0;
#endif
}
return true;
}
// Read. Return < 0 on error.
ssize_t ScopedSocket::ReadWithTimeout(char* buf,
size_t bufsize,
absl::Duration timeout) const {
CHECK(buf);
CHECK(valid());
for (;;) {
#ifdef _WIN32
// Since WSAPoll (Windows poll API) is broken, we should use select on Win.
// See: http://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
fd_set fdset;
FD_ZERO(&fdset);
MSVC_PUSH_DISABLE_WARNING_FOR_FD_SET();
FD_SET(static_cast<SOCKET>(fd_), &fdset);
MSVC_POP_WARNING();
TIMEVAL timeout_tv = absl::ToTimeval(timeout);
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms740141(v=vs.85).aspx
int result = select(fd_ + 1, &fdset, nullptr, nullptr, &timeout_tv);
if (result == SOCKET_ERROR) {
PLOG(ERROR) << "GOMA: read select error";
return FAIL;
}
if (result == 0) {
LOG(WARNING) << "GOMA: read select timeout " << timeout;
return ERR_TIMEOUT;
}
CHECK(FD_ISSET(fd_, &fdset))
<< "GOMA: read select returned but read not ready."
<< " fd_=" << fd_;
#else
struct pollfd pfd;
pfd.fd = fd_;
pfd.events = POLLIN;
const int timeout_ms = static_cast<int>(absl::ToInt64Milliseconds(timeout));
int result;
while ((result = poll(&pfd, 1, timeout_ms)) == -1) {
if (errno != EINTR)
break;
}
if (result == -1) {
PLOG(ERROR) << "GOMA: read poll error";
return FAIL;
}
if (result == 0) {
LOG(WARNING) << "GOMA: read poll timeout " << timeout;
return ERR_TIMEOUT;
}
CHECK(pfd.revents & POLLIN)
<< "GOMA: read poll returned but read not ready."
<< " fd_=" << fd_;
#endif
ssize_t ret = Read(buf, bufsize);
if (ret == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
PLOG(ERROR) << "read";
}
return ret;
}
}
ssize_t ScopedSocket::WriteWithTimeout(const char* buf,
size_t bufsize,
absl::Duration timeout) const {
CHECK(buf);
CHECK(valid());
for (;;) {
#ifdef _WIN32
// Since WSAPoll (Windows poll API) is broken, we should use select on Win.
// See: http://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
fd_set fdset;
FD_ZERO(&fdset);
MSVC_PUSH_DISABLE_WARNING_FOR_FD_SET();
FD_SET(fd_, &fdset);
MSVC_POP_WARNING();
TIMEVAL timeout_tv = absl::ToTimeval(timeout);
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms740141(v=vs.85).aspx
int result = select(fd_ + 1, nullptr, &fdset, nullptr, &timeout_tv);
if (result == SOCKET_ERROR) {
PLOG(ERROR) << "GOMA: write select error";
return FAIL;
}
if (result == 0) {
LOG(ERROR) << "GOMA: write select timeout " << timeout;
return ERR_TIMEOUT;
}
CHECK(FD_ISSET(fd_, &fdset))
<< "GOMA: write select returned but write not ready."
<< " fd_=" << fd_;
#else
struct pollfd pfd;
pfd.fd = fd_;
pfd.events = POLLOUT;
const int timeout_ms = static_cast<int>(absl::ToInt64Milliseconds(timeout));
int result;
while ((result = poll(&pfd, 1, timeout_ms)) == -1) {
if (errno != EINTR)
break;
}
if (result == -1) {
PLOG(ERROR) << "GOMA: write poll error";
return FAIL;
}
if (result == 0) {
LOG(ERROR) << "GOMA: write poll timeout" << timeout;
return ERR_TIMEOUT;
}
CHECK(pfd.revents & POLLOUT)
<< "GOMA: write poll returned > 0 but write not ready."
<< " fd_=" << fd_;
#endif
ssize_t ret = Write(buf, bufsize);
if (ret == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
PLOG(ERROR) << "write";
}
return ret;
}
}
// Write string to socket. Return negative (Errno) on fail, OK on success.
int ScopedSocket::WriteString(absl::string_view message,
absl::Duration timeout) const {
const char *p = message.data();
int size = message.size();
while (size > 0) {
int ret = WriteWithTimeout(p, size, timeout);
if (ret < 0) {
PLOG(ERROR) << "write failure: " << ret
<< " written=" << (message.size() - size) << " size=" << size
<< " out of " << message.size();
return ret;
}
p += ret;
size -= ret;
}
return OK;
}
string ScopedSocket::GetLastErrorMessage() const {
char message[1024];
#ifndef _WIN32
// Meaning of returned value of strerror_r is different between
// XSI and GNU. Need to ignore.
(void)strerror_r(errno, message, sizeof(message));
#else
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
WSAGetLastError(), 0,
message, sizeof(message), nullptr);
#endif
return message;
}
} // namespace devtools_goma