// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(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 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 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 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 void binread(T& t) { // Read and return a thing of type T in binary format. char* ptr = reinterpret_cast(&t); readall(ptr,sizeof(t)); } template T binread(void) { // Read and return a thing of type T in binary format. T t; binread(t); return t; } template 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 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(&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 void binwrite(const T& t) { // Write a thing of type T in binary format. const char* ptr = reinterpret_cast(&t); writeall(ptr,sizeof(T)); } template 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(buf1); v[0].iov_len = bytes1; v[1].iov_base = const_cast(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(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 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 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 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 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 int ioctl(int request, T* argp) { // Directly provides the functionality of the ioctl system call. int rc = ::ioctl(fd, request, reinterpret_cast(argp)); if (rc==-1) { throw_ErrnoException("ioctl("+boost::lexical_cast(request)+","+fn+")"); } return rc; } template int ioctl(int request, T& arg) { return ioctl(request,&arg); } int ioctl(int request) { // ioctl with no data. return ioctl(request,NULL); } template 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(argp)); if (rc==-1) { if (errno==EAGAIN) { done = false; return 0; } throw_ErrnoException("ioctl("+boost::lexical_cast(request)+","+fn+")"); } done = true; return rc; } template 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(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(timeout_whole); tv.tv_usec = static_cast(1000000.0*timeout_frac); return wait_until_(i,&tv); } }; #endif