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