1 /*
2  * This file is part of Siril, an astronomy image processor.
3  * Copyright (C) 2005-2011 Francois Meyer (dulle at free.fr)
4  * Copyright (C) 2012-2021 team free-astro (see more in AUTHORS file)
5  * Reference site is https://free-astro.org/index.php/Siril
6  *
7  * Siril is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Siril is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Siril. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 /* This file manages the external command stream to siril, a named pipe */
22 
23 #define PIPE_NAME_R "siril_command.in"
24 #define PIPE_NAME_W "siril_command.out"
25 #define PIPE_PATH_R "/tmp/" PIPE_NAME_R  // TODO: use g_get_tmp_dir()
26 #define PIPE_PATH_W "/tmp/" PIPE_NAME_W  // TODO: use g_get_tmp_dir()
27 #define PIPE_MSG_SZ 512	// max input command length
28 
29 #include <stdio.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <string.h>
34 #include <sys/stat.h>
35 #include <fcntl.h>
36 #include <signal.h>
37 #ifdef _WIN32
38 // doc at: https://docs.microsoft.com/en-us/windows/desktop/ipc/named-pipes
39 // samples from: https://docs.microsoft.com/en-us/windows/desktop/ipc/using-pipes
40 // With windows, pipes can be bidirectional. To keep common code between windows and the
41 //	rest of the world, we still use unidirectional modes for windows named pipes.
42 // It's also different in the philosophy of pipe creation, where a pipe is created only
43 //	for one client and has to be created again to serve another.
44 #include <windows.h>
45 #include <conio.h>
46 #include <tchar.h>
47 #else
48 #include <sys/select.h>
49 #endif
50 
51 #include "core/siril.h"
52 #include "core/siril_log.h"
53 #include "pipe.h"
54 #include "command_line_processor.h"
55 //#include "processing.h"
56 	void stop_processing_thread();	// avoid including everything
57 	gpointer waiting_for_thread();
58 	gboolean get_thread_run();
59 #include "gui/progress_and_log.h"
60 
61 #ifdef _WIN32
62 LPTSTR lpszPipename_r = TEXT("\\\\.\\pipe\\" PIPE_NAME_R);
63 LPTSTR lpszPipename_w = TEXT("\\\\.\\pipe\\" PIPE_NAME_W);
64 HANDLE hPipe_r = INVALID_HANDLE_VALUE;
65 HANDLE hPipe_w = INVALID_HANDLE_VALUE;
66 #else
67 static int pipe_fd_r = -1;
68 static int pipe_fd_w = -1;
69 #endif
70 
71 static GThread *pipe_thread_w, *worker_thread;
72 static int pipe_active;
73 static GCond write_cond, read_cond;
74 static GMutex write_mutex, read_mutex;
75 static GList *command_list, *pending_writes;
76 // ^ could use GQueue instead since it's used as a queue, avoids the cells memory leak
77 
78 #ifndef _WIN32
sigpipe_handler(int signum)79 static void sigpipe_handler(int signum) { }	// do nothing
80 #endif
81 
pipe_create()82 int pipe_create() {
83 #ifdef _WIN32
84 	if (hPipe_w != INVALID_HANDLE_VALUE || hPipe_r != INVALID_HANDLE_VALUE)
85 		return 0;
86 
87 	hPipe_w = CreateNamedPipe(
88 			lpszPipename_w,           // pipe name
89 			PIPE_ACCESS_OUTBOUND,     // write access
90 			PIPE_TYPE_MESSAGE |       // message type pipe
91 			PIPE_READMODE_MESSAGE |   // message-read mode
92 			PIPE_WAIT,                // blocking mode
93 			PIPE_UNLIMITED_INSTANCES, // max. instances
94 			3*PIPE_MSG_SZ,            // output buffer size
95 			0,                        // input buffer size
96 			0,                        // client time-out
97 			NULL);                    // default security attribute
98 	if (hPipe_w == INVALID_HANDLE_VALUE)
99 	{
100 		siril_log_message(_("Output pipe creation failed with error %d\n"), GetLastError());
101 		return -1;
102 	}
103 
104 	hPipe_r = CreateNamedPipe(
105 			lpszPipename_r,           // pipe name
106 			PIPE_ACCESS_INBOUND,      // read access
107 			PIPE_TYPE_MESSAGE |       // message type pipe
108 			PIPE_READMODE_MESSAGE |   // message-read mode
109 			PIPE_WAIT,                // blocking mode
110 			PIPE_UNLIMITED_INSTANCES, // max. instances
111 			3*PIPE_MSG_SZ,            // output buffer size
112 			0,                        // input buffer size
113 			0,                        // client time-out
114 			NULL);                    // default security attribute
115 	if (hPipe_r == INVALID_HANDLE_VALUE)
116 	{
117 		siril_log_message(_("Input pipe creation failed with error %d\n"), GetLastError());
118 		return -1;
119 	}
120 #else
121 	if (pipe_fd_r >= 0 || pipe_fd_w > 0) return 0;
122 
123 	struct sigaction sa;
124 	sa.sa_handler = sigpipe_handler;
125 	sigemptyset(&sa.sa_mask);
126 	sa.sa_flags = SA_RESTART; /* Restart functions if
127 				     interrupted by handler */
128 	if (sigaction(SIGPIPE, &sa, NULL) == -1) {
129 		perror("sigaction");
130 		return -1;
131 	}
132 
133 	struct stat st;
134 	if (stat(PIPE_PATH_R, &st)) {
135 		if (mkfifo(PIPE_PATH_R, 0666)) {
136 			siril_log_message(_("Could not create the named pipe "PIPE_PATH_R"\n"));
137 			perror("mkfifo");
138 			return -1;
139 		}
140 	}
141 	else if (!S_ISFIFO(st.st_mode)) {
142 		siril_log_message(_("The named pipe file " PIPE_PATH_R " already exists but is not a fifo, cannot create or open\n"));
143 		return -1;
144 	}
145 
146 	if (stat(PIPE_PATH_W, &st)) {
147 		if (mkfifo(PIPE_PATH_W, 0666)) {
148 			siril_log_message(_("Could not create the named pipe "PIPE_PATH_W"\n"));
149 			perror("mkfifo");
150 			return -1;
151 		}
152 	}
153 	else if (!S_ISFIFO(st.st_mode)) {
154 		siril_log_message(_("The named pipe file " PIPE_PATH_W " already exists but is not a fifo, cannot create or open\n"));
155 		return -1;
156 	}
157 #endif
158 	return 0;
159 }
160 
pipe_write(const char * string)161 static int pipe_write(const char *string) {
162 #ifdef _WIN32
163 	int length;
164 	DWORD  retval ;
165 	if (hPipe_w == INVALID_HANDLE_VALUE)
166 		return -1;
167 	length = strlen(string);
168 	BOOL result = WriteFile(hPipe_w, string, length, &retval, NULL);
169 	if(result && retval == length)
170 		return 0;
171 	int err = GetLastError();
172 	if (err == ERROR_BROKEN_PIPE) {
173 		fprintf(stderr, "Output stream disconnected.\n");
174 		return 1;
175 	}
176 	else if (err == ERROR_NO_DATA) {
177 		fprintf(stderr, "Output stream closed on receiving side.\n");
178 		return 1;
179 	}
180 	else {
181 		fprintf(stderr, "Error writing to output stream; error code was 0x%08x.\n", err);
182 		return 1;
183 	}
184 #else
185 	int length, retval;
186 	if (pipe_fd_w <= 0)
187 		return -1;
188 	length = strlen(string);
189 	retval = write(pipe_fd_w, string, length);
190 	// buffer full, short writes and disconnections are treated as errors
191 	return retval != length;
192 #endif
193 }
194 
pipe_send_message(pipe_message msgtype,pipe_verb verb,const char * arg)195 int pipe_send_message(pipe_message msgtype, pipe_verb verb, const char *arg) {
196 #ifdef _WIN32
197 	if (hPipe_w == INVALID_HANDLE_VALUE) return -1;
198 #else
199 	if (pipe_fd_w <= 0) return -1;
200 #endif
201 	char *msg = NULL;
202 
203 	switch (msgtype) {
204 		case PIPE_LOG:
205 			msg = malloc(strlen(arg) + 6);
206 			sprintf(msg, "log: %s", arg);
207 			break;
208 		case PIPE_STATUS:
209 			msg = malloc((arg ? strlen(arg) : 0) + 20);
210 			switch (verb) {
211 				case PIPE_STARTING:
212 					sprintf(msg, "status: starting %s", arg);
213 					break;
214 				case PIPE_SUCCESS:
215 					sprintf(msg, "status: success %s", arg);
216 					break;
217 				case PIPE_ERROR:
218 					sprintf(msg, "status: error %s", arg);
219 					break;
220 				case PIPE_EXIT:
221 					sprintf(msg, "status: exit\n");
222 					break;
223 				case PIPE_NA:
224 					free(msg);
225 					return -1;
226 			}
227 			break;
228 		case PIPE_PROGRESS:
229 			msg = strdup(arg);
230 			break;
231 	}
232 
233 	if (msg) {
234 		g_mutex_lock(&write_mutex);
235 		pending_writes = g_list_append(pending_writes, msg);
236 
237 		g_cond_signal(&write_cond);
238 		g_mutex_unlock(&write_mutex);
239 	}
240 	return 0;
241 }
242 
enqueue_command(char * command)243 int enqueue_command(char *command) {
244 	if (!strncmp(command, "cancel", 6))
245 		return 1;
246 	if ((command[0] >= 'a' && command[0] <= 'z') ||
247 			(command[0] >= 'A' && command[0] <= 'Z')) {
248 		g_mutex_lock(&read_mutex);
249 		command_list = g_list_append(command_list, command);
250 		g_cond_signal(&read_cond);
251 		g_mutex_unlock(&read_mutex);
252 	}
253 	return 0;
254 }
255 
empty_command_queue()256 void empty_command_queue() {
257 	g_mutex_lock(&read_mutex);
258 	while (command_list) {
259 		free(command_list->data);
260 		command_list = g_list_next(command_list);
261 	}
262 	g_mutex_unlock(&read_mutex);
263 }
264 
read_pipe(void * p)265 void *read_pipe(void *p) {
266 #ifdef _WIN32
267 	do {
268 		/* try to open the pipe */
269 		// will block until the other end is opened
270 		if (!ConnectNamedPipe(hPipe_r, NULL) && GetLastError() != ERROR_PIPE_CONNECTED) {
271 			siril_log_message(_("Could not open the named pipe\n"));
272 			break;
273 		}
274 
275 		fprintf(stdout, "opened read pipe\n");
276 		/* now, try to read from it */
277 		int bufstart = 0;
278 		DWORD len;
279 		char buf[PIPE_MSG_SZ];
280 		do
281 		{
282 			// Read from the pipe.
283 			BOOL fSuccess = ReadFile(
284 					hPipe_r,         // pipe handle
285 					buf+bufstart,    // buffer to receive reply
286 					PIPE_MSG_SZ-1-bufstart, // size of buffer
287 					&len,            // number of bytes read
288 					NULL);           // not overlapped
289 
290 			if ((fSuccess || GetLastError() == ERROR_MORE_DATA) && len > 0) {
291 				int i = 0, nbnl = 0;
292 				buf[len] = '\0';
293 				while (i < len && buf[i] != '\0') {
294 					if (buf[i] == '\n')
295 						nbnl++;
296 					i++;
297 				}
298 				if (nbnl == 0) {
299 					pipe_send_message(PIPE_STATUS, PIPE_ERROR, _("command too long or malformed\n"));
300 					fSuccess = FALSE;
301 				}
302 
303 				if (fSuccess) {
304 					/* we have several commands in the buffer, we need to
305 					 * cut them, enqueue them and prepare next buffer for
306 					 * incomplete commands */
307 					char backup_char;
308 					char *command = NULL ;
309 					for (i = 0; i < len && buf[i] != '\0'; i++) {
310 						if (buf[i] == '\n') {
311 							backup_char = buf[i + 1];
312 							buf[i + 1] = '\0';
313 							command = strdup(buf+bufstart);
314 							buf[i + 1] = backup_char;
315 							bufstart = i + 1;
316 
317 							if (enqueue_command(command)) {
318 								fSuccess = FALSE;
319 								break;
320 							}
321 						}
322 					}
323 					if (bufstart == i)
324 						bufstart = 0;
325 					else memcpy(buf, buf+bufstart, len-bufstart);
326 				}
327 
328 			}
329 			if (!fSuccess && GetLastError() != ERROR_MORE_DATA) {
330 				fprintf(stdout, "closed read pipe\n");
331 				CloseHandle(hPipe_r);
332 				hPipe_r = INVALID_HANDLE_VALUE;
333 				empty_command_queue();
334 				if (get_thread_run()) {
335 					stop_processing_thread();
336 					pipe_send_message(PIPE_STATUS, PIPE_ERROR, _("command interrupted\n"));
337 				}
338 				break;
339 			}
340 		} while (1);
341 	} while (pipe_active);
342 #else
343 	do {
344 		// open will block until the other end is opened
345 		fprintf(stdout, "read pipe waiting to be opened...\n");
346 		if ((pipe_fd_r = open(PIPE_PATH_R, O_RDONLY)) == -1) {
347 			siril_log_message(_("Could not open the named pipe\n"));
348 			perror("open");
349 			break;
350 		}
351 		fprintf(stdout, "opened read pipe\n");
352 
353 		int bufstart = 0;
354 		char buf[PIPE_MSG_SZ];
355 		do {
356 			int retval;
357 			fd_set rfds;
358 			FD_ZERO(&rfds);
359 			FD_SET(pipe_fd_r, &rfds);
360 
361 			retval = select(pipe_fd_r+1, &rfds, NULL, NULL, NULL);
362 			if (retval == 1) {
363 				char *command;
364 				int len = read(pipe_fd_r, buf+bufstart, PIPE_MSG_SZ-1-bufstart);
365 				if (len == -1 || len == 0)
366 					retval = -1;
367 				else {
368 					int i = 0, nbnl = 0;
369 					buf[len] = '\0';
370 					while (i < len && buf[i] != '\0') {
371 						if (buf[i] == '\n')
372 							nbnl++;
373 						i++;
374 					}
375 					if (nbnl == 0) {
376 						pipe_send_message(PIPE_STATUS, PIPE_ERROR, _("command too long or malformed\n"));
377 						retval = -1;
378 					}
379 
380 					if (retval == 1) {
381 						/* we have several commands in the buffer, we need to
382 						 * cut them, enqueue them and prepare next buffer for
383 						 * incomplete commands */
384 						char backup_char;
385 						for (i = 0; i < len && buf[i] != '\0'; i++) {
386 							if (buf[i] == '\n') {
387 								backup_char = buf[i + 1];
388 								buf[i + 1] = '\0';
389 								command = strdup(buf+bufstart);
390 								buf[i + 1] = backup_char;
391 								bufstart = i + 1;
392 
393 								if (enqueue_command(command)) {
394 									retval = -1;
395 									free(command);
396 									break;
397 								}
398 							}
399 						}
400 						if (bufstart == i)
401 							bufstart = 0;
402 						else memcpy(buf, buf+bufstart, len-bufstart);
403 					}
404 				}
405 			}
406 			if (retval <= 0) {
407 				fprintf(stdout, "closed read pipe\n");
408 				close(pipe_fd_r);
409 				pipe_fd_r = -1;
410 				empty_command_queue();
411 				if (get_thread_run()) {
412 					stop_processing_thread();
413 					pipe_send_message(PIPE_STATUS, PIPE_ERROR, _("command interrupted\n"));
414 				}
415 				break;
416 			}
417 		} while (1);
418 	} while (pipe_active);
419 #endif
420 
421 	return GINT_TO_POINTER(pipe_active ? -1 : 0);
422 }
423 
process_commands(void * p)424 void *process_commands(void *p) {
425 	while (pipe_active) {
426 		char *command;
427 		g_mutex_lock(&read_mutex);
428 		while (!command_list && pipe_active) {
429 			fprintf(stdout, "waiting for commands to be read from the pipe\n");
430 			g_cond_wait(&read_cond, &read_mutex);
431 		}
432 		if (!pipe_active) {
433 			g_mutex_unlock(&read_mutex);
434 			break;
435 		}
436 
437 		command = (char*)command_list->data;
438 		command_list = g_list_next(command_list);
439 		g_mutex_unlock(&read_mutex);
440 
441 		pipe_send_message(PIPE_STATUS, PIPE_STARTING, command);
442 		int retval = processcommand(command);
443 		if (waiting_for_thread()) {
444 			empty_command_queue();
445 			retval = 1;
446 		}
447 		if (retval)
448 			pipe_send_message(PIPE_STATUS, PIPE_ERROR, command);
449 		else pipe_send_message(PIPE_STATUS, PIPE_SUCCESS, command);
450 		free(command);
451 	}
452 	return NULL;
453 }
454 
write_pipe(void * p)455 static void *write_pipe(void *p) {
456 	do {
457 		fprintf(stdout, "write pipe waiting to be opened...\n");
458 #ifdef _WIN32
459 		// will block until the other end is opened
460 		if (!ConnectNamedPipe(hPipe_w, NULL) && GetLastError() != ERROR_PIPE_CONNECTED) {
461 			siril_log_message(_("Could not open the named pipe\n"));
462 			break;
463 		}
464 #else
465 		// open will block until the other end is opened
466 		if ((pipe_fd_w = open(PIPE_PATH_W, O_WRONLY)) == -1) {
467 			siril_log_message(_("Could not open the named pipe\n"));
468 			perror("open");
469 			break;
470 		}
471 #endif
472 		fprintf(stdout, "opened write pipe\n");
473 
474 		do {
475 			char *msg;
476 			// wait for messages to write
477 			g_mutex_lock(&write_mutex);
478 			while (!pending_writes && pipe_active)
479 				g_cond_wait(&write_cond, &write_mutex);
480 			if (!pipe_active) {
481 				g_mutex_unlock(&write_mutex);
482 				break;
483 			}
484 
485 			msg = (char *)pending_writes->data;
486 			pending_writes = g_list_next(pending_writes);
487 			g_mutex_unlock(&write_mutex);
488 
489 			if (pipe_write(msg)) {
490 #ifdef _WIN32
491 				CloseHandle(hPipe_w);
492 				hPipe_w = INVALID_HANDLE_VALUE;
493 #else
494 				fprintf(stdout, "closed write pipe\n");
495 				close(pipe_fd_w);
496 				pipe_fd_w = -1;
497 #endif
498 				free(msg);
499 				break;
500 			}
501 			free(msg);
502 		} while (1);
503 	} while (pipe_active);
504 	return GINT_TO_POINTER(-1);
505 }
506 
507 /* not reentrant */
pipe_start()508 int pipe_start() {
509 	if (pipe_active)
510 		return 0;
511 	if (pipe_create())
512 		return -1;
513 
514 	pipe_active = 1;
515 	worker_thread = g_thread_new("worker", process_commands, NULL);
516 	pipe_thread_w = g_thread_new("pipe writer", write_pipe, NULL);
517 	return 0;
518 }
519 
520 /* not working, not used: blocked open calls are not signaled,
521  * and blocked write_cond throws a deadlock error */
pipe_stop()522 void pipe_stop() {
523 	fprintf(stdout, "closing pipes\n");
524 	g_mutex_lock(&read_mutex);
525 	g_mutex_lock(&write_mutex);
526 	pipe_active = 0;
527 #ifdef _WIN32
528 	if (hPipe_r != INVALID_HANDLE_VALUE)
529 		CloseHandle(hPipe_r);
530 	hPipe_r = INVALID_HANDLE_VALUE;
531 	if (hPipe_w != INVALID_HANDLE_VALUE)
532 		CloseHandle(hPipe_w);
533 	hPipe_w = INVALID_HANDLE_VALUE;
534 #else
535 	if (pipe_fd_r >= 0)
536 		close(pipe_fd_r);
537 	pipe_fd_r = -1;
538 	if (pipe_fd_w > 0)
539 		close(pipe_fd_w);
540 	pipe_fd_w = -1;
541 #endif
542 	g_cond_signal(&write_cond);
543 	g_cond_signal(&read_cond);
544 	g_mutex_unlock(&read_mutex);
545 	g_mutex_unlock(&write_mutex);
546 	if (pipe_thread_w)
547 		g_thread_join(pipe_thread_w);
548 	if (worker_thread)
549 		g_thread_join(worker_thread);
550 }
551