1 // SciTE - Scintilla based Text Editor
2 /** @file DirectorExtension.cxx
3 ** Extension for communicating with a director program.
4 ** This allows external client programs (and internal extensions) to communicate
5 ** with instances of SciTE. The original scheme required you to define the property
6 ** ipc.scite.name to be a valid (but _not_ created) pipename, which becomes the
7 ** 'request pipe' for sending commands to SciTE. (The set of available commands
8 ** is defined in SciTEBase::PerformOne()). One also had to specify a property
9 ** ipc.director.name to be an _existing_ pipe which would receive notifications (like
10 ** when a file is opened, buffers switched, etc).
11 **
12 **
13 ** This version supports the old protocol, so existing clients such as ScitePM still
14 ** work as before. But it is no longer necessary to specify these ipc properties.
15 ** If ipc.scite.name is not defined, then a new request pipe is created of the form
16 ** /tmp/SciTE.<pid>.in using the current pid. This pipename is put back into
17 ** ipc.scite.name (this is useful for internal extensions that want to find _another_
18 ** instance of SciTE). This pipe will be removed when SciTE closes normally,
19 ** so listing all files with the pattern '/tmp/SciTE.*.in' will give the currently
20 ** running SciTE instances.
21 **
22 ** If a client wants to receive notifications, they must ask using
23 ** the register command, i.e. send ':<path to temp file>:register:' to the request
24 ** pipe. SciTE will create a new notify pipe (of the form /tmp/SciTE.<pid>.<k>.out)
25 ** and write it into the temp file, which the client can read and open.
26 **
27 ** This version also supports the 'correspondent' concept used by the Win32
28 ** version; requests of the form ':<my pipe>:<command>:<args>' make any results
29 ** get sent back to the specified, existing pipename <my pipe>. For example,
30 ** ':/tmp/mypipe:askproperty:SciteDefaultHome' will make SciTE write the value of
31 ** the standard property 'SciteDefaultHome' to the pipe /tmp/mypipe.
32 **/
33 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
34 // The License.txt file describes the conditions under which this software may be distributed.
35
36 #include <stdlib.h>
37 #include <string.h>
38 #include <stdio.h>
39 #include <time.h>
40 #include <errno.h>
41
42 #include <string>
43 #include <string_view>
44 #include <vector>
45 #include <map>
46 #include <set>
47 #include <memory>
48 #include <chrono>
49 #include <atomic>
50 #include <mutex>
51
52 #include <fcntl.h>
53 #include <unistd.h>
54 #include <sys/types.h>
55 #include <sys/stat.h>
56
57 #include <gtk/gtk.h>
58
59 #include "ILexer.h"
60
61 #include "ScintillaTypes.h"
62 #include "ScintillaMessages.h"
63 #include "ScintillaCall.h"
64
65 #include "Scintilla.h"
66
67 #include "GUI.h"
68 #include "ScintillaWindow.h"
69 #include "StringList.h"
70 #include "StringHelpers.h"
71 #include "FilePath.h"
72 #include "StyleDefinition.h"
73 #include "PropSetFile.h"
74 #include "Extender.h"
75 #include "SciTE.h"
76 #include "JobQueue.h"
77 #include "Cookie.h"
78 #include "Worker.h"
79 #include "MatchMarker.h"
80 #include "SciTEBase.h"
81 #include "DirectorExtension.h"
82
83 static int fdDirector = 0;
84 static int fdCorrespondent = 0;
85 static int fdReceiver = 0;
86 static bool startedByDirector = false;
87 static bool shuttingDown = false;
88
89 // the number of notify connections this SciTE instance will handle.
90 const int MAX_PIPES = 20;
91
92 #define TMP_FILENAME_LENGTH 1024
93
94 static char requestPipeName[TMP_FILENAME_LENGTH];
95
96 //#define IF_DEBUG(x) x;
97 #define IF_DEBUG(x)
98
99 IF_DEBUG(static FILE *fdDebug = 0)
100
101 // managing a list of notification pipes
102 // this also allows for proper cleanup of any pipes
103 // that aren't _directly_ specified by the director.
104 struct PipeEntry {
105 int fd;
106 char *name;
~PipeEntryPipeEntry107 ~PipeEntry() {
108 free(name);
109 name = 0;
110 }
111 };
112 static PipeEntry s_send_pipes[MAX_PIPES];
113 static int s_send_cnt = 0;
114
SendPipeAvailable()115 static bool SendPipeAvailable() {
116 return s_send_cnt < MAX_PIPES-1;
117 }
118
AddSendPipe(int fd,const char * name)119 static void AddSendPipe(int fd, const char *name) {
120 if (SendPipeAvailable()) {
121 PipeEntry &entry = s_send_pipes[s_send_cnt++];
122 entry.fd = fd;
123 if (name)
124 entry.name = strdup(name);
125 else
126 entry.name = NULL;
127 }
128 }
129
RemoveSendPipes()130 static void RemoveSendPipes() {
131 for (int i = 0; i < s_send_cnt; ++i) {
132 PipeEntry &entry = s_send_pipes[i];
133 close(entry.fd);
134 if (entry.name)
135 remove(entry.name);
136 }
137 }
138
MakePipe(const char * pipeName)139 static bool MakePipe(const char *pipeName) {
140 int res;
141 res = mkfifo(pipeName, 0777);
142 return res == 0;
143 }
144
OpenPipe(const char * pipeName)145 static int OpenPipe(const char *pipeName) {
146 int fd = open(pipeName, O_RDWR | O_NONBLOCK);
147 return fd;
148 }
149
150 // we now send notifications to _all_ the notification pipes registered!
SendPipeCommand(const char * pipeCommand)151 static bool SendPipeCommand(const char *pipeCommand) {
152 int size;
153 if (fdCorrespondent) {
154 size = write(fdCorrespondent,pipeCommand,strlen(pipeCommand));
155 size += write(fdCorrespondent,"\n",1);
156 IF_DEBUG(fprintf(fdDebug, "Send correspondent: %s %d bytes to %d\n", pipeCommand, size,fdCorrespondent))
157 } else {
158 for (int i = 0; i < s_send_cnt; ++i) {
159 int fd = s_send_pipes[i].fd;
160 // put a linefeed after the notification!
161 size = write(fd, pipeCommand, strlen(pipeCommand));
162 size += write(fd,"\n",1);
163 IF_DEBUG(fprintf(fdDebug, "Send pipecommand: %s %d bytes to %d\n", pipeCommand, size,fd))
164 }
165 }
166 (void)size; // to keep compiler happy if we aren't debugging...
167 return true;
168 }
169
ReceiverPipeSignal(GIOChannel * source,GIOCondition condition,void * data)170 static gboolean ReceiverPipeSignal(GIOChannel *source, GIOCondition condition, void *data) {
171 #ifndef GDK_VERSION_3_6
172 gdk_threads_enter();
173 #endif
174
175 if ((condition & G_IO_IN) == G_IO_IN) {
176 std::string pipeString;
177 char pipeData[8192];
178 gsize readLength;
179 GError *error = NULL;
180 GIOStatus status = g_io_channel_read_chars(source, pipeData,
181 sizeof(pipeData) - 1, &readLength, &error);
182 while ((status != G_IO_STATUS_ERROR) && (readLength > 0)) {
183 pipeData[readLength] = '\0';
184 pipeString.append(pipeData);
185 status = g_io_channel_read_chars(source, pipeData,
186 sizeof(pipeData) - 1, &readLength, &error);
187 }
188 DirectorExtension *ext = static_cast<DirectorExtension *>(data);
189 ext->HandleStringMessage(pipeString.c_str());
190 }
191 #ifndef GDK_VERSION_3_6
192 gdk_threads_leave();
193 #endif
194 return TRUE;
195 }
196
SendDirector(const char * verb,const char * arg=0)197 static void SendDirector(const char *verb, const char *arg = 0) {
198 IF_DEBUG(fprintf(fdDebug, "SendDirector:(%s, %s): fdDirector = %d\n", verb, arg, fdDirector))
199 if (s_send_cnt) {
200 std::string addressedMessage;
201 addressedMessage += verb;
202 addressedMessage += ":";
203 if (arg)
204 addressedMessage += arg;
205 //send the message through all the registered pipes
206 ::SendPipeCommand(addressedMessage.c_str());
207 } else {
208 IF_DEBUG(fprintf(fdDebug, "SendDirector: no notify pipes\n"))
209 }
210 }
211
not_empty(const char * s)212 static bool not_empty(const char *s) {
213 return s && *s;
214 }
215
CheckEnvironment(ExtensionAPI * host)216 static void CheckEnvironment(ExtensionAPI *host) {
217 if (!host)
218 return ;
219 if (!fdDirector) {
220 std::string director = host->Property("ipc.director.name");
221 if (director.length() > 0) {
222 startedByDirector = true;
223 fdDirector = OpenPipe(director.c_str());
224 AddSendPipe(fdDirector,NULL); // we won't remove this pipe!
225 }
226 }
227 }
228
Instance()229 DirectorExtension &DirectorExtension::Instance() {
230 static DirectorExtension singleton;
231 return singleton;
232 }
233
Initialise(ExtensionAPI * host_)234 bool DirectorExtension::Initialise(ExtensionAPI *host_) {
235 host = host_;
236 IF_DEBUG(fdDebug = fopen("/tmp/SciTE.log", "w"))
237 CheckEnvironment(host);
238 // always try to create the receive (request) pipe, even if not started by
239 // an external director
240 CreatePipe();
241 IF_DEBUG(fprintf(fdDebug, "Initialise: fdReceiver: %d\n", fdReceiver))
242 // but do crash out if we failed and were started by an external director.
243 if (!fdReceiver && startedByDirector) {
244 exit(3);
245 }
246 return true;
247 }
248
Finalise()249 bool DirectorExtension::Finalise() {
250 ::SendDirector("closing");
251 // close and remove all the notification pipes (except ipc.director.name)
252 RemoveSendPipes();
253 // close our request pipe
254 if (fdReceiver != 0) {
255 close(fdReceiver);
256 }
257 // and remove it if we generated it automatically (i.e. not ipc.scite.name)
258 if (not_empty(requestPipeName)) {
259 remove(requestPipeName);
260 }
261 IF_DEBUG(fprintf(fdDebug,"finished\n"))
262 IF_DEBUG(fclose(fdDebug))
263
264 g_source_remove(inputWatcher);
265 inputWatcher = 0;
266 g_io_channel_unref(inputChannel);
267 inputChannel = 0;
268
269 return true;
270 }
271
Clear()272 bool DirectorExtension::Clear() {
273 return false;
274 }
275
Load(const char *)276 bool DirectorExtension::Load(const char *) {
277 return false;
278 }
279
OnOpen(const char * path)280 bool DirectorExtension::OnOpen(const char *path) {
281 CheckEnvironment(host);
282 if (not_empty(path)) {
283 ::SendDirector("opened", path);
284 }
285 return false;
286 }
287
OnSwitchFile(const char * path)288 bool DirectorExtension::OnSwitchFile(const char *path) {
289 CheckEnvironment(host);
290 if (not_empty(path)) {
291 ::SendDirector("switched", path);
292 }
293 return false;
294 }
295
OnSave(const char * path)296 bool DirectorExtension::OnSave(const char *path) {
297 CheckEnvironment(host);
298 if (not_empty(path)) {
299 ::SendDirector("saved", path);
300 }
301 return false;
302 }
303
OnClose(const char * path)304 bool DirectorExtension::OnClose(const char *path) {
305 CheckEnvironment(host);
306 if (not_empty(path)) {
307 ::SendDirector("closed", path);
308 }
309 return false;
310 }
311
NeedsOnClose()312 bool DirectorExtension::NeedsOnClose() {
313 CheckEnvironment(host);
314 return s_send_cnt > 0;
315 }
316
OnChar(char)317 bool DirectorExtension::OnChar(char) {
318 return false;
319 }
320
OnExecute(const char * cmd)321 bool DirectorExtension::OnExecute(const char *cmd) {
322 CheckEnvironment(host);
323 ::SendDirector("macro:run", cmd);
324 return false;
325 }
326
OnSavePointReached()327 bool DirectorExtension::OnSavePointReached() {
328 return false;
329 }
330
OnSavePointLeft()331 bool DirectorExtension::OnSavePointLeft() {
332 return false;
333 }
334
OnStyle(SA::Position,SA::Position,int,StyleWriter *)335 bool DirectorExtension::OnStyle(SA::Position, SA::Position, int, StyleWriter *) {
336 return false;
337 }
338
339 // These should probably have arguments
340
OnDoubleClick()341 bool DirectorExtension::OnDoubleClick() {
342 return false;
343 }
344
OnUpdateUI()345 bool DirectorExtension::OnUpdateUI() {
346 return false;
347 }
348
OnMarginClick()349 bool DirectorExtension::OnMarginClick() {
350 return false;
351 }
352
OnMacro(const char * command,const char * params)353 bool DirectorExtension::OnMacro(const char *command, const char *params) {
354 SendDirector(command, params);
355 return false;
356 }
357
SendProperty(const char * prop)358 bool DirectorExtension::SendProperty(const char *prop) {
359 CheckEnvironment(host);
360 if (not_empty(prop)) {
361 ::SendDirector("property", prop);
362 }
363 return false;
364 }
365
HandleStringMessage(const char * message)366 void DirectorExtension::HandleStringMessage(const char *message) {
367 static int kount = 1;
368 // Message may contain multiple commands separated by '\n'
369 StringList wlMessage(true);
370 wlMessage.Set(message);
371 IF_DEBUG(fprintf(fdDebug, "HandleStringMessage: got %s\n", message))
372 for (size_t i = 0; i < wlMessage.Length(); i++) {
373 // Message format is [:return address:]command:argument
374 char *cmd = wlMessage[i];
375 char *corresp = NULL;
376 if (*cmd == ':') { // see same routine in ../win32/DirectorExtension.cxx!
377 // there is a return address
378 char *colon = strchr(cmd + 1,':');
379 if (colon) {
380 *colon = '\0';
381 corresp = cmd + 1;
382 cmd = colon + 1;
383 }
384 }
385 if (isprefix(cmd, "closing:")) {
386 fdDirector = 0;
387 if (startedByDirector && host) {
388 shuttingDown = true;
389 host->ShutDown();
390 shuttingDown = false;
391 }
392 } else if (isprefix(cmd, "register:")) {
393 // we handle this verb specially - an extension has asked us for a notify
394 // pipe which it can listen to. We make up a unique name based on our
395 // pid and a sequence count.
396 // There is an (artificial) limit on the number of notify pipes;
397 // if there are no more slots, then the returned pipename is '*'
398 char pipeName[TMP_FILENAME_LENGTH];
399 if (! SendPipeAvailable()) {
400 StringCopy(pipeName,"*");
401 } else {
402 sprintf(pipeName,"%s/SciTE.%d.%d.out", g_get_tmp_dir(), getpid(), kount++);
403 }
404 if (corresp == NULL) {
405 fprintf(stderr,"SciTE Director: bad request\n");
406 return;
407 } else {
408 // the registering client has passed us a path for receiving the notify pipe name.
409 // this has to be a _regular_ file, which may not exist.
410 fdCorrespondent = open(corresp,O_WRONLY | O_CREAT, S_IRWXU);
411 IF_DEBUG(fprintf(fdDebug,"register '%s' %d\n",corresp,fdCorrespondent))
412 if (fdCorrespondent == -1) {
413 fdCorrespondent = 0;
414 fprintf(stderr,"SciTE Director: cannot open result file '%s'\n",corresp);
415 return;
416 }
417 if (fdCorrespondent != 0) {
418 size_t size = write(fdCorrespondent, pipeName, strlen(pipeName));
419 size += write(fdCorrespondent, "\n", 1);
420 if (size == 0)
421 fprintf(stderr,"SciTE Director: cannot write pipe name\n");
422
423 }
424 }
425 if (SendPipeAvailable()) {
426 MakePipe(pipeName);
427 int fd = OpenPipe(pipeName);
428 AddSendPipe(fd, pipeName);
429 }
430 } else if (host) {
431 if (corresp != NULL) {
432 // the client has passed us a pipename to receive the results of this command
433 fdCorrespondent = OpenPipe(corresp);
434 IF_DEBUG(fprintf(fdDebug,"corresp '%s' %d\n",corresp,fdCorrespondent))
435 if (fdCorrespondent == -1) {
436 fdCorrespondent = 0;
437 fprintf(stderr,"SciTE Director: cannot open correspondent pipe '%s'\n",corresp);
438 return;
439 }
440 }
441 host->Perform(cmd);
442 }
443 if (fdCorrespondent != 0) {
444 close(fdCorrespondent);
445 fdCorrespondent = 0;
446 }
447 }
448 }
449
CreatePipe(bool)450 void DirectorExtension::CreatePipe(bool) {
451 if (!host)
452 return;
453 bool tryStandardPipeCreation;
454 std::string pipeName = host->Property("ipc.scite.name");
455
456 fdReceiver = -1;
457 inputWatcher = -1;
458 inputChannel = 0;
459 requestPipeName[0] = '\0';
460
461 // check we have been given a specific pipe name
462 if (pipeName.length() > 0) {
463 IF_DEBUG(fprintf(fdDebug, "CreatePipe: if (not_empty(pipeName)): '%s'\n", pipeName.c_str()))
464 fdReceiver = OpenPipe(pipeName.c_str());
465 // there isn't a pipe - so create one
466 if (fdReceiver == -1 && errno == ENOENT) {
467 IF_DEBUG(fprintf(fdDebug, "CreatePipe: Non found - making\n"))
468 if (MakePipe(pipeName.c_str())) {
469 fdReceiver = OpenPipe(pipeName.c_str());
470 if (fdReceiver == -1) {
471 perror("CreatePipe: could not open newly created pipe");
472 }
473 } else {
474 perror("CreatePipe: could not create ipc.scite.name");
475 }
476 // We don't need a new pipe, we're supposed to have one
477 tryStandardPipeCreation = false;
478 } else if (fdReceiver == -1) {
479 // there are quite a few errors related to open...
480 // maybe the pipe is already owned by another SciTE instance...
481 // we'll just try creating a new one
482 perror("CreatePipe: opening ipc.scite.name failed");
483 tryStandardPipeCreation = true;
484 } else {
485 // cool - we can open it
486 tryStandardPipeCreation = false;
487 }
488 } else {
489 tryStandardPipeCreation = true;
490 }
491
492 // We were not given a name or we could'nt open it
493 if (tryStandardPipeCreation) {
494 sprintf(requestPipeName,"%s/SciTE.%d.in", g_get_tmp_dir(), getpid());
495 IF_DEBUG(fprintf(fdDebug, "Creating pipe %s\n", requestPipeName))
496 MakePipe(requestPipeName);
497 fdReceiver = OpenPipe(requestPipeName);
498 }
499
500 // If we were able to open a pipe, listen to it
501 if (fdReceiver != -1) {
502 // store the inputWatcher so we can remove it.
503 inputChannel = g_io_channel_unix_new(fdReceiver);
504 inputWatcher = g_io_add_watch(inputChannel, G_IO_IN, (GIOFunc)ReceiverPipeSignal, this);
505 //inputWatcher = gdk_input_add(fdReceiver, GDK_INPUT_READ, ReceiverPipeSignal, this);
506 // if we were not supplied with an explicit ipc.scite.name, then set this
507 // property to be the constructed pipe name.
508 if (pipeName.length() > 0) {
509 host->SetProperty("ipc.scite.name", requestPipeName);
510 }
511 return;
512 }
513
514 // if we arrive here, we must have failed
515 fdReceiver = 0;
516 }
517