1 /*******************************************************************************
2 * *
3 * nc.c -- Nirvana Editor client program for nedit server processes *
4 * *
5 * Copyright (C) 1999 Mark Edel *
6 * *
7 * This is free software; you can redistribute it and/or modify it under the *
8 * terms of the GNU General Public License as published by the Free Software *
9 * Foundation; either version 2 of the License, or (at your option) any later *
10 * version. In addition, you may distribute version of this program linked to *
11 * Motif or Open Motif. See README for details. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * November, 1995 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
32
33 #include "server_common.h"
34 #include "../util/fileUtils.h"
35 #include "../util/utils.h"
36 #include "../util/prefFile.h"
37 #include "../util/system.h"
38
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <limits.h>
42 #include <string.h>
43 #ifdef VMS
44 #include <lib$routines.h>
45 #include descrip
46 #include ssdef
47 #include syidef
48 #include "../util/VMSparam.h"
49 #include "../util/VMSutils.h"
50 #else
51 #ifndef __MVS__
52 #include <sys/param.h>
53 #endif
54 #include <sys/types.h>
55 #include <sys/utsname.h>
56 #include <unistd.h>
57 #include <pwd.h>
58 #include "../util/clearcase.h"
59 #endif /* VMS */
60 #ifdef __EMX__
61 #include <process.h>
62 #endif
63
64 #include <X11/Intrinsic.h>
65 #include <X11/Xatom.h>
66
67 #ifdef HAVE_DEBUG_H
68 #include "../debug.h"
69 #endif
70
71 #define APP_NAME "nc"
72 #define APP_CLASS "NEditClient"
73
74 #define PROPERTY_CHANGE_TIMEOUT (Preferences.timeOut * 1000) /* milliseconds */
75 #define SERVER_START_TIMEOUT (Preferences.timeOut * 3000) /* milliseconds */
76 #define REQUEST_TIMEOUT (Preferences.timeOut * 1000) /* milliseconds */
77 #define FILE_OPEN_TIMEOUT (Preferences.timeOut * 3000) /* milliseconds */
78
79 typedef struct
80 {
81 char* shell;
82 char* serverRequest;
83 } CommandLine;
84
85 static void timeOutProc(Boolean *timeOutReturn, XtIntervalId *id);
86 static int startServer(const char *message, const char *commandLine);
87 static CommandLine processCommandLine(int argc, char** argv);
88 static void parseCommandLine(int argc, char **arg, CommandLine *cmdLine);
89 static void nextArg(int argc, char **argv, int *argIndex);
90 static void copyCommandLineArg(CommandLine *cmdLine, const char *arg);
91 static void printNcVersion(void);
92 static Boolean findExistingServer(XtAppContext context,
93 Window rootWindow,
94 Atom serverExistsAtom);
95 static void startNewServer(XtAppContext context,
96 Window rootWindow,
97 char* commandLine,
98 Atom serverExistsAtom);
99 static void waitUntilRequestProcessed(XtAppContext context,
100 Window rootWindow,
101 char* commandString,
102 Atom serverRequestAtom);
103 static void waitUntilFilesOpenedOrClosed(XtAppContext context,
104 Window rootWindow);
105
106 Display *TheDisplay;
107 XtAppContext AppContext;
108 static Atom currentWaitForAtom;
109 static Atom noAtom = (Atom)(-1);
110
111 static const char cmdLineHelp[] =
112 #ifdef VMS
113 "[Sorry, no on-line help available.]\n"; /* Why is that ? */
114 #else
115 "Usage: nc [-read] [-create]\n"
116 " [-line n | +n] [-do command] [-lm languagemode]\n"
117 " [-svrname name] [-svrcmd command]\n"
118 " [-ask] [-noask] [-timeout seconds]\n"
119 " [-geometry geometry | -g geometry] [-icon | -iconic]\n"
120 " [-tabbed] [-untabbed] [-group] [-wait]\n"
121 " [-V | -version] [-h|-help]\n"
122 " [-xrm resourcestring] [-display [host]:server[.screen]]\n"
123 " [--] [file...]\n";
124 #endif /*VMS*/
125
126 /* Structure to hold X Resource values */
127 static struct {
128 int autoStart;
129 char serverCmd[2*MAXPATHLEN]; /* holds executable name + flags */
130 char serverName[MAXPATHLEN];
131 int waitForClose;
132 int timeOut;
133 } Preferences;
134
135 /* Application resources */
136 static PrefDescripRec PrefDescrip[] = {
137 {"autoStart", "AutoStart", PREF_BOOLEAN, "True",
138 &Preferences.autoStart, NULL, True},
139 {"serverCommand", "ServerCommand", PREF_STRING, "nedit -server",
140 Preferences.serverCmd, (void *)sizeof(Preferences.serverCmd), False},
141 {"serverName", "serverName", PREF_STRING, "", Preferences.serverName,
142 (void *)sizeof(Preferences.serverName), False},
143 {"waitForClose", "WaitForClose", PREF_BOOLEAN, "False",
144 &Preferences.waitForClose, NULL, False},
145 {"timeOut", "TimeOut", PREF_INT, "10",
146 &Preferences.timeOut, NULL, False}
147 };
148
149 /* Resource related command line options */
150 static XrmOptionDescRec OpTable[] = {
151 {"-ask", ".autoStart", XrmoptionNoArg, (caddr_t)"False"},
152 {"-noask", ".autoStart", XrmoptionNoArg, (caddr_t)"True"},
153 {"-svrname", ".serverName", XrmoptionSepArg, (caddr_t)NULL},
154 {"-svrcmd", ".serverCommand", XrmoptionSepArg, (caddr_t)NULL},
155 {"-wait", ".waitForClose", XrmoptionNoArg, (caddr_t)"True"},
156 {"-timeout", ".timeOut", XrmoptionSepArg, (caddr_t)NULL}
157 };
158
159 /* Struct to hold info about files being opened and edited. */
160 typedef struct _FileListEntry {
161 Atom waitForFileOpenAtom;
162 Atom waitForFileClosedAtom;
163 char* path;
164 struct _FileListEntry *next;
165 } FileListEntry;
166
167 typedef struct {
168 int waitForOpenCount;
169 int waitForCloseCount;
170 FileListEntry* fileList;
171 } FileListHead;
172 static FileListHead fileListHead;
173
setPropertyValue(Atom atom)174 static void setPropertyValue(Atom atom) {
175 XChangeProperty(TheDisplay,
176 RootWindow(TheDisplay, DefaultScreen(TheDisplay)),
177 atom, XA_STRING,
178 8, PropModeReplace,
179 (unsigned char *)"True", 4);
180 }
181
182 /* Add another entry to the file entry list, if it doesn't exist yet. */
addToFileList(const char * path)183 static void addToFileList(const char *path)
184 {
185 FileListEntry *item;
186
187 /* see if the file already exists in the list */
188 for (item = fileListHead.fileList; item; item = item->next) {
189 if (!strcmp(item->path, path))
190 break;
191 }
192
193 /* Add the atom to the head of the file list if it wasn't found. */
194 if (item == 0) {
195 item = malloc(sizeof(item[0]));
196 item->waitForFileOpenAtom = None;
197 item->waitForFileClosedAtom = None;
198 item->path = (char*)malloc(strlen(path)+1);
199 strcpy(item->path, path);
200 item->next = fileListHead.fileList;
201 fileListHead.fileList = item;
202 }
203 }
204
205 /* Creates the properties for the various paths */
createWaitProperties(void)206 static void createWaitProperties(void)
207 {
208 FileListEntry *item;
209
210 for (item = fileListHead.fileList; item; item = item->next) {
211 fileListHead.waitForOpenCount++;
212 item->waitForFileOpenAtom =
213 CreateServerFileOpenAtom(Preferences.serverName, item->path);
214 setPropertyValue(item->waitForFileOpenAtom);
215
216 if (Preferences.waitForClose == True) {
217 fileListHead.waitForCloseCount++;
218 item->waitForFileClosedAtom =
219 CreateServerFileClosedAtom(Preferences.serverName,
220 item->path,
221 False);
222 setPropertyValue(item->waitForFileClosedAtom);
223 }
224 }
225 }
226
main(int argc,char ** argv)227 int main(int argc, char **argv)
228 {
229 XtAppContext context;
230 Window rootWindow;
231 CommandLine commandLine;
232 Atom serverExistsAtom, serverRequestAtom;
233 XrmDatabase prefDB;
234 Boolean serverExists;
235
236 /* Initialize toolkit and get an application context */
237 XtToolkitInitialize();
238 AppContext = context = XtCreateApplicationContext();
239
240 #ifdef VMS
241 /* Convert the command line to Unix style */
242 ConvertVMSCommandLine(&argc, &argv);
243 #endif /*VMS*/
244 #ifdef __EMX__
245 /* expand wildcards if necessary */
246 _wildcard(&argc, &argv);
247 #endif
248
249 /* Read the preferences command line into a database (note that we
250 don't support the .nc file anymore) */
251 prefDB = CreatePreferencesDatabase(NULL, APP_CLASS,
252 OpTable, XtNumber(OpTable), (unsigned *)&argc, argv);
253
254 /* Process the command line before calling XtOpenDisplay, because the
255 latter consumes certain command line arguments that we still need
256 (-icon, -geometry ...) */
257 commandLine = processCommandLine(argc, argv);
258
259 /* Open the display and find the root window */
260 TheDisplay = XtOpenDisplay (context, NULL, APP_NAME, APP_CLASS, NULL,
261 0, &argc, argv);
262 if (!TheDisplay) {
263 XtWarning ("nc: Can't open display\n");
264 exit(EXIT_FAILURE);
265 }
266 rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
267
268 /* Read the application resources into the Preferences data structure */
269 RestorePreferences(prefDB, XtDatabase(TheDisplay), APP_NAME,
270 APP_CLASS, PrefDescrip, XtNumber(PrefDescrip));
271
272 /* Make sure that the time out unit is at least 1 second and not too
273 large either (overflow!). */
274 if (Preferences.timeOut < 1) {
275 Preferences.timeOut = 1;
276 } else if (Preferences.timeOut > 1000) {
277 Preferences.timeOut = 1000;
278 }
279
280 #ifndef VMS
281 /* For Clearcase users who have not set a server name, use the clearcase
282 view name. Clearcase views make files with the same absolute path names
283 but different contents (and therefore can't be edited in the same nedit
284 session). This should have no bad side-effects for non-clearcase users */
285 if (Preferences.serverName[0] == '\0') {
286 const char* viewTag = GetClearCaseViewTag();
287 if (viewTag != NULL && strlen(viewTag) < MAXPATHLEN) {
288 strcpy(Preferences.serverName, viewTag);
289 }
290 }
291 #endif /* VMS */
292
293 /* Create the wait properties for the various files. */
294 createWaitProperties();
295
296 /* Monitor the properties on the root window */
297 XSelectInput(TheDisplay, rootWindow, PropertyChangeMask);
298
299 /* Create the server property atoms on the current DISPLAY. */
300 CreateServerPropertyAtoms(Preferences.serverName,
301 &serverExistsAtom,
302 &serverRequestAtom);
303
304 serverExists = findExistingServer(context,
305 rootWindow,
306 serverExistsAtom);
307
308 if (serverExists == False)
309 startNewServer(context, rootWindow, commandLine.shell, serverExistsAtom);
310
311 waitUntilRequestProcessed(context,
312 rootWindow,
313 commandLine.serverRequest,
314 serverRequestAtom);
315
316 waitUntilFilesOpenedOrClosed(context, rootWindow);
317
318 XtCloseDisplay(TheDisplay);
319 XtFree(commandLine.shell);
320 XtFree(commandLine.serverRequest);
321 return 0;
322 }
323
324
325 /*
326 ** Xt timer procedure for timeouts on NEdit server requests
327 */
timeOutProc(Boolean * timeOutReturn,XtIntervalId * id)328 static void timeOutProc(Boolean *timeOutReturn, XtIntervalId *id)
329 {
330 /* NOTE: XtAppNextEvent() does call this routine but
331 ** doesn't return unless there are more events.
332 ** Hence, we generate this (synthetic) event to break the deadlock
333 */
334 Window rootWindow = RootWindow(TheDisplay, DefaultScreen(TheDisplay));
335 if (currentWaitForAtom != noAtom) {
336 XChangeProperty(TheDisplay, rootWindow, currentWaitForAtom, XA_STRING,
337 8, PropModeReplace, (unsigned char *)"", strlen(""));
338 }
339
340 /* Flag that the timeout has occurred. */
341 *timeOutReturn = True;
342 }
343
344
345
findExistingServer(XtAppContext context,Window rootWindow,Atom serverExistsAtom)346 static Boolean findExistingServer(XtAppContext context,
347 Window rootWindow,
348 Atom serverExistsAtom)
349 {
350 Boolean serverExists = True;
351 unsigned char *propValue;
352 int getFmt;
353 Atom dummyAtom;
354 unsigned long dummyULong, nItems;
355
356 /* See if there might be a server (not a guaranty), by translating the
357 root window property NEDIT_SERVER_EXISTS_<user>_<host> */
358 if (XGetWindowProperty(TheDisplay, rootWindow, serverExistsAtom, 0,
359 INT_MAX, False, XA_STRING, &dummyAtom, &getFmt, &nItems,
360 &dummyULong, &propValue) != Success || nItems == 0) {
361 serverExists = False;
362 } else {
363 Boolean timeOut = False;
364 XtIntervalId timerId;
365
366 XFree(propValue);
367
368 /* Remove the server exists property to make sure the server is
369 ** running. If it is running it will get recreated.
370 */
371 XDeleteProperty(TheDisplay, rootWindow, serverExistsAtom);
372 XSync(TheDisplay, False);
373 timerId = XtAppAddTimeOut(context,
374 PROPERTY_CHANGE_TIMEOUT,
375 (XtTimerCallbackProc)timeOutProc,
376 &timeOut);
377 currentWaitForAtom = serverExistsAtom;
378
379 while (!timeOut) {
380 /* NOTE: XtAppNextEvent() does call the timeout routine but
381 ** doesn't return unless there are more events. */
382 XEvent event;
383 const XPropertyEvent *e = (const XPropertyEvent *)&event;
384 XtAppNextEvent(context, &event);
385
386 /* We will get a PropertyNewValue when the server recreates
387 ** the server exists atom. */
388 if (e->type == PropertyNotify &&
389 e->window == rootWindow &&
390 e->atom == serverExistsAtom) {
391 if (e->state == PropertyNewValue) {
392 break;
393 }
394 }
395 XtDispatchEvent(&event);
396 }
397
398 /* Start a new server if the timeout expired. The server exists
399 ** property was not recreated. */
400 if (timeOut) {
401 serverExists = False;
402 } else {
403 XtRemoveTimeOut(timerId);
404 }
405 }
406
407 return(serverExists);
408 }
409
410
411
412
startNewServer(XtAppContext context,Window rootWindow,char * commandLine,Atom serverExistsAtom)413 static void startNewServer(XtAppContext context,
414 Window rootWindow,
415 char* commandLine,
416 Atom serverExistsAtom)
417 {
418 Boolean timeOut = False;
419 XtIntervalId timerId;
420
421 /* Add back the server name resource from the command line or resource
422 database to the command line for starting the server. If -svrcmd
423 appeared on the original command line, it was removed by
424 CreatePreferencesDatabase before the command line was recorded
425 in commandLine.shell. Moreover, if no server name was specified, it
426 may have defaulted to the ClearCase view tag. */
427 if (Preferences.serverName[0] != '\0') {
428 strcat(commandLine, " -svrname ");
429 strcat(commandLine, Preferences.serverName);
430 }
431 switch (startServer("No servers available, start one? (y|n) [y]: ",
432 commandLine))
433 {
434 case -1: /* Start failed */
435 XtCloseDisplay(TheDisplay);
436 exit(EXIT_FAILURE);
437 break;
438 case -2: /* Start canceled by user */
439 XtCloseDisplay(TheDisplay);
440 exit(EXIT_SUCCESS);
441 break;
442 }
443
444 /* Set up a timeout proc in case the server is dead. The standard
445 selection timeout is probably a good guess at how long to wait
446 for this style of inter-client communication as well */
447 timerId = XtAppAddTimeOut(context,
448 SERVER_START_TIMEOUT,
449 (XtTimerCallbackProc)timeOutProc,
450 &timeOut);
451 currentWaitForAtom = serverExistsAtom;
452
453 /* Wait for the server to start */
454 while (!timeOut) {
455 XEvent event;
456 const XPropertyEvent *e = (const XPropertyEvent *)&event;
457 /* NOTE: XtAppNextEvent() does call the timeout routine but
458 ** doesn't return unless there are more events. */
459 XtAppNextEvent(context, &event);
460
461 /* We will get a PropertyNewValue when the server updates
462 ** the server exists atom. If the property is deleted the
463 ** the server must have died. */
464 if (e->type == PropertyNotify &&
465 e->window == rootWindow &&
466 e->atom == serverExistsAtom) {
467 if (e->state == PropertyNewValue) {
468 break;
469 } else if (e->state == PropertyDelete) {
470 fprintf(stderr, "%s: The server failed to start.\n", APP_NAME);
471 XtCloseDisplay(TheDisplay);
472 exit(EXIT_FAILURE);
473 }
474 }
475 XtDispatchEvent(&event);
476 }
477 /* Exit if the timeout expired. */
478 if (timeOut) {
479 fprintf(stderr, "%s: The server failed to start (time-out).\n", APP_NAME);
480 XtCloseDisplay(TheDisplay);
481 exit(EXIT_FAILURE);
482 } else {
483 XtRemoveTimeOut(timerId);
484 }
485 }
486
487 /*
488 ** Prompt the user about starting a server, with "message", then start server
489 */
startServer(const char * message,const char * commandLineArgs)490 static int startServer(const char *message, const char *commandLineArgs)
491 {
492 char c, *commandLine;
493 #ifdef VMS
494 int spawnFlags = 1 /* + 1<<3 */; /* NOWAIT, NOKEYPAD */
495 int spawn_sts;
496 struct dsc$descriptor_s *cmdDesc;
497 char *nulDev = "NL:";
498 struct dsc$descriptor_s *nulDevDesc;
499 #else
500 int sysrc;
501 #endif /* !VMS */
502
503 /* prompt user whether to start server */
504 if (!Preferences.autoStart) {
505 printf("%s", message);
506 do {
507 c = getc(stdin);
508 } while (c == ' ' || c == '\t');
509 if (c != 'Y' && c != 'y' && c != '\n')
510 return (-2);
511 }
512
513 /* start the server */
514 #ifdef VMS
515 commandLine = XtMalloc(strlen(Preferences.serverCmd) +
516 strlen(commandLineArgs) + 3);
517 sprintf(commandLine, "%s %s", Preferences.serverCmd, commandLineArgs);
518 cmdDesc = NulStrToDesc(commandLine); /* build command descriptor */
519 nulDevDesc = NulStrToDesc(nulDev); /* build "NL:" descriptor */
520 spawn_sts = lib$spawn(cmdDesc, nulDevDesc, 0, &spawnFlags, 0,0,0,0,0,0,0,0);
521 XtFree(commandLine);
522 if (spawn_sts != SS$_NORMAL) {
523 fprintf(stderr, "Error return from lib$spawn: %d\n", spawn_sts);
524 fprintf(stderr, "Nedit server not started.\n");
525 return (-1);
526 } else {
527 FreeStrDesc(cmdDesc);
528 return 0;
529 }
530 #else
531 #if defined(__EMX__) /* OS/2 */
532 /* Unfortunately system() calls a shell determined by the environment
533 variables COMSPEC and EMXSHELL. We have to figure out which one. */
534 {
535 char *sh_spec, *sh, *base;
536 char *CMD_EXE="cmd.exe";
537
538 commandLine = XtMalloc(strlen(Preferences.serverCmd) +
539 strlen(commandLineArgs) + 15);
540 sh = getenv ("EMXSHELL");
541 if (sh == NULL)
542 sh = getenv ("COMSPEC");
543 if (sh == NULL)
544 sh = CMD_EXE;
545 sh_spec=XtNewString(sh);
546 base=_getname(sh_spec);
547 _remext(base);
548 if (stricmp (base, "cmd") == 0 || stricmp (base, "4os2") == 0) {
549 sprintf(commandLine, "start /C /MIN %s %s",
550 Preferences.serverCmd, commandLineArgs);
551 } else {
552 sprintf(commandLine, "%s %s &",
553 Preferences.serverCmd, commandLineArgs);
554 }
555 XtFree(sh_spec);
556 }
557 #else /* Unix */
558 commandLine = XtMalloc(strlen(Preferences.serverCmd) +
559 strlen(commandLineArgs) + 3);
560 sprintf(commandLine, "%s %s&", Preferences.serverCmd, commandLineArgs);
561 #endif
562
563 sysrc=system(commandLine);
564 XtFree(commandLine);
565
566 if (sysrc==0)
567 return 0;
568 else
569 return (-1);
570 #endif /* !VMS */
571 }
572
573 /* Reconstruct the command line in string commandLine in case we have to
574 * start a server (nc command line args parallel nedit's). Include
575 * -svrname if nc wants a named server, so nedit will match. Special
576 * characters are protected from the shell by escaping EVERYTHING with \
577 */
processCommandLine(int argc,char ** argv)578 static CommandLine processCommandLine(int argc, char** argv)
579 {
580 CommandLine commandLine;
581 int i;
582 int length = 0;
583
584 for (i=1; i<argc; i++) {
585 length += 1 + strlen(argv[i])*4 + 2;
586 }
587 commandLine.shell = XtMalloc(length+1 + 9 + MAXPATHLEN);
588 *commandLine.shell = '\0';
589
590 /* Convert command line arguments into a command string for the server */
591 parseCommandLine(argc, argv, &commandLine);
592 if (commandLine.serverRequest == NULL) {
593 fprintf(stderr, "nc: Invalid commandline argument\n");
594 exit(EXIT_FAILURE);
595 }
596
597 return(commandLine);
598 }
599
600
601 /*
602 ** Converts command line into a command string suitable for passing to
603 ** the server
604 */
parseCommandLine(int argc,char ** argv,CommandLine * commandLine)605 static void parseCommandLine(int argc, char **argv, CommandLine *commandLine)
606 {
607 #define MAX_RECORD_HEADER_LENGTH 38
608 char name[MAXPATHLEN], path[MAXPATHLEN];
609 const char *toDoCommand = "", *langMode = "", *geometry = "";
610 char *commandString, *outPtr;
611 int lineNum = 0, read = 0, create = 0, iconic = 0, tabbed = -1, length = 0;
612 int i, lineArg, nRead, charsWritten, opts = True;
613 int fileCount = 0, group = 0, isTabbed;
614
615 /* Allocate a string for output, for the maximum possible length. The
616 maximum length is calculated by assuming every argument is a file,
617 and a complete record of maximum length is created for it */
618 for (i=1; i<argc; i++) {
619 length += MAX_RECORD_HEADER_LENGTH + strlen(argv[i]) + MAXPATHLEN;
620 }
621 /* In case of no arguments, must still allocate space for one record header */
622 if (length < MAX_RECORD_HEADER_LENGTH)
623 {
624 length = MAX_RECORD_HEADER_LENGTH;
625 }
626 commandString = XtMalloc(length+1);
627
628 /* Parse the arguments and write the output string */
629 outPtr = commandString;
630 for (i=1; i<argc; i++) {
631 if (opts && !strcmp(argv[i], "--")) {
632 opts = False; /* treat all remaining arguments as filenames */
633 continue;
634 } else if (opts && !strcmp(argv[i], "-do")) {
635 nextArg(argc, argv, &i);
636 toDoCommand = argv[i];
637 } else if (opts && !strcmp(argv[i], "-lm")) {
638 copyCommandLineArg(commandLine, argv[i]);
639 nextArg(argc, argv, &i);
640 langMode = argv[i];
641 copyCommandLineArg(commandLine, argv[i]);
642 } else if (opts && (!strcmp(argv[i], "-g") ||
643 !strcmp(argv[i], "-geometry"))) {
644 copyCommandLineArg(commandLine, argv[i]);
645 nextArg(argc, argv, &i);
646 geometry = argv[i];
647 copyCommandLineArg(commandLine, argv[i]);
648 } else if (opts && !strcmp(argv[i], "-read")) {
649 read = 1;
650 } else if (opts && !strcmp(argv[i], "-create")) {
651 create = 1;
652 } else if (opts && !strcmp(argv[i], "-tabbed")) {
653 tabbed = 1;
654 group = 0; /* override -group option */
655 } else if (opts && !strcmp(argv[i], "-untabbed")) {
656 tabbed = 0;
657 group = 0; /* override -group option */
658 } else if (opts && !strcmp(argv[i], "-group")) {
659 group = 2; /* 2: start new group, 1: in group */
660 } else if (opts && (!strcmp(argv[i], "-iconic") ||
661 !strcmp(argv[i], "-icon"))) {
662 iconic = 1;
663 copyCommandLineArg(commandLine, argv[i]);
664 } else if (opts && !strcmp(argv[i], "-line")) {
665 nextArg(argc, argv, &i);
666 nRead = sscanf(argv[i], "%d", &lineArg);
667 if (nRead != 1)
668 fprintf(stderr, "nc: argument to line should be a number\n");
669 else
670 lineNum = lineArg;
671 } else if (opts && (*argv[i] == '+')) {
672 nRead = sscanf((argv[i]+1), "%d", &lineArg);
673 if (nRead != 1)
674 fprintf(stderr, "nc: argument to + should be a number\n");
675 else
676 lineNum = lineArg;
677 } else if (opts && (!strcmp(argv[i], "-ask") || !strcmp(argv[i], "-noask"))) {
678 ; /* Ignore resource-based arguments which are processed later */
679 } else if (opts && (!strcmp(argv[i], "-svrname") ||
680 !strcmp(argv[i], "-svrcmd"))) {
681 nextArg(argc, argv, &i); /* Ignore rsrc args with data */
682 } else if (opts && (!strcmp(argv[i], "-xrm") ||
683 !strcmp(argv[i], "-display"))) {
684 copyCommandLineArg(commandLine, argv[i]);
685 nextArg(argc, argv, &i); /* Ignore rsrc args with data */
686 copyCommandLineArg(commandLine, argv[i]);
687 } else if (opts && (!strcmp(argv[i], "-version") || !strcmp(argv[i], "-V"))) {
688 printNcVersion();
689 exit(EXIT_SUCCESS);
690 } else if (opts && (!strcmp(argv[i], "-h") ||
691 !strcmp(argv[i], "-help"))) {
692 fprintf(stderr, "%s", cmdLineHelp);
693 exit(EXIT_SUCCESS);
694 } else if (opts && (*argv[i] == '-')) {
695 #ifdef VMS
696 *argv[i] = '/';
697 #endif /*VMS*/
698 fprintf(stderr, "nc: Unrecognized option %s\n%s", argv[i],
699 cmdLineHelp);
700 exit(EXIT_FAILURE);
701 } else {
702 #ifdef VMS
703 int numFiles, j, oldLength;
704 char **nameList = NULL, *newCommandString;
705 /* Use VMS's LIB$FILESCAN for filename in argv[i] to process */
706 /* wildcards and to obtain a full VMS file specification */
707 numFiles = VMSFileScan(argv[i], &nameList, NULL, INCLUDE_FNF);
708 /* Should we warn the user if he provided a -line or -do switch
709 and a wildcard pattern that expands to more than one file? */
710 /* for each expanded file name do: */
711 for (j = 0; j < numFiles; ++j) {
712 oldLength = outPtr-commandString;
713 newCommandString = XtMalloc(oldLength+length+1);
714 strncpy(newCommandString, commandString, oldLength);
715 XtFree(commandString);
716 commandString = newCommandString;
717 outPtr = newCommandString + oldLength;
718 if (ParseFilename(nameList[j], name, path) != 0) {
719 /* An Error, most likely too long paths/strings given */
720 commandLine->serverRequest = NULL;
721 return;
722 }
723 strcat(path, name);
724
725 /* determine if file is to be openned in new tab, by
726 factoring the options -group, -tabbed & -untabbed */
727 if (group == 2) {
728 isTabbed = 0; /* start a new window for new group */
729 group = 1; /* next file will be within group */
730 }
731 else if (group == 1) {
732 isTabbed = 1; /* new tab for file in group */
733 }
734 else {
735 isTabbed = tabbed; /* not in group */
736 }
737
738 /* See below for casts */
739 sprintf(outPtr, "%d %d %d %d %d %ld %ld %ld %ld\n%s\n%s\n%s\n%s\n%n",
740 lineNum, read, create, iconic, tabbed, (long) strlen(path),
741 (long) strlen(toDoCommand), (long) strlen(langMode),
742 (long) strlen(geometry),
743 path, toDoCommand, langMode, geometry, &charsWritten);
744 outPtr += charsWritten;
745 free(nameList[j]);
746
747 /* Create the file open atoms for the paths supplied */
748 addToFileList(path);
749 fileCount++;
750 }
751 if (nameList != NULL)
752 free(nameList);
753 #else
754 if (ParseFilename(argv[i], name, path) != 0) {
755 /* An Error, most likely too long paths/strings given */
756 commandLine->serverRequest = NULL;
757 return;
758 }
759 strcat(path, name);
760
761 /* determine if file is to be openned in new tab, by
762 factoring the options -group, -tabbed & -untabbed */
763 if (group == 2) {
764 isTabbed = 0; /* start a new window for new group */
765 group = 1; /* next file will be within group */
766 }
767 else if (group == 1) {
768 isTabbed = 1; /* new tab for file in group */
769 }
770 else {
771 isTabbed = tabbed; /* not in group */
772 }
773
774 /* SunOS 4 acc or acc and/or its runtime library has a bug
775 such that %n fails (segv) if it follows a string in a
776 printf or sprintf. The silly code below avoids this.
777
778 The "long" cast on strlen() is necessary because size_t
779 is 64 bit on Alphas, and 32-bit on most others. There is
780 no printf format specifier for "size_t", thanx, ANSI. */
781 sprintf(outPtr, "%d %d %d %d %d %ld %ld %ld %ld\n%n", lineNum,
782 read, create, iconic, isTabbed, (long) strlen(path),
783 (long) strlen(toDoCommand), (long) strlen(langMode),
784 (long) strlen(geometry), &charsWritten);
785 outPtr += charsWritten;
786 strcpy(outPtr, path);
787 outPtr += strlen(path);
788 *outPtr++ = '\n';
789 strcpy(outPtr, toDoCommand);
790 outPtr += strlen(toDoCommand);
791 *outPtr++ = '\n';
792 strcpy(outPtr, langMode);
793 outPtr += strlen(langMode);
794 *outPtr++ = '\n';
795 strcpy(outPtr, geometry);
796 outPtr += strlen(geometry);
797 *outPtr++ = '\n';
798
799 /* Create the file open atoms for the paths supplied */
800 addToFileList(path);
801 fileCount++;
802 #endif /* VMS */
803
804 /* These switches only affect the next filename argument, not all */
805 toDoCommand = "";
806 lineNum = 0;
807 }
808 }
809 #ifdef VMS
810 VMSFileScanDone();
811 #endif /*VMS*/
812
813 /* If there's an un-written -do command, or we are to open a new window,
814 * or user has requested iconic state, but not provided a file name,
815 * create a server request with an empty file name and requested
816 * iconic state (and optional language mode and geometry).
817 */
818 if (toDoCommand[0] != '\0' || fileCount == 0) {
819 sprintf(outPtr, "0 0 0 %d %d 0 %ld %ld %ld\n\n%n", iconic, tabbed,
820 (long) strlen(toDoCommand),
821 (long) strlen(langMode), (long) strlen(geometry), &charsWritten);
822 outPtr += charsWritten;
823 strcpy(outPtr, toDoCommand);
824 outPtr += strlen(toDoCommand);
825 *outPtr++ = '\n';
826 strcpy(outPtr, langMode);
827 outPtr += strlen(langMode);
828 *outPtr++ = '\n';
829 strcpy(outPtr, geometry);
830 outPtr += strlen(geometry);
831 *outPtr++ = '\n';
832 }
833
834 *outPtr = '\0';
835 commandLine->serverRequest = commandString;
836 }
837
838
waitUntilRequestProcessed(XtAppContext context,Window rootWindow,char * commandString,Atom serverRequestAtom)839 static void waitUntilRequestProcessed(XtAppContext context,
840 Window rootWindow,
841 char* commandString,
842 Atom serverRequestAtom)
843 {
844 XtIntervalId timerId;
845 Boolean timeOut = False;
846
847 /* Set the NEDIT_SERVER_REQUEST_<user>_<host> property on the root
848 window to activate the server */
849 XChangeProperty(TheDisplay, rootWindow, serverRequestAtom, XA_STRING, 8,
850 PropModeReplace, (unsigned char *)commandString,
851 strlen(commandString));
852
853 /* Set up a timeout proc in case the server is dead. The standard
854 selection timeout is probably a good guess at how long to wait
855 for this style of inter-client communication as well */
856 timerId = XtAppAddTimeOut(context,
857 REQUEST_TIMEOUT,
858 (XtTimerCallbackProc)timeOutProc,
859 &timeOut);
860 currentWaitForAtom = serverRequestAtom;
861
862 /* Wait for the property to be deleted to know the request was processed */
863 while (!timeOut) {
864 XEvent event;
865 const XPropertyEvent *e = (const XPropertyEvent *)&event;
866
867 XtAppNextEvent(context, &event);
868 if (e->window == rootWindow &&
869 e->atom == serverRequestAtom &&
870 e->state == PropertyDelete)
871 break;
872 XtDispatchEvent(&event);
873 }
874
875 /* Exit if the timeout expired. */
876 if (timeOut) {
877 fprintf(stderr, "%s: The server did not respond to the request.\n", APP_NAME);
878 XtCloseDisplay(TheDisplay);
879 exit(EXIT_FAILURE);
880 } else {
881 XtRemoveTimeOut(timerId);
882 }
883 }
884
waitUntilFilesOpenedOrClosed(XtAppContext context,Window rootWindow)885 static void waitUntilFilesOpenedOrClosed(XtAppContext context,
886 Window rootWindow)
887 {
888 XtIntervalId timerId;
889 Boolean timeOut = False;
890
891 /* Set up a timeout proc so we don't wait forever if the server is dead.
892 The standard selection timeout is probably a good guess at how
893 long to wait for this style of inter-client communication as
894 well */
895 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT,
896 (XtTimerCallbackProc)timeOutProc, &timeOut);
897 currentWaitForAtom = noAtom;
898
899 /* Wait for all of the windows to be opened by server,
900 * and closed if -wait was supplied */
901 while (fileListHead.fileList) {
902 XEvent event;
903 const XPropertyEvent *e = (const XPropertyEvent *)&event;
904
905 XtAppNextEvent(context, &event);
906
907 /* Update the fileList and check if all files have been closed. */
908 if (e->type == PropertyNotify && e->window == rootWindow) {
909 FileListEntry *item;
910
911 if (e->state == PropertyDelete) {
912 for (item = fileListHead.fileList; item; item = item->next) {
913 if (e->atom == item->waitForFileOpenAtom) {
914 /* The 'waitForFileOpen' property is deleted when the file is opened */
915 fileListHead.waitForOpenCount--;
916 item->waitForFileOpenAtom = None;
917
918 /* Reset the timer while we wait for all files to be opened. */
919 XtRemoveTimeOut(timerId);
920 timerId = XtAppAddTimeOut(context, FILE_OPEN_TIMEOUT,
921 (XtTimerCallbackProc)timeOutProc, &timeOut);
922 } else if (e->atom == item->waitForFileClosedAtom) {
923 /* When file is opened in -wait mode the property
924 * is deleted when the file is closed.
925 */
926 fileListHead.waitForCloseCount--;
927 item->waitForFileClosedAtom = None;
928 }
929 }
930
931 if (fileListHead.waitForOpenCount == 0 && !timeOut) {
932 XtRemoveTimeOut(timerId);
933 }
934
935 if (fileListHead.waitForOpenCount == 0 &&
936 fileListHead.waitForCloseCount == 0) {
937 break;
938 }
939 }
940 }
941
942 /* We are finished if we are only waiting for files to open and
943 ** the file open timeout has expired. */
944 if (!Preferences.waitForClose && timeOut) {
945 break;
946 }
947
948 XtDispatchEvent(&event);
949 }
950 }
951
952
nextArg(int argc,char ** argv,int * argIndex)953 static void nextArg(int argc, char **argv, int *argIndex)
954 {
955 if (*argIndex + 1 >= argc) {
956 #ifdef VMS
957 *argv[*argIndex] = '/';
958 #endif /*VMS*/
959 fprintf(stderr, "nc: %s requires an argument\n%s",
960 argv[*argIndex], cmdLineHelp);
961 exit(EXIT_FAILURE);
962 }
963 (*argIndex)++;
964 }
965
966 /* Copies a given nc command line argument to the server startup command
967 ** line (-icon, -geometry, -xrm, ...) Special characters are protected from
968 ** the shell by escaping EVERYTHING with \
969 ** Note that the .shell string in the command line structure is large enough
970 ** to hold the escaped characters.
971 */
copyCommandLineArg(CommandLine * commandLine,const char * arg)972 static void copyCommandLineArg(CommandLine *commandLine, const char *arg)
973 {
974 const char *c;
975 char *outPtr = commandLine->shell + strlen(commandLine->shell);
976 #if defined(VMS) || defined(__EMX__)
977 /* Non-Unix shells don't want/need esc */
978 for (c=arg; *c!='\0'; c++) {
979 *outPtr++ = *c;
980 }
981 *outPtr++ = ' ';
982 *outPtr = '\0';
983 #else
984 *outPtr++ = '\'';
985 for (c=arg; *c!='\0'; c++) {
986 if (*c == '\'') {
987 *outPtr++ = '\'';
988 *outPtr++ = '\\';
989 }
990 *outPtr++ = *c;
991 if (*c == '\'') {
992 *outPtr++ = '\'';
993 }
994 }
995 *outPtr++ = '\'';
996 *outPtr++ = ' ';
997 *outPtr = '\0';
998 #endif /* VMS */
999 }
1000
1001 /* Print version of 'nc' */
printNcVersion(void)1002 static void printNcVersion(void ) {
1003 static const char *const ncHelpText = \
1004 "nc (NEdit) Version 5.7 (January 2017)\n\n\
1005 Built on: %s, %s, %s\n\
1006 Built at: %s, %s\n";
1007
1008 fprintf(stdout, ncHelpText,
1009 COMPILE_OS, COMPILE_MACHINE, COMPILE_COMPILER,
1010 __DATE__, __TIME__);
1011 }
1012