/** * \file lyxclient.C * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * * \author Joćo Luis M. Assirati * * Full author contact details are available in file CREDITS. */ #include #include #include #include // getpid(), getppid() #include #include // select() #include // opendir(), closedir(), readdir() #include #include // stat() #include // socket(), connect() #include #include // fcntl() #include // getenv() #include using std::string; using std::vector; using std::cout; using std::cerr; using std::cin; using std::endl; namespace support { string itoa(unsigned int i) { string str; if(!i) { str = "0"; } else { while((0 < i) && (i <= 9)) { str = static_cast('0' + i % 10) + str; i /= 10; } } return str; } bool prefixIs(string const & a, string const & pre) { char const * p_a = a.c_str(); char const * p_pre = pre.c_str(); while ((*p_a != '\0') && (*p_pre != '\0') && (*p_a == *p_pre)) { ++p_a; ++p_pre; } if (*p_pre == '\0') return true; return false; } // Parts stolen from lyx::support::DirList() // Returns the absolute pathnames of all lyx local sockets vector lyxSockets(string const & dir, string const & pid) { vector dirlist; DIR * dirp = ::opendir(dir.c_str()); if (!dirp) { cerr << "lyxclient: Could not read dir " << dir << ": " << strerror(errno); return dirlist; } dirent * dire; while ((dire = ::readdir(dirp))) { string const fil = dire->d_name; if (prefixIs(fil, "lyx_tmpdir" + pid)) { string lyxsocket = dir + '/' + fil + "/lyxsocket"; struct stat status; // not checking if it is a socket -- just if it exists // and has reading permissions if (!::stat(lyxsocket.c_str(), &status)) { dirlist.push_back(lyxsocket); } } } ::closedir(dirp); return dirlist; } namespace socktools { int connect(string const & name) { int fd; // File descriptor for the socket sockaddr_un addr; // Structure that hold the socket address // char sun_path[108] string::size_type len = name.size(); if(len > 107) { cerr << "lyxclient: Socket address '" << name << "' too long." << endl; return -1; } // Synonims for AF_UNIX are AF_LOCAL and AF_FILE addr.sun_family = AF_UNIX; name.copy(addr.sun_path, 107); addr.sun_path[len] = '\0'; if((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) { cerr << "lyxclient: Could not create socket descriptor: " << strerror(errno) << endl; return -1; } if(::connect(fd, (struct sockaddr *)&addr, SUN_LEN(&addr)) == -1) { cerr << "lyxclient: Could not connect to socket " << name << ": " << strerror(errno) << endl; ::close(fd); return -1; } if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { cerr << "lyxclient: Could not set O_NONBLOCK for socket: " << strerror(errno) << endl; ::close(fd); return -1; } return fd; } } // namespace socktools } // namespace support // Class IOWatch ------------------------------------------------------------ class IOWatch { public: IOWatch(); void clear(); void addfd(int); bool wait(double); bool wait(); bool isset(int fd); private: fd_set des; fd_set act; }; IOWatch::IOWatch() { clear(); } void IOWatch::clear() { FD_ZERO(&des); } void IOWatch::addfd(int fd) { FD_SET(fd, &des); } bool IOWatch::wait(double timeout) { timeval to; to.tv_sec = static_cast(timeout); to.tv_usec = static_cast((timeout - to.tv_sec)*1E6); act = des; return select(FD_SETSIZE, &act, (fd_set *)0, (fd_set *)0, &to); } bool IOWatch::wait() { act = des; return select(FD_SETSIZE, &act, (fd_set *)0, (fd_set *)0, (timeval *)0); } bool IOWatch::isset(int fd) { return FD_ISSET(fd, &act); } // ~Class IOWatch ------------------------------------------------------------ // Class LyXDataSocket ------------------------------------------------------- // Modified LyXDataSocket class for use with the client class LyXDataSocket { public: LyXDataSocket(string const &); ~LyXDataSocket(); // File descriptor of the connection int fd() const; // Connection status bool connected() const; // Line buffered input from the socket bool readln(string &); // Write the string + '\n' to the socket void writeln(string const &); private: // File descriptor for the data socket int fd_; // True if the connection is up bool connected_; // buffer for input data string buffer; }; LyXDataSocket::LyXDataSocket(string const & address) { if ((fd_ = support::socktools::connect(address)) == -1) { connected_ = false; } else { connected_ = true; } } LyXDataSocket::~LyXDataSocket() { ::close(fd_); } int LyXDataSocket::fd() const { return fd_; } bool LyXDataSocket::connected() const { return connected_; } // Returns true if there was a complete line to input // A line is of the form : // A line not of this form will not be passed // The line read is splitted and stored in 'key' and 'value' bool LyXDataSocket::readln(string & line) { int const charbuf_size = 100; char charbuf[charbuf_size]; // buffer for the ::read() system call int count; string::size_type pos; // read and store characters in buffer while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) { charbuf[count] = '\0'; // turn it into a c string buffer += charbuf; } // Error conditions. The buffer must still be // processed for lines read if (count == 0) { // EOF -- connection closed connected_ = false; } else if ((count == -1) && (errno != EAGAIN)) { // IO error cerr << "lyxclient: IO error." << endl; connected_ = false; } // Cut a line from buffer if ((pos = buffer.find('\n')) == string::npos) return false; // No complete line stored line = buffer.substr(0, pos); buffer = buffer.substr(pos + 1); return true; } // Write a line of the form : to the socket void LyXDataSocket::writeln(string const & line) { string linen(line + '\n'); int size = linen.size(); int written = ::write(fd_, linen.c_str(), size); if (written < size) { // Allways mean end of connection. if ((written == -1) && (errno == EPIPE)) { // The program will also receive a SIGPIPE // that must be catched cerr << "lyxclient: connection closed while writing." << endl; } else { // Anything else, including errno == EAGAIN, must be // considered IO error. EAGAIN should never happen // when line is small cerr << "lyxclient: IO error: " << strerror(errno); } connected_ = false; } } // ~Class LyXDataSocket ------------------------------------------------------- // Class CmdLineParser ------------------------------------------------------- class CmdLineParser { public: typedef int (*optfunc)(vector const & args); std::map helper; std::map isset; bool parse(int, char * []); vector nonopt; }; bool CmdLineParser::parse(int argc, char * argv[]) { int opt = 1; while(opt < argc) { vector args; if(helper[argv[opt]]) { isset[argv[opt]] = true; int arg = opt + 1; while((arg < argc) && (!helper[argv[arg]])) { args.push_back(argv[arg]); ++arg; } int taken = helper[argv[opt]](args); if(taken == -1) return false; opt += 1 + taken; } else { if(argv[opt][0] == '-') { if((argv[opt][1] == '-') && (argv[opt][2]== '\0')) { ++opt; while(opt < argc) { nonopt.push_back(argv[opt]); ++opt; } return true; } else { cerr << "lyxclient: unknown option " << argv[opt] << endl; return false; } } nonopt.push_back(argv[opt]); ++opt; } } return true; } // ~Class CmdLineParser ------------------------------------------------------- namespace cmdline { void usage() { cerr << "Usage: lyxclient [options]" << endl << "Options are:" << endl << " -a address set address of the lyx socket" << endl << " -t directory set system temporary directory" << endl << " -p pid select a running lyx by pid" << endl << " -c command send a single command and quit" << endl << " -g file row send a command to go to file and row" << endl << " -n name set client name" << endl << " -h name display this help end exit" << endl << "If -a is not used, lyxclient will use the arguments of -t and -p to look for" << endl << "a running lyx. If -t is not set, 'directory' defaults to /tmp. If -p is set," << endl << "lyxclient will connect only to a lyx with the specified pid. Options -c and -g" << endl << "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient" << endl << "will read commands from standard input and disconnect when command read is BYE:" << endl; } int h(vector const & arg) { usage(); exit(0); } string clientName(support::itoa(::getppid()) + ">" + support::itoa(::getpid())); int n(vector const & arg) { if(arg.size() < 1) { cerr << "lyxclient: The option -n requires 1 argument." << endl; return -1; } clientName = arg[0]; return 1; } string singleCommand; int c(vector const & arg) { if(arg.size() < 1) { cerr << "lyxclient: The option -c requires 1 argument." << endl; return -1; } singleCommand = arg[0]; return 1; } int g(vector const & arg) { if(arg.size() < 2) { cerr << "lyxclient: The option -g requires 2 arguments." << endl; return -1; } singleCommand = "LYXCMD:server-goto-file-row " + static_cast(arg[0]) + ' ' + static_cast(arg[1]); return 2; } // 0 if LYXSOCKET is not set in the environment char * serverAddress = getenv("LYXSOCKET"); int a(vector const & arg) { if(arg.size() < 1) { cerr << "lyxclient: The option -a requires 1 argument." << endl; return -1; } // -a supercedes LYXSOCKET environment variable serverAddress = arg[0]; return 1; } string mainTmp("/tmp"); int t(vector const & arg) { if(arg.size() < 1) { cerr << "lyxclient: The option -t requires 1 argument." << endl; return -1; } mainTmp = arg[0]; return 1; } string serverPid; // Init to empty string int p(vector const & arg) { if(arg.size() < 1) { cerr << "lyxclient: The option -p requires 1 argument." << endl; return -1; } serverPid = arg[0]; return 1; } } // namespace cmdline using support::prefixIs; int main(int argc, char * argv[]) { CmdLineParser args; args.helper["-h"] = cmdline::h; args.helper["-c"] = cmdline::c; args.helper["-g"] = cmdline::g; args.helper["-n"] = cmdline::n; args.helper["-a"] = cmdline::a; args.helper["-t"] = cmdline::t; args.helper["-p"] = cmdline::p; // Command line failure conditions: if((!args.parse(argc, argv)) || (args.isset["-c"] && args.isset["-g"]) || (args.isset["-a"] && args.isset["-p"])) { cmdline::usage(); return 1; } LyXDataSocket * server; if(cmdline::serverAddress) { server = new LyXDataSocket(cmdline::serverAddress); if(!server->connected()) { cerr << "lyxclient: " << "Could not connect to " << cmdline::serverAddress << endl; return 1; } } else { // We have to look for an address. // serverPid can be empty. vector addrs = support::lyxSockets(cmdline::mainTmp, cmdline::serverPid); vector::iterator addr = addrs.begin(); vector::iterator end = addrs.end(); while (addr < end) { server = new LyXDataSocket(*addr); if(server->connected()) break; cerr << "lyxclient: " << "Could not connect to " << *addr << endl; delete server; ++addr; } if(addr == end) { cerr << "lyxclient: No suitable server found." << endl; return 1; } cerr << "lyxclient: " << "Connected to " << *addr << endl; } int const serverfd = server->fd(); IOWatch iowatch; iowatch.addfd(serverfd); // Used to read from server string answer; // Send greeting server->writeln("HELLO:" + cmdline::clientName); // wait at most 2 seconds until server responds iowatch.wait(2.0); if(iowatch.isset(serverfd) && server->readln(answer)) { if(prefixIs(answer, "BYE:")) { cerr << "lyxclient: Server disconnected." << endl; cout << answer << endl; return 1; } } else { cerr << "lyxclient: No answer from server." << endl; return 1; } if(args.isset["-g"] || args.isset["-c"]) { server->writeln(cmdline::singleCommand); iowatch.wait(2.0); if(iowatch.isset(serverfd) && server->readln(answer)) { cout << answer; if(prefixIs(answer, "ERROR:")) return 1; return 0; } else { cerr << "lyxclient: No answer from server." << endl; return 1; } } // Take commands from stdin iowatch.addfd(0); // stdin bool saidbye = false; while((!saidbye) && server->connected()) { iowatch.wait(); if(iowatch.isset(0)) { string command; cin >> command; if(command == "BYE:") { server->writeln("BYE:"); saidbye = true; } else { server->writeln("LYXCMD:" + command); } } if(iowatch.isset(serverfd)) { while(server->readln(answer)) cout << answer << endl; } } return 0; }