]> git.lyx.org Git - lyx.git/blob - src/client/client.cpp
Account for old versions of Pygments
[lyx.git] / src / client / client.cpp
1 /**
2  * \file client.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author João Luis M. Assirati
7  * \author Lars Gullik Bjønnes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12
13 #include <config.h>
14
15 #include "support/ConsoleApplication.h"
16 #include "support/debug.h"
17 #include "support/FileName.h"
18 #include "support/FileNameList.h"
19 #include "support/lstrings.h"
20 #include "support/Messages.h"
21 #include "support/unicode.h"
22 #include "support/unique_ptr.h"
23
24 // getpid(), getppid()
25 #ifdef HAVE_SYS_TYPES_H
26 # include <sys/types.h>
27 #endif
28 #ifdef HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31
32 // struct timeval
33 #ifdef HAVE_SYS_TIME_H
34 # include <sys/time.h>
35 #endif
36
37 // select()
38 #ifdef HAVE_SYS_SELECT_H
39 # include <sys/select.h>
40 #endif
41
42 // socket(), connect()
43 #ifdef HAVE_SYS_SOCKET_H
44 # include <sys/socket.h>
45 #endif
46 #include <sys/un.h>
47
48 // fcntl()
49 #include <fcntl.h>
50
51 // strerror()
52 #include <string.h>
53
54 #include <cerrno>
55 #include <cstdio>
56 #include <cstdlib>
57 #include <exception>
58 #include <string>
59 #include <vector>
60 #include <map>
61 #include <iostream>
62
63
64 using namespace std;
65 using namespace lyx::support;
66
67 namespace lyx {
68
69 // Dummy verbose support
70 bool verbose = false;
71
72 // Dummy LyXRC support
73 struct LyXRC {
74         string icon_set;
75 } lyxrc;
76
77 // Keep the linker happy on Windows
78 void lyx_exit(int)
79 {}
80
81 // Dummy language support
82 Messages const & getGuiMessages()
83 {
84         static Messages lyx_messages;
85
86         return lyx_messages;
87 }
88
89
90 Messages const & getMessages(string const &)
91 {
92         return getGuiMessages();
93 }
94
95
96 namespace support {
97
98 string itoa(unsigned int i)
99 {
100         char buf[20];
101         sprintf(buf, "%d", i);
102         return buf;
103 }
104
105
106 /// Returns the absolute pathnames of all lyx local sockets in
107 /// file system encoding.
108 /// Parts stolen from lyx::support::DirList().
109 FileNameList lyxSockets(string const & dir, string const & pid)
110 {
111         FileNameList dirlist;
112
113         FileName dirpath(dir + "/");
114
115         if (!dirpath.exists() || !dirpath.isDirectory()) {
116                 lyxerr << dir << " does not exist or is not a directory."
117                        << endl;
118                 return dirlist;
119         }
120
121         FileNameList dirs = dirpath.dirList("");
122         FileNameList::const_iterator it = dirs.begin();
123         FileNameList::const_iterator end = dirs.end();
124
125         for (; it != end; ++it) {
126                 if (!it->isDirectory())
127                         continue;
128                 string const tmpdir = it->absFileName();
129                 if (!contains(tmpdir, "lyx_tmpdir" + pid))
130                         continue;
131
132                 FileName lyxsocket(tmpdir + "/lyxsocket");
133                 if (lyxsocket.exists())
134                         dirlist.push_back(lyxsocket);
135         }
136
137         return dirlist;
138 }
139
140
141 namespace socktools {
142
143
144 /// Connect to the socket \p name.
145 int connect(FileName const & name)
146 {
147         int fd; // File descriptor for the socket
148         sockaddr_un addr; // Structure that hold the socket address
149
150         string const encoded = name.toFilesystemEncoding();
151         // char sun_path[108]
152         string::size_type len = encoded.size();
153         if (len > 107) {
154                 cerr << "lyxclient: Socket address '" << name
155                      << "' too long." << endl;
156                 return -1;
157         }
158         // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
159         addr.sun_family = AF_UNIX;
160         encoded.copy(addr.sun_path, 107);
161         addr.sun_path[len] = '\0';
162
163         if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
164                 cerr << "lyxclient: Could not create socket descriptor: "
165                      << strerror(errno) << endl;
166                 return -1;
167         }
168         if (::connect(fd,
169                       reinterpret_cast<struct sockaddr *>(&addr),
170                       sizeof(addr)) == -1) {
171                 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
172                      << ": " << strerror(errno) << endl;
173                 ::close(fd);
174                 return -1;
175         }
176         if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
177                 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
178                      << strerror(errno) << endl;
179                 ::close(fd);
180                 return -1;
181         }
182         return fd;
183 }
184
185
186 } // namespace socktools
187 } // namespace support
188
189
190
191 /////////////////////////////////////////////////////////////////////
192 //
193 // IOWatch
194 //
195 /////////////////////////////////////////////////////////////////////
196
197 class IOWatch {
198 public:
199         IOWatch();
200         void clear();
201         void addfd(int);
202         bool wait(double);
203         bool wait();
204         bool isset(int fd);
205 private:
206         fd_set des;
207         fd_set act;
208 };
209
210
211 IOWatch::IOWatch()
212 {
213         clear();
214 }
215
216
217 void IOWatch::clear()
218 {
219         FD_ZERO(&des);
220 }
221
222
223 void IOWatch::addfd(int fd)
224 {
225         FD_SET(fd, &des);
226 }
227
228
229 bool IOWatch::wait(double timeout)
230 {
231         timeval to;
232         to.tv_sec = static_cast<long int>(timeout);
233         to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
234         act = des;
235         return select(FD_SETSIZE, &act,
236                       (fd_set *)0, (fd_set *)0, &to);
237 }
238
239
240 bool IOWatch::wait()
241 {
242         act = des;
243         return select(FD_SETSIZE, &act,
244                       (fd_set *)0, (fd_set *)0, (timeval *)0);
245 }
246
247
248 bool IOWatch::isset(int fd)
249 {
250         return FD_ISSET(fd, &act);
251 }
252
253
254
255 /////////////////////////////////////////////////////////////////////
256 //
257 // LyXDataSocket
258 //
259 /////////////////////////////////////////////////////////////////////
260
261 // Modified LyXDataSocket class for use with the client
262 class LyXDataSocket {
263 public:
264         LyXDataSocket(FileName const &);
265         ~LyXDataSocket();
266         // File descriptor of the connection
267         int fd() const;
268         // Connection status
269         bool connected() const;
270         // Line buffered input from the socket
271         bool readln(string &);
272         // Write the string + '\n' to the socket
273         void writeln(string const &);
274 private:
275         // File descriptor for the data socket
276         int fd_;
277         // True if the connection is up
278         bool connected_;
279         // buffer for input data
280         string buffer;
281 };
282
283
284 LyXDataSocket::LyXDataSocket(FileName const & address)
285 {
286         if ((fd_ = socktools::connect(address)) == -1) {
287                 connected_ = false;
288         } else {
289                 connected_ = true;
290         }
291 }
292
293
294 LyXDataSocket::~LyXDataSocket()
295 {
296         ::close(fd_);
297 }
298
299
300 int LyXDataSocket::fd() const
301 {
302         return fd_;
303 }
304
305
306 bool LyXDataSocket::connected() const
307 {
308         return connected_;
309 }
310
311
312 // Returns true if there was a complete line to input
313 // A line is of the form <key>:<value>
314 //   A line not of this form will not be passed
315 // The line read is split and stored in 'key' and 'value'
316 bool LyXDataSocket::readln(string & line)
317 {
318         int const charbuf_size = 100;
319         char charbuf[charbuf_size]; // buffer for the ::read() system call
320         int count;
321         string::size_type pos;
322
323         // read and store characters in buffer
324         while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
325                 charbuf[count] = '\0'; // turn it into a c string
326                 buffer += charbuf;
327         }
328
329         // Error conditions. The buffer must still be
330         // processed for lines read
331         if (count == 0) { // EOF -- connection closed
332                 connected_ = false;
333         } else if ((count == -1) && (errno != EAGAIN)) { // IO error
334                 cerr << "lyxclient: IO error." << endl;
335                 connected_ = false;
336         }
337
338         // Cut a line from buffer
339         if ((pos = buffer.find('\n')) == string::npos)
340                 return false; // No complete line stored
341         line = buffer.substr(0, pos);
342         buffer = buffer.substr(pos + 1);
343         return true;
344 }
345
346
347 // Write a line of the form <key>:<value> to the socket
348 void LyXDataSocket::writeln(string const & line)
349 {
350         string linen(line + '\n');
351         int size = linen.size();
352         int written = ::write(fd_, linen.c_str(), size);
353         if (written < size) { // Allways mean end of connection.
354                 if ((written == -1) && (errno == EPIPE)) {
355                         // The program will also receive a SIGPIPE
356                         // that must be catched
357                         cerr << "lyxclient: connection closed while writing."
358                              << endl;
359                 } else {
360                         // Anything else, including errno == EAGAIN, must be
361                         // considered IO error. EAGAIN should never happen
362                         // when line is small
363                         cerr << "lyxclient: IO error: " << strerror(errno);
364                 }
365                 connected_ = false;
366         }
367 }
368
369
370 /////////////////////////////////////////////////////////////////////
371 //
372 // CmdLineParser
373 //
374 /////////////////////////////////////////////////////////////////////
375
376 class CmdLineParser {
377 public:
378         typedef int (*optfunc)(vector<docstring> const & args);
379         map<string, optfunc> helper;
380         map<string, bool> isset;
381         bool parse(int, char * []);
382         vector<char *> nonopt;
383 };
384
385
386 bool CmdLineParser::parse(int argc, char * argv[])
387 {
388         int opt = 1;
389         while (opt < argc) {
390                 vector<docstring> args;
391                 if (helper[argv[opt]]) {
392                         isset[argv[opt]] = true;
393                         int arg = opt + 1;
394                         while ((arg < argc) && (!helper[argv[arg]])) {
395                                 args.push_back(from_local8bit(argv[arg]));
396                                 ++arg;
397                         }
398                         int taken = helper[argv[opt]](args);
399                         if (taken == -1)
400                                 return false;
401                         opt += 1 + taken;
402                 } else {
403                         if (argv[opt][0] == '-') {
404                                 if ((argv[opt][1] == '-')
405                                    && (argv[opt][2]== '\0')) {
406                                         ++opt;
407                                         while (opt < argc) {
408                                                 nonopt.push_back(argv[opt]);
409                                                 ++opt;
410                                         }
411                                         return true;
412                                 } else {
413                                         cerr << "lyxclient: unknown option "
414                                              << argv[opt] << endl;
415                                         return false;
416                                 }
417                         }
418                         nonopt.push_back(argv[opt]);
419                         ++opt;
420                 }
421         }
422         return true;
423 }
424 // ~Class CmdLineParser -------------------------------------------------------
425
426
427
428 namespace cmdline {
429
430 docstring mainTmp(from_ascii("/tmp"));
431
432
433 class StopException : public exception
434 {
435 public:
436         StopException(int status) : status_(status) {}
437         int status() const { return status_; }
438 private:
439         int status_;
440 };
441
442
443 void usage()
444 {
445         cerr <<
446                 "Usage: lyxclient [options]\n"
447           "Options are:\n"
448           "  -a address    set address of the lyx socket\n"
449           "  -t directory  set system temporary directory (for detecting sockets)\n"
450           "  -p pid        select a running lyx by pidi\n"
451           "  -c command    send a single command and quit (LYXCMD prefix needed)\n"
452           "  -g file row   send a command to go to file and row\n"
453           "  -n name       set client name\n"
454           "  -h name       display this help end exit\n"
455           "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
456           "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
457           "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
458           "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
459           "will read commands from standard input and disconnect when command read is BYE:\n"
460           "\n"
461           "System directory is: " << to_utf8(cmdline::mainTmp)
462            << endl;
463 }
464
465
466 int h(vector<docstring> const &)
467 {
468         usage();
469         throw StopException(EXIT_SUCCESS);
470 }
471
472
473 docstring clientName =
474         from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
475
476 int n(vector<docstring> const & arg)
477 {
478         if (arg.empty()) {
479                 cerr << "lyxclient: The option -n requires 1 argument."
480                      << endl;
481                 return -1;
482         }
483         clientName = arg[0];
484         return 1;
485 }
486
487
488 docstring singleCommand;
489
490
491 int c(vector<docstring> const & arg)
492 {
493         if (arg.empty()) {
494                 cerr << "lyxclient: The option -c requires 1 argument."
495                      << endl;
496                 return -1;
497         }
498         singleCommand = arg[0];
499         return 1;
500 }
501
502
503 int g(vector<docstring> const & arg)
504 {
505         if (arg.size() < 2) {
506                 cerr << "lyxclient: The option -g requires 2 arguments."
507                      << endl;
508                 return -1;
509         }
510         singleCommand = "LYXCMD:command-sequence "
511                 "server-goto-file-row "
512                         + arg[0] + ' '
513                         + arg[1] + "; " +
514                 "lyx-activate";
515         return 2;
516 }
517
518
519 // empty if LYXSOCKET is not set in the environment
520 docstring serverAddress;
521
522
523 int a(vector<docstring> const & arg)
524 {
525         if (arg.empty()) {
526                 cerr << "lyxclient: The option -a requires 1 argument."
527                      << endl;
528                 return -1;
529         }
530         // -a supercedes LYXSOCKET environment variable
531         serverAddress = arg[0];
532         return 1;
533 }
534
535
536
537
538 int t(vector<docstring> const & arg)
539 {
540         if (arg.empty()) {
541                 cerr << "lyxclient: The option -t requires 1 argument."
542                      << endl;
543                 return -1;
544         }
545         mainTmp = arg[0];
546         return 1;
547 }
548
549
550 string serverPid; // Init to empty string
551
552
553 int p(vector<docstring> const & arg)
554 {
555         if (arg.empty()) {
556                 cerr << "lyxclient: The option -p requires 1 argument."
557                      << endl;
558                 return -1;
559         }
560         serverPid = to_ascii(arg[0]);
561         return 1;
562 }
563
564
565 } // namespace cmdline
566
567 /// The main application class
568 class LyXClientApp : public ConsoleApplication
569 {
570 public:
571         LyXClientApp(int & argc, char * argv[])
572                 : ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
573                   argc_(argc), argv_(argv)
574         {
575         }
576         void doExec()
577         {
578                 try {
579                         int const exit_status = run();
580                         exit(exit_status);
581                 }
582                 catch (cmdline::StopException & e) {
583                         exit(e.status());
584                 }
585         }
586 private:
587         int run();
588         int & argc_;
589         char ** argv_;
590 };
591
592
593 int LyXClientApp::run()
594 {
595         // qt changes this, and our numeric conversions require the C locale
596         setlocale(LC_NUMERIC, "C");
597
598         // Set defaults
599         char const * const lyxsocket = getenv("LYXSOCKET");
600         if (lyxsocket)
601                 cmdline::serverAddress = from_local8bit(lyxsocket);
602
603         // Default temporary
604         cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
605
606         // Command line builder
607         CmdLineParser args;
608         args.helper["-h"] = cmdline::h;
609         args.helper["-c"] = cmdline::c;
610         args.helper["-g"] = cmdline::g;
611         args.helper["-n"] = cmdline::n;
612         args.helper["-a"] = cmdline::a;
613         args.helper["-t"] = cmdline::t;
614         args.helper["-p"] = cmdline::p;
615
616         // Command line failure conditions:
617         if ((!args.parse(argc_, argv_))
618            || (args.isset["-c"] && args.isset["-g"])
619            || (args.isset["-a"] && args.isset["-p"])) {
620                 cmdline::usage();
621                 return EXIT_FAILURE;
622         }
623
624         unique_ptr<LyXDataSocket> server;
625
626         if (!cmdline::serverAddress.empty()) {
627                 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
628                 if (!server->connected()) {
629                         cerr << "lyxclient: " << "Could not connect to "
630                              << to_utf8(cmdline::serverAddress) << endl;
631                         return EXIT_FAILURE;
632                 }
633         } else {
634                 // We have to look for an address.
635                 // serverPid can be empty.
636                 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
637                 FileNameList::const_iterator addr = addrs.begin();
638                 FileNameList::const_iterator end = addrs.end();
639                 for (; addr != end; ++addr) {
640                         // Caution: addr->string() is in filesystem encoding
641                         server.reset(new LyXDataSocket(*addr));
642                         if (server->connected())
643                                 break;
644                         lyxerr << "lyxclient: " << "Could not connect to "
645                              << addr->absFileName() << endl;
646                 }
647                 if (addr == end) {
648                         lyxerr << "lyxclient: No suitable server found."
649                                << endl;
650                         return EXIT_FAILURE;
651                 }
652                 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
653         }
654
655         int const serverfd = server->fd();
656
657         IOWatch iowatch;
658         iowatch.addfd(serverfd);
659
660         // Used to read from server
661         string answer;
662
663         // Send greeting
664         server->writeln("HELLO:" + to_utf8(cmdline::clientName));
665         // wait at most 2 seconds until server responds
666         iowatch.wait(2.0);
667         if (iowatch.isset(serverfd) && server->readln(answer)) {
668                 if (prefixIs(answer, "BYE:")) {
669                         cerr << "lyxclient: Server disconnected." << endl;
670                         cout << answer << endl;
671                         return EXIT_FAILURE;
672                 }
673         } else {
674                 cerr << "lyxclient: No answer from server." << endl;
675                 return EXIT_FAILURE;
676         }
677
678         if (args.isset["-g"] || args.isset["-c"]) {
679                 server->writeln(to_utf8(cmdline::singleCommand));
680                 iowatch.wait(2.0);
681                 if (iowatch.isset(serverfd) && server->readln(answer)) {
682                         cout << answer;
683                         if (prefixIs(answer, "ERROR:"))
684                                 return EXIT_FAILURE;
685                         return EXIT_SUCCESS;
686                 } else {
687                         cerr << "lyxclient: No answer from server." << endl;
688                         return EXIT_FAILURE;
689                 }
690         }
691
692         // Take commands from stdin
693         iowatch.addfd(0); // stdin
694         bool saidbye = false;
695         while ((!saidbye) && server->connected()) {
696                 iowatch.wait();
697                 if (iowatch.isset(0)) {
698                         string command;
699                         getline(cin, command);
700                         if (command.empty())
701                                 continue;
702                         if (command == "BYE:") {
703                                 server->writeln("BYE:");
704                                 saidbye = true;
705                         } else {
706                                 server->writeln("LYXCMD:" + command);
707                         }
708                 }
709                 if (iowatch.isset(serverfd)) {
710                         while(server->readln(answer))
711                                 cout << answer << endl;
712                 }
713         }
714
715         return EXIT_SUCCESS;
716 }
717
718 } // namespace lyx
719
720
721 int main(int argc, char * argv[])
722 {
723         lyx::lyxerr.setStream(cerr);
724
725         lyx::LyXClientApp app(argc, argv);
726         return app.exec();
727 }
728
729
730 namespace boost {
731
732 void assertion_failed(char const* a, char const* b, char const* c, long d)
733 {
734         lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '
735                 << d << '\n';
736 }
737
738 } // namespace boost