* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
- * \author Lars Gullik Bjønnes
+ * \author Lars Gullik Bjønnes
* \author Jean-Marc Lasgouttes
* \author Angus Leeming
* \author John Levon
+ * \author Enrico Forestieri
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "Server.h"
-#include "support/debug.h"
#include "FuncRequest.h"
#include "LyXAction.h"
#include "LyXFunc.h"
+
#include "frontends/Application.h"
+#include "support/debug.h"
#include "support/FileName.h"
#include "support/lstrings.h"
-#include "support/lyxlib.h"
+#include "support/os.h"
#include <boost/bind.hpp>
+#ifdef _WIN32
+#include <QCoreApplication>
+#endif
+
#include <cerrno>
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#include <fcntl.h>
+using namespace std;
+using namespace lyx::support;
+using os::external_path;
namespace lyx {
-using support::compare;
-using support::FileName;
-using support::rtrim;
-using support::split;
-
-using std::endl;
-using std::string;
-
-
/////////////////////////////////////////////////////////////////////
//
// LyXComm
//
/////////////////////////////////////////////////////////////////////
-#if !defined (HAVE_MKFIFO)
+#if defined(_WIN32)
+
+class PipeEvent : public QEvent {
+public:
+ ///
+ PipeEvent(HANDLE inpipe) : QEvent(QEvent::User), inpipe_(inpipe)
+ {}
+ ///
+ HANDLE pipe() const { return inpipe_; }
+
+private:
+ HANDLE inpipe_;
+};
+
+namespace {
+
+char * errormsg()
+{
+ void * msgbuf;
+ DWORD error = GetLastError();
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &msgbuf, 0, NULL);
+ return static_cast<char *>(msgbuf);
+}
+
+
+extern "C" {
+
+DWORD WINAPI pipeServerWrapper(void * arg)
+{
+ LyXComm * lyxcomm = reinterpret_cast<LyXComm *>(arg);
+ lyxcomm->pipeServer();
+ return 1;
+}
+
+}
+
+} // namespace anon
+
+LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
+ : pipename_(pip), client_(cli), clientcb_(ccb)
+{
+ ready_ = false;
+ openConnection();
+}
+
+
+void LyXComm::pipeServer()
+{
+ int const bufsize = 1024;
+ HANDLE inpipe;
+
+ outpipe_ = CreateNamedPipe(external_path(outPipeName()).c_str(),
+ PIPE_ACCESS_OUTBOUND, PIPE_NOWAIT,
+ PIPE_UNLIMITED_INSTANCES,
+ bufsize, 0, 0, NULL);
+ if (outpipe_ == INVALID_HANDLE_VALUE) {
+ lyxerr << "LyXComm: Could not create pipe "
+ << outPipeName() << '\n' << errormsg() << endl;
+ return;
+ }
+ ConnectNamedPipe(outpipe_, NULL);
+ // Now change to blocking mode
+ DWORD mode = PIPE_WAIT;
+ SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+
+ while (!checkStopServerEvent()) {
+ inpipe = CreateNamedPipe(external_path(inPipeName()).c_str(),
+ PIPE_ACCESS_INBOUND, PIPE_WAIT,
+ PIPE_UNLIMITED_INSTANCES,
+ 0, bufsize, 0, NULL);
+ if (inpipe == INVALID_HANDLE_VALUE) {
+ lyxerr << "LyXComm: Could not create pipe "
+ << inPipeName() << '\n' << errormsg() << endl;
+ break;
+ }
+
+ BOOL connected = ConnectNamedPipe(inpipe, NULL);
+ // Check whether we are signaled to shutdown the pipe server.
+ if (checkStopServerEvent()) {
+ CloseHandle(inpipe);
+ break;
+ }
+ if (connected || GetLastError() == ERROR_PIPE_CONNECTED) {
+ PipeEvent * event = new PipeEvent(inpipe);
+ QCoreApplication::postEvent(this,
+ static_cast<QEvent *>(event));
+ } else
+ CloseHandle(inpipe);
+ }
+ CloseHandle(outpipe_);
+}
+
+
+bool LyXComm::event(QEvent * e)
+{
+ if (e->type() == QEvent::User) {
+ read_ready(static_cast<PipeEvent *>(e)->pipe());
+ return true;
+ }
+ return false;
+}
+
+
+BOOL LyXComm::checkStopServerEvent()
+{
+ return WaitForSingleObject(stopserver_, 0) == WAIT_OBJECT_0;
+}
+
+
+void LyXComm::openConnection()
+{
+ LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
+
+ // If we are up, that's an error
+ if (ready_) {
+ lyxerr << "LyXComm: Already connected" << endl;
+ return;
+ }
+ // We assume that we don't make it
+ ready_ = false;
+
+ if (pipename_.empty()) {
+ LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
+ return;
+ }
+
+ stopserver_ = CreateEvent(NULL, TRUE, FALSE, NULL);
+ DWORD tid;
+ HANDLE thread = CreateThread(NULL, 0, pipeServerWrapper,
+ static_cast<void *>(this), 0, &tid);
+ if (!thread) {
+ lyxerr << "LyXComm: Could not create pipe server thread: "
+ << errormsg() << endl;
+ return;
+ } else
+ CloseHandle(thread);
+
+ // We made it!
+ ready_ = true;
+ LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
+}
+
+
+/// Close pipes
+void LyXComm::closeConnection()
+{
+ LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
+
+ if (pipename_.empty()) {
+ LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
+ return;
+ }
+
+ if (!ready_) {
+ LYXERR0("LyXComm: Already disconnected");
+ return;
+ }
+
+ SetEvent(stopserver_);
+ HANDLE hpipe = CreateFile(external_path(inPipeName()).c_str(),
+ GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hpipe != INVALID_HANDLE_VALUE)
+ CloseHandle(hpipe);
+
+ ready_ = false;
+}
+
+
+void LyXComm::emergencyCleanup()
+{
+ if (!pipename_.empty()) {
+ SetEvent(stopserver_);
+ HANDLE hpipe = CreateFile(external_path(inPipeName()).c_str(),
+ GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hpipe != INVALID_HANDLE_VALUE)
+ CloseHandle(hpipe);
+ }
+}
+
+void LyXComm::read_ready(HANDLE inpipe)
+{
+ string read_buffer_;
+ read_buffer_.erase();
+
+ int const charbuf_size = 100;
+ char charbuf[charbuf_size];
+
+ DWORD status;
+ while (ReadFile(inpipe, charbuf, charbuf_size - 1, &status, NULL)) {
+ if (status > 0) {
+ charbuf[status] = '\0'; // turn it into a c string
+ read_buffer_ += rtrim(charbuf, "\r");
+ // commit any commands read
+ while (read_buffer_.find('\n') != string::npos) {
+ // split() grabs the entire string if
+ // the delim /wasn't/ found. ?:-P
+ string cmd;
+ read_buffer_= split(read_buffer_, cmd, '\n');
+ cmd = rtrim(cmd, "\r");
+ LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
+ << ", read_buffer_:" << read_buffer_
+ << ", cmd:" << cmd);
+ if (!cmd.empty())
+ clientcb_(client_, cmd);
+ //\n or not \n?
+ }
+ }
+ }
+ if (GetLastError() != ERROR_BROKEN_PIPE) {
+ // An error occurred
+ LYXERR0("LyXComm: " << errormsg());
+ if (!read_buffer_.empty()) {
+ LYXERR0("LyXComm: truncated command: " << read_buffer_);
+ read_buffer_.erase();
+ }
+ }
+ // Client closed the pipe, so disconnect and close.
+ DisconnectNamedPipe(inpipe);
+ CloseHandle(inpipe);
+ FlushFileBuffers(outpipe_);
+ DisconnectNamedPipe(outpipe_);
+ // Temporarily change to non-blocking mode otherwise
+ // ConnectNamedPipe() would block waiting for a connection.
+ DWORD mode = PIPE_NOWAIT;
+ SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+ ConnectNamedPipe(outpipe_, NULL);
+ mode = PIPE_WAIT;
+ SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+}
+
+
+void LyXComm::send(string const & msg)
+{
+ if (msg.empty()) {
+ LYXERR0("LyXComm: Request to send empty string. Ignoring.");
+ return;
+ }
+
+ LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
+
+ if (pipename_.empty()) return;
+
+ if (!ready_) {
+ LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
+ return;
+ }
+
+ bool success;
+ int count = 0;
+ do {
+ DWORD sent;
+ success = WriteFile(outpipe_, msg.c_str(), msg.length(), &sent, NULL);
+ if (!success) {
+ DWORD error = GetLastError();
+ if (error == ERROR_NO_DATA) {
+ DisconnectNamedPipe(outpipe_);
+ DWORD mode = PIPE_NOWAIT;
+ SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+ ConnectNamedPipe(outpipe_, NULL);
+ mode = PIPE_WAIT;
+ SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+ } else if (error != ERROR_PIPE_LISTENING)
+ break;
+ }
+ Sleep(100);
+ ++count;
+ } while (!success && count < 100);
+
+ if (!success) {
+ lyxerr << "LyXComm: Error sending message: " << msg
+ << '\n' << errormsg()
+ << "\nLyXComm: Resetting connection" << endl;
+ closeConnection();
+ openConnection();
+ }
+}
+
+
+#elif !defined (HAVE_MKFIFO)
// We provide a stub class that disables the lyxserver.
-LyXComm::LyXComm(std::string const &, Server *, ClientCallbackfct)
+LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
{}
void LyXComm::openConnection()
#else // defined (HAVE_MKFIFO)
-LyXComm::LyXComm(std::string const & pip, Server * cli, ClientCallbackfct ccb)
+LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
: pipename_(pip), client_(cli), clientcb_(ccb)
{
ready_ = false;
int LyXComm::startPipe(string const & file, bool write)
{
+ static bool stalepipe = false;
FileName const filename(file);
- if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
- lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
- << "If no other LyX program is active, please delete"
- " the pipe by hand and try again." << endl;
- pipename_.erase();
- return -1;
+ if (filename.exists()) {
+ if (!write) {
+ // Let's see whether we have a stale pipe.
+ int fd = ::open(filename.toFilesystemEncoding().c_str(),
+ O_WRONLY | O_NONBLOCK);
+ if (fd >= 0) {
+ // Another LyX instance is using it.
+ ::close(fd);
+ } else if (errno == ENXIO) {
+ // No process is reading from the other end.
+ stalepipe = true;
+ LYXERR(Debug::LYXSERVER,
+ "LyXComm: trying to remove "
+ << filename);
+ filename.removeFile();
+ }
+ } else if (stalepipe) {
+ LYXERR(Debug::LYXSERVER, "LyXComm: trying to remove "
+ << filename);
+ filename.removeFile();
+ stalepipe = false;
+ }
+ if (filename.exists()) {
+ lyxerr << "LyXComm: Pipe " << filename
+ << " already exists.\nIf no other LyX program"
+ " is active, please delete the pipe by hand"
+ " and try again."
+ << endl;
+ pipename_.erase();
+ return -1;
+ }
}
if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
int const charbuf_size = 100;
char charbuf[charbuf_size];
+ // As O_NONBLOCK is set, until no data is available for reading,
+ // read() doesn't block but returns -1 and set errno to EAGAIN.
+ // After a client that opened the pipe for writing, closes it
+ // (and no other client is using the pipe), read() would always
+ // return 0 and thus the connection has to be reset.
+
errno = 0;
int status;
// the single = is intended here.
clientcb_(client_, cmd);
//\n or not \n?
}
- }
- if (errno == EAGAIN) {
- errno = 0;
- return;
- }
- if (errno != 0) {
+ } else {
+ if (errno == EAGAIN) {
+ // Nothing to read, continue
+ errno = 0;
+ return;
+ }
+ // An error occurred, better bailing out
LYXERR0("LyXComm: " << strerror(errno));
if (!read_buffer_.empty()) {
LYXERR0("LyXComm: truncated command: " << read_buffer_);
}
}
- // The connection gets reset in errno != EAGAIN
- // Why does it need to be reset if errno == 0?
+ // The connection gets reset when read() returns 0 (meaning that the
+ // last client closed the pipe) or an error occurred, in which case
+ // read() returns -1 and errno != EAGAIN.
closeConnection();
openConnection();
errno = 0;
server->callback(msg);
}
-Server::Server(LyXFunc * f, std::string const & pipes)
+Server::Server(LyXFunc * f, string const & pipes)
: numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
{}
}
-// Send a notify messge to a client, called by WorkAreaKeyPress
+// Send a notify message to a client, called by WorkAreaKeyPress
void Server::notifyClient(string const & s)
{
pipes_.send("NOTIFY:" + s + "\n");
} // namespace lyx
+
+#ifdef _WIN32
+#include "moc_Server.cpp"
+#endif