1 /*******************************************************************************
2 *									       *
3 * file.c -- Nirvana Editor file i/o					       *
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 versions 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 * May 10, 1991								       *
24 *									       *
25 * Written by Mark Edel							       *
26 *									       *
27 *******************************************************************************/
28 
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
32 
33 #include "file.h"
34 #include "textBuf.h"
35 #include "text.h"
36 #include "window.h"
37 #include "preferences.h"
38 #include "undo.h"
39 #include "menu.h"
40 #include "tags.h"
41 #include "server.h"
42 #include "interpret.h"
43 #include "../util/misc.h"
44 #include "../util/DialogF.h"
45 #include "../util/fileUtils.h"
46 #include "../util/getfiles.h"
47 #include "../util/printUtils.h"
48 #include "../util/utils.h"
49 #include "../util/nedit_malloc.h"
50 
51 #include <errno.h>
52 #include <limits.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 
58 #ifdef VMS
59 #include "../util/VMSparam.h"
60 #include <types.h>
61 #include <stat.h>
62 #include <unixio.h>
63 #else
64 #include <sys/types.h>
65 #include <sys/stat.h>
66 #ifndef __MVS__
67 #include <sys/param.h>
68 #endif
69 #include <fcntl.h>
70 #endif /*VMS*/
71 
72 #include <Xm/Xm.h>
73 #include <Xm/ToggleB.h>
74 #include <Xm/FileSB.h>
75 #include <Xm/RowColumn.h>
76 #include <Xm/Form.h>
77 #include <Xm/Label.h>
78 
79 #ifdef HAVE_DEBUG_H
80 #include "../debug.h"
81 #endif
82 
83 #include <inttypes.h>
84 
85 /* Maximum frequency in miliseconds of checking for external modifications.
86    The periodic check is only performed on buffer modification, and the check
87    interval is only to prevent checking on every keystroke in case of a file
88    system which is slow to process stat requests (which I'm not sure exists) */
89 #define MOD_CHECK_INTERVAL 3000
90 
91 static int doSave(WindowInfo *window);
92 static void safeClose(WindowInfo *window);
93 static int doOpen(WindowInfo *window, const char *name, const char *path,
94      int flags);
95 static void backupFileName(WindowInfo *window, char *name, size_t len);
96 static int writeBckVersion(WindowInfo *window);
97 static int bckError(WindowInfo *window, const char *errString, const char *file);
98 static int fileWasModifiedExternally(WindowInfo *window);
99 static const char *errorString(void);
100 static void addWrapNewlines(WindowInfo *window);
101 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData);
102 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData);
103 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName);
104 static int min(int i1, int i2);
105 static void modifiedWindowDestroyedCB(Widget w, XtPointer clientData,
106     XtPointer callData);
107 static void forceShowLineNumbers(WindowInfo *window);
108 
109 #ifdef VMS
110 void removeVersionNumber(char *fileName);
111 #endif /*VMS*/
112 
EditNewFile(WindowInfo * inWindow,char * geometry,int iconic,const char * languageMode,const char * defaultPath)113 WindowInfo *EditNewFile(WindowInfo *inWindow, char *geometry, int iconic,
114         const char *languageMode, const char *defaultPath)
115 {
116     char name[MAXPATHLEN];
117     WindowInfo *window;
118     size_t pathlen;
119     char *path;
120 
121     /*... test for creatability? */
122 
123     /* Find a (relatively) unique name for the new file */
124     UniqueUntitledName(name);
125 
126     /* create new window/document */
127     if (inWindow)
128 	window = CreateDocument(inWindow, name);
129     else
130 	window = CreateWindow(name, geometry, iconic);
131 
132     path = window->path;
133     strcpy(window->filename, name);
134     strcpy(path, (defaultPath && *defaultPath) ? defaultPath : GetCurrentDir());
135     pathlen = strlen(window->path);
136 #ifndef VMS
137     /* do we have a "/" at the end? if not, add one */
138     if (0 < pathlen && path[pathlen - 1] != '/' && pathlen < MAXPATHLEN - 1) {
139         strcpy(&path[pathlen], "/");
140     }
141 #else /* VMS */
142     /* A logical name should be followed by a colon so that the filename can
143        be added to it to make a full file specification; otherwise a directory
144        path had the form
145          device_or_logicalname:[dir.dir.dir]
146        this requires no separator before the file name.
147      */
148     if (0 < pathlen && strchr(":]", path[pathlen - 1]) == NULL) {
149         strcpy(&path[pathlen], ":"); /* could not find a separator at end */
150     }
151     /* TODO: is this enough for VMS? what of posix emulation? */
152     /* TODO: what about other platforms? */
153 #endif /* VMS */
154     SetWindowModified(window, FALSE);
155     CLEAR_ALL_LOCKS(window->lockReasons);
156     UpdateWindowReadOnly(window);
157     UpdateStatsLine(window);
158     UpdateWindowTitle(window);
159     RefreshTabState(window);
160 
161     if (languageMode == NULL)
162     	DetermineLanguageMode(window, True);
163     else
164 	SetLanguageMode(window, FindLanguageMode(languageMode), True);
165 
166     ShowTabBar(window, GetShowTabBar(window));
167 
168     if (iconic && IsIconic(window))
169         RaiseDocument(window);
170     else
171         RaiseDocumentWindow(window);
172 
173     SortTabBar(window);
174     return window;
175 }
176 
177 /*
178 ** Open an existing file specified by name and path.  Use the window inWindow
179 ** unless inWindow is NULL or points to a window which is already in use
180 ** (displays a file other than Untitled, or is Untitled but modified).  Flags
181 ** can be any of:
182 **
183 **	CREATE: 		If file is not found, (optionally) prompt the
184 **				user whether to create
185 **	SUPPRESS_CREATE_WARN	When creating a file, don't ask the user
186 **	PREF_READ_ONLY		Make the file read-only regardless
187 **
188 ** If languageMode is passed as NULL, it will be determined automatically
189 ** from the file extension or file contents.
190 **
191 ** If bgOpen is True, then the file will be open in background. This
192 ** works in association with the SetLanguageMode() function that has
193 ** the syntax highlighting deferred, in order to speed up the file-
194 ** opening operation when multiple files are being opened in succession.
195 */
EditExistingFile(WindowInfo * inWindow,const char * name,const char * path,int flags,char * geometry,int iconic,const char * languageMode,int tabbed,int bgOpen)196 WindowInfo *EditExistingFile(WindowInfo *inWindow, const char *name,
197         const char *path, int flags, char *geometry, int iconic,
198 	const char *languageMode, int tabbed, int bgOpen)
199 {
200     WindowInfo *window;
201     char fullname[MAXPATHLEN];
202 
203     /* first look to see if file is already displayed in a window */
204     window = FindWindowWithFile(name, path);
205     if (window != NULL) {
206     	if (!bgOpen) {
207 	    if (iconic)
208 		RaiseDocument(window);
209 	    else
210 		RaiseDocumentWindow(window);
211     	}
212 	return window;
213     }
214 
215     /* If an existing window isn't specified; or the window is already
216        in use (not Untitled or Untitled and modified), or is currently
217        busy running a macro; create the window */
218     if (inWindow == NULL) {
219 	window = CreateWindow(name, geometry, iconic);
220     }
221     else if (inWindow->filenameSet || inWindow->fileChanged ||
222 	    inWindow->macroCmdData != NULL) {
223 	if (tabbed) {
224 	    window = CreateDocument(inWindow, name);
225     	}
226 	else {
227 	    window = CreateWindow(name, geometry, iconic);
228 	}
229     }
230     else {
231     	/* open file in untitled document */
232     	window = inWindow;
233     	strcpy(window->path, path);
234     	strcpy(window->filename, name);
235         if (!iconic && !bgOpen) {
236             RaiseDocumentWindow(window);
237         }
238     }
239 
240     /* Open the file */
241     if (!doOpen(window, name, path, flags)) {
242 	/* The user may have destroyed the window instead of closing the
243 	   warning dialog; don't close it twice */
244 	safeClose(window);
245 
246     	return NULL;
247     }
248     forceShowLineNumbers(window);
249 
250     /* Decide what language mode to use, trigger language specific actions */
251     if (languageMode == NULL)
252     	DetermineLanguageMode(window, True);
253     else
254 	SetLanguageMode(window, FindLanguageMode(languageMode), True);
255 
256     /* update tab label and tooltip */
257     RefreshTabState(window);
258     SortTabBar(window);
259     ShowTabBar(window, GetShowTabBar(window));
260 
261     if (!bgOpen)
262         RaiseDocument(window);
263 
264     /* Bring the title bar and statistics line up to date, doOpen does
265        not necessarily set the window title or read-only status */
266     UpdateWindowTitle(window);
267     UpdateWindowReadOnly(window);
268     UpdateStatsLine(window);
269 
270     /* Add the name to the convenience menu of previously opened files */
271     strcpy(fullname, path);
272     strcat(fullname, name);
273     if(GetPrefAlwaysCheckRelTagsSpecs())
274       	AddRelTagsFile(GetPrefTagFile(), path, TAG);
275     AddToPrevOpenMenu(fullname);
276 
277     return window;
278 }
279 
RevertToSaved(WindowInfo * window)280 void RevertToSaved(WindowInfo *window)
281 {
282     char name[MAXPATHLEN], path[MAXPATHLEN];
283     int i;
284     int insertPositions[MAX_PANES], topLines[MAX_PANES];
285     int horizOffsets[MAX_PANES];
286     int openFlags = 0;
287     Widget text;
288 
289     /* Can't revert untitled windows */
290     if (!window->filenameSet)
291     {
292         DialogF(DF_WARN, window->shell, 1, "Error",
293                 "Window '%s' was never saved, can't re-read", "OK",
294                 window->filename);
295         return;
296     }
297 
298     /* save insert & scroll positions of all of the panes to restore later */
299     for (i=0; i<=window->nPanes; i++) {
300     	text = i==0 ? window->textArea : window->textPanes[i-1];
301     	insertPositions[i] = TextGetCursorPos(text);
302     	TextGetScroll(text, &topLines[i], &horizOffsets[i]);
303     }
304 
305     /* re-read the file, update the window title if new file is different */
306     strcpy(name, window->filename);
307     strcpy(path, window->path);
308     RemoveBackupFile(window);
309     ClearUndoList(window);
310     openFlags |= IS_USER_LOCKED(window->lockReasons) ? PREF_READ_ONLY : 0;
311     if (!doOpen(window, name, path, openFlags)) {
312 	/* This is a bit sketchy.  The only error in doOpen that irreperably
313             damages the window is "too much binary data".  It should be
314             pretty rare to be reverting something that was fine only to find
315             that now it has too much binary data. */
316         if (!window->fileMissing)
317 	    safeClose(window);
318         else {
319             /* Treat it like an externally modified file */
320             window->lastModTime=0;
321             window->fileMissing=FALSE;
322         }
323     	return;
324     }
325     forceShowLineNumbers(window);
326     UpdateWindowTitle(window);
327     UpdateWindowReadOnly(window);
328 
329     /* restore the insert and scroll positions of each pane */
330     for (i=0; i<=window->nPanes; i++) {
331     	text = i==0 ? window->textArea : window->textPanes[i-1];
332 	TextSetCursorPos(text, insertPositions[i]);
333 	TextSetScroll(text, topLines[i], horizOffsets[i]);
334     }
335 }
336 
337 /*
338 ** Checks whether a window is still alive, and closes it only if so.
339 ** Intended to be used when the file could not be opened for some reason.
340 ** Normally the window is still alive, but the user may have closed the
341 ** window instead of the error dialog. In that case, we shouldn't close the
342 ** window a second time.
343 */
safeClose(WindowInfo * window)344 static void safeClose(WindowInfo *window)
345 {
346     WindowInfo* p = WindowList;
347     while(p) {
348         if (p == window) {
349 	    CloseWindow(window);
350             return;
351         }
352         p = p->next;
353     }
354 }
355 
doOpen(WindowInfo * window,const char * name,const char * path,int flags)356 static int doOpen(WindowInfo *window, const char *name, const char *path,
357      int flags)
358 {
359     char fullname[MAXPATHLEN];
360     struct stat statbuf;
361     int fileLen, readLen;
362     char *fileString, *c;
363     FILE *fp = NULL;
364     int fd;
365     int resp;
366 
367     /* initialize lock reasons */
368     CLEAR_ALL_LOCKS(window->lockReasons);
369 
370     /* Update the window data structure */
371     strcpy(window->filename, name);
372     strcpy(window->path, path);
373     window->filenameSet = TRUE;
374     window->fileMissing = TRUE;
375 
376    /* Get the full name of the file */
377     strcpy(fullname, path);
378     strcat(fullname, name);
379 
380     /* Open the file */
381 #ifndef DONT_USE_ACCESS
382                    /* The only advantage of this is if you use clearcase,
383     	    	      which messes up the mtime of files opened with r+,
384 		      even if they're never actually written.
385 		      To avoid requiring special builds for clearcase users,
386 		      this is now the default. */
387     {
388 	if ((fp = fopen(fullname, "r")) != NULL) {
389     	    if(access(fullname, W_OK) != 0)
390                 SET_PERM_LOCKED(window->lockReasons, TRUE);
391 #else
392     fp = fopen(fullname, "rb+");
393     if (fp == NULL) {
394     	/* Error opening file or file is not writeable */
395 	fp = fopen(fullname, "rb");
396 	if (fp != NULL) {
397 	    /* File is read only */
398             SET_PERM_LOCKED(window->lockReasons, TRUE);
399 #endif
400 	} else if (flags & CREATE && errno == ENOENT) {
401 	    /* Give option to create (or to exit if this is the only window) */
402 	    if (!(flags & SUPPRESS_CREATE_WARN)) {
403                 /* on Solaris 2.6, and possibly other OSes, dialog won't
404 		   show if parent window is iconized. */
405                 RaiseShellWindow(window->shell, False);
406 
407                 /* ask user for next action if file not found */
408                 if (WindowList == window && window->next == NULL) {
409                     resp = DialogF(DF_WARN, window->shell, 3, "New File",
410                             "Can't open %s:\n%s", "New File", "Cancel",
411                             "Exit NEdit", fullname, errorString());
412                 }
413 	        else {
414                     resp = DialogF(DF_WARN, window->shell, 2, "New File",
415                             "Can't open %s:\n%s", "New File", "Cancel", fullname,
416                             errorString());
417                 }
418 
419                 if (resp == 2) {
420                     return FALSE;
421                 }
422                 else if (resp == 3) {
423                     exit(EXIT_SUCCESS);
424                 }
425             }
426 
427             /* Test if new file can be created */
428             if ((fd = creat(fullname, 0666)) == -1) {
429                 DialogF(DF_ERR, window->shell, 1, "Error creating File",
430                         "Can't create %s:\n%s", "OK", fullname, errorString());
431                 return FALSE;
432             }
433 	    else {
434 #ifdef VMS
435                 /* get correct version number and close before removing */
436                 getname(fd, fullname);
437 #endif
438                 close(fd);
439                 remove(fullname);
440             }
441 
442 	    SetWindowModified(window, FALSE);
443             if ((flags & PREF_READ_ONLY) != 0) {
444                 SET_USER_LOCKED(window->lockReasons, TRUE);
445             }
446 	    UpdateWindowReadOnly(window);
447 	    return TRUE;
448         }
449 	else {
450             /* A true error */
451             DialogF(DF_ERR, window->shell, 1, "Error opening File",
452                     "Could not open %s%s:\n%s", "OK", path, name,
453                     errorString());
454             return FALSE;
455         }
456     }
457 
458     /* Get the length of the file, the protection mode, and the time of the
459        last modification to the file */
460     if (fstat(fileno(fp), &statbuf) != 0) {
461         fclose(fp);
462         window->filenameSet = FALSE; /* Temp. prevent check for changes. */
463         DialogF(DF_ERR, window->shell, 1, "Error opening File",
464                 "Error opening %s", "OK", name);
465         window->filenameSet = TRUE;
466         return FALSE;
467     }
468 
469     if (S_ISDIR(statbuf.st_mode)) {
470         fclose(fp);
471         window->filenameSet = FALSE; /* Temp. prevent check for changes. */
472         DialogF(DF_ERR, window->shell, 1, "Error opening File",
473                 "Can't open directory %s", "OK", name);
474         window->filenameSet = TRUE;
475         return FALSE;
476     }
477 
478 #ifdef S_ISBLK
479     if (S_ISBLK(statbuf.st_mode)) {
480         fclose(fp);
481         window->filenameSet = FALSE; /* Temp. prevent check for changes. */
482         DialogF(DF_ERR, window->shell, 1, "Error opening File",
483                 "Can't open block device %s", "OK", name);
484         window->filenameSet = TRUE;
485         return FALSE;
486     }
487 #endif
488     fileLen = statbuf.st_size;
489 
490     /* Allocate space for the whole contents of the file (unfortunately) */
491     fileString = (char *)NEditMalloc(fileLen+1);  /* +1 = space for null */
492     if (fileString == NULL) {
493         fclose(fp);
494         window->filenameSet = FALSE; /* Temp. prevent check for changes. */
495         DialogF(DF_ERR, window->shell, 1, "Error while opening File",
496                 "File is too large to edit", "OK");
497         window->filenameSet = TRUE;
498         return FALSE;
499     }
500 
501     /* Read the file into fileString and terminate with a null */
502     readLen = fread(fileString, sizeof(char), fileLen, fp);
503     if (ferror(fp)) {
504         fclose(fp);
505         window->filenameSet = FALSE; /* Temp. prevent check for changes. */
506         DialogF(DF_ERR, window->shell, 1, "Error while opening File",
507                 "Error reading %s:\n%s", "OK", name, errorString());
508         window->filenameSet = TRUE;
509         NEditFree(fileString);
510         return FALSE;
511     }
512     fileString[readLen] = 0;
513 
514     /* Close the file */
515     if (fclose(fp) != 0) {
516         /* unlikely error */
517         DialogF(DF_WARN, window->shell, 1, "Error while opening File",
518                 "Unable to close file", "OK");
519         /* we read it successfully, so continue */
520     }
521 
522     /* Any errors that happen after this point leave the window in a
523         "broken" state, and thus RevertToSaved will abandon the window if
524         window->fileMissing is FALSE and doOpen fails. */
525     window->fileMode = statbuf.st_mode;
526     window->fileUid = statbuf.st_uid;
527     window->fileGid = statbuf.st_gid;
528     window->lastModTime = statbuf.st_mtime;
529     window->device = statbuf.st_dev;
530     window->inode = statbuf.st_ino;
531     window->fileMissing = FALSE;
532 
533     /* Detect and convert DOS and Macintosh format files */
534     if (GetPrefForceOSConversion()) {
535         window->fileFormat = FormatOfFile(fileString);
536         if (window->fileFormat == DOS_FILE_FORMAT) {
537             ConvertFromDosFileString(fileString, &readLen, NULL);
538         } else if (window->fileFormat == MAC_FILE_FORMAT) {
539             ConvertFromMacFileString(fileString, readLen);
540         }
541     }
542 
543     /* Display the file contents in the text widget */
544     window->ignoreModify = True;
545     BufSetAll(window->buffer, fileString);
546     window->ignoreModify = False;
547 
548     /* Check that the length that the buffer thinks it has is the same
549        as what we gave it.  If not, there were probably nuls in the file.
550        Substitute them with another character.  If that is impossible, warn
551        the user, make the file read-only, and force a substitution */
552     if (window->buffer->length != readLen) {
553         if (!BufSubstituteNullChars(fileString, readLen, window->buffer)) {
554             resp = DialogF(DF_ERR, window->shell, 2, "Error while opening File",
555                     "Too much binary data in file.  You may view\n"
556                     "it, but not modify or re-save its contents.", "View",
557                     "Cancel");
558             if (resp == 2) {
559                 return FALSE;
560             }
561 
562             SET_TMBD_LOCKED(window->lockReasons, TRUE);
563             for (c = fileString; c < &fileString[readLen]; c++) {
564                 if (*c == '\0') {
565                     *c = (char) 0xfe;
566                 }
567             }
568             window->buffer->nullSubsChar = (char) 0xfe;
569         }
570         window->ignoreModify = True;
571         BufSetAll(window->buffer, fileString);
572         window->ignoreModify = False;
573     }
574 
575     /* Release the memory that holds fileString */
576     NEditFree(fileString);
577 
578     /* Set window title and file changed flag */
579     if ((flags & PREF_READ_ONLY) != 0) {
580         SET_USER_LOCKED(window->lockReasons, TRUE);
581     }
582     if (IS_PERM_LOCKED(window->lockReasons)) {
583 	window->fileChanged = FALSE;
584 	UpdateWindowTitle(window);
585     } else {
586 	SetWindowModified(window, FALSE);
587 	if (IS_ANY_LOCKED(window->lockReasons)) {
588 	    UpdateWindowTitle(window);
589         }
590     }
591     UpdateWindowReadOnly(window);
592 
593     return TRUE;
594 }
595 
596 int IncludeFile(WindowInfo *window, const char *name)
597 {
598     struct stat statbuf;
599     int fileLen, readLen;
600     char *fileString;
601     FILE *fp = NULL;
602 
603     /* Open the file */
604     fp = fopen(name, "rb");
605     if (fp == NULL)
606     {
607         DialogF(DF_ERR, window->shell, 1, "Error opening File",
608                 "Could not open %s:\n%s", "OK", name, errorString());
609         return FALSE;
610     }
611 
612     /* Get the length of the file */
613     if (fstat(fileno(fp), &statbuf) != 0)
614     {
615         DialogF(DF_ERR, window->shell, 1, "Error opening File",
616                 "Error opening %s", "OK", name);
617         fclose(fp);
618         return FALSE;
619     }
620 
621     if (S_ISDIR(statbuf.st_mode))
622     {
623         DialogF(DF_ERR, window->shell, 1, "Error opening File",
624                 "Can't open directory %s", "OK", name);
625         fclose(fp);
626         return FALSE;
627     }
628     fileLen = statbuf.st_size;
629 
630     /* allocate space for the whole contents of the file */
631     fileString = (char *)NEditMalloc(fileLen+1);  /* +1 = space for null */
632     if (fileString == NULL)
633     {
634         DialogF(DF_ERR, window->shell, 1, "Error opening File",
635                 "File is too large to include", "OK");
636         fclose(fp);
637         return FALSE;
638     }
639 
640     /* read the file into fileString and terminate with a null */
641     readLen = fread(fileString, sizeof(char), fileLen, fp);
642     if (ferror(fp))
643     {
644         DialogF(DF_ERR, window->shell, 1, "Error opening File",
645                 "Error reading %s:\n%s", "OK", name, errorString());
646         fclose(fp);
647         NEditFree(fileString);
648         return FALSE;
649     }
650     fileString[readLen] = 0;
651 
652     /* Detect and convert DOS and Macintosh format files */
653     switch (FormatOfFile(fileString)) {
654         case DOS_FILE_FORMAT:
655             ConvertFromDosFileString(fileString, &readLen, NULL);
656             break;
657         case MAC_FILE_FORMAT:
658             ConvertFromMacFileString(fileString, readLen);
659             break;
660         default:
661             /*  Default is Unix, no conversion necessary.  */
662             break;
663     }
664 
665     /* If the file contained ascii nulls, re-map them */
666     if (!BufSubstituteNullChars(fileString, readLen, window->buffer))
667     {
668         DialogF(DF_ERR, window->shell, 1, "Error opening File",
669                 "Too much binary data in file", "OK");
670     }
671 
672     /* close the file */
673     if (fclose(fp) != 0)
674     {
675         /* unlikely error */
676         DialogF(DF_WARN, window->shell, 1, "Error opening File",
677                 "Unable to close file", "OK");
678         /* we read it successfully, so continue */
679     }
680 
681     /* insert the contents of the file in the selection or at the insert
682        position in the window if no selection exists */
683     if (window->buffer->primary.selected)
684     	BufReplaceSelected(window->buffer, fileString);
685     else
686     	BufInsert(window->buffer, TextGetCursorPos(window->lastFocus),
687     		fileString);
688 
689     /* release the memory that holds fileString */
690     NEditFree(fileString);
691 
692     return TRUE;
693 }
694 
695 /*
696 ** Close all files and windows, leaving one untitled window
697 */
698 int CloseAllFilesAndWindows(void)
699 {
700     while (WindowList->next != NULL ||
701     		WindowList->filenameSet || WindowList->fileChanged) {
702         /*
703          * When we're exiting through a macro, the document running the
704          * macro does not disappear from the list, so we could get stuck
705          * in an endless loop if we try to close it. Therefore, we close
706          * other documents first. (Note that the document running the macro
707          * may get closed because it is in the same window as another
708          * document that gets closed, but it won't disappear; it becomes
709          * Untitled.)
710          */
711         if (WindowList == MacroRunWindow() && WindowList->next != NULL) {
712             if (!CloseAllDocumentInWindow(WindowList->next)) {
713                 return False;
714             }
715         }
716         else {
717             if (!CloseAllDocumentInWindow(WindowList)) {
718                 return False;
719             }
720         }
721     }
722 
723     return TRUE;
724 }
725 
726 int CloseFileAndWindow(WindowInfo *window, int preResponse)
727 {
728     int response, stat;
729 
730     /* Make sure that the window is not in iconified state */
731     if (window->fileChanged)
732     	RaiseDocumentWindow(window);
733 
734     /* If the window is a normal & unmodified file or an empty new file,
735        or if the user wants to ignore external modifications then
736        just close it.  Otherwise ask for confirmation first. */
737     if (!window->fileChanged &&
738             /* Normal File */
739             ((!window->fileMissing && window->lastModTime > 0) ||
740             /* New File*/
741              (window->fileMissing && window->lastModTime == 0) ||
742             /* File deleted/modified externally, ignored by user. */
743             !GetPrefWarnFileMods()))
744     {
745         CloseWindow(window);
746         /* up-to-date windows don't have outstanding backup files to close */
747     } else
748     {
749         if (preResponse == PROMPT_SBC_DIALOG_RESPONSE)
750         {
751             response = DialogF(DF_WARN, window->shell, 3, "Save File",
752             "Save %s before closing?", "Yes", "No", "Cancel", window->filename);
753         } else
754         {
755             response = preResponse;
756         }
757 
758         if (response == YES_SBC_DIALOG_RESPONSE)
759         {
760             /* Save */
761             stat = SaveWindow(window);
762             if (stat)
763             {
764                 CloseWindow(window);
765             } else
766             {
767                 return FALSE;
768             }
769         } else if (response == NO_SBC_DIALOG_RESPONSE)
770         {
771             /* Don't Save */
772             RemoveBackupFile(window);
773             CloseWindow(window);
774         } else /* 3 == Cancel */
775         {
776             return FALSE;
777         }
778     }
779     return TRUE;
780 }
781 
782 int SaveWindow(WindowInfo *window)
783 {
784     int stat;
785 
786     /* Try to ensure our information is up-to-date */
787     CheckForChangesToFile(window);
788 
789     /* Return success if the file is normal & unchanged or is a
790         read-only file. */
791     if ( (!window->fileChanged && !window->fileMissing &&
792             window->lastModTime > 0) ||
793             IS_ANY_LOCKED_IGNORING_PERM(window->lockReasons))
794     	return TRUE;
795     /* Prompt for a filename if this is an Untitled window */
796     if (!window->filenameSet)
797     	return SaveWindowAs(window, NULL, False);
798 
799     /* Check for external modifications and warn the user */
800     if (GetPrefWarnFileMods() && fileWasModifiedExternally(window))
801     {
802         stat = DialogF(DF_WARN, window->shell, 2, "Save File",
803         "%s has been modified by another program.\n\n"
804         "Continuing this operation will overwrite any external\n"
805         "modifications to the file since it was opened in NEdit,\n"
806         "and your work or someone else's may potentially be lost.\n\n"
807         "To preserve the modified file, cancel this operation and\n"
808         "use Save As... to save this file under a different name,\n"
809         "or Revert to Saved to revert to the modified version.",
810         "Continue", "Cancel", window->filename);
811         if (stat == 2)
812         {
813             /* Cancel and mark file as externally modified */
814             window->lastModTime = 0;
815             window->fileMissing = FALSE;
816             return FALSE;
817         }
818     }
819 
820 #ifdef VMS
821     RemoveBackupFile(window);
822     stat = doSave(window);
823 #else
824     if (writeBckVersion(window))
825     	return FALSE;
826     stat = doSave(window);
827     if (stat)
828         RemoveBackupFile(window);
829 #endif /*VMS*/
830     return stat;
831 }
832 
833 int SaveWindowAs(WindowInfo *window, const char *newName, int addWrap)
834 {
835     int response, retVal, fileFormat;
836     char fullname[MAXPATHLEN], filename[MAXPATHLEN], pathname[MAXPATHLEN];
837     WindowInfo *otherWindow;
838 
839     /* Get the new name for the file */
840     if (newName == NULL) {
841 	response = PromptForNewFile(window, "Save File As", fullname,
842 		&fileFormat, &addWrap);
843 	if (response != GFN_OK)
844     	    return FALSE;
845 	window->fileFormat = fileFormat;
846     } else
847     {
848         strcpy(fullname, newName);
849     }
850 
851     if (1 == NormalizePathname(fullname))
852     {
853         return False;
854     }
855 
856     /* Add newlines if requested */
857     if (addWrap)
858     	addWrapNewlines(window);
859 
860     if (ParseFilename(fullname, filename, pathname) != 0) {
861        return FALSE;
862     }
863 
864     /* If the requested file is this file, just save it and return */
865     if (!strcmp(window->filename, filename) &&
866     	    !strcmp(window->path, pathname)) {
867 	if (writeBckVersion(window))
868     	    return FALSE;
869 	return doSave(window);
870     }
871 
872     /* If the file is open in another window, make user close it.  Note that
873        it is possible for user to close the window by hand while the dialog
874        is still up, because the dialog is not application modal, so after
875        doing the dialog, check again whether the window still exists. */
876     otherWindow = FindWindowWithFile(filename, pathname);
877     if (otherWindow != NULL)
878     {
879         response = DialogF(DF_WARN, window->shell, 2, "File open",
880         "%s is open in another NEdit window", "Cancel",
881         "Close Other Window", filename);
882 
883         if (response == 1)
884         {
885             return FALSE;
886         }
887         if (otherWindow == FindWindowWithFile(filename, pathname))
888         {
889             if (!CloseFileAndWindow(otherWindow, PROMPT_SBC_DIALOG_RESPONSE))
890             {
891                 return FALSE;
892             }
893         }
894     }
895 
896     /* Destroy the file closed property for the original file */
897     DeleteFileClosedProperty(window);
898 
899     /* Change the name of the file and save it under the new name */
900     RemoveBackupFile(window);
901     strcpy(window->filename, filename);
902     strcpy(window->path, pathname);
903     window->fileMode = 0;
904     window->fileUid = 0;
905     window->fileGid = 0;
906     CLEAR_ALL_LOCKS(window->lockReasons);
907     retVal = doSave(window);
908     UpdateWindowReadOnly(window);
909     RefreshTabState(window);
910 
911     /* Add the name to the convenience menu of previously opened files */
912     AddToPrevOpenMenu(fullname);
913 
914     /*  If name has changed, language mode may have changed as well, unless
915         it's an Untitled window for which the user already set a language
916         mode; it's probably the right one.  */
917     if (PLAIN_LANGUAGE_MODE == window->languageMode || window->filenameSet) {
918         DetermineLanguageMode(window, False);
919     }
920     window->filenameSet = True;
921 
922     /* Update the stats line and window title with the new filename */
923     UpdateWindowTitle(window);
924     UpdateStatsLine(window);
925 
926     SortTabBar(window);
927     return retVal;
928 }
929 
930 static int doSave(WindowInfo *window)
931 {
932     char *fileString = NULL;
933     char fullname[MAXPATHLEN];
934     struct stat statbuf;
935     FILE *fp;
936     int fileLen, result;
937 
938     /* Get the full name of the file */
939     strcpy(fullname, window->path);
940     strcat(fullname, window->filename);
941 
942     /*  Check for root and warn him if he wants to write to a file with
943         none of the write bits set.  */
944     if ((0 == getuid())
945             && (0 == stat(fullname, &statbuf))
946             && !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)))
947     {
948         result = DialogF(DF_WARN, window->shell, 2, "Writing Read-only File",
949                 "File '%s' is marked as read-only.\n"
950                 "Do you want to save anyway?",
951                 "Save", "Cancel", window->filename);
952         if (1 != result)
953         {
954             return True;
955         }
956     }
957 
958 #ifdef VMS
959     /* strip the version number from the file so VMS will begin a new one */
960     removeVersionNumber(fullname);
961 #endif
962 
963     /* add a terminating newline if the file doesn't already have one for
964        Unix utilities which get confused otherwise
965        NOTE: this must be done _before_ we create/open the file, because the
966              (potential) buffer modification can trigger a check for file
967              changes. If the file is created for the first time, it has
968              zero size on disk, and the check would falsely conclude that the
969              file has changed on disk, and would pop up a warning dialog */
970     if (BufGetCharacter(window->buffer, window->buffer->length - 1) != '\n'
971             && window->buffer->length != 0
972             && GetPrefAppendLF())
973     {
974         BufInsert(window->buffer, window->buffer->length, "\n");
975     }
976 
977     /* open the file */
978 #ifdef VMS
979     fp = fopen(fullname, "w", "rfm = stmlf");
980 #else
981     fp = fopen(fullname, "wb");
982 #endif /* VMS */
983     if (fp == NULL)
984     {
985         result = DialogF(DF_WARN, window->shell, 2, "Error saving File",
986                 "Unable to save %s:\n%s\n\nSave as a new file?",
987                 "Save As...", "Cancel",
988         window->filename, errorString());
989 
990         if (result == 1)
991         {
992             return SaveWindowAs(window, NULL, 0);
993         }
994         return FALSE;
995     }
996 
997 #ifdef VMS
998     /* get the complete name of the file including the new version number */
999     fgetname(fp, fullname);
1000 #endif
1001 
1002     /* get the text buffer contents and its length */
1003     fileString = BufGetAll(window->buffer);
1004     fileLen = window->buffer->length;
1005 
1006     /* If null characters are substituted for, put them back */
1007     BufUnsubstituteNullChars(fileString, window->buffer);
1008 
1009     /* If the file is to be saved in DOS or Macintosh format, reconvert */
1010     if (window->fileFormat == DOS_FILE_FORMAT)
1011     {
1012         if (!ConvertToDosFileString(&fileString, &fileLen))
1013         {
1014             DialogF(DF_ERR, window->shell, 1, "Out of Memory",
1015                     "Out of memory!  Try\nsaving in Unix format", "OK");
1016             return FALSE;
1017         }
1018     } else if (window->fileFormat == MAC_FILE_FORMAT)
1019     {
1020         ConvertToMacFileString(fileString, fileLen);
1021     }
1022 
1023     /* write to the file */
1024 #ifdef IBM_FWRITE_BUG
1025     write(fileno(fp), fileString, fileLen);
1026 #else
1027     fwrite(fileString, sizeof(char), fileLen, fp);
1028 #endif
1029     if (ferror(fp))
1030     {
1031         DialogF(DF_ERR, window->shell, 1, "Error saving File",
1032                 "%s not saved:\n%s", "OK", window->filename, errorString());
1033         fclose(fp);
1034         remove(fullname);
1035         NEditFree(fileString);
1036         return FALSE;
1037     }
1038 
1039     /* close the file */
1040     if (fclose(fp) != 0)
1041     {
1042         DialogF(DF_ERR, window->shell, 1, "Error closing File",
1043                 "Error closing file:\n%s", "OK", errorString());
1044         NEditFree(fileString);
1045         return FALSE;
1046     }
1047 
1048     /* free the text buffer copy returned from XmTextGetString */
1049     NEditFree(fileString);
1050 
1051 #ifdef VMS
1052     /* reflect the fact that NEdit is now editing a new version of the file */
1053     ParseFilename(fullname, window->filename, window->path);
1054 #endif /*VMS*/
1055 
1056     /* success, file was written */
1057     SetWindowModified(window, FALSE);
1058 
1059     /* update the modification time */
1060     if (stat(fullname, &statbuf) == 0) {
1061 	window->lastModTime = statbuf.st_mtime;
1062         window->fileMissing = FALSE;
1063         window->device = statbuf.st_dev;
1064         window->inode = statbuf.st_ino;
1065     } else {
1066         /* This needs to produce an error message -- the file can't be
1067             accessed! */
1068 	window->lastModTime = 0;
1069         window->fileMissing = TRUE;
1070         window->device = 0;
1071         window->inode = 0;
1072     }
1073 
1074     return TRUE;
1075 }
1076 
1077 /*
1078 ** Create a backup file for the current window.  The name for the backup file
1079 ** is generated using the name and path stored in the window and adding a
1080 ** tilde (~) on UNIX and underscore (_) on VMS to the beginning of the name.
1081 */
1082 int WriteBackupFile(WindowInfo *window)
1083 {
1084     char *fileString = NULL;
1085     char name[MAXPATHLEN];
1086     FILE *fp;
1087     int fd, fileLen;
1088 
1089     /* Generate a name for the autoSave file */
1090     backupFileName(window, name, sizeof(name));
1091 
1092     /* remove the old backup file.
1093        Well, this might fail - we'll notice later however. */
1094     remove(name);
1095 
1096     /* open the file, set more restrictive permissions (using default
1097         permissions was somewhat of a security hole, because permissions were
1098         independent of those of the original file being edited */
1099 #ifdef VMS
1100     if ((fp = fopen(name, "w", "rfm = stmlf")) == NULL)
1101 #else
1102     if ((fd = open(name, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
1103             || (fp = fdopen(fd, "w")) == NULL)
1104 #endif /* VMS */
1105     {
1106         DialogF(DF_WARN, window->shell, 1, "Error writing Backup",
1107                 "Unable to save backup for %s:\n%s\n"
1108                 "Automatic backup is now off", "OK", window->filename,
1109                 errorString());
1110         window->autoSave = FALSE;
1111         SetToggleButtonState(window, window->autoSaveItem, FALSE, FALSE);
1112         return FALSE;
1113     }
1114 
1115     /* Set VMS permissions */
1116 #ifdef VMS
1117     chmod(name, S_IRUSR | S_IWUSR);
1118 #endif
1119 
1120     /* get the text buffer contents and its length */
1121     fileString = BufGetAll(window->buffer);
1122     fileLen = window->buffer->length;
1123 
1124     /* If null characters are substituted for, put them back */
1125     BufUnsubstituteNullChars(fileString, window->buffer);
1126 
1127     /* add a terminating newline if the file doesn't already have one */
1128     if (fileLen != 0 && fileString[fileLen-1] != '\n')
1129     	fileString[fileLen++] = '\n'; 	 /* null terminator no longer needed */
1130 
1131     /* write out the file */
1132 #ifdef IBM_FWRITE_BUG
1133     write(fileno(fp), fileString, fileLen);
1134 #else
1135     fwrite(fileString, sizeof(char), fileLen, fp);
1136 #endif
1137     if (ferror(fp))
1138     {
1139         DialogF(DF_ERR, window->shell, 1, "Error saving Backup",
1140                 "Error while saving backup for %s:\n%s\n"
1141                 "Automatic backup is now off", "OK", window->filename,
1142                 errorString());
1143         fclose(fp);
1144         remove(name);
1145         NEditFree(fileString);
1146         window->autoSave = FALSE;
1147         return FALSE;
1148     }
1149 
1150     /* close the backup file */
1151     if (fclose(fp) != 0) {
1152 	NEditFree(fileString);
1153 	return FALSE;
1154     }
1155 
1156     /* Free the text buffer copy returned from XmTextGetString */
1157     NEditFree(fileString);
1158 
1159     return TRUE;
1160 }
1161 
1162 /*
1163 ** Remove the backup file associated with this window
1164 */
1165 void RemoveBackupFile(WindowInfo *window)
1166 {
1167     char name[MAXPATHLEN];
1168 
1169     /* Don't delete backup files when backups aren't activated. */
1170     if (window->autoSave == FALSE)
1171         return;
1172 
1173     backupFileName(window, name, sizeof(name));
1174     remove(name);
1175 }
1176 
1177 /*
1178 ** Generate the name of the backup file for this window from the filename
1179 ** and path in the window data structure & write into name
1180 */
1181 static void backupFileName(WindowInfo *window, char *name, size_t len)
1182 {
1183     char bckname[MAXPATHLEN];
1184 #ifdef VMS
1185     if (window->filenameSet)
1186     	sprintf(name, "%s_%s", window->path, window->filename);
1187     else
1188     	sprintf(name, "%s_%s", "SYS$LOGIN:", window->filename);
1189 #else
1190     if (window->filenameSet)
1191     {
1192         sprintf(name, "%s~%s", window->path, window->filename);
1193     } else
1194     {
1195         strcpy(bckname, "~");
1196         strncat(bckname, window->filename, MAXPATHLEN - 1);
1197         PrependHome(bckname, name, len);
1198     }
1199 #endif /*VMS*/
1200 }
1201 
1202 /*
1203 ** If saveOldVersion is on, copies the existing version of the file to
1204 ** <filename>.bck in anticipation of a new version being saved.  Returns
1205 ** True if backup fails and user requests that the new file not be written.
1206 */
1207 static int writeBckVersion(WindowInfo *window)
1208 {
1209 #ifndef VMS
1210     char fullname[MAXPATHLEN], bckname[MAXPATHLEN];
1211     struct stat statbuf;
1212     int in_fd, out_fd;
1213     char *io_buffer;
1214 #define IO_BUFFER_SIZE ((size_t)(1024*1024))
1215 
1216     /* Do only if version backups are turned on */
1217     if (!window->saveOldVersion) {
1218     	return False;
1219     }
1220 
1221     /* Get the full name of the file */
1222     strcpy(fullname, window->path);
1223     strcat(fullname, window->filename);
1224 
1225     /* Generate name for old version */
1226     if ((strlen(fullname) + 5) > (size_t) MAXPATHLEN) {
1227         return bckError(window, "file name too long", window->filename);
1228     }
1229     sprintf(bckname, "%s.bck", fullname);
1230 
1231     /* Delete the old backup file */
1232     /* Errors are ignored; we'll notice them later. */
1233     remove(bckname);
1234 
1235     /* open the file being edited.  If there are problems with the
1236        old file, don't bother the user, just skip the backup */
1237     in_fd = open(fullname, O_RDONLY);
1238     if (in_fd<0) {
1239     	return FALSE;
1240     }
1241 
1242     /* Get permissions of the file.
1243        We preserve the normal permissions but not ownership, extended
1244        attributes, et cetera. */
1245     if (fstat(in_fd, &statbuf) != 0) {
1246         close(in_fd);
1247 	return FALSE;
1248     }
1249 
1250     /* open the destination file exclusive and with restrictive permissions. */
1251     out_fd = open(bckname, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, S_IRUSR | S_IWUSR);
1252     if (out_fd < 0) {
1253         close(in_fd);
1254         return bckError(window, "Error open backup file", bckname);
1255     }
1256 
1257     /* Set permissions on new file */
1258     if (fchmod(out_fd, statbuf.st_mode) != 0) {
1259         close(in_fd);
1260         close(out_fd);
1261         remove(bckname);
1262         return bckError(window, "fchmod() failed", bckname);
1263     }
1264 
1265     /* Allocate I/O buffer */
1266     io_buffer = (char*) NEditMalloc(IO_BUFFER_SIZE);
1267     if (NULL == io_buffer) {
1268         close(in_fd);
1269         close(out_fd);
1270         remove(bckname);
1271 	return bckError(window, "out of memory", bckname);
1272     }
1273 
1274     /* copy loop */
1275     for(;;) {
1276         ssize_t bytes_read;
1277         ssize_t bytes_written;
1278         bytes_read = read(in_fd, io_buffer, IO_BUFFER_SIZE);
1279 
1280         if (bytes_read < 0) {
1281             close(in_fd);
1282             close(out_fd);
1283             remove(bckname);
1284             free(io_buffer);
1285             return bckError(window, "read() error", window->filename);
1286         }
1287 
1288         if (0 == bytes_read) {
1289             break; /* EOF */
1290         }
1291 
1292         /* write to the file */
1293         bytes_written = write(out_fd, io_buffer, (size_t) bytes_read);
1294         if (bytes_written != bytes_read) {
1295             close(in_fd);
1296             close(out_fd);
1297             remove(bckname);
1298             free(io_buffer);
1299             return bckError(window, errorString(), bckname);
1300         }
1301     }
1302 
1303     /* close the input and output files */
1304     close(in_fd);
1305     close(out_fd);
1306 
1307     free(io_buffer);
1308 
1309 #endif /* VMS */
1310 
1311     return FALSE;
1312 }
1313 
1314 /*
1315 ** Error processing for writeBckVersion, gives the user option to cancel
1316 ** the subsequent save, or continue and optionally turn off versioning
1317 */
1318 static int bckError(WindowInfo *window, const char *errString, const char *file)
1319 {
1320     int resp;
1321 
1322     resp = DialogF(DF_ERR, window->shell, 3, "Error writing Backup",
1323             "Couldn't write .bck (last version) file.\n%s: %s", "Cancel Save",
1324             "Turn off Backups", "Continue", file, errString);
1325     if (resp == 1)
1326     	return TRUE;
1327     if (resp == 2) {
1328     	window->saveOldVersion = FALSE;
1329 #ifndef VMS
1330     	SetToggleButtonState(window, window->saveLastItem, FALSE, FALSE);
1331 #endif
1332     }
1333     return FALSE;
1334 }
1335 
1336 void PrintWindow(WindowInfo *window, int selectedOnly)
1337 {
1338     textBuffer *buf = window->buffer;
1339     selection *sel = &buf->primary;
1340     char *fileString = NULL;
1341     int fileLen;
1342 
1343     /* get the contents of the text buffer from the text area widget.  Add
1344        wrapping newlines if necessary to make it match the displayed text */
1345     if (selectedOnly) {
1346     	if (!sel->selected) {
1347     	    XBell(TheDisplay, 0);
1348 	    return;
1349 	}
1350 	if (sel->rectangular) {
1351     	    fileString = BufGetSelectionText(buf);
1352     	    fileLen = strlen(fileString);
1353     	} else
1354     	    fileString = TextGetWrapped(window->textArea, sel->start, sel->end,
1355     	    	    &fileLen);
1356     } else
1357     	fileString = TextGetWrapped(window->textArea, 0, buf->length, &fileLen);
1358 
1359     /* If null characters are substituted for, put them back */
1360     BufUnsubstituteNullChars(fileString, buf);
1361 
1362         /* add a terminating newline if the file doesn't already have one */
1363     if (fileLen != 0 && fileString[fileLen-1] != '\n')
1364     	fileString[fileLen++] = '\n'; 	 /* null terminator no longer needed */
1365 
1366     /* Print the string */
1367     PrintString(fileString, fileLen, window->shell, window->filename);
1368 
1369     /* Free the text buffer copy returned from XmTextGetString */
1370     NEditFree(fileString);
1371 }
1372 
1373 /*
1374 ** Print a string (length is required).  parent is the dialog parent, for
1375 ** error dialogs, and jobName is the print title.
1376 */
1377 void PrintString(const char *string, int length, Widget parent, const char *jobName)
1378 {
1379     char tmpFileName[L_tmpnam];    /* L_tmpnam defined in stdio.h */
1380     FILE *fp;
1381     int fd;
1382 
1383     /* Generate a temporary file name */
1384     /*  If the glibc is used, the linker issues a warning at this point. This is
1385 	very thoughtful of him, but does not apply to NEdit. The recommended
1386 	replacement mkstemp(3) uses the same algorithm as NEdit, namely
1387 	    1. Create a filename
1388 	    2. Open the file with the O_CREAT|O_EXCL flags
1389 	So all an attacker can do is a DoS on the print function. */
1390     tmpnam(tmpFileName);
1391 
1392     /* open the temporary file */
1393 #ifdef VMS
1394     if ((fp = fopen(tmpFileName, "w", "rfm = stmlf")) == NULL)
1395 #else
1396     if ((fd = open(tmpFileName, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || (fp = fdopen(fd, "w")) == NULL)
1397 #endif /* VMS */
1398     {
1399         DialogF(DF_WARN, parent, 1, "Error while Printing",
1400                 "Unable to write file for printing:\n%s", "OK",
1401                 errorString());
1402         return;
1403     }
1404 
1405 #ifdef VMS
1406     chmod(tmpFileName, S_IRUSR | S_IWUSR);
1407 #endif
1408 
1409     /* write to the file */
1410 #ifdef IBM_FWRITE_BUG
1411     write(fileno(fp), string, length);
1412 #else
1413     fwrite(string, sizeof(char), length, fp);
1414 #endif
1415     if (ferror(fp))
1416     {
1417         DialogF(DF_ERR, parent, 1, "Error while Printing",
1418                 "%s not printed:\n%s", "OK", jobName, errorString());
1419         fclose(fp); /* should call close(fd) in turn! */
1420         remove(tmpFileName);
1421         return;
1422     }
1423 
1424     /* close the temporary file */
1425     if (fclose(fp) != 0)
1426     {
1427         DialogF(DF_ERR, parent, 1, "Error while Printing",
1428                 "Error closing temp. print file:\n%s", "OK",
1429                 errorString());
1430         remove(tmpFileName);
1431         return;
1432     }
1433 
1434     /* Print the temporary file, then delete it and return success */
1435 #ifdef VMS
1436     strcat(tmpFileName, ".");
1437     PrintFile(parent, tmpFileName, jobName, True);
1438 #else
1439     PrintFile(parent, tmpFileName, jobName);
1440     remove(tmpFileName);
1441 #endif /*VMS*/
1442     return;
1443 }
1444 
1445 /*
1446 ** Wrapper for GetExistingFilename which uses the current window's path
1447 ** (if set) as the default directory.
1448 */
1449 int PromptForExistingFile(WindowInfo *window, char *prompt, char *fullname)
1450 {
1451     char *savedDefaultDir;
1452     int retVal;
1453 
1454     /* Temporarily set default directory to window->path, prompt for file,
1455        then, if the call was unsuccessful, restore the original default
1456        directory */
1457     savedDefaultDir = GetFileDialogDefaultDirectory();
1458     if (*window->path != '\0')
1459     	SetFileDialogDefaultDirectory(window->path);
1460     retVal = GetExistingFilename(window->shell, prompt, fullname);
1461     if (retVal != GFN_OK)
1462     	SetFileDialogDefaultDirectory(savedDefaultDir);
1463 
1464     NEditFree(savedDefaultDir);
1465 
1466     return retVal;
1467 }
1468 
1469 /*
1470 ** Wrapper for HandleCustomNewFileSB which uses the current window's path
1471 ** (if set) as the default directory, and asks about embedding newlines
1472 ** to make wrapping permanent.
1473 */
1474 int PromptForNewFile(WindowInfo *window, char *prompt, char *fullname,
1475     	int *fileFormat, int *addWrap)
1476 {
1477     int n, retVal;
1478     Arg args[20];
1479     XmString s1, s2;
1480     Widget fileSB, wrapToggle;
1481     Widget formatForm, formatBtns, unixFormat, dosFormat, macFormat;
1482     char *savedDefaultDir;
1483 
1484     *fileFormat = window->fileFormat;
1485 
1486     /* Temporarily set default directory to window->path, prompt for file,
1487        then, if the call was unsuccessful, restore the original default
1488        directory */
1489     savedDefaultDir = GetFileDialogDefaultDirectory();
1490     if (*window->path != '\0')
1491     	SetFileDialogDefaultDirectory(window->path);
1492 
1493     /* Present a file selection dialog with an added field for requesting
1494        long line wrapping to become permanent via inserted newlines */
1495     n = 0;
1496     XtSetArg(args[n],
1497             XmNselectionLabelString,
1498             s1 = XmStringCreateLocalized("New File Name:")); n++;
1499     XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
1500     XtSetArg(args[n],
1501             XmNdialogTitle,
1502             s2 = XmStringCreateSimple(prompt)); n++;
1503     fileSB = CreateFileSelectionDialog(window->shell,"FileSelect",args,n);
1504     XmStringFree(s1);
1505     XmStringFree(s2);
1506     formatForm = XtVaCreateManagedWidget("formatForm", xmFormWidgetClass,
1507 	    fileSB, NULL);
1508     formatBtns = XtVaCreateManagedWidget("formatBtns",
1509             xmRowColumnWidgetClass, formatForm,
1510             XmNradioBehavior, XmONE_OF_MANY,
1511             XmNorientation, XmHORIZONTAL,
1512             XmNpacking, XmPACK_TIGHT,
1513             XmNtopAttachment, XmATTACH_FORM,
1514             XmNleftAttachment, XmATTACH_FORM,
1515             NULL);
1516     XtVaCreateManagedWidget("formatBtns", xmLabelWidgetClass, formatBtns,
1517 	    XmNlabelString, s1=XmStringCreateSimple("Format:"), NULL);
1518     XmStringFree(s1);
1519     unixFormat = XtVaCreateManagedWidget("unixFormat",
1520             xmToggleButtonWidgetClass, formatBtns,
1521             XmNlabelString, s1 = XmStringCreateSimple("Unix"),
1522             XmNset, *fileFormat == UNIX_FILE_FORMAT,
1523             XmNuserData, (XtPointer)UNIX_FILE_FORMAT,
1524             XmNmarginHeight, 0,
1525             XmNalignment, XmALIGNMENT_BEGINNING,
1526             XmNmnemonic, 'U',
1527             NULL);
1528     XmStringFree(s1);
1529     XtAddCallback(unixFormat, XmNvalueChangedCallback, setFormatCB,
1530     	    fileFormat);
1531     dosFormat = XtVaCreateManagedWidget("dosFormat",
1532             xmToggleButtonWidgetClass, formatBtns,
1533             XmNlabelString, s1 = XmStringCreateSimple("DOS"),
1534             XmNset, *fileFormat == DOS_FILE_FORMAT,
1535             XmNuserData, (XtPointer)DOS_FILE_FORMAT,
1536             XmNmarginHeight, 0,
1537             XmNalignment, XmALIGNMENT_BEGINNING,
1538             XmNmnemonic, 'O',
1539             NULL);
1540     XmStringFree(s1);
1541     XtAddCallback(dosFormat, XmNvalueChangedCallback, setFormatCB,
1542     	    fileFormat);
1543     macFormat = XtVaCreateManagedWidget("macFormat",
1544             xmToggleButtonWidgetClass, formatBtns,
1545             XmNlabelString, s1 = XmStringCreateSimple("Macintosh"),
1546             XmNset, *fileFormat == MAC_FILE_FORMAT,
1547             XmNuserData, (XtPointer)MAC_FILE_FORMAT,
1548             XmNmarginHeight, 0,
1549             XmNalignment, XmALIGNMENT_BEGINNING,
1550             XmNmnemonic, 'M',
1551             NULL);
1552     XmStringFree(s1);
1553     XtAddCallback(macFormat, XmNvalueChangedCallback, setFormatCB,
1554     	    fileFormat);
1555     if (window->wrapMode == CONTINUOUS_WRAP) {
1556 	wrapToggle = XtVaCreateManagedWidget("addWrap",
1557                 xmToggleButtonWidgetClass, formatForm,
1558                 XmNlabelString, s1 = XmStringCreateSimple("Add line breaks where wrapped"),
1559                 XmNalignment, XmALIGNMENT_BEGINNING,
1560                 XmNmnemonic, 'A',
1561                 XmNtopAttachment, XmATTACH_WIDGET,
1562                 XmNtopWidget, formatBtns,
1563                 XmNleftAttachment, XmATTACH_FORM,
1564                 NULL);
1565 	XtAddCallback(wrapToggle, XmNvalueChangedCallback, addWrapCB,
1566     	    	addWrap);
1567 	XmStringFree(s1);
1568     }
1569     *addWrap = False;
1570     XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_LABEL),
1571             XmNmnemonic, 'l',
1572             XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT),
1573             NULL);
1574     XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST_LABEL),
1575             XmNmnemonic, 'D',
1576             XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST),
1577             NULL);
1578     XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST_LABEL),
1579             XmNmnemonic, 'F',
1580             XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST),
1581             NULL);
1582     XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_SELECTION_LABEL),
1583             XmNmnemonic, prompt[strspn(prompt, "lFD")],
1584             XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT),
1585             NULL);
1586     AddDialogMnemonicHandler(fileSB, FALSE);
1587     RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT));
1588     RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT));
1589     retVal = HandleCustomNewFileSB(fileSB, fullname,
1590     	    window->filenameSet ? window->filename : NULL);
1591 
1592     if (retVal != GFN_OK)
1593     	SetFileDialogDefaultDirectory(savedDefaultDir);
1594 
1595     NEditFree(savedDefaultDir);
1596 
1597     return retVal;
1598 }
1599 
1600 /*
1601 ** Find a name for an untitled file, unique in the name space of in the opened
1602 ** files in this session, i.e. Untitled or Untitled_nn, and write it into
1603 ** the string "name".
1604 */
1605 void UniqueUntitledName(char *name)
1606 {
1607     WindowInfo *w;
1608     int i;
1609 
1610    for (i=0; i<INT_MAX; i++) {
1611     	if (i == 0)
1612     	    sprintf(name, "Untitled");
1613     	else
1614     	    sprintf(name, "Untitled_%d", i);
1615 	for (w=WindowList; w!=NULL; w=w->next)
1616      	    if (!strcmp(w->filename, name))
1617     	    	break;
1618     	if (w == NULL)
1619     	    break;
1620     }
1621 }
1622 
1623 /*
1624 ** Callback that guards us from trying to access a window after it has
1625 ** been destroyed while a modal dialog is up.
1626 */
1627 static void modifiedWindowDestroyedCB(Widget w, XtPointer clientData,
1628     XtPointer callData)
1629 {
1630     *(Bool*)clientData = TRUE;
1631 }
1632 
1633 /*
1634 ** Check if the file in the window was changed by an external source.
1635 ** and put up a warning dialog if it has.
1636 */
1637 void CheckForChangesToFile(WindowInfo *window)
1638 {
1639     static WindowInfo* lastCheckWindow = NULL;
1640     static Time lastCheckTime = 0;
1641     char fullname[MAXPATHLEN];
1642     struct stat statbuf;
1643     Time timestamp;
1644     FILE *fp;
1645     int resp, silent = 0;
1646     XWindowAttributes winAttr;
1647     Boolean windowIsDestroyed = False;
1648 
1649     if(!window->filenameSet)
1650         return;
1651 
1652     /* If last check was very recent, don't impact performance */
1653     timestamp = XtLastTimestampProcessed(XtDisplay(window->shell));
1654     if (window == lastCheckWindow &&
1655             timestamp - lastCheckTime < MOD_CHECK_INTERVAL)
1656         return;
1657     lastCheckWindow = window;
1658     lastCheckTime = timestamp;
1659 
1660     /* Update the status, but don't pop up a dialog if we're called
1661        from a place where the window might be iconic (e.g., from the
1662        replace dialog) or on another desktop.
1663 
1664        This works, but I bet it costs a round-trip to the server.
1665        Might be better to capture MapNotify/Unmap events instead.
1666 
1667        For tabs that are not on top, we don't want the dialog either,
1668        and we don't even need to contact the server to find out. By
1669        performing this check first, we avoid a server round-trip for
1670        most files in practice. */
1671     if (!IsTopDocument(window))
1672         silent = 1;
1673     else {
1674         XGetWindowAttributes(XtDisplay(window->shell),
1675                              XtWindow(window->shell),
1676                              &winAttr);
1677 
1678         if (winAttr.map_state != IsViewable)
1679             silent = 1;
1680     }
1681 
1682     /* Get the file mode and modification time */
1683     strcpy(fullname, window->path);
1684     strcat(fullname, window->filename);
1685     if (stat(fullname, &statbuf) != 0) {
1686         /* Return if we've already warned the user or we can't warn him now */
1687         if (window->fileMissing || silent) {
1688             return;
1689         }
1690 
1691         /* Can't stat the file -- maybe it's been deleted.
1692            The filename is now invalid */
1693         window->fileMissing = TRUE;
1694         window->lastModTime = 1;
1695         window->device = 0;
1696         window->inode = 0;
1697 
1698         /* Warn the user, if they like to be warned (Maybe this should be its
1699             own preference setting: GetPrefWarnFileDeleted()) */
1700         if (GetPrefWarnFileMods()) {
1701             char* title;
1702             char* body;
1703 
1704             /* See note below about pop-up timing and XUngrabPointer */
1705             XUngrabPointer(XtDisplay(window->shell), timestamp);
1706 
1707             /* If the window (and the dialog) are destroyed while the dialog
1708                is up (typically closed via the window manager), we should
1709                avoid accessing the window afterwards. */
1710             XtAddCallback(window->shell, XmNdestroyCallback,
1711                           modifiedWindowDestroyedCB, &windowIsDestroyed);
1712 
1713             /*  Set title, message body and button to match stat()'s error.  */
1714             switch (errno) {
1715                 case ENOENT:
1716                     /*  A component of the path file_name does not exist.  */
1717                     title = "File not Found";
1718                     body = "File '%s' (or directory in its path)\n"
1719                             "no longer exists.\n"
1720                             "Another program may have deleted or moved it.";
1721                     resp = DialogF(DF_ERR, window->shell, 2, title, body,
1722                             "Save", "Cancel", window->filename);
1723                     break;
1724                 case EACCES:
1725                     /*  Search permission denied for a path component. We add
1726                         one to the response because Re-Save wouldn't really
1727                         make sense here.  */
1728                     title = "Permission Denied";
1729                     body = "You no longer have access to file '%s'.\n"
1730                             "Another program may have changed the permissions of\n"
1731                             "one of its parent directories.";
1732                     resp = 1 + DialogF(DF_ERR, window->shell, 1, title, body,
1733                             "Cancel", window->filename);
1734                     break;
1735                 default:
1736                     /*  Everything else. This hints at an internal error (eg.
1737                         ENOTDIR) or at some bad state at the host.  */
1738                     title = "File not Accessible";
1739                     body = "Error while checking the status of file '%s':\n"
1740                             "    '%s'\n"
1741                             "Please make sure that no data is lost before closing\n"
1742                             "this window.";
1743                     resp = DialogF(DF_ERR, window->shell, 2, title, body,
1744                             "Save", "Cancel", window->filename,
1745                             errorString());
1746                     break;
1747             }
1748 
1749             if (!windowIsDestroyed) {
1750                 XtRemoveCallback(window->shell, XmNdestroyCallback,
1751                                  modifiedWindowDestroyedCB, &windowIsDestroyed);
1752             }
1753 
1754             switch (resp) {
1755                 case 1:
1756                     SaveWindow(window);
1757                     break;
1758                 /*  Good idea, but this leads to frequent crashes, see
1759                     SF#1578869. Reinsert this if circumstances change by
1760                     uncommenting this part and inserting a "Close" button
1761                     before each Cancel button above.
1762                 case 2:
1763                     CloseWindow(window);
1764                     return;
1765                 */
1766             }
1767         }
1768 
1769         /* A missing or (re-)saved file can't be read-only. */
1770         /*  TODO: A document without a file can be locked though.  */
1771         /* Make sure that the window was not destroyed behind our back! */
1772         if (!windowIsDestroyed) {
1773             SET_PERM_LOCKED(window->lockReasons, False);
1774             UpdateWindowTitle(window);
1775             UpdateWindowReadOnly(window);
1776         }
1777         return;
1778     }
1779 
1780     /* Check that the file's read-only status is still correct (but
1781        only if the file can still be opened successfully in read mode) */
1782     if (window->fileMode != statbuf.st_mode ||
1783         window->fileUid != statbuf.st_uid ||
1784         window->fileGid != statbuf.st_gid) {
1785         window->fileMode = statbuf.st_mode;
1786         window->fileUid = statbuf.st_uid;
1787         window->fileGid = statbuf.st_gid;
1788         if ((fp = fopen(fullname, "r")) != NULL) {
1789             int readOnly;
1790             fclose(fp);
1791 #ifndef DONT_USE_ACCESS
1792             readOnly = access(fullname, W_OK) != 0;
1793 #else
1794             if (((fp = fopen(fullname, "r+")) != NULL)) {
1795                 readOnly = FALSE;
1796                 fclose(fp);
1797             } else
1798                 readOnly = TRUE;
1799 #endif
1800             if (IS_PERM_LOCKED(window->lockReasons) != readOnly) {
1801                 SET_PERM_LOCKED(window->lockReasons, readOnly);
1802                 UpdateWindowTitle(window);
1803                 UpdateWindowReadOnly(window);
1804             }
1805         }
1806     }
1807 
1808     /* Warn the user if the file has been modified, unless checking is
1809        turned off or the user has already been warned.  Popping up a dialog
1810        from a focus callback (which is how this routine is usually called)
1811        seems to catch Motif off guard, and if the timing is just right, the
1812        dialog can be left with a still active pointer grab from a Motif menu
1813        which is still in the process of popping down.  The workaround, below,
1814        of calling XUngrabPointer is inelegant but seems to fix the problem. */
1815     if (!silent &&
1816             ((window->lastModTime != 0 &&
1817               window->lastModTime != statbuf.st_mtime) ||
1818              window->fileMissing)  ){
1819         window->lastModTime = 0;        /* Inhibit further warnings */
1820         window->fileMissing = FALSE;
1821         if (!GetPrefWarnFileMods())
1822             return;
1823         if (GetPrefWarnRealFileMods() &&
1824 	    !cmpWinAgainstFile(window, fullname)) {
1825 	    /* Contents hasn't changed. Update the modification time. */
1826 	    window->lastModTime = statbuf.st_mtime;
1827 	    return;
1828 	}
1829         XUngrabPointer(XtDisplay(window->shell), timestamp);
1830         if (window->fileChanged)
1831             resp = DialogF(DF_WARN, window->shell, 2,
1832                     "File modified externally",
1833                     "%s has been modified by another program.  Reload?\n\n"
1834                     "WARNING: Reloading will discard changes made in this\n"
1835                     "editing session!", "Reload", "Cancel", window->filename);
1836         else
1837             resp = DialogF(DF_WARN, window->shell, 2,
1838                     "File modified externally",
1839                     "%s has been modified by another\nprogram.  Reload?",
1840                     "Reload", "Cancel", window->filename);
1841         if (resp == 1)
1842             RevertToSaved(window);
1843     }
1844 }
1845 
1846 /*
1847 ** Return true if the file displayed in window has been modified externally
1848 ** to nedit.  This should return FALSE if the file has been deleted or is
1849 ** unavailable.
1850 */
1851 static int fileWasModifiedExternally(WindowInfo *window)
1852 {
1853     char fullname[MAXPATHLEN];
1854     struct stat statbuf;
1855 
1856     if(!window->filenameSet)
1857         return FALSE;
1858     /* if (window->lastModTime == 0)
1859 	return FALSE; */
1860     strcpy(fullname, window->path);
1861     strcat(fullname, window->filename);
1862     if (stat(fullname, &statbuf) != 0)
1863         return FALSE;
1864     if (window->lastModTime == statbuf.st_mtime)
1865 	return FALSE;
1866     if (GetPrefWarnRealFileMods() &&
1867 	!cmpWinAgainstFile(window, fullname)) {
1868 	return FALSE;
1869     }
1870     return TRUE;
1871 }
1872 
1873 /*
1874 ** Check the read-only or locked status of the window and beep and return
1875 ** false if the window should not be written in.
1876 */
1877 int CheckReadOnly(WindowInfo *window)
1878 {
1879     if (IS_ANY_LOCKED(window->lockReasons)) {
1880     	XBell(TheDisplay, 0);
1881 	return True;
1882     }
1883     return False;
1884 }
1885 
1886 /*
1887 ** Wrapper for strerror so all the calls don't have to be ifdef'd for VMS.
1888 */
1889 static const char *errorString(void)
1890 {
1891 #ifdef VMS
1892     return strerror(errno, vaxc$errno);
1893 #else
1894     return strerror(errno);
1895 #endif
1896 }
1897 
1898 #ifdef VMS
1899 /*
1900 ** Removing the VMS version number from a file name (if has one).
1901 */
1902 void removeVersionNumber(char *fileName)
1903 {
1904     char *versionStart;
1905 
1906     versionStart = strrchr(fileName, ';');
1907     if (versionStart != NULL)
1908     	*versionStart = '\0';
1909 }
1910 #endif /*VMS*/
1911 
1912 /*
1913 ** Callback procedure for File Format toggle buttons.  Format is stored
1914 ** in userData field of widget button
1915 */
1916 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData)
1917 {
1918     if (XmToggleButtonGetState(w)) {
1919         XtPointer userData;
1920         XtVaGetValues(w, XmNuserData, &userData, NULL);
1921         *(int*) clientData = (int) (intptr_t) userData;
1922     }
1923 }
1924 
1925 /*
1926 ** Callback procedure for toggle button requesting newlines to be inserted
1927 ** to emulate continuous wrapping.
1928 */
1929 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData)
1930 {
1931     int resp;
1932     int *addWrap = (int *)clientData;
1933 
1934     if (XmToggleButtonGetState(w))
1935     {
1936         resp = DialogF(DF_WARN, w, 2, "Add Wrap",
1937                 "This operation adds permanent line breaks to\n"
1938                 "match the automatic wrapping done by the\n"
1939                 "Continuous Wrap mode Preferences Option.\n\n"
1940                 "*** This Option is Irreversable ***\n\n"
1941                 "Once newlines are inserted, continuous wrapping\n"
1942                 "will no longer work automatically on these lines", "OK",
1943                 "Cancel");
1944         if (resp == 2)
1945         {
1946             XmToggleButtonSetState(w, False, False);
1947             *addWrap = False;
1948         } else
1949         {
1950             *addWrap = True;
1951         }
1952     } else
1953     {
1954         *addWrap = False;
1955     }
1956 }
1957 
1958 /*
1959 ** Change a window created in NEdit's continuous wrap mode to the more
1960 ** conventional Unix format of embedded newlines.  Indicate to the user
1961 ** by turning off Continuous Wrap mode.
1962 */
1963 static void addWrapNewlines(WindowInfo *window)
1964 {
1965     int fileLen, i, insertPositions[MAX_PANES], topLines[MAX_PANES];
1966     int horizOffset;
1967     Widget text;
1968     char *fileString;
1969 
1970     /* save the insert and scroll positions of each pane */
1971     for (i=0; i<=window->nPanes; i++) {
1972     	text = i==0 ? window->textArea : window->textPanes[i-1];
1973     	insertPositions[i] = TextGetCursorPos(text);
1974     	TextGetScroll(text, &topLines[i], &horizOffset);
1975     }
1976 
1977     /* Modify the buffer to add wrapping */
1978     fileString = TextGetWrapped(window->textArea, 0,
1979     	    window->buffer->length, &fileLen);
1980     BufSetAll(window->buffer, fileString);
1981     NEditFree(fileString);
1982 
1983     /* restore the insert and scroll positions of each pane */
1984     for (i=0; i<=window->nPanes; i++) {
1985     	text = i==0 ? window->textArea : window->textPanes[i-1];
1986 	TextSetCursorPos(text, insertPositions[i]);
1987 	TextSetScroll(text, topLines[i], 0);
1988     }
1989 
1990     /* Show the user that something has happened by turning off
1991        Continuous Wrap mode */
1992     SetToggleButtonState(window, window->continuousWrapItem, False, True);
1993 }
1994 
1995 /*
1996  * Number of bytes read at once by cmpWinAgainstFile
1997  */
1998 #define PREFERRED_CMPBUF_LEN 32768
1999 
2000 /*
2001  * Check if the contens of the textBuffer *buf is equal
2002  * the contens of the file named fileName. The format of
2003  * the file (UNIX/DOS/MAC) is handled properly.
2004  *
2005  * Return values
2006  *   0: no difference found
2007  *  !0: difference found or could not compare contents.
2008  */
2009 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName)
2010 {
2011     char    fileString[PREFERRED_CMPBUF_LEN + 2];
2012     struct  stat statbuf;
2013     int     fileLen, restLen, nRead, bufPos, rv, offset, filePos;
2014     char    pendingCR = 0;
2015     int	    fileFormat = window->fileFormat;
2016     char    message[MAXPATHLEN+50];
2017     textBuffer *buf = window->buffer;
2018     FILE   *fp;
2019 
2020     fp = fopen(fileName, "r");
2021     if (!fp)
2022         return (1);
2023     if (fstat(fileno(fp), &statbuf) != 0) {
2024         fclose(fp);
2025         return (1);
2026     }
2027 
2028     fileLen = statbuf.st_size;
2029     /* For DOS files, we can't simply check the length */
2030     if (fileFormat != DOS_FILE_FORMAT) {
2031 	if (fileLen != buf->length) {
2032 	    fclose(fp);
2033 	    return (1);
2034         }
2035     } else {
2036 	/* If a DOS file is smaller on disk, it's certainly different */
2037 	if (fileLen < buf->length) {
2038 	    fclose(fp);
2039 	    return (1);
2040 	}
2041     }
2042 
2043     /* For large files, the comparison can take a while. If it takes too long,
2044        the user should be given a clue about what is happening. */
2045     sprintf(message, "Comparing externally modified %s ...", window->filename);
2046     restLen = min(PREFERRED_CMPBUF_LEN, fileLen);
2047     bufPos = 0;
2048     filePos = 0;
2049     while (restLen > 0) {
2050         AllWindowsBusy(message);
2051         if (pendingCR) {
2052            fileString[0] = pendingCR;
2053            offset = 1;
2054         } else {
2055            offset = 0;
2056         }
2057 
2058         nRead = fread(fileString+offset, sizeof(char), restLen, fp);
2059         if (nRead != restLen) {
2060             fclose(fp);
2061             AllWindowsUnbusy();
2062             return (1);
2063         }
2064         filePos += nRead;
2065 
2066         nRead += offset;
2067 
2068         /* check for on-disk file format changes, but only for the first hunk */
2069         if (bufPos == 0 && fileFormat != FormatOfFile(fileString)) {
2070             fclose(fp);
2071             AllWindowsUnbusy();
2072             return (1);
2073         }
2074 
2075         if (fileFormat == MAC_FILE_FORMAT)
2076             ConvertFromMacFileString(fileString, nRead);
2077         else if (fileFormat == DOS_FILE_FORMAT)
2078             ConvertFromDosFileString(fileString, &nRead, &pendingCR);
2079 
2080         /* Beware of 0 chars ! */
2081         BufSubstituteNullChars(fileString, nRead, buf);
2082         rv = BufCmp(buf, bufPos, nRead, fileString);
2083         if (rv) {
2084             fclose(fp);
2085             AllWindowsUnbusy();
2086             return (rv);
2087         }
2088         bufPos += nRead;
2089         restLen = min(fileLen - filePos, PREFERRED_CMPBUF_LEN);
2090     }
2091     AllWindowsUnbusy();
2092     fclose(fp);
2093     if (pendingCR) {
2094 	rv = BufCmp(buf, bufPos, 1, &pendingCR);
2095 	if (rv) {
2096 	    return (rv);
2097 	}
2098 	bufPos += 1;
2099     }
2100     if (bufPos != buf->length) {
2101 	return (1);
2102     }
2103     return (0);
2104 }
2105 
2106 /*
2107 ** Force ShowLineNumbers() to re-evaluate line counts for the window if line
2108 ** counts are required.
2109 */
2110 static void forceShowLineNumbers(WindowInfo *window)
2111 {
2112     Boolean showLineNum = window->showLineNumbers;
2113     if (showLineNum) {
2114         window->showLineNumbers = False;
2115         ShowLineNumbers(window, showLineNum);
2116     }
2117 }
2118 
2119 static int min(int i1, int i2)
2120 {
2121     return i1 <= i2 ? i1 : i2;
2122 }
2123