865 lines
24 KiB
C++
865 lines
24 KiB
C++
// FileDescriptor.hh
|
|
// This file is part of libpbe; see http://anyterm.org/
|
|
// (C) 2006-2007 Philip Endecott
|
|
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#ifndef libpbe_FileDescriptor_hh
|
|
#define libpbe_FileDescriptor_hh
|
|
|
|
#include "Exception.hh"
|
|
#include "missing_syscalls.hh"
|
|
#include "compiler_magic.hh"
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <boost/noncopyable.hpp>
|
|
#include <boost/scoped_array.hpp>
|
|
|
|
#include <cmath>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/ioctl.h>
|
|
#include <errno.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include <ext/stdio_filebuf.h>
|
|
|
|
|
|
namespace pbe {
|
|
|
|
class FileDescriptor: public boost::noncopyable {
|
|
// C++ wrapper around the traditional POSIX file descriptor.
|
|
// Some methods are simple wrappers around a standard POSIX library
|
|
// function. In addition there is a rich set of read/write methods and
|
|
// an easy to use wrapper for select().
|
|
// In all cases, exceptions are used to report error conditions.
|
|
|
|
protected:
|
|
const int fd;
|
|
const std::string fn;
|
|
const bool close_on_destr;
|
|
|
|
public:
|
|
enum open_mode_t { read_only=O_RDONLY, read_write=O_RDWR, write_only=O_WRONLY,
|
|
create=O_WRONLY|O_CREAT };
|
|
|
|
FileDescriptor(std::string fn_, open_mode_t open_mode):
|
|
// Constructor that opens a file given a file name and an access mode.
|
|
// The file will be closed when the FileDescriptor is destructed.
|
|
// I set close-on-exec for all FileDescriptors, since this seems like the
|
|
// sensible default for me.
|
|
fd(open(fn_.c_str(),open_mode,0666)),
|
|
fn('"'+fn_+'"'),
|
|
close_on_destr(true)
|
|
{
|
|
if (fd==-1) {
|
|
throw_ErrnoException("open("+fn+")");
|
|
}
|
|
// Set close-on-exec.
|
|
// There is a race condition here: if another thread has forked
|
|
// between the calls to open and fcntl, it will get the fd, which is
|
|
// bad. It may be fixable in the future if we get O_CLOEXEC.
|
|
// See http://lwn.net/Articles/236486/.
|
|
int rc = fcntl(fd,F_SETFD,FD_CLOEXEC);
|
|
if (rc==-1) {
|
|
throw_ErrnoException("fcntl("+fn+",F_SETFD,FD_CLOEXEC)");
|
|
}
|
|
}
|
|
|
|
FileDescriptor(int fd_, bool close_on_destr_=true):
|
|
// Constructor that takes an existing file descriptor.
|
|
// The file will be closed when the FileDescriptor is destructed
|
|
// unless the optional close_on_destr_ parameter is false.
|
|
fd(fd_),
|
|
fn("fd"+boost::lexical_cast<std::string>(fd)),
|
|
close_on_destr(close_on_destr_)
|
|
{
|
|
}
|
|
|
|
FileDescriptor(int fd_, std::string fn_, bool close_on_destr_=true):
|
|
// Constructor that takes an existing file descriptor.
|
|
// A name, for use in error messages, is supplied.
|
|
// The file will be closed when the FileDescriptor is destructed
|
|
// unless the optional close_on_destr_ parameter is false.
|
|
fd(fd_),
|
|
fn(fn_),
|
|
close_on_destr(close_on_destr_)
|
|
{
|
|
}
|
|
|
|
~FileDescriptor() {
|
|
if (close_on_destr) {
|
|
int rc = ::close(fd);
|
|
if (rc==-1) {
|
|
//throw_ErrnoException("close("+fn+")");
|
|
// Don't throw exceptions from destructors, in case the destructor is being
|
|
// called during exception processing.
|
|
// TODO need a better fix for this.
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void close() {
|
|
// Call close. This is only necessary if the user wants to close the fd before
|
|
// it goes out of scope for some reason; further operations on it are obviously
|
|
// not allowed. It also has the benefit of checking the return value, which
|
|
// the destructor does not do.
|
|
int rc = ::close(fd);
|
|
if (rc==-1) {
|
|
throw_ErrnoException("close("+fn+")");
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Various implementation of READ:
|
|
//
|
|
|
|
size_t read(char* buf, size_t max_bytes) {
|
|
// Directly provides almost the functionality of the read system call.
|
|
// At most max_bytes are read into the buffer. Fewer bytes may be
|
|
// read at EOF, when a socket has less data waiting, etc. The number
|
|
// of bytes actually read is returned.
|
|
// If EINTR is returned, the call is retried.
|
|
while (1) {
|
|
ssize_t n = ::read(fd,buf,max_bytes);
|
|
if (n==-1) {
|
|
if (errno==EINTR) {
|
|
continue;
|
|
}
|
|
throw_ErrnoException("read("+fn+")");
|
|
}
|
|
return n;
|
|
}
|
|
}
|
|
|
|
std::string read(size_t max_bytes) {
|
|
// Provides the functionality of the read sytem call, but returns
|
|
// the data in a std::string. At most max_bytes are read and returned.
|
|
// Fewer bytes may be read at EOF, when a socket has less data waiting,
|
|
// etc.
|
|
boost::scoped_array<char> buf(new char[max_bytes]);
|
|
size_t bytes = read(buf.get(),max_bytes);
|
|
return std::string(buf.get(),bytes);
|
|
}
|
|
|
|
class EndOfFile: public StrException {
|
|
public:
|
|
EndOfFile(): StrException("EOF") {};
|
|
};
|
|
|
|
void readall(char* buf, size_t bytes) {
|
|
// Reads exactly bytes bytes into the buffer, making more than one
|
|
// call to read as necessary. Throws EOF if end-of-file is reached
|
|
// before the required number of bytes has been read.
|
|
while (bytes>0) {
|
|
size_t n = read(buf,bytes);
|
|
if (n==0) {
|
|
throw EndOfFile();
|
|
}
|
|
buf += n;
|
|
bytes -= n;
|
|
}
|
|
}
|
|
|
|
std::string readall(size_t bytes) {
|
|
// Reads exactly bytes bytes from the file descriptor and returns them
|
|
// as a std::string, making more than one call to read as necessary.
|
|
// Throws EOF if end-of-file is reached before the required number of
|
|
// bytes has been read.
|
|
boost::scoped_array<char> buf(new char[bytes]);
|
|
readall(buf.get(),bytes);
|
|
return std::string(buf.get(),bytes);
|
|
}
|
|
|
|
size_t readmax(char* buf, size_t bytes) {
|
|
// Reads exactly bytes bytes into the buffer, making more than one
|
|
// call to read as necessary, unless end-of-file is reached before
|
|
// the required number of bytes has been read, in which case all the
|
|
// available bytes are read. The number of bytes read is returned.
|
|
size_t bytes_read = 0;
|
|
while (bytes>0) {
|
|
size_t n = read(buf,bytes);
|
|
if (n==0) {
|
|
return bytes_read;
|
|
}
|
|
bytes_read += n;
|
|
buf += n;
|
|
bytes -= n;
|
|
}
|
|
return bytes_read;
|
|
}
|
|
|
|
std::string readmax(size_t bytes) {
|
|
// Reads exactly bytes bytes from the file descriptor and returns them
|
|
// as a std::string, making more than one call to read as necessary,
|
|
// unless end-of-file is reached before the required number of bytes
|
|
// has been read, in which case all the available bytes are read.
|
|
boost::scoped_array<char> buf(new char[bytes]);
|
|
size_t bytes_read = readmax(buf.get(),bytes);
|
|
return std::string(buf.get(),bytes_read);
|
|
}
|
|
|
|
std::string readsome(void) {
|
|
// Reads an unspecified number of bytes from the file descriptor and
|
|
// returns them as a std::string. Use this call if you want to
|
|
// process the contents of the file in chunks and don't care about
|
|
// the chunk size.
|
|
// Currently returns an empty string at EOF; should it throw EndOfFile?
|
|
// (No - readall() below relies on it returning an empty string.)
|
|
char buf[BUFSIZ];
|
|
int bytes = read(buf,BUFSIZ);
|
|
return std::string(buf,bytes);
|
|
}
|
|
|
|
std::string readall() {
|
|
// Reads everything from the file descriptor until no more data is available
|
|
// (i.e. end of file)
|
|
std::string s;
|
|
std::string some;
|
|
while ((some=readsome()) != "") {
|
|
s += some;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
template <typename T>
|
|
void binread(T& t) {
|
|
// Read and return a thing of type T in binary format.
|
|
char* ptr = reinterpret_cast<char*>(&t);
|
|
readall(ptr,sizeof(t));
|
|
}
|
|
|
|
template <typename T>
|
|
T binread(void) {
|
|
// Read and return a thing of type T in binary format.
|
|
T t;
|
|
binread(t);
|
|
return t;
|
|
}
|
|
|
|
template <typename T>
|
|
void binread_at(off_t pos, T& t) {
|
|
// Read and return a thing of type T in binary format at position pos in the file.
|
|
seek(pos);
|
|
binread(t);
|
|
}
|
|
|
|
std::string read_until_idle(float timeout) {
|
|
// Read something, and then keep reading until nothing more has been
|
|
// read for at least timeout.
|
|
std::string s = readsome();
|
|
while (wait_until(readable(),timeout)) {
|
|
s += readsome();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
void set_nonblocking() {
|
|
// Calls fcntl to make an open fd non-blocking on subsequent reads and writes.
|
|
int flags = fcntl(fd,F_GETFL);
|
|
flags |= O_NONBLOCK;
|
|
int rc = fcntl(fd,F_SETFL,flags);
|
|
if (rc==-1) {
|
|
throw_ErrnoException("fcntl("+fn+",F_SETFL,|O_NONBLOCK)");
|
|
}
|
|
}
|
|
|
|
void set_blocking() {
|
|
// Calls fcntl to make an open fd blocking on subsequent reads and writes.
|
|
int flags = fcntl(fd,F_GETFL);
|
|
flags &=~ O_NONBLOCK;
|
|
int rc = fcntl(fd,F_SETFL,flags);
|
|
if (rc==-1) {
|
|
throw_ErrnoException("fcntl("+fn+",F_SETFL,&~O_NONBLOCK)");
|
|
}
|
|
}
|
|
|
|
class scoped_nonblocking {
|
|
FileDescriptor& fd;
|
|
public:
|
|
scoped_nonblocking(FileDescriptor& fd_): fd(fd_) {
|
|
fd.set_nonblocking();
|
|
}
|
|
~scoped_nonblocking() {
|
|
fd.set_blocking(); // FIXME maybe it was already nonblocking!
|
|
}
|
|
};
|
|
|
|
size_t try_read(char* buf, size_t max_bytes, bool& readable) {
|
|
// Directly provides the functionality of the read system call.
|
|
// At most max_bytes are read into the buffer. Fewer bytes may be
|
|
// read at EOF, when a socket has less data waiting, etc. Zero bytes
|
|
// will be read if no data is waiting and the operation would block.
|
|
// The number of bytes actually read is returned, and readable is
|
|
// set to indicate whether the file was readable. (If the return
|
|
// value is zero and readable is true, we're at EOF.)
|
|
scoped_nonblocking nb(*this);
|
|
ssize_t n = ::read(fd,buf,max_bytes);
|
|
// FIXME what about EINTR?
|
|
if (n==-1) {
|
|
if (errno==EAGAIN) {
|
|
readable = false;
|
|
return 0;
|
|
}
|
|
throw_ErrnoException("read("+fn+")");
|
|
}
|
|
readable = true;
|
|
return n;
|
|
}
|
|
|
|
bool try_readall(char* buf, size_t bytes) {
|
|
// Try to read exactly bytes bytes into the buffer, making more than one
|
|
// call to read as necessary. Throws EOF if end-of-file is reached
|
|
// before the required number of bytes has been read. Returns false if
|
|
// the operation would block. (Hmm, data will be discarded if a partial
|
|
// read completes but a subsequent full read would block.)
|
|
while (bytes>0) {
|
|
bool readable;
|
|
size_t n = try_read(buf,bytes,readable);
|
|
if (!readable) {
|
|
return false;
|
|
}
|
|
if (n==0) {
|
|
throw EndOfFile();
|
|
}
|
|
buf += n;
|
|
bytes -= n;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <typename T>
|
|
bool try_binread(T& t) {
|
|
// Try to read a thing of type T in binary format. If the operation would block
|
|
// (e.g. a serial port or socket with insifficient data currently available)
|
|
// return false immediately.
|
|
char* ptr = reinterpret_cast<char*>(&t);
|
|
return try_readall(ptr,sizeof(t));
|
|
}
|
|
|
|
std::string try_readsome(void) {
|
|
// Tries to reads an unspecified number of bytes from the file descriptor and
|
|
// returns them as a std::string. If nothing can be read at present, returns an
|
|
// empty string.
|
|
char buf[BUFSIZ];
|
|
bool readable;
|
|
int bytes = try_read(buf,BUFSIZ,readable);
|
|
return std::string(buf,bytes);
|
|
}
|
|
|
|
//
|
|
// Various implementations of WRITE:
|
|
//
|
|
|
|
size_t write(const char* buf, size_t max_bytes) PBE_WARN_RESULT_IGNORED {
|
|
// Directly provides the functionality of the write system call.
|
|
// At most max_bytes are written from the buffer. Fewer bytes may be
|
|
// written under various circumstances. The number of bytes actually
|
|
// written is returned.
|
|
ssize_t n = ::write(fd,buf,max_bytes);
|
|
if (n==-1) {
|
|
throw_ErrnoException("write("+fn+")");
|
|
}
|
|
return n;
|
|
}
|
|
|
|
size_t write(std::string s) PBE_WARN_RESULT_IGNORED {
|
|
// Provides the functionality of the write system call, but with the data
|
|
// coming from a std::string. Not all of the data may be written under
|
|
// various circumstances. The number of bytes actually written is
|
|
// returned.
|
|
return write(s.data(),s.length());
|
|
}
|
|
|
|
void writeall(const char* buf, size_t bytes) {
|
|
// Write all bytes bytes from buf, making repeated calls to write
|
|
// as necessary.
|
|
while (bytes>0) {
|
|
ssize_t n = write(buf,bytes);
|
|
if (n==-1) {
|
|
throw_ErrnoException("write("+fn+")");
|
|
}
|
|
// What happens if 0 bytes are written?
|
|
buf += n;
|
|
bytes -= n;
|
|
}
|
|
}
|
|
|
|
void writeall(std::string s) {
|
|
// Write all of string s, making repeated calls to write as necessary.
|
|
writeall(s.data(),s.length());
|
|
}
|
|
|
|
template <typename T>
|
|
void binwrite(const T& t) {
|
|
// Write a thing of type T in binary format.
|
|
const char* ptr = reinterpret_cast<const char*>(&t);
|
|
writeall(ptr,sizeof(T));
|
|
}
|
|
|
|
template <typename T>
|
|
void binwrite_at(off_t pos, const T& t) {
|
|
// Write a thing of type T in binary format at position pos in the file.
|
|
seek(pos);
|
|
binwrite(t);
|
|
}
|
|
|
|
void writeallv2(const char* buf1, size_t bytes1, const char* buf2, size_t bytes2) {
|
|
// Writes all bytes from buf1 and all bytes from buf2 using the writev system call.
|
|
iovec v[2];
|
|
v[0].iov_base = const_cast<char*>(buf1);
|
|
v[0].iov_len = bytes1;
|
|
v[1].iov_base = const_cast<char*>(buf2);
|
|
v[1].iov_len = bytes2;
|
|
size_t bytes_written = 0;
|
|
while (1) {
|
|
int rc = writev(fd,v,2);
|
|
if (rc==-1) {
|
|
throw_ErrnoException("writev("+fn+")");
|
|
}
|
|
bytes_written += rc;
|
|
if (bytes_written == bytes1+bytes2) {
|
|
return;
|
|
}
|
|
if (bytes_written >= bytes1) {
|
|
break;
|
|
}
|
|
v[0].iov_base = static_cast<char*>(v[0].iov_base)+rc;
|
|
v[0].iov_len -= rc;
|
|
}
|
|
writeall(buf2+bytes_written-bytes1, bytes1+bytes2-bytes_written);
|
|
}
|
|
|
|
|
|
int dup() {
|
|
// Directly provides the functionality of the dup() system call.
|
|
// The duplicate file descriptor is returned as an int, not as a FileDesriptor
|
|
// object. A FileDescriptor object can of course be constructed from the int.
|
|
int d = ::dup(fd);
|
|
if (d==-1) {
|
|
throw_ErrnoException("dup("+fn+")");
|
|
}
|
|
return d;
|
|
}
|
|
|
|
// The following code allows a C++ stream to be created from a
|
|
// file descriptor. This relies on a non-standard extension in
|
|
// GNU libstdc++. Not only is this non-standard, but it has
|
|
// changed slightly in different versions of the library.
|
|
// The following is known to work with g++ 3.4.4, and hopefully
|
|
// newer versions. If you have an earlier version that works
|
|
// with this please let me know.
|
|
#if __GLIBCXX__ >= 20050421
|
|
#define LIBPBE_HAS_FILEDESCRIPTOR_STREAMS
|
|
|
|
class istream: public std::istream {
|
|
private:
|
|
__gnu_cxx::stdio_filebuf<char> fbuf;
|
|
public:
|
|
istream(FileDescriptor& fd, int bufsize=BUFSIZ):
|
|
fbuf(fd.dup(), std::ios::in, bufsize)
|
|
{
|
|
rdbuf(&fbuf);
|
|
}
|
|
};
|
|
|
|
class ostream: public std::ostream {
|
|
private:
|
|
__gnu_cxx::stdio_filebuf<char> fbuf;
|
|
public:
|
|
ostream(FileDescriptor& fd, int bufsize=BUFSIZ):
|
|
fbuf(fd.dup(), std::ios::out, bufsize)
|
|
{
|
|
rdbuf(&fbuf);
|
|
}
|
|
};
|
|
|
|
// The following should work with gcc 3.3.
|
|
// Note that the libstdc++ version symbol has changed from __GLIBCPP__
|
|
// to __GLIBCXX__ at some point.
|
|
// If you have an earlier or later version that works with this
|
|
// please let me know.
|
|
#elif (__GLIBCPP__ >= 20040214) && (__GLIBCPP__ <= 20050503)
|
|
#define LIBPBE_HAS_FILEDESCRIPTOR_STREAMS
|
|
|
|
class istream: public std::istream {
|
|
private:
|
|
__gnu_cxx::stdio_filebuf<char> fbuf;
|
|
public:
|
|
istream(FileDescriptor& fd, int bufsize=BUFSIZ):
|
|
std::istream(&fbuf),
|
|
fbuf(fd.dup(), std::ios::in, true, bufsize)
|
|
// (I'm concerned about the order of construction here)
|
|
{}
|
|
};
|
|
|
|
class ostream: public std::ostream {
|
|
private:
|
|
__gnu_cxx::stdio_filebuf<char> fbuf;
|
|
public:
|
|
ostream(FileDescriptor& fd, int bufsize=BUFSIZ):
|
|
std::ostream(&fbuf),
|
|
fbuf(fd.dup(), std::ios::out, true, bufsize)
|
|
// ditto
|
|
{}
|
|
};
|
|
|
|
#endif
|
|
// Information about and/or patches for other versions of g++ are welcome.
|
|
|
|
|
|
void set_nodelay() {
|
|
// Calls setscokopt to disable Nagle's algorithm for this socket.
|
|
int flag = 1;
|
|
int rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
|
|
if (rc==-1) {
|
|
throw_ErrnoException("setsockopt("+fn+",TCP_NODELAY)");
|
|
}
|
|
}
|
|
|
|
|
|
struct in_addr get_peer_ip_addr() {
|
|
// For an fd that is a socket, finds the IP address of the other end of the
|
|
// connection.
|
|
// For a non-socket, returns the loopback address.
|
|
struct sockaddr_in client_addr;
|
|
socklen_t client_addr_len = sizeof(client_addr);
|
|
int rc = getpeername(fd, (struct sockaddr*)&client_addr,
|
|
&client_addr_len);
|
|
if (rc==-1) {
|
|
if (errno==ENOTSOCK) {
|
|
struct in_addr a;
|
|
a.s_addr = htonl(INADDR_LOOPBACK);
|
|
return a;
|
|
} else {
|
|
throw_ErrnoException("getpeername("+fn+")");
|
|
}
|
|
}
|
|
if (client_addr.sin_family!=AF_INET) {
|
|
throw "socket is not an AF_INET socket";
|
|
}
|
|
return client_addr.sin_addr;
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
int ioctl(int request, T* argp) {
|
|
// Directly provides the functionality of the ioctl system call.
|
|
int rc = ::ioctl(fd, request, reinterpret_cast<char*>(argp));
|
|
if (rc==-1) {
|
|
throw_ErrnoException("ioctl("+boost::lexical_cast<std::string>(request)+","+fn+")");
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
template <typename T>
|
|
int ioctl(int request, T& arg) {
|
|
return ioctl(request,&arg);
|
|
}
|
|
|
|
int ioctl(int request) {
|
|
// ioctl with no data.
|
|
return ioctl<void>(request,NULL);
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
int try_ioctl(int request, T* argp, bool& done) {
|
|
// Directly provides the functionality of the ioctl system call, non-blocking;
|
|
// done is false if the ioctl would block.
|
|
int rc = ::ioctl(fd, request, reinterpret_cast<char*>(argp));
|
|
if (rc==-1) {
|
|
if (errno==EAGAIN) {
|
|
done = false;
|
|
return 0;
|
|
}
|
|
throw_ErrnoException("ioctl("+boost::lexical_cast<std::string>(request)+","+fn+")");
|
|
}
|
|
done = true;
|
|
return rc;
|
|
}
|
|
|
|
template <typename T>
|
|
int try_ioctl(int request, T& arg, bool& done) {
|
|
return try_ioctl(request,&arg,done);
|
|
}
|
|
|
|
int try_ioctl(int request, bool& done) {
|
|
// try_ioctl with no data.
|
|
return try_ioctl<void>(request,NULL,done);
|
|
}
|
|
|
|
|
|
void get_sigio(void)
|
|
// Ask the kernel to send this process or thread SIGIO when this fd becomes
|
|
// readable or writable.
|
|
{
|
|
int r = fcntl(fd,F_SETOWN,getpid()); // should be gettid
|
|
if (r==-1) {
|
|
throw_ErrnoException("fcntl("+fn+",F_SETOWN)");
|
|
}
|
|
r = fcntl(fd,F_SETFL,O_ASYNC);
|
|
if (r==-1) {
|
|
throw_ErrnoException("fnctl("+fn+",F_SETFL,O_ASYNC)");
|
|
}
|
|
}
|
|
|
|
|
|
enum whence_t {seek_set=SEEK_SET, seek_cur=SEEK_CUR, seek_end=SEEK_END};
|
|
|
|
off_t seek(off_t offset, whence_t whence = seek_set) {
|
|
// Directly provides the functionality of the lseek() system call.
|
|
// By default the offset is interpretted relative to the start of the file.
|
|
off_t r = lseek(fd,offset,whence);
|
|
if (r==(off_t)-1) {
|
|
throw_ErrnoException("lseek("+fn+")");
|
|
}
|
|
return r;
|
|
}
|
|
|
|
|
|
off_t getpos() {
|
|
return seek(0,seek_cur);
|
|
}
|
|
|
|
off_t file_length() {
|
|
// Returns the length of the file. This is done by seeking to the end.
|
|
off_t pos = getpos();
|
|
off_t len = seek(0,seek_end);
|
|
seek(pos);
|
|
return len;
|
|
}
|
|
|
|
|
|
void* mmap(size_t length, open_mode_t open_mode, off_t offset = 0, bool copy_on_write=false) {
|
|
int prot = 0;
|
|
if (open_mode == read_only || open_mode == read_write) {
|
|
prot |= PROT_READ;
|
|
}
|
|
if (open_mode == write_only || open_mode == read_write) {
|
|
prot |= PROT_WRITE;
|
|
}
|
|
void* ptr = ::mmap(0, length, prot, copy_on_write ? MAP_PRIVATE : MAP_SHARED, fd, offset);
|
|
if (ptr==MAP_FAILED) {
|
|
throw_ErrnoException("mmap("+fn+")");
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
|
|
void truncate(off_t length) {
|
|
int rc = ftruncate(fd,length);
|
|
if (rc==-1) {
|
|
throw_ErrnoException("truncate("+fn+")");
|
|
}
|
|
}
|
|
|
|
|
|
void sync() {
|
|
int rc = ::fsync(fd);
|
|
if (rc==-1) {
|
|
throw_ErrnoException("fsync("+fn+")");
|
|
}
|
|
}
|
|
|
|
#if ! (defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__)
|
|
// These systems don't have fdatasync
|
|
void datasync() {
|
|
int rc = ::fdatasync(fd);
|
|
if (rc==-1) {
|
|
throw_ErrnoException("fdatasync("+fn+")");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
private:
|
|
|
|
struct select_item_readable {
|
|
const int fd;
|
|
select_item_readable(int fd_): fd(fd_) {}
|
|
};
|
|
|
|
struct select_item_writeable {
|
|
const int fd;
|
|
select_item_writeable(int fd_): fd(fd_) {}
|
|
};
|
|
|
|
struct select_item_exception {
|
|
const int fd;
|
|
select_item_exception(int fd_): fd(fd_) {}
|
|
};
|
|
|
|
class select_info {
|
|
private:
|
|
fd_set readfds;
|
|
fd_set writefds;
|
|
fd_set exceptfds;
|
|
int max_fd;
|
|
|
|
public:
|
|
void clear() {
|
|
FD_ZERO(&readfds);
|
|
FD_ZERO(&writefds);
|
|
FD_ZERO(&exceptfds);
|
|
max_fd=0;
|
|
}
|
|
|
|
void set_readable(int fd) {
|
|
FD_SET(fd,&readfds);
|
|
max_fd=std::max(max_fd,fd);
|
|
}
|
|
|
|
void set_writeable(int fd) {
|
|
FD_SET(fd,&writefds);
|
|
max_fd=std::max(max_fd,fd);
|
|
}
|
|
|
|
void set_exception(int fd) {
|
|
FD_SET(fd,&exceptfds);
|
|
max_fd=std::max(max_fd,fd);
|
|
}
|
|
|
|
select_info(select_item_readable i) {
|
|
clear();
|
|
set_readable(i.fd);
|
|
}
|
|
|
|
select_info(select_item_writeable i) {
|
|
clear();
|
|
set_writeable(i.fd);
|
|
}
|
|
|
|
select_info(select_item_exception i) {
|
|
clear();
|
|
set_exception(i.fd);
|
|
}
|
|
|
|
friend int wait_until_(FileDescriptor::select_info, struct timeval*);
|
|
};
|
|
|
|
|
|
public:
|
|
|
|
select_item_readable readable() const {
|
|
return select_item_readable(fd);
|
|
}
|
|
select_item_writeable writeable() const {
|
|
return select_item_writeable(fd);
|
|
}
|
|
select_item_exception exception() const {
|
|
return select_item_exception(fd);
|
|
}
|
|
|
|
friend select_info operator||(select_info, select_item_readable);
|
|
friend select_info operator||(select_info, select_item_writeable);
|
|
friend select_info operator||(select_info, select_item_exception);
|
|
friend int wait_until_(FileDescriptor::select_info, struct timeval*);
|
|
friend int wait_until(FileDescriptor::select_info);
|
|
friend int wait_until(FileDescriptor::select_info, float);
|
|
|
|
|
|
bool operator==(int rhs) const {
|
|
return fd==rhs;
|
|
}
|
|
|
|
bool operator==(const FileDescriptor& rhs) const {
|
|
return fd==rhs.fd;
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
inline FileDescriptor::select_info
|
|
operator||(FileDescriptor::select_info lhs,
|
|
FileDescriptor::select_item_readable rhs) {
|
|
FileDescriptor::select_info i = lhs;
|
|
i.set_readable(rhs.fd);
|
|
return i;
|
|
}
|
|
|
|
inline FileDescriptor::select_info
|
|
operator||(FileDescriptor::select_info lhs,
|
|
FileDescriptor::select_item_writeable rhs) {
|
|
FileDescriptor::select_info i = lhs;
|
|
i.set_writeable(rhs.fd);
|
|
return i;
|
|
}
|
|
|
|
inline FileDescriptor::select_info
|
|
operator||(FileDescriptor::select_info lhs,
|
|
FileDescriptor::select_item_exception rhs) {
|
|
FileDescriptor::select_info i = lhs;
|
|
i.set_exception(rhs.fd);
|
|
return i;
|
|
}
|
|
|
|
|
|
inline int wait_until_(FileDescriptor::select_info i, struct timeval* tv) {
|
|
int rc;
|
|
do {
|
|
rc = select(i.max_fd+1, &i.readfds, &i.writefds, &i.exceptfds, tv);
|
|
if (rc==-1) {
|
|
if (errno==EINTR) {
|
|
continue;
|
|
}
|
|
throw_ErrnoException("select()");
|
|
}
|
|
} while (0);
|
|
|
|
if (rc==0) {
|
|
return -1;
|
|
}
|
|
for (int n=0; n<=i.max_fd; n++) {
|
|
if (FD_ISSET(n,&i.readfds) || FD_ISSET(n,&i.writefds)
|
|
|| FD_ISSET(n,&i.exceptfds)) {
|
|
return n;
|
|
}
|
|
}
|
|
throw "not reached";
|
|
}
|
|
|
|
inline int wait_until(FileDescriptor::select_info i) {
|
|
// Returns a file descriptor number that changed.
|
|
return wait_until_(i,NULL);
|
|
}
|
|
|
|
inline int wait_until(FileDescriptor::select_info i,
|
|
float timeout) {
|
|
// Returns -1 if timed out, else a file descriptor number that changed.
|
|
struct timeval tv;
|
|
float timeout_whole;
|
|
float timeout_frac;
|
|
timeout_frac = modff(timeout, &timeout_whole);
|
|
tv.tv_sec = static_cast<int>(timeout_whole);
|
|
tv.tv_usec = static_cast<int>(1000000.0*timeout_frac);
|
|
return wait_until_(i,&tv);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
#endif
|