1 /************************************************************************
2  * This program is Copyright (C) 1986-1996 by Jonathan Payne.  JOVE is  *
3  * provided to you without charge, and with no warranty.  You may give  *
4  * away copies of JOVE, including sources, provided that this notice is *
5  * included in all the files.                                           *
6  ************************************************************************/
7 
8 #include "jove.h"
9 #include "list.h"
10 #include "fp.h"
11 #include "jctype.h"
12 #include "disp.h"
13 #include "scandir.h"
14 #include "ask.h"
15 #include "fmt.h"
16 #include "insert.h"
17 #include "marks.h"
18 #include "sysprocs.h"
19 #include "proc.h"
20 #include "wind.h"	/* only used by JReadFile for fixup */
21 #include "rec.h"
22 
23 #ifdef MAC
24 # include "mac.h"
25 #else
26 # include <sys/stat.h>
27 #endif
28 
29 #ifdef UNIX
30 # include <sys/file.h>
31 #endif
32 
33 #ifdef MSFILESYSTEM
34 # include <fcntl.h>
35 # include <io.h>
36 # include <direct.h>
37 # include <dos.h>
38 # include <stdlib.h>	/* _splitpath, _makepath */
39 extern int UNMACRO(rename)(const char *old, const char *new);	/* <stdin.h> */
40 # ifndef _MAX_DIR
41 #  define _MAX_DIR FILESIZE
42 # endif
43 # ifndef _MAX_FNAME
44 #  define _MAX_FNAME 9
45 # endif
46 # ifndef _MAX_EXT
47 #  define _MAX_EXT 4
48 # endif
49 #endif /* MSFILESYSTEM */
50 
51 #include <errno.h>
52 
53 private void
54 	filemunge proto((char *newname)),
55 	chk_divergence proto((Buffer *thisbuf, char *fname, char *how));
56 
57 private struct block	*lookup_block proto((daddr));
58 
59 private char	*getblock proto((daddr, bool));
60 
61 private bool	f_getputl proto((LinePtr line,File *fp));
62 
63 #ifdef BACKUPFILES
64 private void
65 	file_backup proto((char *fname));
66 #endif
67 
68 long	io_chars;		/* number of chars in this open_file */
69 int	io_lines;		/* number of lines in this open_file */
70 
71 #ifdef BACKUPFILES
72 bool	BkupOnWrite = NO;	/* VAR: make backup files when writing */
73 #endif
74 
75 #ifndef MSFILESYSTEM
76 
77 #define	Dchdir(to)  chdir(to)
78 
79 #else /* MSFILESYSTEM */
80 
81 # ifdef WIN32
82 #  define _dos_getdrive(dd)		(*(dd)=_getdrive())
83 #  define _dos_setdrive(d, n)	((*(n)=_getdrive()), _chdrive((d)))
84 # endif
85 
86 # ifdef ZTCDOS
87 
88 #  define _dos_getdrive(dd)	dos_getdrive(dd)
89 #  define _dos_setdrive(d, n)	dos_setdrive((d), (n))
90 
91 private void
_splitpath(const char * path,char * drv,char * dir,char * fn,char * ext)92 _splitpath(const char *path, char *drv, char *dir, char *fn, char *ext)
93 {
94 	const char	*p;
95 	size_t	l;
96 
97 	if (path[0]!='\0' && path[1]==':') {
98 		*drv++ = *path++;
99 		*drv++ = *path++;
100 	}
101 	*drv = '\0';
102 
103 	p = strrchr(path, '/');
104 	if (p != NULL) {
105 		/* ??? should we have / at the end of the directory? */
106 		p++;
107 		memcpy(dir, path, (size_t) (p-path));
108 		dir += p-path;
109 		path = p;
110 	}
111 	*dir = '\0';
112 
113 	p = strchr(path, '.');
114 	if (p == NULL)
115 		p = path+strlen(path);
116 	l = p-path;
117 	if (l > 8)
118 		l = 8;
119 	null_ncpy(fn, path, l);
120 
121 	l = strlen(p);
122 	if (l > 4)
123 		l = 4;
124 	null_ncpy(ext, p, l);
125 }
126 
127 private void
_makepath(char * path,const char * drv,const char * dir,const char * fn,const char * ext)128 _makepath(char *path, const char *drv, const char *dir, const char *fn, const char *ext)
129 {
130 	if (drv[0] != '\0') {
131 		*path++ = drv[0];
132 		*path++ = ':';
133 		*path = '\0';
134 	}
135 	if (dir[0] != '\0') {
136 		strcpy(path, dir);
137 		path += strlen(path);
138 		if (path[-1] != '/' && path[-1] != '\\') {
139 			*path++ = '/';
140 			*path = '\0';
141 		}
142 	}
143 	strcpy(path, fn);
144 	path += strlen(path);
145 	switch (ext[0]) {
146 	case '\0':
147 		break;
148 	default:
149 		*path++ = '.';
150 		/*FALLTHROUGH*/
151 	case '.':
152 		strcpy(path, ext);
153 		break;
154 	}
155 }
156 # endif /* ZTCDOS */
157 
158 # if defined(ZTCDOS) || defined(__WATCOMC__)
159 /* The Zortech C and Watcom libraries do not provide mktemp
160  * (nor should they -- ANSI defines tmpnam).
161  * Unfortunately tmpnam doesn't do what we want either, so we roll
162  * one by hand.  Since mktemp has no way of reporting errors,
163  * we're not too careful about checking for them.
164  */
165 char *
mktemp(buf)166 mktemp(buf)
167 char	*buf;
168 {
169 	char	*seq;
170 	char	*p;
171 
172 	for (seq = buf + strlen(buf); seq>buf && seq[-1]=='X'; seq--)
173 		;
174 	for (p = seq; *p != '\0'; p++)
175 		*p = '0';
176 	for (;;) {
177 		struct stat	sb;
178 
179 		if (stat(buf, &sb) < 0
180 #  ifdef ZTCDOS
181 		/* Zortech yields ENOTDIR when path isn't found,
182 		 * even if the prefix does exist.  Pretty silly!
183 		 */
184 		&& errno == ENOTDIR
185 #  else
186 		&& errno == ENOENT
187 #  endif
188 		)
189 			break;
190 		for (p = seq; ; ) {
191 			if (*p == '\0') {
192 				abort();	/* we ran out of possible names! */
193 			} else if (*p == '9') {
194 				*p++ = '0';
195 			} else {
196 				*p += 1;
197 				break;
198 			}
199 		}
200 	}
201 	return buf;
202 }
203 
204 # endif /* defined(ZTCDOS) || defined(__WATCOMC__) */
205 
206 /* Change drive and directory
207  * This is not quite like a UNIX chdir because each drive has a
208  * separate current directory.  If the path is not absolute, it
209  * will be relative to the current directory of the drive.
210  * On the other hand, this is not the DOS chdir because it does
211  * change the current drive if one is specified.
212  */
213 private int
Dchdir(to)214 Dchdir(to)
215 char *to;
216 {
217 	unsigned d, dd;
218 
219 	if (to[1] == ':') {
220 		d = CharUpcase(to[0]) - ('A' - 1);
221 		if (d < 'A'-('A'-1) || 'Z'-('A'-1) < d)
222 			complain("invalid drive");
223 		_dos_getdrive(&dd);
224 		if (dd != d)
225 			_dos_setdrive(d, &dd);	/* ??? no failure report? */
226 		to += 2;	/* skip drive spec */
227 	}
228 	return *to == '\0'? 0 : chdir(to);
229 }
230 
231 private char *
fixpath(path)232 fixpath(path)
233 char *path;
234 {
235 	char *p;
236 
237 	for (p = path; *p != '\0'; p++)
238 		if (*p == '\\')
239 			*p = '/';
240 # ifdef MSDOS
241 	return strlwr(path);
242 # else
243 	return path; /* Win32 is case-preserving. */
244 # endif
245 }
246 
247 private void
abspath(so,dest)248 abspath(so, dest)
249 char *so, *dest;
250 {
251 	char	cwd[FILESIZE],
252 		cwdD[3], cwdDIR[_MAX_DIR], cwdF[_MAX_FNAME], cwdEXT[_MAX_EXT],
253 		soD[3], soDIR[_MAX_DIR], soF[_MAX_FNAME], soEXT[_MAX_EXT];
254 
255 	_splitpath(fixpath(so), soD, soDIR, soF, soEXT);
256 	getcwd(cwd, FILESIZE);
257 	if (*soD != '\0') {
258 		/* Find current working directory on specified drive
259 		 * There is a DOS system call to do this (service 0x47),
260 		 * but the C library doesn't have a glue routine for it.
261 		 * We get the same effect with this kludgy code.
262 		 */
263 		Dchdir(soD);	/* note: without a path, current directory is unchanged */
264 		getcwd(cwdDIR, FILESIZE);
265 		cwd[2] = '\0';	/* toss away path, leaving only drive spec */
266 		Dchdir(cwd);
267 		strcpy(cwd, cwdDIR);
268 	}
269 	(void) fixpath(cwd);
270 	if (cwd[strlen(cwd)-1] != '/')
271 		strcat(cwd, "/x.x");	/* need dummy filename */
272 
273 	_splitpath(fixpath(cwd), cwdD, cwdDIR, cwdF, cwdEXT);
274 	/* Reconstruct the path as follows:
275 	 * - If it is NOT a UNC (network) name, and doesn't have a drive letter,
276 	 *   add one.
277 	 * - If it is a relative path, add the current drive/directory
278 	 *   to convert it to an absolute path.
279 	 */
280 	_makepath(dest,
281 		*soD == '\0' && (soDIR[0]!='/'||soDIR[1]!='/') ? cwdD : soD,
282 		*soDIR != '/'? strcat(cwdDIR, soDIR) : soDIR, soF, soEXT);
283 	fixpath(dest);	/* can't do it often enough */
284 }
285 
286 #endif /* MSFILESYSTEM */
287 
288 
289 void
close_file(fp)290 close_file(fp)
291 File	*fp;
292 {
293 	if (fp != NULL) {
294 		if (fp->f_flags & F_TELLALL)
295 			add_mess(" %d lines, %D characters.",
296 				 io_lines, io_chars);
297 		f_close(fp);
298 	}
299 }
300 
301 /* Write the region from line1/char1 to line2/char2 to FP.  This
302    never CLOSES the file since we don't know if we want to. */
303 
304 bool	EndWNewline = 1;	/* VAR: end files with a blank line */
305 
306 void
putreg(fp,line1,char1,line2,char2,makesure)307 putreg(fp, line1, char1, line2, char2, makesure)
308 register File	*fp;
309 LinePtr	line1,
310 	line2;
311 int	char1,
312 	char2;
313 bool	makesure;
314 {
315 	if (makesure)
316 		(void) fixorder(&line1, &char1, &line2, &char2);
317 	while (line1 != line2->l_next) {
318 		register char	*lp = lcontents(line1) + char1;
319 
320 		if (line1 == line2) {
321 			fputnchar(lp, (char2 - char1), fp);
322 			io_chars += (char2 - char1);
323 		} else {
324 			register char	c;
325 
326 			while ((c = *lp++) != '\0') {
327 				f_putc(c, fp);
328 				io_chars += 1;
329 			}
330 		}
331 		if (line1 != line2) {
332 			io_lines += 1;
333 			io_chars += 1;
334 #ifdef USE_CRLF
335 			f_putc('\r', fp);
336 #endif /* USE_CRLF */
337 			f_putc(EOL, fp);
338 		}
339 		line1 = line1->l_next;
340 		char1 = 0;
341 	}
342 	flushout(fp);
343 }
344 
345 private void
dofread(fp)346 dofread(fp)
347 register File	*fp;
348 {
349 	char	end[LBSIZE];
350 	bool	xeof;
351 	LinePtr	savel = curline;
352 	int	savec = curchar;
353 
354 	strcpy(end, linebuf + curchar);
355 	xeof = f_gets(fp, linebuf + curchar, (size_t) (LBSIZE - curchar));
356 	SavLine(curline, linebuf);
357 	while(!xeof) {
358 		curline = listput(curbuf, curline);
359 		xeof = f_getputl(curline, fp);
360 	}
361 	getDOT();
362 	linecopy(linebuf, (curchar = strlen(linebuf)), end);
363 	SavLine(curline, linebuf);
364 	IFixMarks(savel, savec, curline, curchar);
365 }
366 
367 void
read_file(file,is_insert)368 read_file(file, is_insert)
369 char	*file;
370 bool	is_insert;
371 {
372 	Bufpos	save;
373 	File	*fp;
374 
375 	if (!is_insert)
376 		curbuf->b_ntbf = NO;
377 	fp = open_file(file, iobuff, F_READ | F_TELLALL, NO);
378 	if (fp == NULL) {
379 		if (!is_insert && errno == ENOENT)
380 			s_mess("(new file)");
381 		else
382 			s_mess(IOerr("open", file));
383 		return;
384 	}
385 	if (!is_insert) {
386 		(void) do_stat(curbuf->b_fname, curbuf, DS_SET);
387 		set_arg_value((fp->f_flags & F_READONLY)? 1 : 0);
388 		TogMinor(ReadOnly);
389 	}
390 
391 	DOTsave(&save);
392 	dofread(fp);
393 	if (is_insert && io_chars > 0) {
394 		modify();
395 		set_mark();
396 	}
397 	SetDot(&save);
398 	getDOT();
399 	close_file(fp);
400 }
401 
402 void
SaveFile()403 SaveFile()
404 {
405 	if (!IsModified(curbuf) && !curbuf->b_diverged) {
406 		if (curbuf->b_fname != NULL)
407 			(void) do_stat(curbuf->b_fname, curbuf, DS_NONE);
408 		if (!curbuf->b_diverged) {
409 			message("No changes need to be written.");
410 			return;
411 		}
412 	}
413 	if (curbuf->b_fname == NULL) {
414 		/* We change LastCmd because otherwise the prompt for
415 		 * the filename will be ": visit-file".  With this
416 		 * fudge, it will be ": write-file".
417 		 */
418 		data_obj	*saved_lc = LastCmd;
419 		static data_obj	dummy = { 0, "write-file" };
420 
421 		LastCmd = &dummy;
422 		JWriteFile();
423 		LastCmd = saved_lc;
424 	} else {
425 		filemunge(curbuf->b_fname);
426 		chk_divergence(curbuf, curbuf->b_fname, "save");
427 		file_write(curbuf->b_fname, NO);
428 	}
429 }
430 
431 char	*HomeDir;	/* home directory */
432 size_t	HomeLen;	/* length of home directory string */
433 
434 private List		*DirStack = NULL;
435 #define dir_name(dp)	((char *) list_data((dp)))
436 #define PWD_PTR		(list_data(DirStack))
437 #define PWD		((char *) PWD_PTR)
438 
439 char *
pwd()440 pwd()
441 {
442 	return PWD;
443 }
444 
445 char *
pr_name(fname,okay_home)446 pr_name(fname, okay_home)
447 char	*fname;
448 bool	okay_home;
449 {
450 	int	n;
451 
452 	if (fname != NULL) {
453 		n = numcomp(fname, PWD);
454 
455 		if ((PWD[n] == '\0') &&	/* Matched to end of PWD */
456 		    (fname[n] == '/'))
457 			return fname + n + 1;
458 
459 		if (okay_home && strcmp(HomeDir, "/") != 0
460 		&& strncmp(fname, HomeDir, HomeLen) == 0
461 		&& fname[HomeLen] == '/')
462 		{
463 			static char	name_buf[100];
464 
465 			swritef(name_buf, sizeof(name_buf),
466 				"~%s", fname + HomeLen);
467 			return name_buf;
468 		}
469 	}
470 	return fname;
471 }
472 
473 void
Chdir()474 Chdir()
475 {
476 	char	dirbuf[FILESIZE];
477 
478 #ifdef MSFILESYSTEM
479 	MatchDir = YES;
480 #endif
481 	(void) ask_file((char *)NULL, PWD, dirbuf);
482 #ifdef MSFILESYSTEM
483 	MatchDir = NO;
484 #endif
485 	if (Dchdir(dirbuf) == -1)
486 	{
487 		s_mess("cd: cannot change into %s.", dirbuf);
488 		return;
489 	}
490 	UpdModLine = YES;
491 	setCWD(dirbuf);
492 	prCWD();
493 #ifdef MAC
494 	Bufchange = YES;
495 #endif
496 }
497 
498 #ifdef USE_GETWD
499 extern char	*getwd proto((char *));
500 
501 /* ARGSUSED bufsize */
502 char *
getcwd(buffer,bufsize)503 getcwd(buffer, bufsize)
504 char	*buffer;
505 size_t	bufsize;
506 {
507 	return getwd(buffer);
508 }
509 #endif
510 
511 #ifdef USE_PWD
512 /* ARGSUSED bufsize */
513 char *
getcwd(buffer,bufsize)514 getcwd(buffer, bufsize)
515 char	*buffer;
516 size_t	bufsize;
517 {
518 	Buffer	*old = curbuf;
519 	char	*ret_val;
520 
521 	/* ??? The use of a buffer ought to be more polite --
522 	 * what if it were already in use?  (a) the buffer contents
523 	 * might be valuable, so we should ask whether we can clobber,
524 	 * and (b) we don't have any code to empty the buffer anyway.
525 	 * Luckily, this is only called once, at the beginning of time,
526 	 * so things ought to be OK.  Perhaps we should delete this buffer
527 	 * after we are finished with it.
528 	 * (MSDOS calls its own version of this routine more often,
529 	 * but that version is quite different.)
530 	 */
531 	SetBuf(do_select((Window *)NULL, "pwd-output"));
532 	curbuf->b_type = B_PROCESS;
533 	(void) UnixToBuf(0, "pwd-output", (char *)NULL, "/bin/pwd");
534 	ToFirst();
535 	strcpy(buffer, linebuf);
536 	SetBuf(old);
537 	return buffer;
538 }
539 #endif /* USE_PWD */
540 
541 /* Check if dn is the name of the current working directory
542    and that it is in cannonical form */
543 
544 bool
chkCWD(dn)545 chkCWD(dn)
546 char	*dn;
547 {
548 #ifdef USE_INO
549 	char	filebuf[FILESIZE];
550 	struct stat	dnstat,
551 			dotstat;
552 
553 	if (dn[0] != '/')
554 		return NO;		/* need absolute pathname */
555 	PathParse(dn, filebuf);
556 	return stat(filebuf, &dnstat) == 0
557 		&& stat(".", &dotstat) == 0
558 		&& dnstat.st_dev == dotstat.st_dev
559 		&& dnstat.st_ino == dotstat.st_ino;
560 #else /* !USE_INO */
561 	return NO;	/* no way of telling */
562 #endif /* !USE_INO */
563 }
564 
565 void
setCWD(d)566 setCWD(d)
567 char	*d;
568 {
569 	if (DirStack == NULL)
570 		list_push(&DirStack, (UnivPtr)NULL);
571 	PWD_PTR = freealloc((UnivPtr) PWD, strlen(d) + 1);
572 	strcpy(PWD, d);
573 }
574 
575 void
getCWD()576 getCWD()
577 {
578 	char	*cwd;
579 	char	pathname[FILESIZE];
580 
581 #ifndef MAC	/* no environment in MacOS */
582 	cwd = getenv("CWD");
583 	if (cwd == NULL || !chkCWD(cwd)) {
584 		cwd = getenv("PWD");
585 		if (cwd == NULL || !chkCWD(cwd)) {
586 #endif
587 			cwd = getcwd(pathname, sizeof(pathname));
588 			if (cwd == NULL)
589 				error("Cannot get current directory");
590 #ifndef MAC	/* no environment in MacOS */
591 		}
592 	}
593 #endif
594 #ifdef MSFILESYSTEM
595 	cwd = fixpath(cwd);
596 #endif /* MSFILESYSTEM */
597 	setCWD(cwd);
598 }
599 
600 void
prDIRS()601 prDIRS()
602 {
603 	register List	*lp;
604 
605 	s_mess(ProcFmt);
606 	for (lp = DirStack; lp != NULL; lp = list_next(lp))
607 		add_mess("%s ", pr_name(dir_name(lp), YES));
608 }
609 
610 void
prCWD()611 prCWD()
612 {
613 	f_mess(": %f => \"%s\"", PWD);
614 	stickymsg = YES;
615 }
616 
617 private void
doPushd(newdir)618 doPushd(newdir)
619 	char	*newdir;
620 {
621 	UpdModLine = YES;
622 	if (*newdir == '\0') {	/* Wants to swap top two entries */
623 		char	*old_top;
624 
625 		if (list_next(DirStack) == NULL)
626 			complain("pushd: no other directory.");
627 		old_top = PWD;
628 		list_data(DirStack) = (UnivPtr) dir_name(list_next(DirStack));
629 		list_data(list_next(DirStack)) = (UnivPtr) old_top;
630 		(void) Dchdir(PWD);
631 	} else {
632 		if (Dchdir(newdir) == -1)
633 		{
634 			s_mess("pushd: cannot change into %s.", newdir);
635 			return;
636 		}
637 		(void) list_push(&DirStack, (UnivPtr)NULL);
638 		setCWD(newdir);
639 	}
640 	prDIRS();
641 }
642 
643 void
Pushd()644 Pushd()
645 {
646 	char	dirbuf[FILESIZE];
647 
648 #ifdef MSFILESYSTEM
649 	MatchDir = YES;
650 #endif
651 	(void) ask_file((char *)NULL, NullStr, dirbuf);
652 #ifdef MSFILESYSTEM
653 	MatchDir = NO;
654 #endif
655 	doPushd(dirbuf);
656 }
657 
658 void
Pushlibd()659 Pushlibd()
660 {
661 	char	dirbuf[FILESIZE];
662 
663 	PathParse(ShareDir, dirbuf);
664 	doPushd(dirbuf);
665 }
666 
667 void
Popd()668 Popd()
669 {
670 	if (list_next(DirStack) == NULL)
671 		complain("popd: directory stack is empty.");
672 	UpdModLine = YES;
673 	free((UnivPtr) list_pop(&DirStack));
674 	(void) Dchdir(PWD);	/* If this doesn't work, we's in deep shit. */
675 	prDIRS();
676 }
677 
678 #ifdef UNIX
679 
680 # ifdef USE_GETPWNAM
681 
682 #include <pwd.h>
683 
684 private void
get_hdir(user,buf)685 get_hdir(user, buf)
686 register char	*user,
687 		*buf;
688 {
689 	struct passwd	*p;
690 
691 	p = getpwnam(user);
692 	endpwent();
693 	if (p == NULL) {
694 		add_mess(" [unknown user: %s]", user);
695 		SitFor(7);
696 		complain((char *)NULL);
697 		/* NOTREACHED */
698 	}
699 	strcpy(buf, p->pw_dir);
700 }
701 
702 # else /* ! USE_GETPWNAM */
703 
704 #  include "re.h"
705 
706 private void
get_hdir(user,buf)707 get_hdir(user, buf)
708 register char	*user,
709 		*buf;
710 {
711 	char	fbuf[LBSIZE],
712 		pattern[100];
713 	register int	u_len;
714 	File	*fp;
715 
716 	u_len = strlen(user);
717 	fp = open_file("/etc/passwd", fbuf, F_READ, YES);
718 	swritef(pattern, sizeof(pattern),
719 		"%s:[^:]*:[^:]*:[^:]*:[^:]*:\\([^:]*\\):", user);
720 	while (!f_gets(fp, genbuf, LBSIZE))
721 		if ((strncmp(genbuf, user, u_len) == 0)
722 		&& LookingAt(pattern, genbuf, 0)) {
723 			putmatch(1, buf, FILESIZE);
724 			close_file(fp);
725 			return;
726 		}
727 	close_file(fp);
728 	add_mess(" [unknown user: %s]", user);
729 	SitFor(7);
730 	complain((char *)NULL);
731 }
732 
733 # endif /* USE_GETPWNAM */
734 #endif /* UNIX */
735 
736 /* Convert path in name into a more-canonical one in intobuf.
737  * - makes path absolute
738  * - handles ~ (and \~, if not MSFILESYSTEM)
739  * - if MSFILESYSTEM, turns \ into /
740  * - on MSDOS, lower cases everything
741  * Note: because \~ is turned into ~, this routine is not idempotent.
742  * ??? I suspect that there are places where in the code that presume
743  * it is idempotent!  DHR
744  */
745 void
PathParse(name,intobuf)746 PathParse(name, intobuf)
747 char	*name,
748 	*intobuf;
749 {
750 	char	localbuf[FILESIZE];
751 
752 	intobuf[0] = localbuf[0] = '\0';
753 	if (*name == '\0')
754 		return;
755 
756 	/* Place pathname in localbuf, with any specified home directory */
757 
758 	if (*name == '~') {
759 		if (name[1] == '/' || name[1] == '\0') {
760 			strcpy(localbuf, HomeDir);
761 			name += 1;
762 		}
763 #ifdef UNIX	/* may add for mac in future */
764 		else {
765 			char	*uendp = strchr(name, '/'),
766 				unamebuf[30];
767 
768 			if (uendp == NULL)
769 				uendp = name + strlen(name);
770 			name += 1;
771 			null_ncpy(unamebuf, name, (size_t) (uendp - name));
772 			get_hdir(unamebuf, localbuf);
773 			name = uendp;
774 		}
775 #endif
776 #ifndef MSFILESYSTEM
777 	} else if (name[0] == '\\' && name[1] == '~') {
778 		/* allow quoting of ~ (but \ is a path separator in MSDOS) */
779 		name += 1;
780 #endif /* MSFILESYSTEM */
781 	}
782 	(void) strcat(localbuf, name);
783 
784 	/* Make path absolute, and prepare for processing each component
785 	 * of the path by placing prefix in intobuf.
786 	 */
787 #ifndef MSFILESYSTEM
788 	strcpy(intobuf, localbuf[0] == '/'? "/" : PWD);
789 #else /* MSFILESYSTEM */
790 	/* Convert to an absolute path, and then fudge thing so that the
791 	 * generic code does not have to deal with drive specifications.
792 	 * If the path starts with '//' it is a UNC name. Otherwise,
793 	 * our absolute path starts with a d: drive specification and
794 	 * uses forward slashes as the path separator (including one
795 	 * right after the drive specification).
796 	 */
797 	abspath(localbuf, intobuf);
798 	if (localbuf[0] == '/' && localbuf[1] == '/') {
799 		strcpy(localbuf, intobuf+1);
800 		intobuf += 1;
801 		intobuf[1] = '\0';
802 	} else {
803 		strcpy(localbuf, intobuf+3);		/* copy back all but d:/ */
804 		intobuf += 2;	/* "forget" drive spec: point to / */
805 		intobuf[1] = '\0';	/* truncate after d:/ */
806 	}
807 #endif /* MSFILESYSTEM */
808 
809 	/* Process each path component, attempting to make the path canonical.
810 	 * Since processing is lexical, it cannot account for links.
811 	 */
812 	{
813 		char
814 			*fp = localbuf,	/* start of current component */
815 			*dp = intobuf;	/* current end of resulting path (but lazy) */
816 
817 		while (*fp != '\0') {
818 			/* for each path component: */
819 			char	*sp = strchr(fp, '/');	/* end of current component */
820 
821 			if (sp != NULL)
822 				*sp = '\0';
823 			dp += strlen(dp);	/* move to end of resulting path */
824 			if (*fp == '\0' || strcmp(fp, ".") == 0) {
825 				/* ignore this component */
826 			} else if (strcmp(fp, "..") == 0) {
827 				/* Strip one directory name from "intobuf".
828 				 * Assume that intobuf[0] == '/'.
829 				 * ??? is this correct for the Mac?  CP/M?
830 				 */
831 				do ; while (dp > intobuf+1 && *--dp != '/');
832 				*dp = '\0';
833 			} else {
834 				if (dp!=intobuf && dp[-1]!='/')
835 					*dp++ = '/';
836 				strcpy(dp, fp);
837 			}
838 			if (sp == NULL)
839 				break;
840 			fp = sp + 1;
841 		}
842 	}
843 }
844 
845 #ifdef UNIX
846 int	CreatMode = DFLT_MODE;	/* VAR: default mode for creat'ing files */
847 #endif
848 
849 private void
DoWriteReg(app)850 DoWriteReg(app)
851 bool	app;
852 {
853 	char	fnamebuf[FILESIZE];
854 	Mark	*mp = CurMark();
855 	File	*fp;
856 
857 	/* Won't get here if there isn't a Mark */
858 	(void) ask_file((char *)NULL, (char *)NULL, fnamebuf);
859 
860 	if (!app) {
861 		filemunge(fnamebuf);
862 		chk_divergence((Buffer *)NULL, fnamebuf, "write-region");
863 #ifdef BACKUPFILES
864 		if (BkupOnWrite)
865 			file_backup(fnamebuf);
866 #endif
867 	}
868 
869 	fp = open_file(fnamebuf, iobuff, app ? F_APPEND|F_TELLALL : F_WRITE|F_TELLALL, YES);
870 	putreg(fp, mp->m_line, mp->m_char, curline, curchar, YES);
871 	close_file(fp);
872 }
873 
874 void
WrtReg()875 WrtReg()
876 {
877 	DoWriteReg(NO);
878 }
879 
880 void
AppReg()881 AppReg()
882 {
883 	DoWriteReg(YES);
884 }
885 
886 bool	OkayBadChars = NO;	/* VAR: allow bad characters in filenames created by JOVE */
887 
888 void
JWriteFile()889 JWriteFile()
890 {
891 	char
892 		fnamebuf[FILESIZE];
893 
894 #ifdef MAC
895 	if (Macmode) {
896 		if (pfile(fnamebuf) == NULL)
897 			return;
898 	} else
899 #endif /* MAC */
900 		(void) ask_file((char *)NULL, curbuf->b_fname, fnamebuf);
901 	/* Don't allow bad characters when creating new files. */
902 	if (!OkayBadChars
903 	&& (curbuf->b_fname==NULL || strcmp(curbuf->b_fname, fnamebuf) != 0))
904 	{
905 #ifdef UNIX
906 		static const char	badchars[] = "!$^&*()~`{}\"'\\|<>? ";
907 #endif
908 #ifdef MSDOS
909 		static const char	badchars[] = "*|<>? ";
910 #endif
911 #ifdef WIN32
912 		static const char	badchars[] = "*|<>?\"";
913 #endif
914 #ifdef MAC
915 		static const char	badchars[] = ":";
916 #endif
917 		register char	*cp = fnamebuf;
918 		register char	c;
919 
920 		while ((c = *cp++) != '\0')
921 			if (!jisprint(c) || strchr(badchars, c)!=NULL)
922 				complain("'%p': bad character in filename.", c);
923 	}
924 
925 	filemunge(fnamebuf);
926 	chk_divergence(curbuf, fnamebuf, "write");
927 	curbuf->b_type = B_FILE;	/* in case it wasn't before */
928 	setfname(curbuf, fnamebuf);
929 	file_write(fnamebuf, NO);
930 }
931 
932 void
WtModBuf()933 WtModBuf()
934 {
935 	if (!ModBufs(NO))
936 		message("[No buffers need saving]");
937 	else
938 		put_bufs(is_an_arg());
939 }
940 
941 void
put_bufs(askp)942 put_bufs(askp)
943 bool	askp;
944 {
945 	register Buffer	*oldb = curbuf,
946 			*b;
947 
948 	for (b = world; b != NULL; b = b->b_next) {
949 		if (!IsModified(b) || b->b_type != B_FILE)
950 			continue;
951 		SetBuf(b);	/* Make this current Buffer */
952 		if (curbuf->b_fname == NULL) {
953 			char	*newname;
954 
955 			newname = ask(NullStr, "Buffer \"%s\" needs a file name; type Return to skip: ", b->b_name);
956 			if (*newname == '\0')
957 				continue;
958 			setfname(b, newname);
959 		}
960 		if (askp && !yes_or_no_p("Write %s? ", curbuf->b_fname))
961 			continue;
962 		SaveFile();
963 	}
964 	SetBuf(oldb);
965 }
966 
967 /* Open file FNAME supplying the buffer IO routine with buffer BUF.
968    HOW is F_READ, F_WRITE or F_APPEND.  HOW can have the F_TELLALL
969    flag to request the displaying of I/O status.  Only if COMPLAINIFBAD
970    will a complain diagnostic be produced for a failed open.
971 
972    NOTE:  This opens the pr_name(fname, NO) of fname.  That is, FNAME
973 	  is usually an entire pathname, which can be slow when the
974 	  pathname is long and there are lots of symbolic links along
975 	  the way (which has become very common in my experience).  So,
976 	  this speeds up opens file names in the local directory.  It
977 	  will not speed up things like "../scm/foo.scm" simply because
978 	  by the time we get here that's already been expanded to an
979 	  absolute pathname.  But this is a start.
980    */
981 
982 File *
open_file(fname,buf,how,complainifbad)983 open_file(fname, buf, how, complainifbad)
984 register char	*fname;
985 char	*buf;
986 register int	how;
987 bool	complainifbad;
988 {
989 	register File	*fp;
990 
991 	io_chars = 0;
992 	io_lines = 0;
993 
994 	fp = f_open(pr_name(fname, NO), how, buf, LBSIZE);
995 	if (fp == NULL) {
996 		if (complainifbad) {
997 			message(IOerr((F_MODE(how) == F_READ) ? "open" : "create",
998 			    fname));
999 			complain((char *)NULL);
1000 		}
1001 	} else {
1002 		const char	*rd_only = NullStr;
1003 
1004 #ifndef MAC
1005 		if (access(pr_name(fname, NO), W_OK) == -1 && errno != ENOENT) {
1006 			rd_only = " [Read only]";
1007 			fp->f_flags |= F_READONLY;
1008 		}
1009 #endif
1010 		if (how & F_TELLALL)
1011 			f_mess("\"%s\"%s", pr_name(fname, YES), rd_only);
1012 	}
1013 	return fp;
1014 }
1015 
1016 /* We're about to write to a file (save-file, write-region, append-region,
1017  * or write-file):  query user when it is an existing but different file.
1018  * Note: even if we are doing an append-region or write-region,
1019  * we assume that the current buffer's file is fair game.
1020  */
1021 private void
filemunge(newname)1022 filemunge(newname)
1023 char	*newname;
1024 {
1025 	if (do_stat(newname, curbuf, DS_NONE) != curbuf && was_file) {
1026 		rbell();
1027 		confirm("\"%s\" already exists; overwrite it? ", newname);
1028 		/* in case user has fiddled some more, refresh stat cache */
1029 		(void) do_stat(newname, (Buffer *)NULL, DS_NONE);
1030 	}
1031 }
1032 
1033 /* Check to see if the file has been modified since it was
1034    last visited or saved.  If so, make sure they know what
1035    they're doing.  Buffer "thisbuf" is tested for divergence;
1036    if thisbuf is NULL, the first buffer with the file is tested.
1037 
1038    To avoid excessive stats, we presume that the stat cache is
1039    already primed.  We refresh it if we get a confirmation because
1040    it left  the user a window of opportunity for fiddling.
1041    */
1042 
1043 private void
chk_divergence(thisbuf,fname,how)1044 chk_divergence(thisbuf, fname, how)
1045 Buffer	*thisbuf;
1046 char	*fname,
1047 	*how;
1048 {
1049 	static const char	mesg[] = "Shall I go ahead and %s anyway? ";
1050 	Buffer	*buf = do_stat(fname, thisbuf, DS_REUSE);
1051 
1052 	if (buf != NULL && buf->b_diverged) {
1053 		rbell();
1054 		redisplay();	/* Ring that bell! */
1055 		TOstart("Warning");
1056 		Typeout("\"%s\" now saved on disk is not what you last", pr_name(fname, YES));
1057 		Typeout("visited or saved.  Probably someone else is editing");
1058 		Typeout("your file at the same time.");
1059 		Typeout(NullStr);
1060 		Typeout("Type \"y\" if I should %s, anyway.", how);
1061 		f_mess(mesg, how);
1062 		TOstop();
1063 		confirm(mesg, how);
1064 		/* in case user has fiddled some more, refresh stat cache */
1065 		(void) do_stat(fname, (Buffer *)NULL, DS_NONE);
1066 	}
1067 }
1068 
1069 void
file_write(fname,app)1070 file_write(fname, app)
1071 char	*fname;
1072 bool	app;
1073 {
1074 	File	*fp;
1075 
1076 #ifdef BACKUPFILES
1077 	if (!app && BkupOnWrite)
1078 		file_backup(fname);
1079 #endif
1080 
1081 	fp = open_file(fname, iobuff, app ? F_APPEND|F_TELLALL : F_WRITE|F_TELLALL, YES);
1082 
1083 	if (EndWNewline) {	/* Make sure file ends with a newLine */
1084 		Bufpos	save;
1085 
1086 		DOTsave(&save);
1087 		ToLast();
1088 		if (length(curline))	/* Not a blank Line */
1089 			LineInsert(1);
1090 		SetDot(&save);
1091 	}
1092 	putreg(fp, curbuf->b_first, 0, curbuf->b_last, length(curbuf->b_last), NO);
1093 	close_file(fp);
1094 	(void) do_stat(curbuf->b_fname, curbuf, DS_SET);
1095 	unmodify();
1096 }
1097 
1098 void
JReadFile()1099 JReadFile()
1100 {
1101 	char
1102 		fnamebuf[FILESIZE];
1103 	bool
1104 		reloading;
1105 	Window
1106 		*wp;
1107 	int
1108 		curlineno;
1109 
1110 #ifdef MAC
1111 	if (Macmode) {
1112 		if (gfile(fnamebuf) == NULL)
1113 			return;
1114 	} else
1115 #endif /* MAC */
1116 		(void) ask_file((char *)NULL, curbuf->b_fname, fnamebuf);
1117 
1118 	if (IsModified(curbuf)
1119 	&& yes_or_no_p("Shall I make your changes to \"%s\" permanent? ", curbuf->b_name))
1120 		SaveFile();
1121 
1122 	(void) do_stat(fnamebuf, (Buffer *)NULL, DS_NONE);	/* prime stat cache */
1123 	chk_divergence(curbuf, fnamebuf, "read");
1124 
1125 	reloading = do_stat(fnamebuf, curbuf, DS_REUSE) == curbuf;
1126 
1127 	/* preserve w_line in each window into curbuf */
1128 	wp = fwind;
1129 	do {
1130 		if (wp->w_bufp == curbuf) {
1131 			/* hijack w_topnum -- nobody was using it anyway */
1132 			wp->w_topnum = reloading? LinesTo(curbuf->b_first, wp->w_line) : 0;
1133 			wp->w_top = wp->w_line = NULL;
1134 			wp->w_flags |= W_TOPGONE;
1135 		}
1136 	} while ((wp = wp->w_next) != fwind);
1137 
1138 	curlineno = reloading? LinesTo(curbuf->b_first, curline) : 0;
1139 
1140 	buf_clear(curbuf);
1141 	setfname(curbuf, fnamebuf);
1142 	read_file(fnamebuf, NO);
1143 
1144 	/* recover dot in each window into curbuf */
1145 	wp = fwind;
1146 	do {
1147 		if (wp->w_bufp == curbuf) {
1148 			wp->w_top = curbuf->b_first;
1149 			wp->w_line = next_line(curbuf->b_first, wp->w_topnum);
1150 		}
1151 	} while ((wp = wp->w_next) != fwind);
1152 
1153 	SetLine(next_line(curbuf->b_first, curlineno));
1154 }
1155 
1156 void
InsFile()1157 InsFile()
1158 {
1159 	char
1160 		fnamebuf[FILESIZE];
1161 #ifdef MAC
1162 	if (Macmode) {
1163 		if (gfile(fnamebuf) == NULL)
1164 			return;
1165 	} else
1166 #endif /* MAC */
1167 		(void) ask_file((char *)NULL, curbuf->b_fname, fnamebuf);
1168 	read_file(fnamebuf, YES);
1169 }
1170 
1171 #include "temp.h"
1172 
1173 bool	DOLsave = NO;	/* Do Lsave flag.  If lines aren't being saved
1174 			   when you think they should have been, this
1175 			   flag is probably not being set, or is being
1176 			   cleared before lsave() was called. */
1177 
1178 private int	nleft,	/* number of good characters left in current block */
1179 		tmpfd = -1;
1180 daddr	DFree = 1;	/* pointer to end of tmp file */
1181 private char	*tfname;	/* pathname of tempfile where buffer lines go */
1182 
1183 private void
tmpinit()1184 tmpinit()
1185 {
1186 	char	buf[FILESIZE];
1187 
1188 	swritef(buf, sizeof(buf), "%s/%s", TmpDir,
1189 #ifdef MAC
1190 		".joveXXX"	/* must match string in mac.c:Ffilter() */
1191 #else
1192 		"joveXXXXXX"
1193 #endif
1194 		);
1195 	tfname = copystr(buf);
1196 #ifdef NO_MKSTEMP
1197 	tfname = mktemp(tfname);
1198 #endif
1199 #ifndef MSFILESYSTEM
1200 #ifndef NO_MKSTEMP
1201 	tmpfd = mkstemp(tfname);
1202 #else
1203 	(void) close(creat(tfname, 0600));
1204 	tmpfd = open(tfname, 2);
1205 #endif
1206 #else /* MSFILESYSTEM */
1207 	tmpfd = open(tfname, O_CREAT|O_EXCL|O_BINARY|O_RDWR, S_IWRITE|S_IREAD);
1208 #endif /* MSFILESYSTEM */
1209 	if (tmpfd == -1)
1210 		complain("Warning: cannot create tmp file! %s", strerror(errno));
1211 #ifdef RECOVER
1212 	rectmpname(strrchr(tfname, '/') + 1);
1213 #endif
1214 }
1215 
1216 /* Close tempfile before execing a child process.
1217  * Since we might be vforking, we must not change any variables
1218  * (in particular tmpfd).
1219  */
1220 void
tmpclose()1221 tmpclose()
1222 {
1223 	if (tmpfd != -1)
1224 		(void) close(tmpfd);
1225 }
1226 
1227 /* Close and remove tempfile before exiting. */
1228 
1229 void
tmpremove()1230 tmpremove()
1231 {
1232 	if (tmpfd != -1) {
1233 		tmpclose();
1234 		(void) unlink(tfname);
1235 	}
1236 }
1237 
1238 /* get a line at `tl' in the tmp file into `buf' which should be LBSIZE
1239    long */
1240 
1241 /* A prototyped definition is needed because daddr might be affected
1242  * by default argument promotions.
1243  */
1244 
1245 int	Jr_Len;		/* length of Just Read Line */
1246 
1247 void
1248 #ifdef USE_PROTOTYPES
get_line(daddr addr,register char * buf)1249 get_line proto((daddr addr, register char *buf))
1250 #else
1251 get_line(addr, buf)
1252 daddr	addr;
1253 register char	*buf;
1254 #endif
1255 {
1256 	register char	*bp,
1257 			*lp;
1258 
1259 	lp = buf;
1260 	bp = getblock(addr, NO);
1261 	do ; while ((*lp++ = *bp++) != '\0');
1262 	Jr_Len = (lp - buf) - 1;
1263 }
1264 
1265 /* Put `buf' and return the disk address */
1266 
1267 daddr
putline(buf)1268 putline(buf)
1269 char	*buf;
1270 {
1271 	register char	*bp,
1272 			*lp;
1273 	register int	nl;
1274 	daddr	line_daddr;
1275 
1276 	lp = buf;
1277 	bp = getblock(DFree, YES);
1278 	nl = nleft;
1279 	while ((*bp = *lp++) != '\0') {
1280 		if (*bp++ == '\n') {
1281 			*--bp = '\0';
1282 			break;
1283 		}
1284 		if (--nl == 0) {
1285 			DFree = blk_chop(DFree) + BLK_CHNKS;
1286 			bp = getblock(DFree, YES);
1287 			lp = buf;	/* start over ... */
1288 			nl = nleft;
1289 		}
1290 	}
1291 	line_daddr = DFree;
1292 	DFree += REQ_CHNKS(lp - buf);	/* (lp - buf) includes the null */
1293 	return line_daddr;
1294 }
1295 
1296 /* The theory is that the critical section of code inside this procedure
1297    will never cause a problem to occur.  Basically, we need to ensure
1298    that two blocks are in memory at the same time, but I think that
1299    this can never screw up. */
1300 
1301 #define lockblock(addr)
1302 #define unlockblock(addr)
1303 
1304 private bool
f_getputl(line,fp)1305 f_getputl(line, fp)
1306 LinePtr	line;
1307 register File	*fp;
1308 {
1309 	register char	*bp;
1310 	register ZXchar	c;
1311 	register int
1312 			nl,
1313 			room = LBSIZE-1;
1314 	char		*base;
1315 
1316 	base = bp = getblock(DFree, YES);
1317 	nl = nleft;
1318 	for (;;) {
1319 		c = f_getc(fp);
1320 		if (c == EOF)
1321 			break;
1322 
1323 		/* We can't store NUL in our buffer, so ignore it.
1324 		 * Similarly, we can only store characters less than NCHARS.
1325 		 * Of course, with a little ingenuity we could store NUL:
1326 		 * NUL could be represented by \n.
1327 		 */
1328 		if (c == '\0'
1329 #if NCHARS != UCHAR_ROOF
1330 		|| c >= NCHARS
1331 #endif
1332 		)
1333 			continue;
1334 
1335 		if (c == EOL) {
1336 #ifdef USE_CRLF
1337 			/* a CR followed by an EOL is treated as a NL.
1338 			 * Bug: the line-buffer is effectively shortened by one character.
1339 			 */
1340 			if (bp != base && bp[-1] == '\r') {
1341 				bp -= 1;
1342 				room += 1;
1343 			}
1344 #endif /* USE_CRLF */
1345 			break;
1346 		}
1347 		if (--room < 0)
1348 			break;	/* no room for this character */
1349 
1350 		if (--nl == 0) {
1351 			char	*newbp;
1352 			size_t	nbytes;
1353 			daddr		old_free_block = blk_chop(DFree);
1354 
1355 			lockblock(old_free_block);
1356 			DFree = old_free_block + BLK_CHNKS;
1357 			nbytes = bp - base;
1358 			newbp = getblock(DFree, YES);
1359 			nl = nleft;
1360 			byte_copy(base, newbp, nbytes);
1361 			bp = newbp + nbytes;
1362 			base = newbp;
1363 			unlockblock(old_free_block);
1364 		}
1365 		*bp++ = c;
1366 	}
1367 	*bp++ = '\0';
1368 	line->l_dline = DFree;
1369 	DFree += REQ_CHNKS(bp - base);
1370 	if (room < 0) {
1371 		add_mess(" [Line too long]");
1372 		rbell();
1373 		return YES;
1374 	}
1375 	if (c == EOF) {
1376 		if (--bp != base)
1377 			add_mess(" [Incomplete last line]");
1378 		return YES;
1379 	}
1380 	io_lines += 1;
1381 	return NO;
1382 }
1383 
1384 typedef struct block {
1385 	char	b_dirty;	/* (bool) */
1386 	daddr	b_bno;
1387 	char	b_buf[JBUFSIZ];
1388 	struct block
1389 		*b_LRUnext,
1390 		*b_LRUprev,
1391 		*b_HASHnext;
1392 } Block;
1393 
1394 #define HASHSIZE	7	/* Primes work best (so I'm told) */
1395 #define B_HASH(bno)	((bno) % HASHSIZE)
1396 
1397 #ifdef MALLOC_CACHE
1398 private Block	*b_cache = NULL;
1399 #else
1400 private Block	b_cache[NBUF];
1401 #endif
1402 
1403 private Block
1404 	*bht[HASHSIZE],		/* Block hash table. Must be zero initially */
1405 	*f_block = NULL,
1406 	*l_block = NULL;
1407 
1408 private daddr	next_bno = 0;
1409 
1410 private void	(*blkio) ptrproto((Block *, SSIZE_T (*) ptrproto((int, UnivPtr, size_t))));
1411 
1412 /* Needed to comfort dumb MS Visual C */
1413 private void real_blkio ptrproto((Block *, SSIZE_T (*) ptrproto((int, UnivPtr, size_t))));
1414 
1415 private void
real_blkio(b,iofcn)1416 real_blkio(b, iofcn)
1417 register Block	*b;
1418 register SSIZE_T	(*iofcn) ptrproto((int, UnivPtr, size_t));
1419 {
1420 	(void) lseek(tmpfd, (long)b->b_bno << JLGBUFSIZ, 0);
1421 	if ((*iofcn)(tmpfd, (UnivPtr) b->b_buf, (size_t)JBUFSIZ) != JBUFSIZ)
1422 		error("[Tmp file %s error: to continue editing would be dangerous]",
1423 			(iofcn == read) ? "READ" : "WRITE");
1424 }
1425 
1426 /* Needed to comfort dumb MS Visual C */
1427 private void fake_blkio ptrproto((Block *, SSIZE_T (*) ptrproto((int, UnivPtr, size_t))));
1428 
1429 private void
fake_blkio(b,iofcn)1430 fake_blkio(b, iofcn)
1431 register Block	*b;
1432 register SSIZE_T	(*iofcn) ptrproto((int, UnivPtr, size_t));
1433 {
1434 	tmpinit();
1435 	blkio = real_blkio;
1436 	real_blkio(b, iofcn);
1437 }
1438 
1439 void
d_cache_init()1440 d_cache_init()
1441 {
1442 	register Block	*bp,	/* Block pointer */
1443 			**hp;	/* Hash pointer */
1444 	register daddr	bno;
1445 
1446 #ifdef MALLOC_CACHE
1447 	if (b_cache == NULL) {
1448 		b_cache = (Block *) calloc((size_t)NBUF,sizeof(Block));
1449 		if (b_cache == NULL)
1450 			error("cannot allocate buffer cache");
1451 	}
1452 #endif /* MALLOC_CACHE */
1453 
1454 	for (bp = b_cache, bno = NBUF; bno-- > 0; bp++) {
1455 		bp->b_dirty = NO;
1456 		bp->b_bno = bno;
1457 		if (l_block == NULL)
1458 			l_block = bp;
1459 		bp->b_LRUprev = NULL;
1460 		bp->b_LRUnext = f_block;
1461 		if (f_block != NULL)
1462 			f_block->b_LRUprev = bp;
1463 		f_block = bp;
1464 
1465 		bp->b_HASHnext = *(hp = &bht[B_HASH(bno)]);
1466 		*hp = bp;
1467 	}
1468 	blkio = fake_blkio;
1469 }
1470 
1471 void
SyncTmp()1472 SyncTmp()
1473 {
1474 	register Block	*b;
1475 #ifdef MSDOS
1476 	register daddr	bno = 0;
1477 
1478 	/* sync the blocks in order, for file systems that don't allow
1479 	   holes (MSDOS).  Perhaps this benefits floppy-based file systems. */
1480 
1481 	for (bno = 0; bno < next_bno; bno++) {
1482 		if ((b = lookup_block(bno)) != NULL && b->b_dirty) {
1483 			(*blkio)(b, (SSIZE_T (*) ptrproto((int, UnivPtr, size_t)))write);
1484 			b->b_dirty = NO;
1485 		}
1486 	}
1487 #else /* !MSDOS */
1488 	for (b = f_block; b != NULL; b = b->b_LRUnext)
1489 		if (b->b_dirty) {
1490 			(*blkio)(b, (SSIZE_T (*) ptrproto((int, UnivPtr, size_t)))write);
1491 			b->b_dirty = NO;
1492 		}
1493 #endif /* !MSDOS */
1494 }
1495 
1496 /* A prototyped definition is needed because daddr might be affected
1497  * by default argument promotions.
1498  */
1499 
1500 private Block *
1501 #ifdef USE_PROTOTYPES
lookup_block(register daddr bno)1502 lookup_block proto((register daddr bno))
1503 #else
1504 lookup_block(bno)
1505 register daddr	bno;
1506 #endif
1507 {
1508 	register Block	*bp;
1509 
1510 	for (bp = bht[B_HASH(bno)]; bp != NULL; bp = bp->b_HASHnext)
1511 		if (bp->b_bno == bno)
1512 			break;
1513 	return bp;
1514 }
1515 
1516 private void
LRUunlink(b)1517 LRUunlink(b)
1518 register Block	*b;
1519 {
1520 	if (b->b_LRUprev == NULL)
1521 		f_block = b->b_LRUnext;
1522 	else
1523 		b->b_LRUprev->b_LRUnext = b->b_LRUnext;
1524 	if (b->b_LRUnext == NULL)
1525 		l_block = b->b_LRUprev;
1526 	else
1527 		b->b_LRUnext->b_LRUprev = b->b_LRUprev;
1528 }
1529 
1530 private Block *
b_unlink(bp)1531 b_unlink(bp)
1532 register Block	*bp;
1533 {
1534 	register Block	*hp,
1535 			*prev = NULL;
1536 
1537 	LRUunlink(bp);
1538 	/* Now that we have the block, we remove it from its position
1539 	   in the hash table, so we can THEN put it somewhere else with
1540 	   it's new block assignment. */
1541 
1542 	for (hp = bht[B_HASH(bp->b_bno)]; hp != NULL; prev = hp, hp = hp->b_HASHnext)
1543 		if (hp == bp)
1544 			break;
1545 	if (hp == NULL) {
1546 		writef("\rBlock %ld missing!", (long)bp->b_bno);
1547 		finish(0);
1548 	}
1549 	if (prev)
1550 		prev->b_HASHnext = hp->b_HASHnext;
1551 	else
1552 		bht[B_HASH(bp->b_bno)] = hp->b_HASHnext;
1553 
1554 	if (bp->b_dirty) {	/* do, now, the delayed write */
1555 		(*blkio)(bp, (SSIZE_T (*) ptrproto((int, UnivPtr, size_t)))write);
1556 		bp->b_dirty = NO;
1557 	}
1558 
1559 	return bp;
1560 }
1561 
1562 /* Get a block which contains at least part of the line with the address
1563    atl.  Returns a pointer to the block and sets the global variable
1564    nleft (number of good characters left in the buffer). */
1565 
1566 /* A prototyped definition is needed because daddr might be affected
1567  * by default argument promotions.
1568  */
1569 
1570 private char *
1571 #ifdef USE_PROTOTYPES
getblock(daddr atl,bool IsWrite)1572 getblock proto((daddr atl, bool IsWrite))
1573 #else
1574 getblock(atl, IsWrite)
1575 daddr	atl;
1576 bool	IsWrite;
1577 #endif
1578 {
1579 	register daddr	bno,
1580 			off;
1581 	register Block	*bp;
1582 	static Block	*lastb = NULL;
1583 
1584 	bno = da_to_bno(atl);
1585 	off = da_to_off(atl);
1586 	/* We don't allow block number MAX_BLOCKS-1 to be used because
1587 	 * NOWHERE_DADDR and NOTYET_DADDR must not be valid disk references,
1588 	 * and we want to prevent space overflow from being undetected
1589 	 * through arithmetic overflow.
1590 	 */
1591 	if (bno >=  MAX_BLOCKS-1)
1592 		error("Tmp file too large.  Get help!");
1593 	nleft = JBUFSIZ - off;
1594 	if (lastb != NULL && lastb->b_bno == bno) {
1595 		bp = lastb;	/* same as last time */
1596 	} else if ((bp = lookup_block(bno)) != NULL) {
1597 		/* The requested block already lives in memory, so we move
1598 		   it to the end of the LRU list (making it Most Recently Used)
1599 		   and then return a pointer to it. */
1600 		if (bp != l_block) {
1601 			LRUunlink(bp);
1602 			if (l_block == NULL)
1603 				f_block = bp;
1604 			else
1605 				l_block->b_LRUnext = bp;
1606 			bp->b_LRUprev = l_block;
1607 			l_block = bp;
1608 			bp->b_LRUnext = NULL;
1609 		}
1610 		if (bno >= next_bno)
1611 			next_bno = bno + 1;
1612 	} else {
1613 		/* The block we want doesn't reside in memory so we take the
1614 		   least recently used clean block (if there is one) and use
1615 		   it.  */
1616 		bp = f_block;
1617 		if (bp->b_dirty)	/* The best block is dirty ... */
1618 			SyncTmp();
1619 
1620 		bp = b_unlink(bp);
1621 		if (l_block == NULL)
1622 			f_block = bp;
1623 		else
1624 			l_block->b_LRUnext = bp;	/* Place it at the end ... */
1625 		bp->b_LRUprev = l_block;
1626 		l_block = bp;
1627 		bp->b_LRUnext = NULL;		/* so it's Most Recently Used */
1628 
1629 		bp->b_dirty = NO;
1630 		bp->b_bno = bno;
1631 		bp->b_HASHnext = bht[B_HASH(bno)];
1632 		bht[B_HASH(bno)] = bp;
1633 
1634 		/* Get the current contents of the block UNLESS this is a new
1635 		   block that's never been looked at before, i.e., it's past
1636 		   the end of the tmp file. */
1637 
1638 		if (bno < next_bno)
1639 			(*blkio)(bp, read);
1640 		else
1641 			next_bno = bno + 1;
1642 	}
1643 	lastb = bp;
1644 	bp->b_dirty |= IsWrite;
1645 	return bp->b_buf + off;
1646 }
1647 
1648 char *
lbptr(line)1649 lbptr(line)
1650 LinePtr	line;
1651 {
1652 	return getblock(line->l_dline, NO);
1653 }
1654 
1655 /* save the current contents of linebuf, if it has changed */
1656 
1657 void
lsave()1658 lsave()
1659 {
1660 	if (curbuf == NULL || !DOLsave)	/* Nothing modified recently */
1661 		return;
1662 
1663 	if (strcmp(lbptr(curline), linebuf) != 0)
1664 		SavLine(curline, linebuf);	/* Put linebuf on the disk. */
1665 	DOLsave = NO;
1666 }
1667 
1668 #ifdef BACKUPFILES
1669 private void
file_backup(fname)1670 file_backup(fname)
1671 char *fname;
1672 {
1673 # ifndef MSFILESYSTEM
1674 	SSIZE_T	rr;
1675 	int
1676 		ffd,
1677 		bffd;
1678 	char
1679 		buf[JBUFSIZ],
1680 		bfname[FILESIZE];
1681 
1682 	/* build backup file name */
1683 	{
1684 		char	*s = strrchr(fname, '/');
1685 		size_t	dirlen = (s == NULL)? 0 : s + 1 - fname;
1686 
1687 		strcpy(bfname, fname);
1688 		swritef(bfname+dirlen, (size_t) (sizeof(bfname) - dirlen), "#%s~",
1689 			fname+dirlen);
1690 	}
1691 
1692 	if ((ffd = open(fname, 0)) < 0)
1693 		return;	/* cannot open original file: nothing to backup, we assume */
1694 
1695 	/* create backup file with same mode as input file */
1696 	{
1697 #  ifdef MAC
1698 		int	mode = CreatMode;	/* dummy */
1699 #  else
1700 		struct stat statbuf;
1701 		int	mode = fstat(ffd, &statbuf) != 0? CreatMode : statbuf.st_mode;
1702 #  endif
1703 
1704 		if ((bffd = creat(bfname, mode)) < 0) {
1705 			int	e = errno;
1706 
1707 			(void) close(ffd);
1708 			complain("[cannot create backup \"%s\": %d %s]",
1709 				bfname, e, strerror(e));
1710 		}
1711 	}
1712 
1713 	/* copy the contents */
1714 	while ((rr = read(ffd, (UnivPtr) buf, sizeof(buf))) > 0) {
1715 		char	*p = buf;
1716 
1717 		while (rr > 0) {
1718 			SSIZE_T	wr = write(bffd, (UnivPtr) p, (size_t) rr);
1719 
1720 			if (wr < 0) {
1721 				int e = errno;
1722 
1723 				close(bffd);
1724 				close(ffd);
1725 				complain("[error writing backup: %d %s]", e, strerror(e));
1726 			}
1727 			p += wr;
1728 			rr -= wr;
1729 		}
1730 	}
1731 
1732 	if (rr < 0 || close(ffd) != 0)
1733 		complain("[error reading \"%s\": %d %s]", fname, errno, strerror(errno));
1734 #  ifdef USE_FSYNC
1735 	if (fsync(bffd) != 0) {
1736 		int	e = errno;
1737 
1738 		(void) close(bffd);
1739 		complain("[error fsyncing backup: %d %s]", e, strerror(e));
1740 	}
1741 #  endif /* USE_FSYNC */
1742 	if (close(bffd) != 0)
1743 		complain("[error closing backup: %d %s]", errno, strerror(errno));
1744 
1745 # else /* MSFILESYSTEM */
1746 	/* This code is designed to fit withing the 8.3 limitation of
1747 	 * MSDOS ("FAT" -- huh!) file systems.  Even though newer versions
1748 	 * of these APIs (Win32) may support longer file names, we may still
1749 	 * be dealing with a FAT file system.
1750 	 */
1751 	char	*dot,
1752 			*slash,
1753 			tmp[FILESIZE];
1754 
1755 	strcpy(tmp, fname);
1756 	slash = basename(tmp);
1757 	if ((dot = strrchr(slash, '.')) != NULL) {
1758 		if (stricmp(dot,".bak") != 0)
1759 			return;
1760 		*dot = '\0';
1761 	}
1762 	strcat(tmp, ".bak");
1763 	unlink(tmp);
1764 	if (rename(fname, tmp) != 0)
1765 		complain("[cannot rename to \"%s\": %s]", tmp, strerror(errno));
1766 # endif /* MSFILESYSTEM */
1767 }
1768 #endif /* BACKUPFILES */
1769