3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author João Luis M. Assirati
8 * Full author contact details are available in file CREDITS.
16 // getpid(), getppid()
17 #include <sys/types.h>
21 #include <sys/select.h>
23 // opendir(), closedir(), readdir()
24 #include <sys/types.h>
30 // socket(), connect()
31 #include <sys/socket.h>
50 string itoa(unsigned int i)
56 while((0 < i) && (i <= 9)) {
57 str = static_cast<char>('0' + i % 10) + str;
65 bool prefixIs(string const & a, string const & pre)
67 char const * p_a = a.c_str();
68 char const * p_pre = pre.c_str();
69 while ((*p_a != '\0') && (*p_pre != '\0') && (*p_a == *p_pre)) {
73 if (*p_pre == '\0') return true;
78 // Parts stolen from lyx::support::DirList()
79 // Returns the absolute pathnames of all lyx local sockets
80 vector<string> lyxSockets(string const & dir, string const & pid)
82 vector<string> dirlist;
83 DIR * dirp = ::opendir(dir.c_str());
85 cerr << "lyxclient: Could not read dir " << dir
86 << ": " << strerror(errno);
91 while ((dire = ::readdir(dirp))) {
92 string const fil = dire->d_name;
93 if (prefixIs(fil, "lyx_tmpdir" + pid)) {
94 string lyxsocket = dir + '/' + fil + "/lyxsocket";
96 // not checking if it is a socket -- just if it exists
97 // and has reading permissions
98 if (!::stat(lyxsocket.c_str(), &status)) {
99 dirlist.push_back(lyxsocket);
108 namespace socktools {
110 int connect(string const & name)
112 int fd; // File descriptor for the socket
113 sockaddr_un addr; // Structure that hold the socket address
115 // char sun_path[108]
116 string::size_type len = name.size();
118 cerr << "lyxclient: Socket address '" << name
119 << "' too long." << endl;
122 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
123 addr.sun_family = AF_UNIX;
124 name.copy(addr.sun_path, 107);
125 addr.sun_path[len] = '\0';
127 if((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
128 cerr << "lyxclient: Could not create socket descriptor: "
129 << strerror(errno) << endl;
132 if(::connect(fd, (struct sockaddr *)&addr, SUN_LEN(&addr)) == -1) {
133 cerr << "lyxclient: Could not connect to socket " << name
134 << ": " << strerror(errno) << endl;
138 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
139 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
140 << strerror(errno) << endl;
147 } // namespace socktools
148 } // namespace support
152 // Class IOWatch ------------------------------------------------------------
171 void IOWatch::clear() {
174 void IOWatch::addfd(int fd) {
177 bool IOWatch::wait(double timeout) {
179 to.tv_sec = static_cast<long int>(timeout);
180 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
182 return select(FD_SETSIZE, &act,
183 (fd_set *)0, (fd_set *)0, &to);
185 bool IOWatch::wait() {
187 return select(FD_SETSIZE, &act,
188 (fd_set *)0, (fd_set *)0, (timeval *)0);
190 bool IOWatch::isset(int fd) {
191 return FD_ISSET(fd, &act);
193 // ~Class IOWatch ------------------------------------------------------------
196 // Class LyXDataSocket -------------------------------------------------------
197 // Modified LyXDataSocket class for use with the client
201 LyXDataSocket(string const &);
203 // File descriptor of the connection
206 bool connected() const;
207 // Line buffered input from the socket
208 bool readln(string &);
209 // Write the string + '\n' to the socket
210 void writeln(string const &);
213 // File descriptor for the data socket
215 // True if the connection is up
217 // buffer for input data
221 LyXDataSocket::LyXDataSocket(string const & address)
223 if ((fd_ = support::socktools::connect(address)) == -1) {
230 LyXDataSocket::~LyXDataSocket()
235 int LyXDataSocket::fd() const
240 bool LyXDataSocket::connected() const
245 // Returns true if there was a complete line to input
246 // A line is of the form <key>:<value>
247 // A line not of this form will not be passed
248 // The line read is splitted and stored in 'key' and 'value'
249 bool LyXDataSocket::readln(string & line)
251 int const charbuf_size = 100;
252 char charbuf[charbuf_size]; // buffer for the ::read() system call
254 string::size_type pos;
256 // read and store characters in buffer
257 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
258 charbuf[count] = '\0'; // turn it into a c string
262 // Error conditions. The buffer must still be
263 // processed for lines read
264 if (count == 0) { // EOF -- connection closed
266 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
267 cerr << "lyxclient: IO error." << endl;
271 // Cut a line from buffer
272 if ((pos = buffer.find('\n')) == string::npos)
273 return false; // No complete line stored
274 line = buffer.substr(0, pos);
275 buffer = buffer.substr(pos + 1);
279 // Write a line of the form <key>:<value> to the socket
280 void LyXDataSocket::writeln(string const & line)
282 string linen(line + '\n');
283 int size = linen.size();
284 int written = ::write(fd_, linen.c_str(), size);
285 if (written < size) { // Allways mean end of connection.
286 if ((written == -1) && (errno == EPIPE)) {
287 // The program will also receive a SIGPIPE
288 // that must be catched
289 cerr << "lyxclient: connection closed while writing."
292 // Anything else, including errno == EAGAIN, must be
293 // considered IO error. EAGAIN should never happen
294 // when line is small
295 cerr << "lyxclient: IO error: " << strerror(errno);
300 // ~Class LyXDataSocket -------------------------------------------------------
303 // Class CmdLineParser -------------------------------------------------------
307 typedef int (*optfunc)(vector<char *> const & args);
308 std::map<string, optfunc> helper;
309 std::map<string, bool> isset;
310 bool parse(int, char * []);
311 vector<char *> nonopt;
314 bool CmdLineParser::parse(int argc, char * argv[])
319 if(helper[argv[opt]]) {
320 isset[argv[opt]] = true;
322 while((arg < argc) && (!helper[argv[arg]])) {
323 args.push_back(argv[arg]);
326 int taken = helper[argv[opt]](args);
327 if(taken == -1) return false;
330 if(argv[opt][0] == '-') {
331 if((argv[opt][1] == '-')
332 && (argv[opt][2]== '\0')) {
335 nonopt.push_back(argv[opt]);
340 cerr << "lyxclient: unknown option "
341 << argv[opt] << endl;
345 nonopt.push_back(argv[opt]);
351 // ~Class CmdLineParser -------------------------------------------------------
359 cerr << "Usage: lyxclient [options]" << endl
360 << "Options are:" << endl
361 << " -a address set address of the lyx socket" << endl
362 << " -t directory set system temporary directory" << endl
363 << " -p pid select a running lyx by pid" << endl
364 << " -c command send a single command and quit" << endl
365 << " -g file row send a command to go to file and row" << endl
366 << " -n name set client name" << endl
367 << " -h name display this help end exit" << endl
368 << "If -a is not used, lyxclient will use the arguments of -t and -p to look for" << endl
369 << "a running lyx. If -t is not set, 'directory' defaults to /tmp. If -p is set," << endl
370 << "lyxclient will connect only to a lyx with the specified pid. Options -c and -g" << endl
371 << "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient" << endl
372 << "will read commands from standard input and disconnect when command read is BYE:"
376 int h(vector<char *> const & arg)
382 string clientName(support::itoa(::getppid()) + ">" + support::itoa(::getpid()));
383 int n(vector<char *> const & arg)
386 cerr << "lyxclient: The option -n requires 1 argument."
394 string singleCommand;
395 int c(vector<char *> const & arg)
398 cerr << "lyxclient: The option -c requires 1 argument."
402 singleCommand = arg[0];
406 int g(vector<char *> const & arg)
409 cerr << "lyxclient: The option -g requires 2 arguments."
413 singleCommand = "LYXCMD:server-goto-file-row "
414 + static_cast<string>(arg[0]) + ' '
415 + static_cast<string>(arg[1]);
419 // 0 if LYXSOCKET is not set in the environment
420 char * serverAddress = getenv("LYXSOCKET");
421 int a(vector<char *> const & arg)
424 cerr << "lyxclient: The option -a requires 1 argument."
428 // -a supercedes LYXSOCKET environment variable
429 serverAddress = arg[0];
433 string mainTmp("/tmp");
434 int t(vector<char *> const & arg)
437 cerr << "lyxclient: The option -t requires 1 argument."
445 string serverPid; // Init to empty string
446 int p(vector<char *> const & arg)
449 cerr << "lyxclient: The option -p requires 1 argument."
457 } // namespace cmdline
459 using support::prefixIs;
461 int main(int argc, char * argv[])
464 args.helper["-h"] = cmdline::h;
465 args.helper["-c"] = cmdline::c;
466 args.helper["-g"] = cmdline::g;
467 args.helper["-n"] = cmdline::n;
468 args.helper["-a"] = cmdline::a;
469 args.helper["-t"] = cmdline::t;
470 args.helper["-p"] = cmdline::p;
471 // Command line failure conditions:
472 if((!args.parse(argc, argv))
473 || (args.isset["-c"] && args.isset["-g"])
474 || (args.isset["-a"] && args.isset["-p"])) {
479 LyXDataSocket * server;
481 if(cmdline::serverAddress) {
482 server = new LyXDataSocket(cmdline::serverAddress);
483 if(!server->connected()) {
484 cerr << "lyxclient: " << "Could not connect to "
485 << cmdline::serverAddress << endl;
489 // We have to look for an address.
490 // serverPid can be empty.
491 vector<string> addrs = support::lyxSockets(cmdline::mainTmp, cmdline::serverPid);
492 vector<string>::iterator addr = addrs.begin();
493 vector<string>::iterator end = addrs.end();
495 server = new LyXDataSocket(*addr);
496 if(server->connected()) break;
497 cerr << "lyxclient: " << "Could not connect to "
503 cerr << "lyxclient: No suitable server found." << endl;
506 cerr << "lyxclient: " << "Connected to " << *addr << endl;
509 int const serverfd = server->fd();
512 iowatch.addfd(serverfd);
514 // Used to read from server
518 server->writeln("HELLO:" + cmdline::clientName);
519 // wait at most 2 seconds until server responds
521 if(iowatch.isset(serverfd) && server->readln(answer)) {
522 if(prefixIs(answer, "BYE:")) {
523 cerr << "lyxclient: Server disconnected." << endl;
524 cout << answer << endl;
528 cerr << "lyxclient: No answer from server." << endl;
532 if(args.isset["-g"] || args.isset["-c"]) {
533 server->writeln(cmdline::singleCommand);
535 if(iowatch.isset(serverfd) && server->readln(answer)) {
537 if(prefixIs(answer, "ERROR:")) return 1;
540 cerr << "lyxclient: No answer from server." << endl;
545 // Take commands from stdin
546 iowatch.addfd(0); // stdin
547 bool saidbye = false;
548 while((!saidbye) && server->connected()) {
550 if(iowatch.isset(0)) {
553 if(command == "BYE:") {
554 server->writeln("BYE:");
557 server->writeln("LYXCMD:" + command);
560 if(iowatch.isset(serverfd)) {
561 while(server->readln(answer))
562 cout << answer << endl;