/grep
/libmspcore.a
/libmspcore.so
-/libmspstrings.a
-/libmspstrings.so
/mspcore.pc
/release
/temp
install true;
};
+ headers "msp/io"
+ {
+ source "src/io";
+ install true;
+ };
+
library "mspcore"
{
source "source/core";
source "source/time";
source "source/strings";
source "source/stringcodec";
+ source "source/io";
install true;
};
1.0
* First released version
-($Id$)
+== Changes from pre-2.0 mspio ==
+
+1.1
+* Filter class makes adding filters easier
+* Memory class enables I/O from/to memory buffers
+* Console size can be queried
+* Bugfixes
+ - Don't consider an empty set for poll an error
+
+1.0
+* First released version
--- /dev/null
+#include "base.h"
+#include "poll.h"
+
+using namespace std;
+
+namespace Msp {
+namespace IO {
+
+Base::Base():
+ mode(M_READ),
+ events(P_NONE),
+ eof_flag(false)
+{ }
+
+Base::~Base()
+{
+ signal_deleted.emit();
+}
+
+bool Base::getline(string &line)
+{
+ line.clear();
+
+ if(eof_flag)
+ return false;
+
+ while(1)
+ {
+ int c = get();
+ if(c==-1 || c=='\n')
+ break;
+ line += c;
+ }
+
+ return !eof_flag || !line.empty();
+}
+
+int Base::get()
+{
+ char c;
+ if(do_read(&c, 1)==0)
+ return -1;
+ return static_cast<unsigned char>(c);
+}
+
+void Base::set_events(PollEvent e)
+{
+ events = e;
+ signal_events_changed.emit(events);
+}
+
+void Base::event(PollEvent ev)
+{
+ if(ev&P_INPUT)
+ signal_data_available.emit();
+
+ on_event(ev);
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_BASE_H_
+#define MSP_IO_BASE_H_
+
+#include <sigc++/sigc++.h>
+#include "mode.h"
+#include "poll.h"
+#include "types.h"
+
+namespace Msp {
+namespace IO {
+
+/**
+Common interface for all I/O objects.
+
+A derived class must call set_events(P_NONE) before it is destroyed to avoid
+leaving stale pointers in an EventDispatcher.
+*/
+class Base
+{
+public:
+ /** Emitted when there is data available for reading. If all data is not
+ read, the signal is emitted again immediately. */
+ sigc::signal<void> signal_data_available;
+
+ /** Emitted when there is no more data to be read. */
+ sigc::signal<void> signal_end_of_file;
+
+ /** Emitted when there is a nonlinearity in I/O (such as a file being
+ seeked) and any data buffered by upper layers needs to be flushed. */
+ sigc::signal<void> signal_flush_required;
+
+ /** Emitted when the I/O object has closed. */
+ sigc::signal<void> signal_closed;
+
+ /** Emitted when the mask of interesting events changes. Mainly for use by
+ EventDispatcher. */
+ sigc::signal<void, PollEvent> signal_events_changed;
+
+ /** Emitted when the object is deleted. Mainly for use by
+ EventDispatcher. */
+ sigc::signal<void> signal_deleted;
+
+protected:
+ Mode mode;
+ PollEvent events;
+ bool eof_flag;
+
+ Base();
+private:
+ Base(const Base &);
+ Base &operator=(const Base &);
+public:
+ virtual ~Base();
+
+ /** Sets blocking mode. When blocking is enabled, most operations won't
+ return until they can make progress. When blocking is disabled, these
+ operations may return immediately with a return code indicating that nothing
+ was done.
+
+ Blocking is enabled by default. */
+ virtual void set_block(bool) { }
+
+ /** Returns the current mode flags. */
+ Mode get_mode() const { return mode; }
+
+protected:
+ virtual unsigned do_write(const char *, unsigned) =0;
+ virtual unsigned do_read(char *, unsigned) =0;
+
+public:
+ /** Writes data from a buffer. Subject to blocking. Returns the number of
+ bytes written, which may be zero for a non-blockin operation. */
+ unsigned write(const char *b, unsigned c) { return do_write(b, c); }
+
+ /** Writes a string. This is identical to calling
+ write(s.data(), s.size()). */
+ unsigned write(const std::string &s) { return do_write(s.data(), s.size()); }
+
+ /** Writes a single character. This is identical to calling
+ write(&c, 1). */
+ virtual unsigned put(char c) { return do_write(&c, 1); }
+
+ /** Reads data into a buffer. Subject to blocking. Returns the number of
+ bytes read, which may be zero for a non-blocking operation. */
+ unsigned read(char *b, unsigned c) { return do_read(b, c); }
+
+ /** Reads characters up to the next linefeed or end-of-file. The linefeed
+ is not included in the line. Returns true if a line was successfully read,
+ false otherwise. */
+ virtual bool getline(std::string &);
+
+ /** Reads a single character. Returns -1 if no character was available due
+ to end-of-file or non-blocking operation. */
+ virtual int get();
+
+ /** Returns the end-of-file flag. */
+ bool eof() const { return eof_flag; }
+
+protected:
+ void set_events(PollEvent);
+
+public:
+ /** Returns a mask of the currently interesting events. Used by
+ EventDispatcher. */
+ PollEvent get_events() const { return events; }
+
+ /** Returns a handle for polling. Should throw if the object does not have
+ an event handle. */
+ virtual Handle get_event_handle() =0;
+
+ /** Notifies the object of an event. Used by EventDispatcher. */
+ void event(PollEvent);
+
+protected:
+ virtual void on_event(PollEvent) { }
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#include <cstring>
+#include "buffered.h"
+#include "except.h"
+
+using namespace std;
+
+namespace Msp {
+namespace IO {
+
+Buffered::Buffered(Base &b, unsigned s):
+ below(b),
+ buf_size(s),
+ buf(new char[buf_size]),
+ begin(buf),
+ end(buf),
+ cur_op(M_NONE)
+{
+ mode = below.get_mode();
+ below.signal_flush_required.connect(sigc::mem_fun(this, &Buffered::flush));
+}
+
+Buffered::~Buffered()
+{
+ try
+ {
+ flush();
+ }
+ catch(...)
+ { }
+
+ delete[] buf;
+}
+
+void Buffered::flush()
+{
+ if(cur_op==M_WRITE)
+ {
+ unsigned used = end-begin;
+ if(used)
+ {
+ unsigned len = below.write(begin, used);
+
+ begin=end = buf;
+
+ if(len<used)
+ throw Exception("Couldn't flush all data");
+ }
+ }
+ else if(cur_op==M_READ)
+ begin=end = buf;
+}
+
+unsigned Buffered::do_write(const char *data, unsigned size)
+{
+ set_op(M_WRITE);
+
+ if(end+size<buf+buf_size)
+ {
+ // All data fits in buffer with whatever is already there
+ memcpy(end, data, size);
+ end += size;
+
+ return size;
+ }
+ else
+ {
+ // Clear the buffer to make more room
+ flush();
+
+ if(size<buf_size)
+ {
+ // Put new data in the buffer to wait for more
+ memcpy(end, data, size);
+ end += size;
+
+ return size;
+ }
+ else
+ // New data still doesn't fit in the buffer, so write it directly
+ return below.write(data, size);
+ }
+}
+
+unsigned Buffered::do_read(char *data, unsigned size)
+{
+ set_op(M_READ);
+
+ if(begin+size<=end)
+ {
+ // The request can be served from the buffer
+ memcpy(data, begin, size);
+ begin += size;
+
+ eof_flag = (below.eof() && begin==end);
+
+ return size;
+ }
+ else
+ {
+ // Give out whatever is in the buffer already
+ memcpy(data, begin, end-begin);
+ unsigned ret = end-begin;
+ begin=end = buf;
+
+ data += ret;
+ size -= ret;
+
+ if(size<buf_size)
+ {
+ // Fill the buffer and serve the rest of the request from it
+ unsigned len = below.read(end, buf+buf_size-end);
+ end += len;
+
+ len = min(static_cast<unsigned>(end-begin), size);
+ memcpy(data, begin, len);
+ begin += len;
+ ret += len;
+ }
+ else
+ // Read the rest directly from the underlying object
+ ret += below.read(data, size);
+
+ eof_flag = (below.eof() && begin==end);
+
+ return ret;
+ }
+}
+
+unsigned Buffered::put(char c)
+{
+ set_op(M_WRITE);
+
+ if(end<buf+buf_size)
+ {
+ *end++ = c;
+ return 1;
+ }
+ else
+ return do_write(&c, 1);
+}
+
+bool Buffered::getline(std::string &line)
+{
+ set_op(M_READ);
+
+ for(char *i=begin; i!=end; ++i)
+ if(*i=='\n')
+ {
+ line.assign(begin, i-begin);
+ begin = i+1;
+ return true;
+ }
+
+ return Base::getline(line);
+}
+
+int Buffered::get()
+{
+ set_op(M_READ);
+
+ if(begin<end)
+ return static_cast<unsigned char>(*begin++);
+
+ char c;
+ if(do_read(&c, 1)==0)
+ return -1;
+ return static_cast<unsigned char>(c);
+}
+
+void Buffered::set_op(Mode op)
+{
+ if(op!=cur_op)
+ flush();
+ cur_op = op;
+}
+
+unsigned Buffered::get_current_size() const
+{
+ return end-begin;
+}
+
+Handle Buffered::get_event_handle()
+{
+ throw Exception("Buffered doesn't support events");
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_BUFFERED_H_
+#define MSP_IO_BUFFERED_H_
+
+#include "base.h"
+
+namespace Msp {
+namespace IO {
+
+class Buffered: public Base
+{
+private:
+ Base &below;
+ unsigned buf_size;
+ char *buf;
+ char *begin;
+ char *end;
+ Mode cur_op;
+
+public:
+ Buffered(Base &, unsigned =8192);
+ ~Buffered();
+
+ void flush();
+
+protected:
+ unsigned do_write(const char *, unsigned);
+ unsigned do_read(char *, unsigned);
+public:
+ unsigned put(char);
+
+ bool getline(std::string &);
+ int get();
+
+private:
+ void set_op(Mode);
+public:
+ Mode get_current_op() const { return cur_op; }
+ unsigned get_current_size() const;
+
+ virtual Handle get_event_handle();
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifndef WIN32
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#endif
+#include <msp/core/except.h>
+#include "console.h"
+
+namespace {
+
+#ifndef WIN32
+termios orig_attr;
+#endif
+
+} // namespace
+
+namespace Msp {
+namespace IO {
+
+Console::Console(unsigned n)
+{
+ if(n>2)
+ throw InvalidParameterValue("Invalid parameter for Console::Console");
+
+ mode = (n==0 ? M_READ : M_WRITE);
+
+#ifdef WIN32
+ switch(n)
+ {
+ case 0: handle = GetStdHandle(STD_INPUT_HANDLE); break;
+ case 1: handle = GetStdHandle(STD_OUTPUT_HANDLE); break;
+ case 2: handle = GetStdHandle(STD_ERROR_HANDLE); break;
+ }
+#else
+ handle = n;
+
+ if(handle==0)
+ tcgetattr(handle, &orig_attr);
+#endif
+
+ if(n==0)
+ set_events(P_INPUT);
+}
+
+Console::~Console()
+{
+#ifndef WIN32
+ if(handle==0)
+ tcsetattr(handle, TCSADRAIN, &orig_attr);
+#endif
+}
+
+void Console::set_block(bool b)
+{
+#ifdef WIN32
+ // XXX Dunno how to do this in win32
+ (void)b;
+#else
+ int flags = fcntl(0, F_GETFL);
+ flags = (flags&~O_NONBLOCK) | (b?0:O_NONBLOCK);
+ fcntl(0, F_SETFL, flags);
+#endif
+}
+
+void Console::set_local_echo(bool e)
+{
+ if(!(mode&M_READ))
+ throw InvalidState("Local echo can only be set on input console");
+
+#ifdef WIN32
+ DWORD m;
+ GetConsoleMode(handle, &m);
+ SetConsoleMode(handle, (m&~ENABLE_ECHO_INPUT) | (e?ENABLE_ECHO_INPUT:0));
+#else
+ termios t;
+ tcgetattr(0, &t);
+ t.c_lflag = (t.c_lflag&~ECHO) | (e?ECHO:0);
+ tcsetattr(0, TCSADRAIN, &t);
+#endif
+}
+
+void Console::set_line_buffer(bool l)
+{
+ if(!(mode&M_READ))
+ throw InvalidState("Line buffering can only be set on input console");
+
+#ifdef WIN32
+ DWORD m;
+ GetConsoleMode(handle, &m);
+ SetConsoleMode(handle, (m&~ENABLE_LINE_INPUT) | (l?ENABLE_LINE_INPUT:0));
+#else
+ // XXX ICANON does more than just set line buffering, may need a bit more thought
+ termios t;
+ tcgetattr(0, &t);
+ t.c_lflag = (t.c_lflag&~ICANON) | (l?ICANON:0);
+ tcsetattr(0, TCSADRAIN, &t);
+#endif
+}
+
+void Console::get_size(unsigned &rows, unsigned &cols)
+{
+ if(!(mode&M_WRITE))
+ throw InvalidState("Size can only be queried from an output console");
+
+#ifdef WIN32
+ // XXX Figure out how to do this
+ rows = 24;
+ cols = 80;
+#else
+ struct winsize wsz;
+ ioctl(handle, TIOCGWINSZ, &wsz);
+ rows = wsz.ws_row;
+ cols = wsz.ws_col;
+#endif
+}
+
+unsigned Console::do_write(const char *buf, unsigned len)
+{
+ if(!(mode&M_WRITE))
+ throw InvalidState("Console is not writable");
+
+#ifdef WIN32
+ DWORD ret;
+ if(!WriteFile(handle, buf, len, &ret, 0))
+ throw SystemError("Writing to console failed", GetLastError());
+#else
+ int ret = ::write(handle, buf, len);
+ if(ret==-1)
+ throw SystemError("Writing to console failed", errno);
+#endif
+
+ return ret;
+}
+
+unsigned Console::do_read(char *buf, unsigned len)
+{
+ if(!(mode&M_READ))
+ throw InvalidState("Console is not readable");
+
+#ifdef WIN32
+ DWORD ret;
+ if(!ReadFile(handle, buf, len, &ret, 0))
+ throw SystemError("Reading from console failed", GetLastError());
+#else
+ int ret = ::read(handle, buf, len);
+ if(ret==-1)
+ {
+ if(errno==EAGAIN)
+ return 0;
+ else
+ throw SystemError("Reading from console failed", errno);
+ }
+ else if(ret==0)
+ eof_flag = true;
+#endif
+
+ return ret;
+}
+
+Handle Console::get_event_handle()
+{
+ return 0;
+}
+
+Console &Console::instance(unsigned n)
+{
+ static Console in(0);
+ static Console out(1);
+ static Console err(2);
+
+ switch(n)
+ {
+ case 0: return in;
+ case 1: return out;
+ case 2: return err;
+ }
+
+ throw InvalidParameterValue("Unknown Console instance requested");
+}
+
+Console &cin = Console::instance(0);
+Console &cout = Console::instance(1);
+Console &cerr = Console::instance(2);
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_CONSOLE_H_
+#define MSP_IO_CONSOLE_H_
+
+#include "base.h"
+
+namespace Msp {
+namespace IO {
+
+/**
+Provides access to standard input, output and error streams. This class can't
+be instantiated directly - use one of the cin, cout and cerr references
+instead.
+*/
+class Console: public Base
+{
+private:
+ Handle handle;
+
+ Console(unsigned);
+public:
+ ~Console();
+
+ virtual void set_block(bool);
+
+ /** If local echo is enabled, characters will appear on the console as they
+ are typed. Can only be used on an input Console. */
+ void set_local_echo(bool);
+
+ /** If line buffering is enabled, input will only be available when a
+ newline is encountered. On some systems, this may also enable line editing.
+ Can only be used on an input Console.
+ */
+ void set_line_buffer(bool);
+
+ /** Retrieves the size of the Console. Can only be used on an output
+ Console. */
+ void get_size(unsigned &rows, unsigned &cols);
+
+protected:
+ virtual unsigned do_write(const char *, unsigned);
+ virtual unsigned do_read(char *, unsigned);
+
+public:
+ virtual Handle get_event_handle();
+
+ static Console &instance(unsigned);
+};
+
+extern Console &cin;
+extern Console &cout;
+extern Console &cerr;
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#include <msp/time/units.h>
+#include "base.h"
+#include "eventdispatcher.h"
+#include "poll.h"
+
+namespace Msp {
+namespace IO {
+
+EventDispatcher::EventDispatcher()
+{ }
+
+void EventDispatcher::add(Base &obj)
+{
+ SlotMap::iterator i = objects.find(&obj);
+ if(i==objects.end())
+ {
+ i = objects.insert(SlotMap::value_type(&obj, Slot(&obj))).first;
+ i->second.evch_conn = obj.signal_events_changed.connect(sigc::bind(sigc::mem_fun(this, &EventDispatcher::object_events_changed), &obj));
+ i->second.del_conn = obj.signal_deleted.connect(sigc::bind(sigc::mem_fun(this, &EventDispatcher::object_deleted), &obj));
+
+ if(obj.get_events())
+ poller.set_object(obj, obj.get_events());
+ }
+}
+
+void EventDispatcher::remove(Base &obj)
+{
+ SlotMap::iterator i = objects.find(&obj);
+ if(i!=objects.end())
+ {
+ i->second.evch_conn.disconnect();
+ i->second.del_conn.disconnect();
+ objects.erase(i);
+
+ poller.set_object(obj, P_NONE);
+ }
+}
+
+void EventDispatcher::tick()
+{
+ if(objects.empty())
+ return;
+
+ if(poller.poll()>0)
+ dispatch();
+}
+
+void EventDispatcher::tick(const Time::TimeDelta &dt)
+{
+ if(objects.empty())
+ return;
+
+ if(poller.poll(dt)>0)
+ dispatch();
+}
+
+void EventDispatcher::object_events_changed(PollEvent ev, Base *obj)
+{
+ poller.set_object(*obj, ev);
+}
+
+void EventDispatcher::object_deleted(Base *obj)
+{
+ remove(*obj);
+}
+
+void EventDispatcher::dispatch()
+{
+ const Poller::SlotSeq &result = poller.get_result();
+ for(Poller::SlotSeq::const_iterator i=result.begin(); i!=result.end(); ++i)
+ i->object->event(i->events);
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef EVENTDISPATCHER_H_
+#define EVENTDISPATCHER_H_
+
+#include <sigc++/connection.h>
+#include <sigc++/trackable.h>
+#include "poll.h"
+
+namespace Msp {
+namespace IO {
+
+/**
+Put your I/O objects inside one of these to get signaled when something happens
+on some of them.
+*/
+class EventDispatcher: public sigc::trackable
+{
+private:
+ struct Slot
+ {
+ Base *obj;
+ sigc::connection evch_conn;
+ sigc::connection del_conn;
+
+ Slot(Base *o): obj(o) { }
+ };
+
+ typedef std::map<Base *, Slot> SlotMap;
+
+ Poller poller;
+ SlotMap objects;
+
+public:
+ EventDispatcher();
+
+ void add(Base &);
+ void remove(Base &);
+
+ /** Checks for and dispatches events. If there are no events available,
+ blocks until there are. */
+ void tick();
+
+ /** Checks for and dispatches events. If there are no events available,
+ waits at most the specified time before returning. */
+ void tick(const Time::TimeDelta &);
+
+private:
+ void object_events_changed(PollEvent, Base *);
+ void object_deleted(Base *);
+ void dispatch();
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifndef MSP_IO_EXCEPT_H_
+#define MSP_IO_EXCEPT_H_
+
+#include <msp/core/except.h>
+
+namespace Msp {
+namespace IO {
+
+class FileNotFound: public Exception
+{
+public:
+ FileNotFound(const std::string &w_, const std::string &f): Exception(w_), filename(f) { }
+ const std::string &get_filename() { return filename; }
+ ~FileNotFound() throw() { }
+private:
+ std::string filename;
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifndef WIN32
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+#include <msp/strings/formatter.h>
+#include "except.h"
+#include "file.h"
+
+using namespace std;
+
+namespace Msp {
+namespace IO {
+
+File::File(const string &fn, Mode m, CreateMode cm)
+{
+ if(!(m&M_RDWR))
+ throw InvalidParameterValue("Invalid read/write mode");
+ if(cm&~(C_CREATE|C_TRUNCATE))
+ throw InvalidParameterValue("Invalid create mode");
+
+ mode = m;
+
+#ifdef WIN32
+ int flags = 0;
+ int create_flags = OPEN_EXISTING;
+
+ if(mode&M_READ)
+ flags |= GENERIC_READ;
+ else if(mode&M_WRITE)
+ {
+ flags |= GENERIC_WRITE;
+
+ switch(static_cast<int>(cm))
+ {
+ case C_NONE: create_flags = OPEN_EXISTING; break;
+ case C_CREATE: create_flags = OPEN_ALWAYS; break;
+ case C_TRUNCATE: create_flags = TRUNCATE_EXISTING; break;
+ case C_CREATE+C_TRUNCATE: create_flags = CREATE_ALWAYS; break;
+ }
+ }
+
+ handle = CreateFile(fn.c_str(), flags, 0, 0, create_flags, FILE_ATTRIBUTE_NORMAL, 0);
+ if(handle==INVALID_HANDLE_VALUE)
+ {
+ int err = GetLastError();
+ if(err==ERROR_FILE_NOT_FOUND)
+ throw FileNotFound("Can't find file "+fn, fn);
+ else
+ throw SystemError(format("Can't open file '%s'", fn), GetLastError());
+ }
+#else
+ int flags = 0;
+ switch(mode&M_RDWR)
+ {
+ case M_READ: flags |= O_RDONLY; break;
+ case M_WRITE: flags |= O_WRONLY; break;
+ case M_RDWR: flags |= O_RDWR; break;
+ default:;
+ }
+
+ if(mode&M_WRITE)
+ {
+ if(cm&C_CREATE)
+ flags |= O_CREAT;
+ if(cm&C_TRUNCATE)
+ flags |= O_TRUNC;
+ }
+ if(mode&M_APPEND)
+ flags |= O_APPEND;
+ if(mode&M_NONBLOCK)
+ flags |= O_NONBLOCK;
+
+ handle = ::open(fn.c_str(), flags, 0666);
+ if(handle==-1)
+ {
+ int err = errno;
+ if(err==ENOENT)
+ throw FileNotFound("Can't find file "+fn, fn);
+ else
+ throw SystemError(format("Can't open file '%s'", fn), err);
+ }
+#endif
+
+ set_events(P_INPUT);
+}
+
+File::~File()
+{
+ close();
+}
+
+void File::close()
+{
+ if(handle==MSP_IO_INVALID_HANDLE)
+ return;
+
+ set_events(P_NONE);
+
+ signal_flush_required.emit();
+
+#ifdef WIN32
+ CloseHandle(handle);
+#else
+ ::close(handle);
+#endif
+
+ handle = MSP_IO_INVALID_HANDLE;
+ signal_closed.emit();
+}
+
+void File::set_block(bool b)
+{
+ check_access(M_NONE);
+
+ mode = (mode&~M_NONBLOCK);
+ if(b)
+ mode = (mode|M_NONBLOCK);
+#ifndef WIN32
+ int flags = fcntl(handle, F_GETFD);
+ fcntl(handle, F_SETFL, (flags&O_NONBLOCK)|(b?0:O_NONBLOCK));
+#endif
+}
+
+unsigned File::do_write(const char *buf, unsigned size)
+{
+ check_access(M_WRITE);
+
+ if(size==0)
+ return 0;
+
+#ifdef WIN32
+ if(mode&M_APPEND)
+ seek(0, S_END);
+ DWORD ret;
+ if(WriteFile(handle, buf, size, &ret, 0)==0)
+ throw SystemError("Writing to file failed", GetLastError());
+#else
+ int ret = ::write(handle, buf, size);
+ if(ret==-1)
+ {
+ if(errno==EAGAIN)
+ return 0;
+ else
+ throw SystemError("Writing to file failed", errno);
+ }
+#endif
+
+ return ret;
+}
+
+unsigned File::do_read(char *buf, unsigned size)
+{
+ check_access(M_READ);
+
+ if(size==0)
+ return 0;
+
+#ifdef WIN32
+ DWORD ret;
+ if(ReadFile(handle, buf, size, &ret, 0)==0)
+ throw SystemError("Reading from file failed", GetLastError());
+#else
+ int ret = ::read(handle, buf, size);
+ if(ret==-1)
+ {
+ if(errno==EAGAIN)
+ return 0;
+ else
+ throw SystemError("Reading from file failed", errno);
+ }
+#endif
+
+ if(ret==0)
+ {
+ eof_flag = true;
+ signal_end_of_file.emit();
+ }
+
+ return ret;
+}
+
+void File::sync()
+{
+#ifndef WIN32
+ signal_flush_required.emit();
+
+ fsync(handle);
+#endif
+}
+
+int File::seek(int off, SeekType st)
+{
+ check_access(M_NONE);
+
+ signal_flush_required.emit();
+
+ int type = sys_seek_type(st);
+#ifdef WIN32
+ DWORD ret = SetFilePointer(handle, off, 0, type);
+ if(ret==INVALID_SET_FILE_POINTER)
+ throw SystemError("Seek failed", GetLastError());
+#else
+ int ret = lseek(handle, off, type);
+ if(ret==-1)
+ throw SystemError("Seek failed", errno);
+#endif
+
+ eof_flag = false;
+
+ return ret;
+}
+
+int File::tell() const
+{
+ check_access(M_NONE);
+
+#ifdef WIN32
+ DWORD ret = SetFilePointer(handle, 0, 0, FILE_CURRENT);
+ if(ret==INVALID_SET_FILE_POINTER)
+ throw SystemError("Tell failed", GetLastError());
+#else
+ int ret = lseek(handle, 0, SEEK_CUR);
+ if(ret==-1)
+ throw SystemError("Tell failed", errno);
+#endif
+
+ return ret;
+}
+
+void File::check_access(Mode m) const
+{
+ if(handle==MSP_IO_INVALID_HANDLE)
+ throw InvalidState("File is not open");
+ if(m==M_READ && !(mode&M_READ))
+ throw InvalidState("File is not readable");
+ if(m==M_WRITE && !(mode&M_WRITE))
+ throw InvalidState("File is not writable");
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_FILE_H_
+#define MSP_IO_FILE_H_
+
+#include <string>
+#include "base.h"
+#include "buffered.h"
+#include "filtered.h"
+#include "seek.h"
+
+namespace Msp {
+namespace IO {
+
+/**
+A class for reading and writing files.
+
+Non-blocking mode is not supported on Win32.
+*/
+class File: public Base
+{
+public:
+ enum CreateMode
+ {
+ C_NONE = 0,
+ C_CREATE = 1,
+ C_TRUNCATE = 2
+ };
+
+private:
+ Handle handle;
+
+public:
+ /** Creates a new file object and opens it. If the create flag is set and
+ write access is requested and the file does exist, it is created. Otherwise
+ a missing file is an error. */
+ File(const std::string &, Mode = M_READ, CreateMode = CreateMode(C_CREATE+C_TRUNCATE));
+ virtual ~File();
+
+ /** Closes the file. Any attempt to access the file after this will cause
+ an exception to be thrown. */
+ void close();
+
+ void set_block(bool);
+
+protected:
+ virtual unsigned do_write(const char *, unsigned);
+ virtual unsigned do_read(char *, unsigned);
+
+public:
+ virtual void sync();
+
+ /** Changes the read/write offset of the file. Returns the new offset. */
+ virtual int seek(int, SeekType);
+
+ /** Returns the current read/write offset of the file. */
+ virtual int tell() const;
+
+ virtual Handle get_event_handle() { return handle; }
+
+private:
+ void check_access(Mode) const;
+};
+
+inline File::CreateMode operator|(File::CreateMode m, File::CreateMode n)
+{ return File::CreateMode(static_cast<int>(m)|static_cast<int>(n)); }
+
+inline File::CreateMode operator&(File::CreateMode m, File::CreateMode n)
+{ return File::CreateMode(static_cast<int>(m)&static_cast<int>(n)); }
+
+inline File::CreateMode operator~(File::CreateMode m)
+{ return File::CreateMode(~static_cast<int>(m)); }
+
+typedef Filtered<File, Buffered> BufferedFile;
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifndef MSP_IO_FILTERED_H_
+#define MSP_IO_FILTERED_H_
+
+namespace Msp {
+namespace IO {
+
+template<typename B, typename F>
+class Filtered: public B
+{
+private:
+ struct Activator
+ {
+ Filtered &f;
+
+ Activator(Filtered &f_): f(f_) { f.active = true; }
+ ~Activator() { f.active = false; }
+ };
+
+ F filter;
+ bool active;
+
+public:
+ Filtered(): filter(*this), active(false) { }
+ ~Filtered() { active = true; }
+
+ template<typename A0>
+ Filtered(A0 a0): B(a0), filter(*this), active(false) { }
+
+ template<typename A0, typename A1>
+ Filtered(A0 a0, A1 a1): B(a0, a1), filter(*this), active(false) { }
+
+protected:
+ virtual unsigned do_write(const char *b, unsigned s)
+ {
+ if(!active)
+ {
+ Activator a(*this);
+ return filter.write(b, s);
+ }
+ else
+ return B::do_write(b, s);
+ }
+
+ virtual unsigned do_read(char *b, unsigned s)
+ {
+ if(!active)
+ {
+ Activator a(*this);
+ return filter.read(b, s);
+ }
+ else
+ return B::do_read(b, s);
+ }
+
+public:
+ virtual unsigned put(char c) { return filter.put(c); }
+ virtual bool getline(std::string &l) { return filter.getline(l); }
+ virtual int get() { return filter.get(); }
+
+ F &get_filter() { return filter; }
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#include <algorithm>
+#include <cstring>
+#include "except.h"
+#include "memory.h"
+
+using namespace std;
+
+namespace Msp {
+namespace IO {
+
+Memory::Memory(char *d, unsigned s)
+{
+ init(d, d+s, M_RDWR);
+}
+
+Memory::Memory(char *b, char *e)
+{
+ init(b, e, M_RDWR);
+}
+
+Memory::Memory(const char *cd, unsigned s)
+{
+ char *d = const_cast<char *>(cd);
+ init(d, d+s, M_READ);
+}
+
+Memory::Memory(const char *b, const char *e)
+{
+ init(const_cast<char *>(b), const_cast<char *>(e), M_READ);
+}
+
+void Memory::init(char *b, char *e, Mode m)
+{
+ begin = b;
+ end = e;
+ pos = begin;
+ mode = m;
+}
+
+unsigned Memory::do_write(const char *buf, unsigned size)
+{
+ check_mode(M_WRITE);
+
+ size = min<unsigned>(size, end-pos);
+ memcpy(pos, buf, size);
+ pos += size;
+ return size;
+}
+
+unsigned Memory::do_read(char *buf, unsigned size)
+{
+ if(pos==end)
+ {
+ eof_flag = true;
+ return 0;
+ }
+
+ size = min<unsigned>(size, end-pos);
+ memcpy(buf, pos, size);
+ pos += size;
+ return size;
+}
+
+unsigned Memory::put(char c)
+{
+ check_mode(M_WRITE);
+ *pos++ = c;
+ return 1;
+}
+
+bool Memory::getline(string &line)
+{
+ char *nl = find(pos, end, '\n');
+ line.assign(pos, nl);
+ bool result = (nl!=pos);
+ pos = nl;
+ return result;
+}
+
+int Memory::get()
+{
+ if(pos==end)
+ {
+ eof_flag = true;
+ return -1;
+ }
+
+ return static_cast<unsigned char>(*pos++);
+}
+
+unsigned Memory::seek(int off, SeekType type)
+{
+ char *new_pos;
+ if(type==S_BEG)
+ new_pos = begin+off;
+ else if(type==S_CUR)
+ new_pos = pos+off;
+ else if(type==S_END)
+ new_pos = end+off;
+ else
+ throw InvalidParameterValue("Invalid seek type");
+
+ if(new_pos<begin || new_pos>end)
+ throw InvalidParameterValue("Invalid seek offset");
+
+ pos = new_pos;
+ return pos-begin;
+}
+
+Handle Memory::get_event_handle()
+{
+ throw Exception("Memory doesn't support events");
+}
+
+void Memory::check_mode(Mode m) const
+{
+ if(m==M_WRITE)
+ {
+ if(!(mode&M_WRITE))
+ throw InvalidState("Memory is not writable");
+ if(pos==end)
+ throw InvalidState("Attempt to write past end of Memory");
+ }
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_MEMORY_H_
+#define MSP_IO_MEMORY_H_
+
+#include "base.h"
+#include "seek.h"
+
+namespace Msp {
+namespace IO {
+
+class Memory: public Base
+{
+private:
+ char *begin;
+ char *end;
+ char *pos;
+
+public:
+ Memory(char *, unsigned);
+ Memory(char *, char *);
+ Memory(const char *, unsigned);
+ Memory(const char *, const char *);
+private:
+ void init(char *, char *, Mode);
+
+ virtual unsigned do_write(const char *, unsigned);
+ virtual unsigned do_read(char *, unsigned);
+public:
+ virtual unsigned put(char);
+ virtual bool getline(std::string &);
+ virtual int get();
+
+ unsigned seek(int, SeekType);
+ unsigned tell() const { return pos-begin; }
+
+ virtual Handle get_event_handle();
+
+private:
+ void check_mode(Mode) const;
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifndef MSP_IO_MODE_H_
+#define MSP_IO_MODE_H_
+
+namespace Msp {
+namespace IO {
+
+enum Mode
+{
+ M_NONE = 0,
+ M_READ = 1,
+ M_WRITE = 2,
+ M_RDWR = M_READ|M_WRITE,
+ M_APPEND = 4,
+ M_NONBLOCK = 8
+};
+
+inline Mode operator|(Mode m, Mode n)
+{ return Mode(static_cast<int>(m)|static_cast<int>(n)); }
+
+inline Mode operator&(Mode m, Mode n)
+{ return Mode(static_cast<int>(m)&static_cast<int>(n)); }
+
+inline Mode operator~(Mode m)
+{ return Mode(~static_cast<int>(m)); }
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifndef WIN32
+#include <fcntl.h>
+#include <errno.h>
+#endif
+#include <msp/strings/formatter.h>
+#include "pipe.h"
+
+using namespace std;
+
+namespace Msp {
+namespace IO {
+
+Pipe::Pipe()
+{
+#ifdef WIN32
+ string name = format("\\\\.\\pipe\\%u.%p", GetCurrentProcessId(), this);
+ handle[0] = CreateNamedPipe(name.c_str(), PIPE_ACCESS_INBOUND|FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 1, 1024, 1024, 0, 0);
+ if(handle[0]==INVALID_HANDLE_VALUE)
+ throw SystemError("Unable to create pipe", GetLastError());
+
+ handle[1] = CreateFile(name.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
+ if(handle[1]==INVALID_HANDLE_VALUE)
+ {
+ unsigned err = GetLastError();
+ CloseHandle(handle[0]);
+ throw SystemError("Unable to create pipe", err);
+ }
+
+ overlapped = 0;
+ event = CreateEvent(0, true, false, 0);
+ buf_size = 1024;
+ buffer = new char[buf_size];
+ buf_avail = 0;
+ buf_next = buffer;
+#else
+ if(pipe(handle)==-1)
+ throw SystemError("Unable to create pipe", errno);
+#endif
+
+ set_events(P_INPUT);
+}
+
+Pipe::~Pipe()
+{
+ close();
+}
+
+void Pipe::close()
+{
+ set_events(P_NONE);
+
+ signal_flush_required.emit();
+#ifdef WIN32
+ CloseHandle(handle[0]);
+ CloseHandle(handle[1]);
+#else
+ ::close(handle[0]);
+ ::close(handle[1]);
+ signal_closed.emit();
+#endif
+}
+
+void Pipe::set_block(bool b)
+{
+ mode = (mode&~M_NONBLOCK);
+ if(b)
+ mode = (mode|M_NONBLOCK);
+
+#ifndef WIN32
+ int flags = fcntl(handle[0], F_GETFD);
+ fcntl(handle[0], F_SETFL, (flags&O_NONBLOCK)|(b?0:O_NONBLOCK));
+ flags = fcntl(handle[1], F_GETFD);
+ fcntl(handle[1], F_SETFL, (flags&O_NONBLOCK)|(b?0:O_NONBLOCK));
+#endif
+}
+
+unsigned Pipe::do_write(const char *buf, unsigned size)
+{
+ if(size==0)
+ return 0;
+
+#ifdef WIN32
+ DWORD ret;
+ if(!WriteFile(handle[1], buf, size, &ret, 0))
+ throw SystemError("Writing to pipe failed", GetLastError());
+#else
+ int ret = ::write(handle[1], buf, size);
+ if(ret==-1)
+ {
+ if(errno==EAGAIN)
+ return 0;
+ else
+ throw SystemError("Writing to pipe failed", errno);
+ }
+#endif
+
+ return ret;
+}
+
+unsigned Pipe::do_read(char *buf, unsigned size)
+{
+ if(size==0)
+ return 0;
+
+#ifdef WIN32
+ // Initiate overlapped read if needed
+ get_event_handle();
+
+ if(overlapped)
+ {
+ DWORD ret;
+ if(!GetOverlappedResult(handle[0], overlapped, &ret, !buf_avail))
+ throw SystemError("Reading from pipe failed", GetLastError());
+ else
+ {
+ buf_avail += ret;
+ delete overlapped;
+ overlapped = 0;
+ }
+ }
+
+ unsigned ret = min(buf_avail, size);
+ memcpy(buf, buf_next, ret);
+ buf_next += ret;
+ buf_avail -= ret;
+
+ // Initiate another overlapped read in case someone is polling us
+ get_event_handle();
+#else
+ int ret = ::read(handle[0], buf, size);
+ if(ret==-1)
+ {
+ if(errno==EAGAIN)
+ return 0;
+ else
+ throw SystemError("Reading from pipe failed", errno);
+ }
+#endif
+
+ if(ret==0)
+ {
+ eof_flag = true;
+ signal_end_of_file.emit();
+ }
+
+ return ret;
+}
+
+Handle Pipe::get_event_handle()
+{
+#ifdef WIN32
+ if(!overlapped && !buf_avail)
+ {
+ overlapped = new OVERLAPPED;
+ memset(overlapped, 0, sizeof(OVERLAPPED));
+ overlapped->hEvent = event;
+
+ DWORD ret;
+ buf_next = buffer;
+ if(!ReadFile(handle[0], buffer, buf_size, &ret, overlapped))
+ {
+ unsigned err = GetLastError();
+ if(err!=ERROR_IO_PENDING)
+ throw SystemError("Failed to start an overlapped read", err);
+ }
+ else
+ {
+ buf_avail = ret;
+ delete overlapped;
+ overlapped = 0;
+ SetEvent(event);
+ }
+ }
+
+ return event;
+#else
+ return handle[0];
+#endif
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_PIPE_H_
+#define MSP_IO_PIPE_H_
+
+#include "base.h"
+
+namespace Msp {
+namespace IO {
+
+class Pipe: public Base
+{
+private:
+ Handle handle[2];
+#ifdef WIN32
+ OVERLAPPED *overlapped;
+ Handle event;
+ unsigned buf_size;
+ char *buffer;
+ unsigned buf_avail;
+ char *buf_next;
+#endif
+
+public:
+ Pipe();
+ ~Pipe();
+
+ void close();
+
+ void set_block(bool);
+
+protected:
+ virtual unsigned do_write(const char *, unsigned);
+ virtual unsigned do_read(char *, unsigned);
+
+public:
+ virtual Handle get_event_handle();
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#include <errno.h>
+#include <msp/strings/formatter.h>
+#include <msp/time/units.h>
+#include "except.h"
+#include "base.h"
+#include "poll.h"
+
+namespace {
+
+using namespace Msp;
+using namespace Msp::IO;
+
+inline int sys_poll_event(PollEvent event)
+{
+ int result = 0;
+
+ if(event&~(P_INPUT|P_PRIO|P_OUTPUT))
+ throw InvalidParameterValue("Invalid poll events");
+
+#ifndef WIN32
+ if(event&P_INPUT)
+ result |= POLLIN;
+ if(event&P_PRIO)
+ result |= POLLPRI;
+ if(event&P_OUTPUT)
+ result |= POLLOUT;
+#endif
+
+ return result;
+}
+
+inline PollEvent poll_event_from_sys(int event)
+{
+ PollEvent result = P_NONE;
+
+#ifdef WIN32
+ // Stop the compiler from complaining about unused parameter
+ event = event;
+#else
+ if(event&POLLIN)
+ result = result|P_INPUT;
+ if(event&POLLPRI)
+ result = result|P_PRIO;
+ if(event&POLLOUT)
+ result = result|P_OUTPUT;
+ if(event&POLLERR)
+ result = result|P_ERROR;
+#endif
+
+ return result;
+}
+
+inline PollEvent do_poll(Base &obj, PollEvent pe, int timeout)
+{
+#ifdef WIN32
+ if(timeout<0)
+ timeout = INFINITE;
+
+ DWORD ret = WaitForSingleObject(obj.get_event_handle(), timeout);
+ if(ret==WAIT_OBJECT_0)
+ return pe;
+ else if(ret==WAIT_FAILED)
+ throw SystemError("Poll failed", GetLastError());
+
+ return P_NONE;
+#else
+ pollfd pfd = {obj.get_event_handle(), sys_poll_event(pe), 0};
+
+ int ret = ::poll(&pfd, 1, timeout);
+ if(ret==-1)
+ {
+ if(errno==EINTR)
+ return P_NONE;
+ else
+ throw SystemError("Poll failed", errno);
+ }
+
+ return poll_event_from_sys(pfd.revents);
+#endif
+}
+
+}
+
+namespace Msp {
+namespace IO {
+
+Poller::Poller():
+ pfd_dirty(false)
+{ }
+
+void Poller::set_object(Base &obj, PollEvent ev)
+{
+ // Verify that the object has an event handle
+ if(ev)
+ obj.get_event_handle();
+
+ SlotMap::iterator i = objects.find(&obj);
+ if(i!=objects.end())
+ {
+ if(ev)
+ i->second.events = ev;
+ else
+ objects.erase(i);
+
+ pfd_dirty = true;
+ }
+ else if(ev)
+ {
+#ifdef WIN32
+ if(objects.size()>=MAXIMUM_WAIT_OBJECTS)
+ throw InvalidState("Maximum number of wait objects reached");
+#endif
+ objects.insert(SlotMap::value_type(&obj, Slot(&obj, ev)));
+
+ pfd_dirty = true;
+ }
+}
+
+int Poller::poll()
+{
+ return do_poll(-1);
+}
+
+int Poller::poll(const Time::TimeDelta &timeout)
+{
+ if(timeout<Time::zero)
+ throw InvalidParameterValue("Invalid timeout");
+
+ return do_poll(static_cast<int>(timeout/Time::msec));
+}
+
+void Poller::rebuild_pfd()
+{
+ pfd.clear();
+
+ pollfd p;
+
+ for(SlotMap::iterator i=objects.begin(); i!=objects.end(); ++i)
+ {
+ p.fd = i->second.object->get_event_handle();
+#ifndef WIN32
+ p.events = sys_poll_event(i->second.events);
+#endif
+ pfd.push_back(p);
+ }
+
+ pfd_dirty = false;
+}
+
+int Poller::do_poll(int timeout)
+{
+ if(pfd_dirty)
+ rebuild_pfd();
+
+ poll_result.clear();
+
+#ifdef WIN32
+ if(timeout<0)
+ timeout = INFINITE;
+
+ DWORD ret = WaitForMultipleObjects(pfd.size(), &pfd.front().fd, false, timeout);
+ if(/*ret>=WAIT_OBJECT_0 &&*/ ret<WAIT_OBJECT_0+pfd.size())
+ {
+ SlotMap::iterator i = objects.begin();
+ advance(i, ret-WAIT_OBJECT_0);
+ poll_result.push_back(Slot(i->second.object, i->second.events));
+
+ return 1;
+ }
+ else if(ret==WAIT_FAILED)
+ throw SystemError("Poll failed", GetLastError());
+
+ return 0;
+#else
+ int ret = ::poll(&pfd.front(), pfd.size(), timeout);
+ if(ret==-1)
+ {
+ if(errno==EINTR)
+ return 0;
+ else
+ throw SystemError("Poll failed", errno);
+ }
+
+ int n = ret;
+ SlotMap::iterator j = objects.begin();
+ for(std::vector<pollfd>::iterator i=pfd.begin(); (i!=pfd.end() && n>0); ++i,++j)
+ if(i->revents)
+ {
+ poll_result.push_back(Slot(j->second.object, poll_event_from_sys(i->revents)));
+ --n;
+ }
+
+ return ret;
+#endif
+}
+
+
+PollEvent poll(Base &obj, PollEvent pe)
+{
+ return do_poll(obj, pe, -1);
+}
+
+PollEvent poll(Base &obj, PollEvent pe, const Time::TimeDelta &timeout)
+{
+ if(timeout<Time::zero)
+ throw InvalidParameterValue("Invalid timeout");
+
+ return do_poll(obj, pe, static_cast<int>(timeout/Time::msec));
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_POLL_H_
+#define MSP_IO_POLL_H_
+
+#ifndef WIN32
+#include <poll.h>
+#endif
+#include <list>
+#include <map>
+#include <vector>
+#include <msp/time/timedelta.h>
+#include "types.h"
+
+namespace Msp {
+namespace IO {
+
+class Base;
+
+enum PollEvent
+{
+ P_NONE = 0,
+ P_INPUT = 1,
+ P_PRIO = 2,
+ P_OUTPUT = 4,
+ P_ERROR = 8
+};
+
+inline PollEvent operator|(PollEvent e, PollEvent f)
+{ return PollEvent(static_cast<int>(e)|static_cast<int>(f)); }
+
+inline PollEvent operator&(PollEvent e, PollEvent f)
+{ return PollEvent(static_cast<int>(e)&static_cast<int>(f)); }
+
+inline PollEvent operator~(PollEvent e)
+{ return PollEvent(~static_cast<int>(e)); }
+
+
+class Poller
+{
+public:
+ struct Slot
+ {
+ Base *object;
+ PollEvent events;
+
+ Slot(Base *o, PollEvent e): object(o), events(e) { }
+ };
+
+ typedef std::list<Slot> SlotSeq;
+private:
+ typedef std::map<Base *, Slot> SlotMap;
+
+#ifdef WIN32
+ struct pollfd
+ {
+ Handle fd;
+ };
+#endif
+
+ SlotMap objects;
+ std::vector<pollfd> pfd;
+ bool pfd_dirty;
+ SlotSeq poll_result;
+
+ void rebuild_pfd();
+ int do_poll(int);
+
+public:
+ Poller();
+
+ void set_object(Base &, PollEvent);
+ int poll();
+ int poll(const Time::TimeDelta &);
+ const SlotSeq &get_result() const { return poll_result; }
+};
+
+PollEvent poll(Base &, PollEvent);
+PollEvent poll(Base &, PollEvent, const Time::TimeDelta &);
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifndef MSP_IO_PRINT_H_
+#define MSP_IO_PRINT_H_
+
+#include <msp/strings/formatter.h>
+#include "base.h"
+#include "console.h"
+
+namespace Msp {
+namespace IO {
+
+/**
+Writes a string to an I/O object. Same as o.write(f). Provided for
+completeness with the other print functions.
+*/
+inline unsigned print(Base &o, const std::string &f)
+{ return o.write(f); }
+
+template<typename A1>
+inline unsigned print(Base &o, const std::string &f, A1 a1)
+{ return print(o, format(f, a1)); }
+
+template<typename A1, typename A2>
+inline unsigned print(Base &o, const std::string &f, A1 a1, A2 a2)
+{ return print(o, format(f, a1, a2)); }
+
+template<typename A1, typename A2, typename A3>
+inline unsigned print(Base &o, const std::string &f, A1 a1, A2 a2, A3 a3)
+{ return print(o, format(f, a1, a2, a3)); }
+
+template<typename A1, typename A2, typename A3, typename A4>
+inline unsigned print(Base &o, const std::string &f, A1 a1, A2 a2, A3 a3, A4 a4)
+{ return print(o, format(f, a1, a2, a3, a4)); }
+
+template<typename A1, typename A2, typename A3, typename A4, typename A5>
+inline unsigned print(Base &o, const std::string &f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5)
+{ return print(o, format(f, a1, a2, a3, a4, a5)); }
+
+/* The following functions print to console */
+
+inline unsigned print(const std::string &f)
+{ return print(cout, f); }
+
+template<typename A1>
+inline unsigned print(const std::string &f, A1 a1)
+{ return print(cout, f, a1); }
+
+template<typename A1, typename A2>
+inline unsigned print(const std::string &f, A1 a1, A2 a2)
+{ return print(cout, f, a1, a2); }
+
+template<typename A1, typename A2, typename A3>
+inline unsigned print(const std::string &f, A1 a1, A2 a2, A3 a3)
+{ return print(cout, f, a1, a2, a3); }
+
+template<typename A1, typename A2, typename A3, typename A4>
+inline unsigned print(const std::string &f, A1 a1, A2 a2, A3 a3, A4 a4)
+{ return print(cout, f, a1, a2, a3, a4); }
+
+template<typename A1, typename A2, typename A3, typename A4, typename A5>
+inline unsigned print(const std::string &f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5)
+{ return print(cout, f, a1, a2, a3, a4, a5); }
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifdef WIN32
+#include <windows.h>
+#endif
+#include "except.h"
+#include "seek.h"
+
+namespace Msp {
+namespace IO {
+
+int sys_seek_type(SeekType st)
+{
+#ifdef WIN32
+ if(st==S_BEG)
+ return FILE_BEGIN;
+ else if(st==S_CUR)
+ return FILE_CURRENT;
+ else if(st==S_END)
+ return FILE_END;
+#else
+ if(st==S_BEG)
+ return SEEK_SET;
+ else if(st==S_CUR)
+ return SEEK_CUR;
+ else if(st==S_END)
+ return SEEK_END;
+#endif
+
+ throw InvalidParameterValue("Invalid seek type");
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_SEEK_H_
+#define MSP_IO_SEEK_H_
+
+namespace Msp {
+namespace IO {
+
+enum SeekType
+{
+ S_BEG,
+ S_CUR,
+ S_END
+};
+
+extern int sys_seek_type(SeekType);
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <termios.h>
+#include <fcntl.h>
+#include <errno.h>
+#endif
+#include <msp/strings/formatter.h>
+#include "except.h"
+#include "serial.h"
+
+using namespace std;
+
+namespace {
+
+using namespace Msp;
+using namespace Msp::IO;
+
+#ifdef WIN32
+typedef DCB DeviceState;
+#else
+typedef termios DeviceState;
+#endif
+
+void get_state(Handle handle, DeviceState &state)
+{
+#ifdef WIN32
+ GetCommState(handle, &state);
+#else
+ tcgetattr(handle, &state);
+#endif
+}
+
+void set_state(Handle handle, DeviceState &state)
+{
+#ifdef WIN32
+ if(SetCommState(handle, &state)==0)
+ throw SystemError("Cannot set serial port parameters", GetLastError());
+#else
+ if(tcsetattr(handle, TCSADRAIN, &state)==-1)
+ throw SystemError("Cannot set serial port parameters", errno);
+#endif
+}
+
+void set_baud_rate(DeviceState &state, unsigned baud)
+{
+#ifdef WIN32
+ state.BaudRate = baud;
+#else
+ speed_t speed;
+ switch(baud)
+ {
+ case 0: speed = B0; break;
+ case 50: speed = B50; break;
+ case 75: speed = B75; break;
+ case 110: speed = B110; break;
+ case 134: speed = B134; break;
+ case 150: speed = B150; break;
+ case 200: speed = B200; break;
+ case 300: speed = B300; break;
+ case 600: speed = B600; break;
+ case 1200: speed = B1200; break;
+ case 1800: speed = B1800; break;
+ case 2400: speed = B2400; break;
+ case 4800: speed = B4800; break;
+ case 9600: speed = B9600; break;
+ case 19200: speed = B19200; break;
+ case 38400: speed = B38400; break;
+ case 57600: speed = B57600; break;
+ case 115200: speed = B115200; break;
+ case 230400: speed = B230400; break;
+ default: throw InvalidParameterValue("Invalid baud rate");
+ }
+
+ cfsetospeed(&state, speed);
+ cfsetispeed(&state, speed);
+#endif
+}
+
+void set_data_bits(DeviceState &state, unsigned bits)
+{
+#ifdef WIN32
+ state.ByteSize = bits;
+#else
+ tcflag_t flag;
+ switch(bits)
+ {
+ case 5: flag = CS5; break;
+ case 6: flag = CS6; break;
+ case 7: flag = CS7; break;
+ case 8: flag = CS8; break;
+ default: throw InvalidParameterValue("Invalid data bit count");
+ }
+
+ state.c_cflag = (state.c_cflag&~CSIZE)|flag;
+#endif
+}
+
+void set_parity(DeviceState &state, Serial::Parity par)
+{
+#ifdef WIN32
+ switch(par)
+ {
+ case Serial::NONE: state.Parity = NOPARITY; break;
+ case Serial::EVEN: state.Parity = EVENPARITY; break;
+ case Serial::ODD: state.Parity = ODDPARITY; break;
+ default: throw InvalidParameterValue("Invalid parity");
+ }
+#else
+ tcflag_t flag;
+ switch(par)
+ {
+ case Serial::NONE: flag = 0; break;
+ case Serial::EVEN: flag = PARENB; break;
+ case Serial::ODD: flag = PARENB|PARODD; break;
+ default: throw InvalidParameterValue("Invalid parity");
+ }
+
+ state.c_cflag = (state.c_cflag&~(PARENB|PARODD))|flag;
+#endif
+}
+
+void set_stop_bits(DeviceState &state, unsigned bits)
+{
+#ifdef WIN32
+ switch(bits)
+ {
+ case 1: state.StopBits = ONESTOPBIT; break;
+ case 2: state.StopBits = TWOSTOPBITS; break;
+ default: throw InvalidParameterValue("Invalid stop bit count");
+ }
+#else
+ tcflag_t flag;
+ switch(bits)
+ {
+ case 1: flag = 0; break;
+ case 2: flag = CSTOPB; break;
+ default: throw InvalidParameterValue("Invalid stop bit count");
+ }
+
+ state.c_cflag = (state.c_cflag&~CSTOPB)|flag;
+#endif
+}
+
+}
+
+
+namespace Msp {
+namespace IO {
+
+Serial::Serial(const string &descr)
+{
+ string::size_type comma = descr.find(',');
+ string port = descr.substr(0, comma);
+
+#ifdef WIN32
+ port = "\\\\.\\"+port;
+
+ handle = CreateFile(port.c_str(), GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+ if(handle==INVALID_HANDLE_VALUE)
+ throw SystemError(format("Can't open serial port '%s'", port), GetLastError());
+ mode = M_READ|M_WRITE;
+
+ COMMTIMEOUTS timeouts;
+ timeouts.ReadIntervalTimeout = MAXDWORD;
+ timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
+ timeouts.ReadTotalTimeoutConstant = MAXDWORD-1;
+ timeouts.WriteTotalTimeoutMultiplier = 0;
+ timeouts.WriteTotalTimeoutConstant = 0;
+ SetCommTimeouts(handle, &timeouts);
+#else
+ if(port.compare(0, 5, "/dev/"))
+ port = "/dev/"+port;
+
+ handle = open(port.c_str(), O_RDWR);
+ if(handle==-1)
+ throw SystemError(format("Can't open serial port '%s'", port), errno);
+ mode = M_READ|M_WRITE;
+
+ termios t;
+ tcgetattr(handle, &t);
+ t.c_lflag &= ~(ECHO|ICANON);
+ t.c_oflag &= ~OPOST;
+ tcsetattr(handle, TCSADRAIN, &t);
+#endif
+
+ if(comma!=string::npos)
+ {
+ try
+ {
+ set_parameters(descr.substr(comma+1));
+ }
+ catch(...)
+ {
+ close();
+ throw;
+ }
+ }
+
+ set_events(P_INPUT);
+}
+
+Serial::~Serial()
+{
+ close();
+}
+
+void Serial::close()
+{
+#ifdef WIN32
+ CloseHandle(handle);
+#else
+ ::close(handle);
+#endif
+}
+
+void Serial::set_block(bool b)
+{
+ if(b)
+ mode = mode|M_NONBLOCK;
+ else
+ mode = mode&~M_NONBLOCK;
+
+#ifndef WIN32
+ int flags = fcntl(handle, F_GETFD);
+ fcntl(handle, F_SETFL, (flags&O_NONBLOCK)|(b?0:O_NONBLOCK));
+#endif
+}
+
+void Serial::set_baud_rate(unsigned rate)
+{
+ DeviceState state;
+ get_state(handle, state);
+ ::set_baud_rate(state, rate);
+ set_state(handle, state);
+}
+
+void Serial::set_data_bits(unsigned bits)
+{
+ DeviceState state;
+ get_state(handle, state);
+ ::set_data_bits(state, bits);
+ set_state(handle, state);
+}
+
+void Serial::set_parity(Parity par)
+{
+ DeviceState state;
+ get_state(handle, state);
+ ::set_parity(state, par);
+ set_state(handle, state);
+}
+
+void Serial::set_stop_bits(unsigned bits)
+{
+ DeviceState state;
+ get_state(handle, state);
+ ::set_stop_bits(state, bits);
+ set_state(handle, state);
+}
+
+void Serial::set_parameters(const string ¶ms)
+{
+ unsigned i;
+ for(i=0; i<params.size() && isdigit(params[i]); ++i) ;
+ if(i+4!=params.size() || params[i]!=',')
+ throw InvalidParameterValue("Invalid parameter string");
+ if(params[i+1]<'5' || params[i+1]>'8')
+ throw InvalidParameterValue("Invalid data bit count");
+ if(params[i+2]!='N' && params[i+2]!='E' && params[i+2]!='O')
+ throw InvalidParameterValue("Invalid parity");
+ if(params[i+3]!='1' && params[i+3]!='2')
+ throw InvalidParameterValue("Invalid stop bit count");
+
+ DeviceState state;
+ get_state(handle, state);
+ ::set_baud_rate(state, lexical_cast<unsigned>(params.substr(0, i)));
+ ::set_data_bits(state, params[i+1]-'0');
+ ::set_parity(state, (params[i+2]=='E' ? EVEN : params[i+2]=='O' ? ODD : NONE));
+ ::set_stop_bits(state, params[i+3]-'0');
+ set_state(handle, state);
+}
+
+unsigned Serial::do_write(const char *buf, unsigned size)
+{
+ if(size==0)
+ return 0;
+
+#ifdef WIN32
+ DWORD ret;
+ if(WriteFile(handle, buf, size, &ret, 0)==0)
+ throw SystemError("Writing to serial port failed", GetLastError());
+#else
+ int ret = ::write(handle, buf, size);
+ if(ret==-1)
+ {
+ if(errno==EAGAIN)
+ return 0;
+ else
+ throw SystemError("Writing to serial port failed", errno);
+ }
+#endif
+
+ return ret;
+}
+
+unsigned Serial::do_read(char *buf, unsigned size)
+{
+ if(size==0)
+ return 0;
+
+#ifdef WIN32
+ DWORD ret;
+ if(ReadFile(handle, buf, size, &ret, 0)==0)
+ throw SystemError("Reading from serial port failed", GetLastError());
+#else
+ int ret = ::read(handle, buf, size);
+ if(ret==-1)
+ {
+ if(errno==EAGAIN)
+ return 0;
+ else
+ throw SystemError("Reading from serial port failed", errno);
+ }
+#endif
+
+ return ret;
+}
+
+Handle Serial::get_event_handle()
+{
+#ifdef WIN32
+ throw Exception("Serial port events not supported on win32 yet");
+#else
+ return handle;
+#endif
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_SERIAL_H_
+#define MSP_IO_SERIAL_H_
+
+#include "base.h"
+
+namespace Msp {
+namespace IO {
+
+class Serial: public Base
+{
+public:
+ enum Parity
+ {
+ NONE,
+ EVEN,
+ ODD
+ };
+
+private:
+ Handle handle;
+
+public:
+ Serial(const std::string &);
+ virtual ~Serial();
+
+private:
+ void close();
+
+public:
+ virtual void set_block(bool);
+
+ void set_baud_rate(unsigned);
+ void set_data_bits(unsigned);
+ void set_parity(Parity);
+ void set_stop_bits(unsigned);
+ void set_parameters(const std::string &);
+
+private:
+ virtual unsigned do_write(const char *, unsigned);
+ virtual unsigned do_read(char *, unsigned);
+
+public:
+ virtual Handle get_event_handle();
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#ifndef MSP_IO_TYPES_H_
+#define MSP_IO_TYPES_H_
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+namespace Msp {
+namespace IO {
+
+#ifdef WIN32
+typedef HANDLE Handle;
+#define MSP_IO_INVALID_HANDLE INVALID_HANDLE_VALUE
+#else
+typedef int Handle;
+#define MSP_IO_INVALID_HANDLE -1
+#endif
+
+} // namespace IO
+} // namespace Msp
+
+#endif
--- /dev/null
+#include "base.h"
+#include "utils.h"
+
+namespace Msp {
+namespace IO {
+
+unsigned read_all(Base &obj, char *buf, unsigned size)
+{
+ unsigned pos = 0;
+ while(pos<size)
+ pos += obj.read(buf+pos, size-pos);
+
+ return pos;
+}
+
+} // namespace IO
+} // namespace Msp
--- /dev/null
+#ifndef MSP_IO_UTILS_H_
+#define MSP_IO_UTILS_H_
+
+namespace Msp {
+namespace IO {
+
+class Base;
+
+/** Reads data from an object. Does not return until the requested amount of
+data is read, regardless of the blocking mode of the object.
+
+Note: If the data is not immediately available and the object is in non-blocking
+mode, this function effectively becomes a busyloop until it can get more
+data. */
+unsigned read_all(Base &, char *, unsigned);
+
+} // namespace IO
+} // namespace Msp
+
+#endif