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