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