2014-10-09 04:38:41 +08:00
|
|
|
//===-- PipePosix.cpp -------------------------------------------*- C++ -*-===//
|
2014-07-03 05:10:39 +08:00
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2014-10-09 04:38:41 +08:00
|
|
|
#include "lldb/Host/posix/PipePosix.h"
|
2015-01-14 07:19:40 +08:00
|
|
|
#include "lldb/Host/FileSystem.h"
|
2015-02-06 00:29:12 +08:00
|
|
|
#include "lldb/Host/HostInfo.h"
|
|
|
|
#include "llvm/ADT/SmallString.h"
|
|
|
|
#include "llvm/Support/FileSystem.h"
|
2015-01-14 07:19:40 +08:00
|
|
|
|
2015-08-10 03:04:41 +08:00
|
|
|
#if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8))
|
2015-09-09 09:17:24 +08:00
|
|
|
#ifndef _GLIBCXX_USE_NANOSLEEP
|
2015-08-10 03:04:41 +08:00
|
|
|
#define _GLIBCXX_USE_NANOSLEEP
|
|
|
|
#endif
|
2015-09-09 09:17:24 +08:00
|
|
|
#endif
|
2015-08-10 03:04:41 +08:00
|
|
|
|
2015-01-14 07:19:40 +08:00
|
|
|
#include <functional>
|
|
|
|
#include <thread>
|
2014-07-03 05:10:39 +08:00
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
#include <errno.h>
|
2014-11-22 00:18:57 +08:00
|
|
|
#include <fcntl.h>
|
2015-02-06 00:29:12 +08:00
|
|
|
#include <limits.h>
|
2014-12-18 02:02:19 +08:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/types.h>
|
2015-01-14 07:19:40 +08:00
|
|
|
#include <sys/stat.h>
|
2014-07-03 05:10:39 +08:00
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
using namespace lldb;
|
2014-07-03 05:10:39 +08:00
|
|
|
using namespace lldb_private;
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
int PipePosix::kInvalidDescriptor = -1;
|
2014-07-03 05:10:39 +08:00
|
|
|
|
|
|
|
enum PIPES { READ, WRITE }; // Constants 0 and 1 for READ and WRITE
|
|
|
|
|
2015-09-14 23:12:49 +08:00
|
|
|
// pipe2 is supported by a limited set of platforms
|
2014-11-22 00:18:57 +08:00
|
|
|
// TODO: Add more platforms that support pipe2.
|
2015-09-14 23:12:49 +08:00
|
|
|
#if defined(__linux__) || (defined(__FreeBSD__) && __FreeBSD__ >= 10) || defined(__NetBSD__)
|
2014-12-18 02:02:19 +08:00
|
|
|
#define PIPE2_SUPPORTED 1
|
|
|
|
#else
|
|
|
|
#define PIPE2_SUPPORTED 0
|
|
|
|
#endif
|
2014-11-22 00:18:57 +08:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
2015-01-14 07:19:40 +08:00
|
|
|
constexpr auto OPEN_WRITER_SLEEP_TIMEOUT_MSECS = 100;
|
|
|
|
|
2014-11-22 00:18:57 +08:00
|
|
|
#if defined(FD_CLOEXEC) && !PIPE2_SUPPORTED
|
|
|
|
bool SetCloexecFlag(int fd)
|
|
|
|
{
|
|
|
|
int flags = ::fcntl(fd, F_GETFD);
|
|
|
|
if (flags == -1)
|
|
|
|
return false;
|
|
|
|
return (::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-01-14 07:19:40 +08:00
|
|
|
std::chrono::time_point<std::chrono::steady_clock>
|
|
|
|
Now()
|
|
|
|
{
|
|
|
|
return std::chrono::steady_clock::now();
|
|
|
|
}
|
|
|
|
|
|
|
|
Error
|
|
|
|
SelectIO(int handle, bool is_read, const std::function<Error(bool&)> &io_handler, const std::chrono::microseconds &timeout)
|
|
|
|
{
|
|
|
|
Error error;
|
|
|
|
fd_set fds;
|
|
|
|
bool done = false;
|
|
|
|
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
|
|
|
const auto finish_time = Now() + timeout;
|
|
|
|
|
|
|
|
while (!done)
|
|
|
|
{
|
|
|
|
struct timeval tv = {0, 0};
|
2015-07-21 19:04:52 +08:00
|
|
|
if (timeout != microseconds::zero())
|
2015-01-14 07:19:40 +08:00
|
|
|
{
|
|
|
|
const auto remaining_dur = duration_cast<microseconds>(finish_time - Now());
|
|
|
|
if (remaining_dur.count() <= 0)
|
|
|
|
{
|
|
|
|
error.SetErrorString("timeout exceeded");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
const auto dur_secs = duration_cast<seconds>(remaining_dur);
|
|
|
|
const auto dur_usecs = remaining_dur % seconds(1);
|
|
|
|
|
|
|
|
tv.tv_sec = dur_secs.count();
|
|
|
|
tv.tv_usec = dur_usecs.count();
|
|
|
|
}
|
|
|
|
else
|
2015-07-21 19:04:52 +08:00
|
|
|
tv.tv_sec = 1;
|
2015-01-14 07:19:40 +08:00
|
|
|
|
|
|
|
FD_ZERO(&fds);
|
|
|
|
FD_SET(handle, &fds);
|
|
|
|
|
|
|
|
const auto retval = ::select(handle + 1,
|
|
|
|
(is_read) ? &fds : nullptr,
|
|
|
|
(is_read) ? nullptr : &fds,
|
2015-07-21 19:04:52 +08:00
|
|
|
nullptr, &tv);
|
2015-01-14 07:19:40 +08:00
|
|
|
if (retval == -1)
|
|
|
|
{
|
|
|
|
if (errno == EINTR)
|
|
|
|
continue;
|
|
|
|
error.SetErrorToErrno();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (retval == 0)
|
|
|
|
{
|
|
|
|
error.SetErrorString("timeout exceeded");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!FD_ISSET(handle, &fds))
|
|
|
|
{
|
|
|
|
error.SetErrorString("invalid state");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
error = io_handler(done);
|
|
|
|
if (error.Fail())
|
|
|
|
{
|
|
|
|
if (error.GetError() == EINTR)
|
|
|
|
continue;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2014-11-22 00:18:57 +08:00
|
|
|
}
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
PipePosix::PipePosix()
|
2015-05-02 00:49:23 +08:00
|
|
|
: m_fds{
|
|
|
|
PipePosix::kInvalidDescriptor,
|
|
|
|
PipePosix::kInvalidDescriptor
|
|
|
|
} {}
|
2015-04-30 01:36:58 +08:00
|
|
|
|
|
|
|
PipePosix::PipePosix(int read_fd, int write_fd)
|
|
|
|
: m_fds{read_fd, write_fd} {}
|
2014-07-03 05:10:39 +08:00
|
|
|
|
2015-05-02 00:49:23 +08:00
|
|
|
PipePosix::PipePosix(PipePosix &&pipe_posix)
|
|
|
|
: PipeBase{std::move(pipe_posix)},
|
|
|
|
m_fds{
|
|
|
|
pipe_posix.ReleaseReadFileDescriptor(),
|
|
|
|
pipe_posix.ReleaseWriteFileDescriptor()
|
|
|
|
} {}
|
|
|
|
|
|
|
|
PipePosix &PipePosix::operator=(PipePosix &&pipe_posix)
|
|
|
|
{
|
|
|
|
PipeBase::operator=(std::move(pipe_posix));
|
|
|
|
m_fds[READ] = pipe_posix.ReleaseReadFileDescriptor();
|
|
|
|
m_fds[WRITE] = pipe_posix.ReleaseWriteFileDescriptor();
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
PipePosix::~PipePosix()
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
|
|
|
Close();
|
|
|
|
}
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
Error
|
|
|
|
PipePosix::CreateNew(bool child_processes_inherit)
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
2014-12-18 02:02:19 +08:00
|
|
|
if (CanRead() || CanWrite())
|
2015-01-14 07:19:40 +08:00
|
|
|
return Error(EINVAL, eErrorTypePOSIX);
|
2014-07-03 05:10:39 +08:00
|
|
|
|
2015-01-14 07:19:40 +08:00
|
|
|
Error error;
|
2014-11-22 00:18:57 +08:00
|
|
|
#if PIPE2_SUPPORTED
|
|
|
|
if (::pipe2(m_fds, (child_processes_inherit) ? 0 : O_CLOEXEC) == 0)
|
2014-12-18 02:02:19 +08:00
|
|
|
return error;
|
2014-11-22 00:18:57 +08:00
|
|
|
#else
|
2014-07-03 05:10:39 +08:00
|
|
|
if (::pipe(m_fds) == 0)
|
2014-11-22 00:18:57 +08:00
|
|
|
{
|
|
|
|
#ifdef FD_CLOEXEC
|
|
|
|
if (!child_processes_inherit)
|
|
|
|
{
|
|
|
|
if (!SetCloexecFlag(m_fds[0]) || !SetCloexecFlag(m_fds[1]))
|
|
|
|
{
|
2014-12-18 02:02:19 +08:00
|
|
|
error.SetErrorToErrno();
|
2014-11-22 00:18:57 +08:00
|
|
|
Close();
|
2014-12-18 02:02:19 +08:00
|
|
|
return error;
|
2014-11-22 00:18:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2014-12-18 02:02:19 +08:00
|
|
|
return error;
|
2014-11-22 00:18:57 +08:00
|
|
|
}
|
|
|
|
#endif
|
2014-10-09 04:38:41 +08:00
|
|
|
|
2015-01-14 07:19:40 +08:00
|
|
|
error.SetErrorToErrno();
|
2014-12-18 02:02:19 +08:00
|
|
|
m_fds[READ] = PipePosix::kInvalidDescriptor;
|
|
|
|
m_fds[WRITE] = PipePosix::kInvalidDescriptor;
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
Error
|
|
|
|
PipePosix::CreateNew(llvm::StringRef name, bool child_process_inherit)
|
|
|
|
{
|
|
|
|
if (CanRead() || CanWrite())
|
2015-01-14 07:19:40 +08:00
|
|
|
return Error("Pipe is already opened");
|
|
|
|
|
|
|
|
Error error;
|
|
|
|
if (::mkfifo(name.data(), 0660) != 0)
|
|
|
|
error.SetErrorToErrno();
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2015-02-06 00:29:12 +08:00
|
|
|
Error
|
|
|
|
PipePosix::CreateWithUniqueName(llvm::StringRef prefix, bool child_process_inherit, llvm::SmallVectorImpl<char>& name)
|
|
|
|
{
|
|
|
|
llvm::SmallString<PATH_MAX> named_pipe_path;
|
|
|
|
llvm::SmallString<PATH_MAX> pipe_spec((prefix + ".%%%%%%").str());
|
|
|
|
FileSpec tmpdir_file_spec;
|
|
|
|
tmpdir_file_spec.Clear();
|
|
|
|
if (HostInfo::GetLLDBPath(ePathTypeLLDBTempSystemDir, tmpdir_file_spec))
|
|
|
|
{
|
|
|
|
tmpdir_file_spec.AppendPathComponent(pipe_spec.c_str());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tmpdir_file_spec.AppendPathComponent("/tmp");
|
|
|
|
tmpdir_file_spec.AppendPathComponent(pipe_spec.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
// It's possible that another process creates the target path after we've
|
|
|
|
// verified it's available but before we create it, in which case we
|
|
|
|
// should try again.
|
|
|
|
Error error;
|
|
|
|
do {
|
|
|
|
llvm::sys::fs::createUniqueFile(tmpdir_file_spec.GetPath().c_str(), named_pipe_path);
|
|
|
|
error = CreateNew(named_pipe_path, child_process_inherit);
|
|
|
|
} while (error.GetError() == EEXIST);
|
|
|
|
|
|
|
|
if (error.Success())
|
|
|
|
name = named_pipe_path;
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
Error
|
2015-01-14 09:31:27 +08:00
|
|
|
PipePosix::OpenAsReader(llvm::StringRef name, bool child_process_inherit)
|
2014-12-18 02:02:19 +08:00
|
|
|
{
|
|
|
|
if (CanRead() || CanWrite())
|
2015-01-14 07:19:40 +08:00
|
|
|
return Error("Pipe is already opened");
|
|
|
|
|
|
|
|
int flags = O_RDONLY | O_NONBLOCK;
|
|
|
|
if (!child_process_inherit)
|
|
|
|
flags |= O_CLOEXEC;
|
|
|
|
|
|
|
|
Error error;
|
|
|
|
int fd = ::open(name.data(), flags);
|
|
|
|
if (fd != -1)
|
|
|
|
m_fds[READ] = fd;
|
2014-12-18 02:02:19 +08:00
|
|
|
else
|
2015-01-14 07:19:40 +08:00
|
|
|
error.SetErrorToErrno();
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
Error
|
2015-01-14 07:19:40 +08:00
|
|
|
PipePosix::OpenAsWriterWithTimeout(llvm::StringRef name, bool child_process_inherit, const std::chrono::microseconds &timeout)
|
2014-12-18 02:02:19 +08:00
|
|
|
{
|
|
|
|
if (CanRead() || CanWrite())
|
2015-01-14 07:19:40 +08:00
|
|
|
return Error("Pipe is already opened");
|
|
|
|
|
|
|
|
int flags = O_WRONLY | O_NONBLOCK;
|
|
|
|
if (!child_process_inherit)
|
|
|
|
flags |= O_CLOEXEC;
|
|
|
|
|
|
|
|
using namespace std::chrono;
|
|
|
|
const auto finish_time = Now() + timeout;
|
|
|
|
|
|
|
|
while (!CanWrite())
|
|
|
|
{
|
2015-07-21 19:04:52 +08:00
|
|
|
if (timeout != microseconds::zero())
|
2015-01-14 07:19:40 +08:00
|
|
|
{
|
|
|
|
const auto dur = duration_cast<microseconds>(finish_time - Now()).count();
|
|
|
|
if (dur <= 0)
|
|
|
|
return Error("timeout exceeded - reader hasn't opened so far");
|
|
|
|
}
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
int fd = ::open(name.data(), flags);
|
|
|
|
if (fd == -1)
|
|
|
|
{
|
|
|
|
const auto errno_copy = errno;
|
|
|
|
// We may get ENXIO if a reader side of the pipe hasn't opened yet.
|
|
|
|
if (errno_copy != ENXIO)
|
|
|
|
return Error(errno_copy, eErrorTypePOSIX);
|
|
|
|
|
|
|
|
std::this_thread::sleep_for(milliseconds(OPEN_WRITER_SLEEP_TIMEOUT_MSECS));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_fds[WRITE] = fd;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Error();
|
2014-07-03 05:10:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2014-12-18 02:02:19 +08:00
|
|
|
PipePosix::GetReadFileDescriptor() const
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
|
|
|
return m_fds[READ];
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2014-12-18 02:02:19 +08:00
|
|
|
PipePosix::GetWriteFileDescriptor() const
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
|
|
|
return m_fds[WRITE];
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2014-12-18 02:02:19 +08:00
|
|
|
PipePosix::ReleaseReadFileDescriptor()
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
|
|
|
const int fd = m_fds[READ];
|
2014-12-18 02:02:19 +08:00
|
|
|
m_fds[READ] = PipePosix::kInvalidDescriptor;
|
2014-07-03 05:10:39 +08:00
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2014-12-18 02:02:19 +08:00
|
|
|
PipePosix::ReleaseWriteFileDescriptor()
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
|
|
|
const int fd = m_fds[WRITE];
|
2014-12-18 02:02:19 +08:00
|
|
|
m_fds[WRITE] = PipePosix::kInvalidDescriptor;
|
2014-07-03 05:10:39 +08:00
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2014-12-18 02:02:19 +08:00
|
|
|
PipePosix::Close()
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
|
|
|
CloseReadFileDescriptor();
|
|
|
|
CloseWriteFileDescriptor();
|
|
|
|
}
|
|
|
|
|
2015-01-14 07:19:40 +08:00
|
|
|
Error
|
|
|
|
PipePosix::Delete(llvm::StringRef name)
|
|
|
|
{
|
2015-05-30 03:52:29 +08:00
|
|
|
return FileSystem::Unlink(FileSpec{name.data(), true});
|
2015-01-14 07:19:40 +08:00
|
|
|
}
|
|
|
|
|
2014-07-03 05:10:39 +08:00
|
|
|
bool
|
2014-12-18 02:02:19 +08:00
|
|
|
PipePosix::CanRead() const
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
2014-12-18 02:02:19 +08:00
|
|
|
return m_fds[READ] != PipePosix::kInvalidDescriptor;
|
2014-07-03 05:10:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2014-12-18 02:02:19 +08:00
|
|
|
PipePosix::CanWrite() const
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
2014-12-18 02:02:19 +08:00
|
|
|
return m_fds[WRITE] != PipePosix::kInvalidDescriptor;
|
2014-07-03 05:10:39 +08:00
|
|
|
}
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
void
|
|
|
|
PipePosix::CloseReadFileDescriptor()
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
2014-12-18 02:02:19 +08:00
|
|
|
if (CanRead())
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
2015-01-14 07:19:40 +08:00
|
|
|
close(m_fds[READ]);
|
2014-12-18 02:02:19 +08:00
|
|
|
m_fds[READ] = PipePosix::kInvalidDescriptor;
|
2014-07-03 05:10:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
void
|
|
|
|
PipePosix::CloseWriteFileDescriptor()
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
2014-12-18 02:02:19 +08:00
|
|
|
if (CanWrite())
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
2015-01-14 07:19:40 +08:00
|
|
|
close(m_fds[WRITE]);
|
2014-12-18 02:02:19 +08:00
|
|
|
m_fds[WRITE] = PipePosix::kInvalidDescriptor;
|
2014-07-03 05:10:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
Error
|
2015-01-14 07:19:40 +08:00
|
|
|
PipePosix::ReadWithTimeout(void *buf, size_t size, const std::chrono::microseconds &timeout, size_t &bytes_read)
|
2014-12-18 02:02:19 +08:00
|
|
|
{
|
|
|
|
bytes_read = 0;
|
2015-01-14 07:19:40 +08:00
|
|
|
if (!CanRead())
|
|
|
|
return Error(EINVAL, eErrorTypePOSIX);
|
|
|
|
|
|
|
|
auto handle = GetReadFileDescriptor();
|
|
|
|
return SelectIO(handle,
|
|
|
|
true,
|
|
|
|
[=, &bytes_read](bool &done)
|
|
|
|
{
|
|
|
|
Error error;
|
|
|
|
auto result = ::read(handle,
|
|
|
|
reinterpret_cast<char*>(buf) + bytes_read,
|
|
|
|
size - bytes_read);
|
|
|
|
if (result != -1)
|
|
|
|
{
|
|
|
|
bytes_read += result;
|
|
|
|
if (bytes_read == size || result == 0)
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
error.SetErrorToErrno();
|
|
|
|
|
|
|
|
return error;
|
|
|
|
},
|
|
|
|
timeout);
|
2014-07-03 05:10:39 +08:00
|
|
|
}
|
|
|
|
|
2014-12-18 02:02:19 +08:00
|
|
|
Error
|
2015-07-21 19:04:52 +08:00
|
|
|
PipePosix::Write(const void *buf, size_t size, size_t &bytes_written)
|
2014-07-03 05:10:39 +08:00
|
|
|
{
|
2014-12-18 02:02:19 +08:00
|
|
|
bytes_written = 0;
|
2015-01-14 07:19:40 +08:00
|
|
|
if (!CanWrite())
|
|
|
|
return Error(EINVAL, eErrorTypePOSIX);
|
|
|
|
|
|
|
|
auto handle = GetWriteFileDescriptor();
|
|
|
|
return SelectIO(handle,
|
|
|
|
false,
|
|
|
|
[=, &bytes_written](bool &done)
|
|
|
|
{
|
|
|
|
Error error;
|
|
|
|
auto result = ::write(handle,
|
|
|
|
reinterpret_cast<const char*>(buf) + bytes_written,
|
|
|
|
size - bytes_written);
|
|
|
|
if (result != -1)
|
|
|
|
{
|
|
|
|
bytes_written += result;
|
|
|
|
if (bytes_written == size)
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
error.SetErrorToErrno();
|
|
|
|
|
|
|
|
return error;
|
|
|
|
},
|
2015-07-21 19:04:52 +08:00
|
|
|
std::chrono::microseconds::zero());
|
2014-07-03 05:10:39 +08:00
|
|
|
}
|