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