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
7 * \author Lars Gullik Bjønnes
9 * Full author contact details are available in file CREDITS.
16 #include "support/FileName.h"
17 #include "support/unicode.h"
18 #include "support/lstrings.h"
20 #include <boost/filesystem/operations.hpp>
21 #include <boost/lexical_cast.hpp>
22 #include <boost/scoped_ptr.hpp>
24 // getpid(), getppid()
25 #ifdef HAVE_SYS_TYPES_H
26 # include <sys/types.h>
33 #ifdef HAVE_SYS_TIME_H
34 # include <sys/time.h>
38 #ifdef HAVE_SYS_SELECT_H
39 # include <sys/select.h>
42 // socket(), connect()
43 #ifdef HAVE_SYS_SOCKET_H
44 # include <sys/socket.h>
61 using support::FileName;
62 using support::prefixIs;
64 using ::boost::scoped_ptr;
65 namespace fs = ::boost::filesystem;
77 string itoa(unsigned int i)
79 return ::boost::lexical_cast<string>(i);
83 /// Returns the absolute pathnames of all lyx local sockets in
84 /// file system encoding.
85 /// Parts stolen from lyx::support::DirList().
86 vector<fs::path> lyxSockets(string const & dir, string const & pid)
88 vector<fs::path> dirlist;
90 fs::path dirpath(dir);
92 if (!fs::exists(dirpath) || !fs::is_directory(dirpath)) {
93 lyxerr << dir << " does not exist or is not a directory."
98 fs::directory_iterator beg((fs::path(dir)));
99 fs::directory_iterator end;
101 for (; beg != end; ++beg) {
102 if (prefixIs(beg->leaf(), "lyx_tmpdir" + pid)) {
103 fs::path lyxsocket = beg->path() / "lyxsocket";
104 if (fs::exists(lyxsocket)) {
105 dirlist.push_back(lyxsocket);
114 namespace socktools {
117 /// Connect to the socket \p name.
118 int connect(FileName const & name)
120 int fd; // File descriptor for the socket
121 sockaddr_un addr; // Structure that hold the socket address
123 string const encoded = name.toFilesystemEncoding();
124 // char sun_path[108]
125 string::size_type len = encoded.size();
127 cerr << "lyxclient: Socket address '" << name
128 << "' too long." << endl;
131 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
132 addr.sun_family = AF_UNIX;
133 encoded.copy(addr.sun_path, 107);
134 addr.sun_path[len] = '\0';
136 if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
137 cerr << "lyxclient: Could not create socket descriptor: "
138 << strerror(errno) << endl;
142 reinterpret_cast<struct sockaddr *>(&addr),
143 sizeof(addr)) == -1) {
144 cerr << "lyxclient: Could not connect to socket " << name.absFilename()
145 << ": " << strerror(errno) << endl;
149 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
150 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
151 << strerror(errno) << endl;
159 } // namespace socktools
160 } // namespace support
164 // Class IOWatch ------------------------------------------------------------
185 void IOWatch::clear()
191 void IOWatch::addfd(int fd)
197 bool IOWatch::wait(double timeout)
200 to.tv_sec = static_cast<long int>(timeout);
201 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
203 return select(FD_SETSIZE, &act,
204 (fd_set *)0, (fd_set *)0, &to);
211 return select(FD_SETSIZE, &act,
212 (fd_set *)0, (fd_set *)0, (timeval *)0);
216 bool IOWatch::isset(int fd) {
217 return FD_ISSET(fd, &act);
219 // ~Class IOWatch ------------------------------------------------------------
222 // Class LyXDataSocket -------------------------------------------------------
223 // Modified LyXDataSocket class for use with the client
224 class LyXDataSocket {
226 LyXDataSocket(FileName const &);
228 // File descriptor of the connection
231 bool connected() const;
232 // Line buffered input from the socket
233 bool readln(string &);
234 // Write the string + '\n' to the socket
235 void writeln(string const &);
237 // File descriptor for the data socket
239 // True if the connection is up
241 // buffer for input data
246 LyXDataSocket::LyXDataSocket(FileName const & address)
248 if ((fd_ = support::socktools::connect(address)) == -1) {
256 LyXDataSocket::~LyXDataSocket()
262 int LyXDataSocket::fd() const
268 bool LyXDataSocket::connected() const
274 // Returns true if there was a complete line to input
275 // A line is of the form <key>:<value>
276 // A line not of this form will not be passed
277 // The line read is splitted and stored in 'key' and 'value'
278 bool LyXDataSocket::readln(string & line)
280 int const charbuf_size = 100;
281 char charbuf[charbuf_size]; // buffer for the ::read() system call
283 string::size_type pos;
285 // read and store characters in buffer
286 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
287 charbuf[count] = '\0'; // turn it into a c string
291 // Error conditions. The buffer must still be
292 // processed for lines read
293 if (count == 0) { // EOF -- connection closed
295 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
296 cerr << "lyxclient: IO error." << endl;
300 // Cut a line from buffer
301 if ((pos = buffer.find('\n')) == string::npos)
302 return false; // No complete line stored
303 line = buffer.substr(0, pos);
304 buffer = buffer.substr(pos + 1);
309 // Write a line of the form <key>:<value> to the socket
310 void LyXDataSocket::writeln(string const & line)
312 string linen(line + '\n');
313 int size = linen.size();
314 int written = ::write(fd_, linen.c_str(), size);
315 if (written < size) { // Allways mean end of connection.
316 if ((written == -1) && (errno == EPIPE)) {
317 // The program will also receive a SIGPIPE
318 // that must be catched
319 cerr << "lyxclient: connection closed while writing."
322 // Anything else, including errno == EAGAIN, must be
323 // considered IO error. EAGAIN should never happen
324 // when line is small
325 cerr << "lyxclient: IO error: " << strerror(errno);
330 // ~Class LyXDataSocket -------------------------------------------------------
333 // Class CmdLineParser -------------------------------------------------------
334 class CmdLineParser {
336 typedef int (*optfunc)(vector<docstring> const & args);
337 std::map<string, optfunc> helper;
338 std::map<string, bool> isset;
339 bool parse(int, char * []);
340 vector<char *> nonopt;
344 bool CmdLineParser::parse(int argc, char * argv[])
348 vector<docstring> args;
349 if (helper[argv[opt]]) {
350 isset[argv[opt]] = true;
352 while ((arg < argc) && (!helper[argv[arg]])) {
353 args.push_back(from_local8bit(argv[arg]));
356 int taken = helper[argv[opt]](args);
361 if (argv[opt][0] == '-') {
362 if ((argv[opt][1] == '-')
363 && (argv[opt][2]== '\0')) {
366 nonopt.push_back(argv[opt]);
371 cerr << "lyxclient: unknown option "
372 << argv[opt] << endl;
376 nonopt.push_back(argv[opt]);
382 // ~Class CmdLineParser -------------------------------------------------------
390 cerr << "Usage: lyxclient [options]" << endl
391 << "Options are:" << endl
392 << " -a address set address of the lyx socket" << endl
393 << " -t directory set system temporary directory" << endl
394 << " -p pid select a running lyx by pid" << endl
395 << " -c command send a single command and quit" << endl
396 << " -g file row send a command to go to file and row" << endl
397 << " -n name set client name" << endl
398 << " -h name display this help end exit" << endl
399 << "If -a is not used, lyxclient will use the arguments of -t and -p to look for" << endl
400 << "a running lyx. If -t is not set, 'directory' defaults to /tmp. If -p is set," << endl
401 << "lyxclient will connect only to a lyx with the specified pid. Options -c and -g" << endl
402 << "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient" << endl
403 << "will read commands from standard input and disconnect when command read is BYE:"
408 int h(vector<docstring> const &)
415 docstring clientName(from_ascii(support::itoa(::getppid()) + ">" + support::itoa(::getpid())));
417 int n(vector<docstring> const & arg)
419 if (arg.size() < 1) {
420 cerr << "lyxclient: The option -n requires 1 argument."
429 docstring singleCommand;
432 int c(vector<docstring> const & arg)
434 if (arg.size() < 1) {
435 cerr << "lyxclient: The option -c requires 1 argument."
439 singleCommand = arg[0];
444 int g(vector<docstring> const & arg)
446 if (arg.size() < 2) {
447 cerr << "lyxclient: The option -g requires 2 arguments."
451 singleCommand = "LYXCMD:server-goto-file-row "
458 // empty if LYXSOCKET is not set in the environment
459 docstring serverAddress;
462 int a(vector<docstring> const & arg)
464 if (arg.size() < 1) {
465 cerr << "lyxclient: The option -a requires 1 argument."
469 // -a supercedes LYXSOCKET environment variable
470 serverAddress = arg[0];
475 docstring mainTmp(from_ascii("/tmp"));
478 int t(vector<docstring> const & arg)
480 if (arg.size() < 1) {
481 cerr << "lyxclient: The option -t requires 1 argument."
490 string serverPid; // Init to empty string
493 int p(vector<docstring> const & arg)
495 if (arg.size() < 1) {
496 cerr << "lyxclient: The option -p requires 1 argument."
500 serverPid = to_ascii(arg[0]);
505 } // namespace cmdline
510 int main(int argc, char * argv[])
513 lyxerr.rdbuf(cerr.rdbuf());
515 char const * const lyxsocket = getenv("LYXSOCKET");
517 cmdline::serverAddress = from_local8bit(lyxsocket);
520 args.helper["-h"] = cmdline::h;
521 args.helper["-c"] = cmdline::c;
522 args.helper["-g"] = cmdline::g;
523 args.helper["-n"] = cmdline::n;
524 args.helper["-a"] = cmdline::a;
525 args.helper["-t"] = cmdline::t;
526 args.helper["-p"] = cmdline::p;
528 // Command line failure conditions:
529 if ((!args.parse(argc, argv))
530 || (args.isset["-c"] && args.isset["-g"])
531 || (args.isset["-a"] && args.isset["-p"])) {
536 scoped_ptr<LyXDataSocket> server;
538 if (!cmdline::serverAddress.empty()) {
539 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
540 if (!server->connected()) {
541 cerr << "lyxclient: " << "Could not connect to "
542 << to_utf8(cmdline::serverAddress) << endl;
546 // We have to look for an address.
547 // serverPid can be empty.
548 vector<fs::path> addrs = support::lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
549 vector<fs::path>::const_iterator addr = addrs.begin();
550 vector<fs::path>::const_iterator end = addrs.end();
551 for (; addr != end; ++addr) {
552 // Caution: addr->string() is in filesystem encoding
553 server.reset(new LyXDataSocket(FileName(to_utf8(from_filesystem8bit(addr->string())))));
554 if (server->connected())
556 lyxerr << "lyxclient: " << "Could not connect to "
557 << addr->string() << endl;
560 lyxerr << "lyxclient: No suitable server found."
564 cerr << "lyxclient: " << "Connected to " << addr->string() << endl;
567 int const serverfd = server->fd();
570 iowatch.addfd(serverfd);
572 // Used to read from server
576 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
577 // wait at most 2 seconds until server responds
579 if (iowatch.isset(serverfd) && server->readln(answer)) {
580 if (prefixIs(answer, "BYE:")) {
581 cerr << "lyxclient: Server disconnected." << endl;
582 cout << answer << endl;
586 cerr << "lyxclient: No answer from server." << endl;
590 if (args.isset["-g"] || args.isset["-c"]) {
591 server->writeln(to_utf8(cmdline::singleCommand));
593 if (iowatch.isset(serverfd) && server->readln(answer)) {
595 if (prefixIs(answer, "ERROR:"))
599 cerr << "lyxclient: No answer from server." << endl;
604 // Take commands from stdin
605 iowatch.addfd(0); // stdin
606 bool saidbye = false;
607 while ((!saidbye) && server->connected()) {
609 if (iowatch.isset(0)) {
612 if (command == "BYE:") {
613 server->writeln("BYE:");
616 server->writeln("LYXCMD:" + command);
619 if (iowatch.isset(serverfd)) {
620 while(server->readln(answer))
621 cout << answer << endl;
631 void assertion_failed(char const* a, char const* b, char const* c, long d)
633 lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '