1 /*
2 * gretl -- Gnu Regression, Econometrics and Time-series Library
3 * Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include "gretl.h"
21 #include "gretl_ipc.h"
22
23 #if defined(__linux) || defined(linux)
24 # include <dirent.h>
25 # include <signal.h>
26 #elif defined(WIN32)
27 # include <windows.h>
28 # include <tlhelp32.h>
29 #elif defined(HAVE_LIBPROC_H) && defined(HAVE_SYS_PROC_INFO_H)
30 # define BSD_PROC 1
31 # include <sys/proc_info.h>
32 # include <libproc.h>
33 #endif
34
35 #if defined(WIN32) || defined(BSD_PROC)
36 # define N_PIDS 8
37 #endif
38
39 #define IPC_DEBUG 0
40
41 /* gretl_ipc: inter-process communication
42
43 First, the functions conditional on the GRETL_PID_FILE definition
44 are to do with the file gretl.pid in the user's "dotdir"; this is
45 used to put a "sequence number" into the gretl main window title
46 bar in case the user chooses to run more than one concurrent gretl
47 process.
48
49 Second, the functions conditional on GRETL_OPEN_HANDLER are to do
50 with the case where a user has one or more gretl processes running
51 already, and performs an action that would by default cause a new
52 instance to be started. We give the user the option to activate an
53 existing gretl instance rather than starting a new one.
54
55 The GRETL_PID_FILE functionality is supported for Linux, MS Windows
56 and systems with BSD libproc. The GRETL_OPEN_HANDLER functionality
57 is supported for Linux and Windows only. Note that on Mac the OS
58 preserves uniqueness of GUI application instances by default -- so
59 the situation is the opposite of that on Linux and Windows. In the
60 GTK-quartz version of gretl we offer a top-level menu item to
61 override this and launch a second (or third, etc.) gretl instance.
62
63 GRETL_OPEN_HANDLER depends on GRETL_PID_FILE for its mechanism but
64 not vice versa.
65 */
66
67 #ifdef GRETL_PID_FILE
68
69 static int my_sequence_number;
70
gretl_sequence_number(void)71 int gretl_sequence_number (void)
72 {
73 return my_sequence_number;
74 }
75
76 # if defined(WIN32)
77
78 /* Fill the @gpids array with PIDs of running gretl processes
79 (other than self, given by @mypid). We use this array to
80 validate PIDs read from dotdir/gretl.pid. Note that we
81 only consider up to N_PIDS = 8 running gretl processes,
82 which hopefully should be more than enough.
83 */
84
get_prior_gretl_pids(long * gpids,long mypid)85 static void get_prior_gretl_pids (long *gpids, long mypid)
86 {
87 HANDLE hsnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
88
89 if (hsnap) {
90 PROCESSENTRY32 pe;
91 int match, j = 0;
92 char *s;
93
94 pe.dwSize = sizeof(PROCESSENTRY32);
95 if (Process32First(hsnap, &pe)) {
96 do {
97 if (pe.th32ProcessID != mypid) {
98 s = strrchr(pe.szExeFile, '\\');
99 if (s != NULL) {
100 match = !strcmp(s + 1, "gretl.exe");
101 } else {
102 match = !strcmp(pe.szExeFile, "gretl.exe");
103 }
104 if (match && j < N_PIDS) {
105 gpids[j++] = pe.th32ProcessID;
106 }
107 }
108 } while (Process32Next(hsnap, &pe));
109 }
110 CloseHandle(hsnap);
111 }
112 }
113
114 # elif defined(BSD_PROC)
115
116 /* The Mac/BSD variant of the above */
117
get_prior_gretl_pids(long * gpids,long mypid)118 static void get_prior_gretl_pids (long *gpids, long mypid)
119 {
120 int nproc = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
121 char buf[PROC_PIDPATHINFO_MAXSIZE];
122 size_t psize;
123 pid_t *pids;
124 int i, j, match;
125
126 psize = nproc * sizeof *pids;
127 pids = calloc(nproc, sizeof *pids);
128 if (pids == NULL) {
129 return;
130 }
131
132 proc_listpids(PROC_ALL_PIDS, 0, pids, psize);
133
134 j = 0;
135 for (i=0; i<nproc; i++) {
136 if (pids[i] == 0 || pids[i] == mypid) {
137 continue;
138 }
139 memset(buf, 0, sizeof buf);
140 proc_pidpath(pids[i], buf, sizeof buf);
141 if (*buf != '\0') {
142 char *s = strrchr(buf, '/');
143
144 if (s != NULL) {
145 match = !strcmp("gretl", s + 1);
146 } else {
147 match = !strcmp("gretl", buf);
148 }
149 if (match && j < N_PIDS) {
150 gpids[j++] = (long) pids[i];
151 }
152 }
153 }
154
155 free(pids);
156 }
157
158 # endif /* get_prior_gretl_pids() variants */
159
160 # if defined(__linux) || defined(linux)
161
162 /* Given the PID @test, retrieved from dotdir/gretl.pid,
163 see if it really represents a prior gretl process. We do
164 this to guard against the possibility that gretl crashes on
165 some occasion (or something else weird happens), in which
166 case the multi-instance "sequence number" would get screwed up.
167
168 Return 1 if @test is OK, zero otherwise.
169 */
170
pid_is_valid(long test,long mypid)171 static int pid_is_valid (long test, long mypid)
172 {
173 char buf[128];
174 char pname[32] = {0};
175 FILE *fp;
176 int err = 0;
177
178 snprintf(buf, sizeof buf, "/proc/%ld/stat", test);
179 fp = gretl_fopen(buf, "r");
180
181 if (fp == NULL) {
182 /* no such pid? */
183 return 0;
184 } else {
185 char state;
186 long pid;
187
188 if ((fscanf(fp, "%ld (%31[^)]) %c", &pid, pname, &state)) != 3) {
189 /* huh? */
190 err = 1;
191 } else if (strcmp(pname, "gretl_x11") && strcmp(pname, "lt-gretl_x11")) {
192 /* it's not gretl */
193 err = 1;
194 } else if (pid == mypid) {
195 /* it's not really a prior instance */
196 err = 1;
197 }
198 fclose(fp);
199 }
200
201 return !err;
202 }
203
204 # else
205
206 /* Windows and Mac: in these cases we have to construct
207 an array of "good" PIDs against which to test.
208 */
209
pid_is_valid(long test,long mypid)210 static int pid_is_valid (long test, long mypid)
211 {
212 static long gpids[N_PIDS] = {0};
213 static int gotpids;
214 int i;
215
216 if (!gotpids) {
217 /* construct array of valid gretl PIDs */
218 get_prior_gretl_pids(gpids, mypid);
219 gotpids = 1;
220 }
221
222 /* check @test against the known good PIDs */
223 for (i=0; i<N_PIDS && gpids[i]; i++) {
224 if (test == gpids[i]) {
225 return 1;
226 }
227 }
228
229 return 0;
230 }
231
232 # endif /* PID validation variants */
233
234 /* Having found one or more invalid PIDs in dotdir/gretl.pid,
235 scratch them out.
236 */
237
prune_pid_file(const char * pidfile,FILE * f1,char * buf,int bufsize,long mypid)238 static int prune_pid_file (const char *pidfile, FILE *f1,
239 char *buf, int bufsize,
240 long mypid)
241 {
242 char newfile[FILENAME_MAX];
243 FILE *f2;
244 int err = 0;
245
246 sprintf(newfile, "%sgretlpid.tmp", gretl_dotdir());
247 f2 = gretl_fopen(newfile, "wb");
248
249 if (f2 == NULL) {
250 err = E_FOPEN;
251 } else {
252 long pid;
253 int m;
254
255 while (fgets(buf, bufsize, f1)) {
256 sscanf(buf, "%ld %d", &pid, &m);
257 if (pid_is_valid(pid, mypid)) {
258 fputs(buf, f2);
259 }
260 }
261
262 fclose(f2);
263 fclose(f1);
264
265 err = gretl_copy_file(newfile, pidfile);
266 if (err) {
267 fprintf(stderr, "copy pid file: err = %d\n", err);
268 }
269 gretl_remove(newfile);
270 }
271
272 return err;
273 }
274
275 /* On start-up, write our PID and sequence number into gretl.pid in
276 dotdir; while we're at it, we check any other (putative) gretl PIDs
277 recorded in that file just in case they've gone bad (e.g. gretl
278 crashed or the OS crashed).
279 */
280
write_pid_to_file(void)281 int write_pid_to_file (void)
282 {
283 const char *dotdir;
284 char pidfile[FILENAME_MAX];
285 FILE *f1;
286 long mypid;
287 int err = 0;
288
289 #ifdef WIN32
290 mypid = (long) GetCurrentProcessId();
291 #else
292 mypid = (long) getpid();
293 #endif
294
295 dotdir = gretl_dotdir();
296 sprintf(pidfile, "%sgretl.pid", dotdir);
297 f1 = gretl_fopen(pidfile, "rb");
298
299 if (f1 == NULL) {
300 /* no pid file, starting from scratch */
301 f1 = gretl_fopen(pidfile, "wb");
302 if (f1 == NULL) {
303 err = E_FOPEN;
304 } else {
305 fprintf(f1, "%ld 1\n", mypid);
306 fclose(f1);
307 }
308 } else {
309 /* we already have a pid file (open as f1) */
310 char newfile[FILENAME_MAX];
311 char buf[32];
312 FILE *f2;
313 long pid;
314 int m, n = 0;
315
316 sprintf(newfile, "%sgretlpid.tmp", dotdir);
317 f2 = gretl_fopen(newfile, "wb");
318
319 if (f2 == NULL) {
320 err = E_FOPEN;
321 } else {
322 while (fgets(buf, sizeof buf, f1)) {
323 sscanf(buf, "%ld %d", &pid, &m);
324 if (pid_is_valid(pid, mypid)) {
325 fputs(buf, f2);
326 n = m;
327 }
328 }
329 n++;
330 fprintf(f2, "%ld %d\n", mypid, n);
331 fclose(f2);
332 my_sequence_number = n;
333 }
334
335 fclose(f1);
336
337 if (err) {
338 gretl_remove(pidfile);
339 } else {
340 err = gretl_copy_file(newfile, pidfile);
341 if (err) {
342 fprintf(stderr, "copy pid file: err = %d\n", err);
343 }
344 gretl_remove(newfile);
345 }
346 }
347
348 return err;
349 }
350
351 /* On exit, delete our PID from gretl.pid in dotdir */
352
delete_pid_from_file(void)353 void delete_pid_from_file (void)
354 {
355 const char *dotdir;
356 char pidfile[FILENAME_MAX];
357 FILE *f1;
358 long mypid;
359 int err = 0;
360
361 #ifdef WIN32
362 mypid = (long) GetCurrentProcessId();
363 #else
364 mypid = (long) getpid();
365 #endif
366
367 dotdir = gretl_dotdir();
368 sprintf(pidfile, "%sgretl.pid", dotdir);
369
370 f1 = gretl_fopen(pidfile, "rb");
371 if (f1 == NULL) {
372 err = E_FOPEN;
373 } else {
374 char newfile[FILENAME_MAX];
375 char buf[32];
376 FILE *f2;
377 long pid;
378 int nleft = 0;
379
380 sprintf(newfile, "%sgretlpid.tmp", dotdir);
381 f2 = gretl_fopen(newfile, "wb");
382
383 if (f2 == NULL) {
384 err = E_FOPEN;
385 } else {
386 while (fgets(buf, sizeof buf, f1)) {
387 sscanf(buf, "%ld", &pid);
388 if (pid != mypid) {
389 fputs(buf, f2);
390 nleft++;
391 }
392 }
393 fclose(f2);
394 }
395
396 fclose(f1);
397
398 if (err) {
399 gretl_remove(pidfile);
400 } else if (nleft > 0) {
401 err = gretl_copy_file(newfile, pidfile);
402 gretl_remove(newfile);
403 } else {
404 gretl_remove(newfile);
405 gretl_remove(pidfile);
406 }
407 }
408 }
409
410 #endif /* GRETL_PID_FILE */
411
412 /* Now come the more ambitious open-handler functions,
413 implemented for Linux and Windows but not required
414 for Mac.
415 */
416
417 #ifdef GRETL_OPEN_HANDLER
418
419 # ifdef WIN32
420 /* message number for inter-program communication */
421 static UINT WM_GRETL;
422 # endif
423
424 /* See if we can find the PID of (the last) prior gretl
425 instance. If so, return the PID, otherwise return 0.
426 We read from dotdir/gretl.pid in the first instance
427 but then validate any PID(s) we find against the current
428 process table.
429 */
430
gretl_prior_instance(void)431 long gretl_prior_instance (void)
432 {
433 char pidfile[FILENAME_MAX];
434 FILE *fp;
435 long mypid, ret = 0;
436
437 # ifdef WIN32
438 if (WM_GRETL == 0) {
439 WM_GRETL = RegisterWindowMessage((LPCTSTR) "gretl_message");
440 }
441 mypid = (long) GetCurrentProcessId();
442 # else
443 mypid = getpid();
444 # endif
445
446 sprintf(pidfile, "%sgretl.pid", gretl_dotdir());
447 fp = gretl_fopen(pidfile, "rb");
448
449 #if IPC_DEBUG
450 fprintf(stderr, "*** gretl_prior_instance: pidfile='%s', fp=%p\n",
451 pidfile, (void *) fp);
452 #endif
453
454 if (fp != NULL) {
455 char buf[32];
456 int prune = 0;
457 int ok, n_valid = 0;
458 long tmp;
459
460 while (fgets(buf, sizeof buf, fp)) {
461 sscanf(buf, "%ld", &tmp);
462 ok = pid_is_valid(tmp, mypid);
463 #if IPC_DEBUG
464 fprintf(stderr, " got prior PID %d, ok = %d\n", (int) tmp, ok);
465 #endif
466 if (ok) {
467 ret = tmp;
468 n_valid++;
469 } else {
470 prune = 1;
471 }
472 }
473
474 if (n_valid == 0) {
475 /* trash the pid file */
476 fclose(fp);
477 gretl_remove(pidfile);
478 } else if (prune) {
479 /* rewrite the pid file */
480 rewind(fp);
481 prune_pid_file(pidfile, fp, buf, sizeof buf, mypid);
482 } else {
483 /* leave well alone */
484 fclose(fp);
485 }
486 }
487
488 return ret;
489 }
490
491 /* Process a message coming from another gretl instance. Such a
492 message calls for a hand-off to "this" instance, and some
493 information regarding the hand-off is contained in a little file in
494 the user's dotdir, with a name on the pattern "open-<pid>" where
495 <pid> should equal "this" instance's PID.
496
497 If the hand-off involves opening a file (which will be the case if
498 the trigger is double-clicking on a gretl-associated file), the
499 name of this file is written into the IPC file; otherwise the IPC
500 file contains the word "none".
501 */
502
process_handoff_message(void)503 static void process_handoff_message (void)
504 {
505 char fname[FILENAME_MAX];
506 FILE *fp;
507 int try_open = 0;
508 long mypid;
509
510 #ifdef WIN32
511 mypid = (long) GetCurrentProcessId();
512 #else
513 mypid = (long) getpid();
514 #endif
515
516 sprintf(fname, "%sopen-%ld", gretl_dotdir(), mypid);
517 fp = gretl_fopen(fname, "rb");
518
519 #if IPC_DEBUG
520 fprintf(stderr, "*** process_handoff_message\n"
521 " fname='%s', fp = %p\n", fname, (void *) fp);
522 #endif
523
524 if (fp != NULL) {
525 char path[FILENAME_MAX];
526
527 if (fgets(path, sizeof path, fp)) {
528 tailstrip(path);
529 if (strcmp(path, "none")) {
530 set_tryfile(path);
531 try_open = 1;
532 }
533 }
534 fclose(fp);
535 }
536
537 remove(fname);
538
539 gtk_window_present(GTK_WINDOW(mdata->main));
540
541 if (try_open) {
542 /* we picked up the name of a file to open */
543 open_tryfile();
544 }
545 }
546
write_request_file(long gpid,const char * fname)547 static gboolean write_request_file (long gpid, const char *fname)
548 {
549 char tmpname[FILENAME_MAX];
550 FILE *fp;
551
552 sprintf(tmpname, "%sopen-%ld", gretl_dotdir(), gpid);
553 fp = gretl_fopen(tmpname, "wb");
554
555 if (fp == NULL) {
556 return FALSE;
557 } else {
558 fprintf(fp, "%s\n", *fname ? fname : "none");
559 fclose(fp);
560 }
561
562 return TRUE;
563 }
564
565 # if defined(__linux) || defined(linux)
566
567 /* Signal handler for the case where a newly started gretl process
568 hands off to a previously running process, passing the name of a
569 file to be opened via a small file in the user's dotdir.
570 */
571
open_handler(int sig,siginfo_t * sinfo,void * context)572 static void open_handler (int sig, siginfo_t *sinfo, void *context)
573 {
574 if (sig == SIGUSR1 && sinfo->si_code == SI_QUEUE) {
575 if (sinfo->si_uid == getuid() &&
576 sinfo->si_value.sival_ptr == (void *) 0xf0) {
577 }
578 process_handoff_message();
579 }
580 }
581
582 /* linux: at start-up, install a handler for SIGUSR1 */
583
install_open_handler(void)584 int install_open_handler (void)
585 {
586 static struct sigaction action;
587
588 action.sa_sigaction = open_handler;
589 sigemptyset(&action.sa_mask);
590 action.sa_flags = SA_SIGINFO;
591
592 return sigaction(SIGUSR1, &action, NULL);
593 }
594
595 /* For the case where a new gretl process has been started (possibly
596 in response to double-clicking a suitable filename), but the user
597 doesn't really want a new process and would prefer that the file be
598 opened in a previously running gretl instance. We send SIGUSR1 to
599 the prior instance and exit. The @fname argument is used if the new
600 instance of gretl was invoked with a filename argument.
601 */
602
forward_open_request(long gpid,const char * fname)603 gboolean forward_open_request (long gpid, const char *fname)
604 {
605 gboolean ok = write_request_file(gpid, fname);
606
607 #if IPC_DEBUG
608 fprintf(stderr, "*** forward_open_request\n");
609 #endif
610
611 if (ok) {
612 union sigval data;
613
614 data.sival_ptr = (void *) 0xf0;
615 /* note: gpid is the PID of the previously running
616 process to which the signal should be sent
617 */
618 ok = (sigqueue(gpid, SIGUSR1, data) == 0);
619 }
620
621 return ok;
622 }
623
624 # elif defined(WIN32)
625
626 /* Below: since POSIX signals are not supported on Windows,
627 we implement the equivalent of the above functionality
628 for Linux using the Windows messaging API.
629 */
630
631 /* If we receive a special WM_GRETL message from another gretl
632 instance, process it and remove it from the message queue;
633 otherwise hand the message off to GDK.
634 */
635
mdata_filter(GdkXEvent * xevent,GdkEvent * event,gpointer data)636 static GdkFilterReturn mdata_filter (GdkXEvent *xevent,
637 GdkEvent *event,
638 gpointer data)
639 {
640 MSG *msg = (MSG *) xevent;
641
642 if (msg->message == WM_GRETL && msg->wParam == 0xf0) {
643 fprintf(stderr, "mdata_filter: got WM_GRETL + 0xf0\n");
644 process_handoff_message();
645 return GDK_FILTER_REMOVE;
646 }
647
648 return GDK_FILTER_CONTINUE;
649 }
650
651 /* Add a filter to intercept WM_GRETL messages, which would
652 otherwise get discarded by GDK */
653
install_open_handler(void)654 int install_open_handler (void)
655 {
656 gdk_window_add_filter(NULL, mdata_filter, NULL);
657 return 0;
658 }
659
660 /* Some window handles matched by pid seem to be
661 spurious: here we screen them and accept only
662 a plausible target for messaging.
663 */
664
plausible_target(HWND hw)665 static int plausible_target (HWND hw)
666 {
667 WINDOWINFO wi = {0};
668 char s[64];
669 int ret = 0;
670
671 wi.cbSize = sizeof(WINDOWINFO);
672
673 if (GetWindowInfo(hw, &wi) != 0) {
674 if ((wi.dwStyle & WS_CAPTION) &&
675 (wi.dwExStyle & WS_EX_ACCEPTFILES)) {
676 s[0] = '\0';
677 GetWindowTextA(hw, s, 63);
678 if (!strcmp(s, "gretl") || !strncmp(s, "gretl: session ", 15)) {
679 return 1;
680 }
681 }
682 }
683
684 return 0;
685 }
686
687 /* Find the window handle associated with a given PID:
688 this is used when we've found the PID of a running
689 gretl instance and we need its window handle for
690 use with SendMessage().
691 */
692
get_hwnd_for_pid(long gpid)693 static HWND get_hwnd_for_pid (long gpid)
694 {
695 HWND hw = NULL, ret = NULL;
696 DWORD pid;
697
698 do {
699 hw = FindWindowEx(NULL, hw, NULL, NULL);
700 if (hw == NULL) {
701 break;
702 }
703 GetWindowThreadProcessId(hw, &pid);
704 if ((long) pid == gpid) {
705 /* PID matched */
706 if (plausible_target(hw)) {
707 /* looks like main gretl window */
708 ret = hw;
709 }
710 }
711 } while (ret == NULL);
712
713 return ret;
714 }
715
716 /* Try forwarding a request to the prior gretl instance with
717 PID @gpid, either to open a file or just to show itself.
718
719 Return TRUE if we're able to do this, otherwise FALSE.
720 */
721
forward_open_request(long gpid,const char * fname)722 gboolean forward_open_request (long gpid, const char *fname)
723 {
724 HWND hw = get_hwnd_for_pid(gpid);
725 gboolean ok = FALSE;
726
727 if (hw != NULL) {
728 long mypid = (long) GetCurrentProcessId();
729
730 ok = write_request_file(gpid, fname);
731 #if IPC_DEBUG
732 fprintf(stderr, "forward_open_request: mypid=%d, ok=%d\n", (int) mypid, ok);
733 #endif
734 if (ok) {
735 SendMessage(hw, WM_GRETL, 0xf0, mypid);
736 }
737 }
738
739 return ok;
740 }
741
742 # endif /* OS variations */
743 #endif /* GRETL_OPEN_HANDLER */
744