diff --git a/server/CConfig.cpp b/server/CConfig.cpp index 7c919277..a09e4c7f 100644 --- a/server/CConfig.cpp +++ b/server/CConfig.cpp @@ -1,5 +1,12 @@ #include "CConfig.h" #include +// FIXME -- fix this with automake and config.h +#if !defined(CONFIG_PLATFORM_LINUX) +#include +#include +#else +#include +#endif // // CConfig @@ -75,6 +82,50 @@ void CConfig::disconnect(const CString& srcName, index->second.m_neighbor[srcSide - kFirstDirection].erase(); } +bool CConfig::isValidScreenName(const CString& name) const +{ + // name is valid if matches validname + // name ::= [A-Za-z0-9] | [A-Za-z0-9][-A-Za-z0-9]*[A-Za-z0-9] + // domain ::= . name + // validname ::= name domain* + + // check each dot separated part + CString::size_type b = 0; + for (;;) { + // find end of part + CString::size_type e = name.find('.', b); + if (e == CString::npos) { + e = name.size(); + } + + // part may not be empty + if (e - b < 1) { + return false; + } + + // check first and last characters + if (!isalnum(name[b]) || !isalnum(name[e - 1])) { + return false; + } + + // check interior characters + for (CString::size_type i = b; i < e; ++i) { + if (!isalnum(name[i]) && name[i] != '-') { + return false; + } + } + + // next part + if (e == name.size()) { + // no more parts + break; + } + b = e + 1; + } + + return true; +} + CConfig::const_iterator CConfig::begin() const { return const_iterator(m_map.begin()); @@ -85,6 +136,11 @@ CConfig::const_iterator CConfig::end() const return const_iterator(m_map.end()); } +bool CConfig::isScreen(const CString& name) +{ + return (m_map.count(name) > 0); +} + CString CConfig::getNeighbor(const CString& srcName, EDirection srcSide) const { @@ -103,3 +159,255 @@ const char* CConfig::dirName(EDirection dir) static const char* s_name[] = { "left", "right", "top", "bottom" }; return s_name[dir - kFirstDirection]; } + +bool CConfig::readLine(istream& s, CString& line) +{ + s >> std::ws; + while (getline(s, line)) { + // strip comments and then trailing whitespace + CString::size_type i = line.rfind('#'); + if (i != CString::npos) { + line.erase(i); + } + i = line.find_last_not_of(" \t"); + if (i != CString::npos) { + line.erase(i + 1); + } + + // return non empty line + if (!line.empty()) { + return true; + } + s >> std::ws; + } + return false; +} + +void CConfig::readSection(istream& s) +{ + static const char s_section[] = "section:"; + static const char s_screens[] = "screens"; + static const char s_links[] = "links"; + + CString line; + if (!readLine(s, line)) { + // no more sections + return; + } + + // should be a section header + if (line.find(s_section) != 0) { + throw XConfigRead("found data outside section"); + } + + // get section name + CString::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1); + if (i == CString::npos) { + throw XConfigRead("section name is missing"); + } + CString name = line.substr(i); + i = name.find_first_of(" \t"); + if (i != CString::npos) { + throw XConfigRead("unexpected data after section name"); + } + + // read section + if (name == s_screens) { + readSectionScreens(s); + } + else if (name == s_links) { + readSectionLinks(s); + } + else { + throw XConfigRead("unknown section name"); + } +} + +void CConfig::readSectionScreens(istream& s) +{ + CString line; + CString name; + while (readLine(s, line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + name = line.substr(0, line.size() - 1); + + // verify validity of screen name + if (!isValidScreenName(name)) { + throw XConfigRead("invalid screen name"); + } + + // add the screen to the configuration + addScreen(name); + } + else if (name.empty()) { + throw XConfigRead("argument before first screen"); + } + else { + throw XConfigRead("unknown argument"); + } + } + throw XConfigRead("unexpected end of screens section"); +} + +void CConfig::readSectionLinks(istream& s) +{ + CString line; + CString screen; + while (readLine(s, line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify we known about the screen + if (!isScreen(screen)) { + throw XConfigRead("unknown screen name"); + } + } + else if (screen.empty()) { + throw XConfigRead("argument before first screen"); + } + else { + // parse argument: `=' + CString::size_type i = line.find_first_of(" \t="); + if (i == 0) { + throw XConfigRead("missing argument name"); + } + if (i == CString::npos) { + throw XConfigRead("missing = in argument"); + } + CString name = line.substr(0, i); + i = line.find_first_not_of(" \t", i); + if (i == CString::npos || line[i] != '=') { + throw XConfigRead("missing = in argument"); + } + i = line.find_first_not_of(" \t", i + 1); + CString value; + if (i != CString::npos) { + value = line.substr(i); + } + + // handle argument + if (name == "left") { + if (!isScreen(value)) { + throw XConfigRead("unknown screen"); + } + connect(screen, kLeft, value); + } + else if (name == "right") { + if (!isScreen(value)) { + throw XConfigRead("unknown screen"); + } + connect(screen, kRight, value); + } + else if (name == "up") { + if (!isScreen(value)) { + throw XConfigRead("unknown screen"); + } + connect(screen, kTop, value); + } + else if (name == "down") { + if (!isScreen(value)) { + throw XConfigRead("unknown screen"); + } + connect(screen, kBottom, value); + } + else { + // unknown argument + throw XConfigRead("unknown argument"); + } + } + } + throw XConfigRead("unexpected end of links section"); +} + + +// +// CConfig I/O +// + +istream& operator>>(istream& s, CConfig& config) +{ + // FIXME -- should track line and column to improve error reporting + + CConfig tmp; + while (s) { + tmp.readSection(s); + } + config = tmp; + return s; +} + +ostream& operator<<(ostream& s, const CConfig& config) +{ + // screens section + s << "section: screens" << endl; + for (CConfig::const_iterator screen = config.begin(); + screen != config.end(); ++screen) { + s << "\t" << screen->c_str() << ":" << endl; + } + s << "end" << endl; + + // links section + CString neighbor; + s << "section: links" << endl; + for (CConfig::const_iterator screen = config.begin(); + screen != config.end(); ++screen) { + s << "\t" << screen->c_str() << ":" << endl; + + neighbor = config.getNeighbor(*screen, CConfig::kLeft); + if (!neighbor.empty()) { + s << "\t\tleft=" << neighbor.c_str() << endl; + } + + neighbor = config.getNeighbor(*screen, CConfig::kRight); + if (!neighbor.empty()) { + s << "\t\tright=" << neighbor.c_str() << endl; + } + + neighbor = config.getNeighbor(*screen, CConfig::kTop); + if (!neighbor.empty()) { + s << "\t\tup=" << neighbor.c_str() << endl; + } + + neighbor = config.getNeighbor(*screen, CConfig::kBottom); + if (!neighbor.empty()) { + s << "\t\tdown=" << neighbor.c_str() << endl; + } + } + s << "end" << endl; + + return s; +} + + +// +// CConfig I/O exceptions +// + +XConfigRead::XConfigRead(const CString& error) : m_error(error) +{ + // do nothing +} + +XConfigRead::~XConfigRead() +{ + // do nothing +} + +CString XConfigRead::getWhat() const throw() +{ + return m_error; +} diff --git a/server/CConfig.h b/server/CConfig.h index 33cb1745..d1d04500 100644 --- a/server/CConfig.h +++ b/server/CConfig.h @@ -3,6 +3,8 @@ #include "BasicTypes.h" #include "CString.h" +#include "XBase.h" +#include #include class CConfig { @@ -53,6 +55,9 @@ public: // manipulators + // note that case is preserved in screen names but has no effect + // FIXME -- make that true + // add/remove screens void addScreen(const CString& name); void removeScreen(const CString& name); @@ -67,19 +72,49 @@ public: // accessors + // returns true iff the given name is a valid screen name. + bool isValidScreenName(const CString&) const; + // iterators over screen names const_iterator begin() const; const_iterator end() const; + // returns true iff name names a screen + bool isScreen(const CString& name); + // get the neighbor in the given direction. returns the empty string // if there is no neighbor in that direction. CString getNeighbor(const CString&, EDirection) const; + // read/write a configuration. operator>> will throw XConfigRead + // on error. + friend istream& operator>>(istream&, CConfig&); + friend ostream& operator<<(ostream&, const CConfig&); + // get the name of a direction (for debugging) static const char* dirName(EDirection); +private: + static bool readLine(istream&, CString&); + void readSection(istream&); + void readSectionScreens(istream&); + void readSectionLinks(istream&); + private: CCellMap m_map; }; +class XConfigRead : public XBase { +public: + XConfigRead(const CString&); + ~XConfigRead(); + +protected: + // XBase overrides + virtual CString getWhat() const throw(); + +private: + CString m_error; +}; + #endif diff --git a/server/server.cpp b/server/server.cpp index 8a209e74..e2b1ffdb 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -4,6 +4,14 @@ #include "CMutex.h" #include "CNetwork.h" #include "CThread.h" +#include + +// +// config file stuff +// + +static const char* s_configFileName = "synergy.conf"; + // // logging thread safety @@ -41,17 +49,19 @@ void realMain() // initialize network library CNetwork::init(); - CConfig config; - config.addScreen("primary"); - config.addScreen("secondary"); - config.addScreen("secondary2"); - config.connect("primary", CConfig::kRight, "secondary"); - config.connect("secondary", CConfig::kLeft, "primary"); - config.connect("secondary", CConfig::kRight, "secondary2"); - config.connect("secondary2", CConfig::kLeft, "secondary"); - CServer* server = NULL; try { + CConfig config; + { + log((CLOG_DEBUG "opening configuration")); + ifstream configStream(s_configFileName); + if (!configStream) { + throw XConfigRead("cannot open configuration"); + } + configStream >> config; + log((CLOG_DEBUG "configuration read successfully")); + } + server = new CServer(); server->setConfig(config); server->run(); @@ -93,6 +103,7 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) return 0; } catch (XBase& e) { + log((CLOG_CRIT "failed: %s", e.what())); CString msg = "failed: "; msg += e.what(); MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR); @@ -116,6 +127,7 @@ int main(int argc, char** argv) return 0; } catch (XBase& e) { + log((CLOG_CRIT "failed: %s", e.what())); fprintf(stderr, "failed: %s\n", e.what()); return 1; }