Coverity fixes from Nelson
[sxemacs] / lib-src / gnuclient.c
1 /* -*-C-*-
2  Client code to allow local and remote editing of files by XEmacs.
3  Copyright (C) 1989 Free Software Foundation, Inc.
4  Copyright (C) 1995 Sun Microsystems, Inc.
5  Copyright (C) 1997 Free Software Foundation, Inc.
6
7 This file is part of SXEmacs.
8
9 SXEmacs is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 SXEmacs is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
22  Author: Andy Norman (ange@hplb.hpl.hp.com), based on
23          'etc/emacsclient.c' from the GNU Emacs 18.52 distribution.
24
25  Please mail bugs and suggestions to the XEmacs maintainer.
26 */
27
28 /* #### This file should be a windows-mode, not console-mode program under
29    Windows. (i.e. its entry point should be WinMain.) gnuattach functionality,
30    to the extent it's used at all, should be retrieved using a script that
31    calls the i.exe wrapper program, to obtain stdio handles.
32
33    #### For that matter, both the functionality of gnuclient and gnuserv
34    should be merged into XEmacs itself using a -remote arg, just like
35    Netscape and other modern programs.
36
37    --ben */
38
39 /*
40  * This file incorporates new features added by Bob Weiner <weiner@mot.com>,
41  * Darrell Kindred <dkindred@cmu.edu> and Arup Mukherjee <arup@cmu.edu>.
42  * GNUATTACH support added by Ben Wing <wing@xemacs.org>.
43  * Please see the note at the end of the README file for details.
44  *
45  * (If gnuserv came bundled with your emacs, the README file is probably
46  * ../etc/gnuserv.README relative to the directory containing this file)
47  */
48
49 #include "gnuserv.h"
50
51 char gnuserv_version[] = "gnuclient version " GNUSERV_VERSION;
52
53 #ifdef HAVE_GETOPT_H
54 #include <getopt.h>
55 #endif
56
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <sys/types.h>
60 #include <sysfile.h>
61
62 #ifdef HAVE_STRING_H
63 #include <string.h>
64 #endif                          /* HAVE_STRING_H */
65
66 #ifdef HAVE_UNISTD_H
67 #include <unistd.h>
68 #endif                          /* HAVE_UNISTD_H */
69
70 #include <signal.h>
71
72 #if !defined(SYSV_IPC) && !defined(UNIX_DOMAIN_SOCKETS) && \
73     !defined(INTERNET_DOMAIN_SOCKETS)
74 int main(int argc, char *argv[])
75 {
76         fprintf(stderr, "Sorry, the Emacs server is only "
77                 "supported on systems that have\n");
78         fprintf(stderr, "Unix Domain sockets, Internet Domain "
79                 "sockets or System V IPC.\n");
80         exit(1);
81 }                               /* main */
82 #else                           /* SYSV_IPC || UNIX_DOMAIN_SOCKETS || INTERNET_DOMAIN_SOCKETS */
83
84 static char cwd[MAXPATHLEN + 2];        /* current working directory when calculated */
85 static char *cp = NULL;         /* ptr into valid bit of cwd above */
86
87 static pid_t emacs_pid;         /* Process id for emacs process */
88
89 void initialize_signals(void);
90
91 static void tell_emacs_to_resume(int sig)
92 {
93         char buffer[GSERV_BUFSZ + 1];
94         int s;                  /* socket / msqid to server */
95         int connect_type;       /* CONN_UNIX, CONN_INTERNET, or
96                                    ONN_IPC */
97
98         /* Why is SYSV so retarded? */
99         /* We want emacs to realize that we are resuming */
100 #ifdef SIGCONT
101         signal(SIGCONT, tell_emacs_to_resume);
102 #endif
103
104         connect_type = make_connection(NULL, 0, &s);
105
106         sprintf(buffer, "(gnuserv-eval '(resume-pid-console %d))",
107                 (int)getpid());
108         send_string(s, buffer);
109
110 #ifdef SYSV_IPC
111         if (connect_type == (int)CONN_IPC)
112                 disconnect_from_ipc_server(s, msgp, FALSE);
113 #else                           /* !SYSV_IPC */
114         if (connect_type != (int)CONN_IPC)
115                 disconnect_from_server(s, FALSE);
116 #endif                          /* !SYSV_IPC */
117 }
118
119 static void pass_signal_to_emacs(int sig)
120 {
121         if (kill(emacs_pid, sig) == -1) {
122                 fprintf(stderr,
123                         "gnuattach: Could not pass signal to emacs process\n");
124                 exit(1);
125         }
126         initialize_signals();
127 }
128
129 void initialize_signals(void)
130 {
131         /* Set up signal handler to pass relevant signals to emacs process.
132            We used to send SIGSEGV, SIGBUS, SIGPIPE, SIGILL and others to
133            Emacs, but I think it's better not to.  I can see no reason why
134            Emacs should SIGSEGV whenever gnuclient SIGSEGV-s, etc.  */
135         signal(SIGQUIT, pass_signal_to_emacs);
136         signal(SIGINT, pass_signal_to_emacs);
137 #ifdef SIGWINCH
138         signal(SIGWINCH, pass_signal_to_emacs);
139 #endif
140
141 #ifdef SIGCONT
142         /* We want emacs to realize that we are resuming */
143         signal(SIGCONT, tell_emacs_to_resume);
144 #endif
145 }
146
147 /*
148   get_current_working_directory -- return the cwd.
149 */
150 static char *get_current_working_directory(void)
151 {
152         if (cp == NULL) {       /* haven't calculated it yet */
153 #ifdef HAVE_GETCWD
154                 if (getcwd(cwd, MAXPATHLEN) == NULL)
155 #else
156                 if (getwd(cwd) == 0)
157 #endif                          /* HAVE_GETCWD */
158                 {
159                         perror(progname);
160                         fprintf(stderr,
161                                 "%s: unable to get current working directory\n",
162                                 progname);
163                         exit(1);
164                 }
165
166                 /* if */
167                 /* on some systems, cwd can look like '@machine/' ... */
168                 /* ignore everything before the first '/' */
169                 for (cp = cwd; *cp && *cp != '/'; ++cp) ;
170
171         }
172         /* if */
173         return cp;
174
175 }                               /* get_current_working_directory */
176
177 /*
178   filename_expand -- try to convert the given filename into a fully-qualified
179                      pathname.
180 */
181 static void filename_expand(char *fullpath, char *filename, size_t fullsize)
182   /* fullpath - returned full pathname */
183   /* filename - filename to expand */
184 {
185         int len;
186         fullpath[0] = '\0';
187
188         if (filename[0] && filename[0] == '/') {
189                 /* Absolute (unix-style) pathname.  Do nothing */
190                 strncat(fullpath, filename, fullsize-1);
191         } else {
192                 /* Assume relative Unix style path.  Get the current directory
193                    and prepend it.  FIXME: need to fix the case of DOS paths like
194                    "\foo", where we need to get the current drive. */
195
196                 strncat(fullpath, get_current_working_directory(), fullsize-1);
197                 len = strlen(fullpath);
198
199                 if (len > 0 && fullpath[len - 1] == '/')        /* trailing slash already? */
200                         ;       /* yep */
201                 else if (len < fullsize-1)
202                         strcat(fullpath, "/");  /* nope, append trailing slash */
203                 /* Don't forget to add the filename! */
204                 strncat(fullpath, filename, fullsize-len-1);
205         }
206 }                               /* filename_expand */
207
208 /* Encase the string in quotes, escape all the backslashes and quotes
209    in string.  */
210 static char *clean_string(const char *s)
211 {
212         int i = 0;
213         char *p, *res;
214
215         {
216                 const char *const_p;
217                 for (const_p = s; *const_p; const_p++, i++) {
218                         if (*const_p == '\\' || *const_p == '\"')
219                                 ++i;
220                         else if (*const_p == '\004')
221                                 i += 3;
222                 }
223         }
224
225         p = res = (char *)malloc(i + 2 + 1);
226         *p++ = '\"';
227         for (; *s; p++, s++) {
228                 switch (*s) {
229                 case '\\':
230                         *p++ = '\\';
231                         *p = '\\';
232                         break;
233                 case '\"':
234                         *p++ = '\\';
235                         *p = '\"';
236                         break;
237                 case '\004':
238                         *p++ = '\\';
239                         *p++ = 'C';
240                         *p++ = '-';
241                         *p = 'd';
242                         break;
243                 default:
244                         *p = *s;
245                 }
246         }
247         *p++ = '\"';
248         *p = '\0';
249         return res;
250 }
251
252 #define GET_ARGUMENT(var, desc) do {                                       \
253  if (*(p + 1)) (var) = p + 1;                                              \
254    else                                                                    \
255      {                                                                     \
256        if (!argv[++i])                                                     \
257          {                                                                 \
258            fprintf (stderr, "%s: `%s' must be followed by an argument\n",  \
259                     progname, desc);                                       \
260            exit (1);                                                       \
261          }                                                                 \
262       (var) = argv[i];                                                     \
263     }                                                                      \
264   over = 1;                                                                \
265 } while (0)
266
267 /* A strdup imitation. */
268 static char *my_strdup(const char *s)
269 {
270         char *new_s = (char *)malloc(strlen(s) + 1);
271         if (new_s)
272                 strcpy(new_s, s);
273         return new_s;
274 }
275
276 int main(int argc, char *argv[])
277 {
278         int starting_line = 1;  /* line to start editing at */
279         char command[MAXPATHLEN + 50];  /* emacs command buffer */
280         char fullpath[MAXPATHLEN + 1];  /* full pathname to file */
281         char *eval_form = NULL; /* form to evaluate with `-eval' */
282         char *eval_function = NULL;     /* function to evaluate with `-f' */
283         char *load_library = NULL;      /* library to load */
284         int quick = 0;          /* quick edit, don't wait for user to
285                                    finish */
286         int batch = 0;          /* batch mode */
287         int view = 0;           /* view only. */
288         int nofiles = 0;
289         int errflg = 0;         /* option error */
290         int s;                  /* socket / msqid to server */
291         int connect_type;       /* CONN_UNIX, CONN_INTERNET, or
292                                  * CONN_IPC */
293         int suppress_windows_system = 0;
294         char *display = NULL;
295 #ifdef INTERNET_DOMAIN_SOCKETS
296         char *hostarg = NULL;   /* remote hostname */
297         char *remotearg;
298         char thishost[HOSTNAMSZ];       /* this hostname */
299         char remotepath[MAXPATHLEN + 1];        /* remote pathname */
300         int rflg = 0;           /* pathname given on cmdline */
301         char *portarg;
302         unsigned short port = 0;        /* port to server */
303 #endif                          /* INTERNET_DOMAIN_SOCKETS */
304         char *path;             /* used indiscriminately */
305 #ifdef SYSV_IPC
306         struct msgbuf *msgp;    /* message */
307 #endif                          /* SYSV_IPC */
308         char *tty = NULL;
309         char buffer[GSERV_BUFSZ + 1];   /* buffer to read pid */
310         char result[GSERV_BUFSZ + 1];
311         int i;
312
313 #ifdef INTERNET_DOMAIN_SOCKETS
314         memset(remotepath, 0, sizeof(remotepath));
315 #endif                          /* INTERNET_DOMAIN_SOCKETS */
316
317         progname = strrchr(argv[0], '/');
318         if (progname)
319                 ++progname;
320         else
321                 progname = argv[0];
322
323 #ifdef USE_TMPDIR
324         tmpdir = getenv("TMPDIR");
325 #endif
326         if (!tmpdir)
327                 tmpdir = "/tmp";
328
329         display = getenv("DISPLAY");
330         if (display)
331                 display = my_strdup(display);
332         else
333                 suppress_windows_system = 1;
334
335         for (i = 1; argv[i] && !errflg; i++) {
336                 if (*argv[i] != '-')
337                         break;
338                 else if (*argv[i] == '-'
339                          && (*(argv[i] + 1) == '\0'
340                              || (*(argv[i] + 1) == '-'
341                                  && *(argv[i] + 2) == '\0'))) {
342                         /* `-' or `--' */
343                         ++i;
344                         break;
345                 }
346
347                 if (!strcmp(argv[i], "-batch") || !strcmp(argv[i], "--batch"))
348                         batch = 1;
349                 else if (!strcmp(argv[i], "-eval")
350                          || !strcmp(argv[i], "--eval")) {
351                         if (!argv[++i]) {
352                                 fprintf(stderr,
353                                         "%s: `-eval' must be followed by an argument\n",
354                                         progname);
355                                 exit(1);
356                         }
357                         eval_form = argv[i];
358                 } else if (!strcmp(argv[i], "-display")
359                            || !strcmp(argv[i], "--display")) {
360                         suppress_windows_system = 0;
361                         if (!argv[++i]) {
362                                 fprintf(stderr,
363                                         "%s: `-display' must be followed by an argument\n",
364                                         progname);
365                                 exit(1);
366                         }
367                         if (display)
368                                 free(display);
369                         /* no need to strdup. */
370                         display = argv[i];
371                 } else if (!strcmp(argv[i], "-nw"))
372                         suppress_windows_system = 1;
373                 else {
374                         /* Iterate over one-letter options. */
375                         char *p;
376                         int over = 0;
377                         for (p = argv[i] + 1; *p && !over; p++) {
378                                 switch (*p) {
379                                 case 'q':
380                                         quick = 1;
381                                         break;
382                                 case 'v':
383                                         view = 1;
384                                         break;
385                                 case 'f':
386                                         GET_ARGUMENT(eval_function, "-f");
387                                         break;
388                                 case 'l':
389                                         GET_ARGUMENT(load_library, "-l");
390                                         break;
391 #ifdef INTERNET_DOMAIN_SOCKETS
392                                 case 'h':
393                                         GET_ARGUMENT(hostarg, "-h");
394                                         break;
395                                 case 'p':
396                                         GET_ARGUMENT(portarg, "-p");
397                                         port = atoi(portarg);
398                                         break;
399                                 case 'r':
400                                         GET_ARGUMENT(remotearg, "-r");
401                                         strncpy(remotepath, remotearg, sizeof(remotepath));
402                                         remotepath[sizeof(remotepath)-1]='\0';
403                                         rflg = 1;
404                                         break;
405 #endif                          /* INTERNET_DOMAIN_SOCKETS */
406                                 default:
407                                         errflg = 1;
408                                 }
409                         }       /* for */
410                 }               /* else */
411         }                       /* for */
412
413         if (errflg) {
414                 fprintf(stderr,
415 #ifdef INTERNET_DOMAIN_SOCKETS
416                         "Usage: %s [-nw] [-display display] [-q] [-v] [-l library]\n"
417                         "       [-batch] [-f function] [-eval form]\n"
418                         "       [-h host] [-p port] [-r remote-path] [[+line] file] ...\n",
419 #else                           /* !INTERNET_DOMAIN_SOCKETS */
420                         "Usage: %s [-nw] [-q] [-v] [-l library] [-f function] [-eval form] "
421                         "[[+line] path] ...\n",
422 #endif                          /* !INTERNET_DOMAIN_SOCKETS */
423                         progname);
424                 exit(1);
425         }
426         if (batch && argv[i]) {
427                 fprintf(stderr, "%s: Cannot specify `-batch' with file names\n",
428                         progname);
429                 exit(1);
430         }
431 #if defined(INTERNET_DOMAIN_SOCKETS)
432         if (suppress_windows_system && hostarg) {
433                 fprintf(stderr, "%s: Remote editing is available only on X\n",
434                         progname);
435                 exit(1);
436         }
437 #endif
438         *result = '\0';
439         if (eval_function || eval_form || load_library) {
440 #if defined(INTERNET_DOMAIN_SOCKETS)
441                 connect_type = make_connection(hostarg, port, &s);
442 #else
443                 connect_type = make_connection(NULL, 0, &s);
444 #endif
445                 sprintf(command, "(gnuserv-eval%s '(progn ",
446                         quick ? "-quickly" : "");
447                 send_string(s, command);
448                 if (load_library) {
449                         send_string(s, "(load-library ");
450                         send_string(s, clean_string(load_library));
451                         send_string(s, ") ");
452                 }
453                 if (eval_form) {
454                         send_string(s, eval_form);
455                 }
456                 if (eval_function) {
457                         send_string(s, "(");
458                         send_string(s, eval_function);
459                         send_string(s, ")");
460                 }
461                 send_string(s, "))");
462                 /* disconnect already sends EOT_STR */
463 #ifdef SYSV_IPC
464                 if (connect_type == (int)CONN_IPC)
465                         disconnect_from_ipc_server(s, msgp, batch && !quick);
466 #else                           /* !SYSV_IPC */
467                 if (connect_type != (int)CONN_IPC)
468                         disconnect_from_server(s, batch && !quick);
469 #endif                          /* !SYSV_IPC */
470         } /* eval_function || eval_form || load_library */
471         else if (batch) {
472                 /* no sexp on the command line, so read it from stdin */
473                 int nb;
474
475 #if defined(INTERNET_DOMAIN_SOCKETS)
476                 connect_type = make_connection(hostarg, port, &s);
477 #else
478                 connect_type = make_connection(NULL, 0, &s);
479 #endif
480                 sprintf(command, "(gnuserv-eval%s '(progn ",
481                         quick ? "-quickly" : "");
482                 send_string(s, command);
483
484                 while ((nb = read(fileno(stdin), buffer, GSERV_BUFSZ - 1)) > 0) {
485                         buffer[nb] = '\0';
486                         send_string(s, buffer);
487                 }
488                 send_string(s, "))");
489                 /* disconnect already sends EOT_STR */
490 #ifdef SYSV_IPC
491                 if (connect_type == (int)CONN_IPC)
492                         disconnect_from_ipc_server(s, msgp, batch && !quick);
493 #else                           /* !SYSV_IPC */
494                 if (connect_type != (int)CONN_IPC)
495                         disconnect_from_server(s, batch && !quick);
496 #endif                          /* !SYSV_IPC */
497         }
498
499         if (!batch) {
500                 if (suppress_windows_system) {
501                         tty = ttyname(0);
502                         if (!tty) {
503                                 fprintf(stderr, "%s: Not connected to a tty",
504                                         progname);
505                                 exit(1);
506                         }
507 #if defined(INTERNET_DOMAIN_SOCKETS)
508                         connect_type = make_connection(hostarg, port, &s);
509 #else
510                         connect_type = make_connection(NULL, 0, &s);
511 #endif
512                         send_string(s, "(gnuserv-eval '(emacs-pid))");
513                         send_string(s, EOT_STR);
514
515                         if (read_line(s, buffer) == 0) {
516                                 fprintf(stderr,
517                                         "%s: Could not establish Emacs process id\n",
518                                         progname);
519                                 exit(1);
520                         }
521                         /* Don't do disconnect_from_server because we have already read
522                            data, and disconnect doesn't do anything else. */
523 #ifdef SYSV_IPC
524                         if (connect_type == (int)CONN_IPC)
525                                 disconnect_from_ipc_server(s, msgp, FALSE);
526 #endif                          /* !SYSV_IPC */
527
528                         emacs_pid = (pid_t) atol(buffer);
529                         initialize_signals();
530                 }
531                 /* suppress_windows_system */
532 #if defined(INTERNET_DOMAIN_SOCKETS)
533                 connect_type = make_connection(hostarg, port, &s);
534 #else
535                 connect_type = make_connection(NULL, 0, &s);
536 #endif
537
538 #ifdef INTERNET_DOMAIN_SOCKETS
539                 if (connect_type == (int)CONN_INTERNET) {
540                         char *ptr;
541                         gethostname(thishost, HOSTNAMSZ);
542                         if (!rflg) {    /* attempt to generate a path
543                                          * to this machine */
544                                 if ((ptr = getenv("GNU_NODE")) != NULL) {
545                                         /* user specified a path */
546                                         strncpy(remotepath, ptr, sizeof(remotepath)-1);
547                                         remotepath[sizeof(remotepath)-1]='\0';
548                                 }
549                         }
550 #if 0                           /* This is really bogus... re-enable it if you must have it! */
551 #if defined (hp9000s300) || defined (hp9000s800)
552                         else if (strcmp(thishost, hostarg)) {   /* try /net/thishost */
553                                 strcpy(remotepath, "/net/");    /* (this fails using internet
554                                                                    addresses) */
555                                 strcat(remotepath, thishost);
556                         }
557 #endif
558 #endif
559                 } else {        /* same machines, no need for path */
560                         remotepath[0] = '\0';   /* default is the empty path */
561                 }
562 #endif                          /* INTERNET_DOMAIN_SOCKETS */
563
564 #ifdef SYSV_IPC
565                 if ((msgp = (struct msgbuf *)
566                      malloc(sizeof *msgp + GSERV_BUFSZ)) == NULL) {
567                         fprintf(stderr,
568                                 "%s: not enough memory for message buffer\n",
569                                 progname);
570                         exit(1);
571                 }
572                 /* if */
573                 msgp->mtext[0] = '\0';  /* ready for later strcats */
574 #endif                          /* SYSV_IPC */
575
576                 if (suppress_windows_system) {
577                         char *term = getenv("TERM");
578                         if (!term) {
579                                 fprintf(stderr, "%s: unknown terminal type\n",
580                                         progname);
581                                 exit(1);
582                         }
583                         sprintf(command,
584                                 "(gnuserv-edit-files '(tty %s %s %d) '(",
585                                 clean_string(tty), clean_string(term),
586                                 (int)getpid());
587                 } else {        /* !suppress_windows_system */
588
589                         if (0) ;
590 #ifdef HAVE_X_WINDOWS
591                         else if (display)
592                                 sprintf(command,
593                                         "(gnuserv-edit-files '(x %s) '(",
594                                         clean_string(display));
595 #endif
596 #ifdef HAVE_GTK
597                         else if (display)
598                                 strcpy(command,
599                                        "(gnuserv-edit-files '(gtk nil) '(");
600 #endif
601                 }               /* !suppress_windows_system */
602                 send_string(s, command);
603
604                 if (!argv[i])
605                         nofiles = 1;
606
607                 for (; argv[i]; i++) {
608                         if (i < argc - 1 && *argv[i] == '+')
609                                 starting_line = atoi(argv[i++]);
610                         else
611                                 starting_line = 1;
612                         /* If the last argument is +something, treat it as a file. */
613                         if (i == argc) {
614                                 starting_line = 1;
615                                 --i;
616                         }
617                         filename_expand(fullpath, argv[i], sizeof(fullpath));
618 #ifdef INTERNET_DOMAIN_SOCKETS
619                         path =
620                             (char *)malloc(strlen(remotepath) +
621                                            strlen(fullpath) + 1);
622                         sprintf(path, "%s%s", remotepath, fullpath);
623 #else
624                         path = my_strdup(fullpath);
625 #endif
626                         sprintf(command, "(%d . %s)", starting_line,
627                                 clean_string(path));
628                         send_string(s, command);
629                         free(path);
630                 }               /* for */
631
632                 sprintf(command, ")%s%s",
633                         (quick
634                          || (nofiles
635                              && !suppress_windows_system)) ? " 'quick" : "",
636                         view ? " 'view" : "");
637                 send_string(s, command);
638                 send_string(s, ")");
639
640 #ifdef SYSV_IPC
641                 if (connect_type == (int)CONN_IPC)
642                         disconnect_from_ipc_server(s, msgp, FALSE);
643 #else                           /* !SYSV_IPC */
644                 if (connect_type != (int)CONN_IPC)
645                         disconnect_from_server(s, FALSE);
646 #endif                          /* !SYSV_IPC */
647         }
648
649         /* not batch */
650         return 0;
651
652 }                               /* main */
653
654 #endif                          /* SYSV_IPC || UNIX_DOMAIN_SOCKETS || INTERNET_DOMAIN_SOCKETS */