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