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