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