home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / jove-4.16-src.tgz / tar.out / bsd / jove / io.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  39KB  |  1,763 lines

  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
  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
  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 *
  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
  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 *
  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
  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
  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
  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
  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
  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
  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 *
  440. pwd()
  441. {
  442.     return PWD;
  443. }
  444.  
  445. char *
  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
  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 *
  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 *
  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
  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
  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
  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
  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
  611. prCWD()
  612. {
  613.     f_mess(": %f => \"%s\"", PWD);
  614.     stickymsg = YES;
  615. }
  616.  
  617. private void
  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
  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
  659. Pushlibd()
  660. {
  661.     char    dirbuf[FILESIZE];
  662.  
  663.     PathParse(ShareDir, dirbuf);
  664.     doPushd(dirbuf);
  665. }
  666.  
  667. void
  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
  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
  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
  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
  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
  875. WrtReg()
  876. {
  877.     DoWriteReg(NO);
  878. }
  879.  
  880. void
  881. AppReg()
  882. {
  883.     DoWriteReg(YES);
  884. }
  885.  
  886. bool    OkayBadChars = NO;    /* VAR: allow bad characters in filenames created by JOVE */
  887.  
  888. void
  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
  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
  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 *
  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
  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
  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
  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
  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
  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
  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.     tfname = mktemp(tfname);
  1197. #ifndef MSFILESYSTEM
  1198.     (void) close(creat(tfname, 0600));
  1199.     tmpfd = open(tfname, 2);
  1200. #else /* MSFILESYSTEM */
  1201.     tmpfd = open(tfname, O_CREAT|O_EXCL|O_BINARY|O_RDWR, S_IWRITE|S_IREAD);
  1202. #endif /* MSFILESYSTEM */
  1203.     if (tmpfd == -1)
  1204.         complain("Warning: cannot create tmp file! %s", strerror(errno));
  1205. #ifdef RECOVER
  1206.     rectmpname(strrchr(tfname, '/') + 1);
  1207. #endif
  1208. }
  1209.  
  1210. /* Close tempfile before execing a child process.
  1211.  * Since we might be vforking, we must not change any variables
  1212.  * (in particular tmpfd).
  1213.  */
  1214. void
  1215. tmpclose()
  1216. {
  1217.     if (tmpfd != -1)
  1218.         (void) close(tmpfd);
  1219. }
  1220.  
  1221. /* Close and remove tempfile before exiting. */
  1222.  
  1223. void
  1224. tmpremove()
  1225. {
  1226.     if (tmpfd != -1) {
  1227.         tmpclose();
  1228.         (void) unlink(tfname);
  1229.     }
  1230. }
  1231.  
  1232. /* get a line at `tl' in the tmp file into `buf' which should be LBSIZE
  1233.    long */
  1234.  
  1235. /* A prototyped definition is needed because daddr might be affected
  1236.  * by default argument promotions.
  1237.  */
  1238.  
  1239. int    Jr_Len;        /* length of Just Read Line */
  1240.  
  1241. void
  1242. #ifdef USE_PROTOTYPES
  1243. getline proto((daddr addr, register char *buf))
  1244. #else
  1245. getline(addr, buf)
  1246. daddr    addr;
  1247. register char    *buf;
  1248. #endif
  1249. {
  1250.     register char    *bp,
  1251.             *lp;
  1252.  
  1253.     lp = buf;
  1254.     bp = getblock(addr, NO);
  1255.     do ; while ((*lp++ = *bp++) != '\0');
  1256.     Jr_Len = (lp - buf) - 1;
  1257. }
  1258.  
  1259. /* Put `buf' and return the disk address */
  1260.  
  1261. daddr
  1262. putline(buf)
  1263. char    *buf;
  1264. {
  1265.     register char    *bp,
  1266.             *lp;
  1267.     register int    nl;
  1268.     daddr    line_daddr;
  1269.  
  1270.     lp = buf;
  1271.     bp = getblock(DFree, YES);
  1272.     nl = nleft;
  1273.     while ((*bp = *lp++) != '\0') {
  1274.         if (*bp++ == '\n') {
  1275.             *--bp = '\0';
  1276.             break;
  1277.         }
  1278.         if (--nl == 0) {
  1279.             DFree = blk_chop(DFree) + BLK_CHNKS;
  1280.             bp = getblock(DFree, YES);
  1281.             lp = buf;    /* start over ... */
  1282.             nl = nleft;
  1283.         }
  1284.     }
  1285.     line_daddr = DFree;
  1286.     DFree += REQ_CHNKS(lp - buf);    /* (lp - buf) includes the null */
  1287.     return line_daddr;
  1288. }
  1289.  
  1290. /* The theory is that the critical section of code inside this procedure
  1291.    will never cause a problem to occur.  Basically, we need to ensure
  1292.    that two blocks are in memory at the same time, but I think that
  1293.    this can never screw up. */
  1294.  
  1295. #define lockblock(addr)
  1296. #define unlockblock(addr)
  1297.  
  1298. private bool
  1299. f_getputl(line, fp)
  1300. LinePtr    line;
  1301. register File    *fp;
  1302. {
  1303.     register char    *bp;
  1304.     register ZXchar    c;
  1305.     register int
  1306.             nl,
  1307.             room = LBSIZE-1;
  1308.     char        *base;
  1309.  
  1310.     base = bp = getblock(DFree, YES);
  1311.     nl = nleft;
  1312.     for (;;) {
  1313.         c = f_getc(fp);
  1314.         if (c == EOF)
  1315.             break;
  1316.  
  1317.         /* We can't store NUL in our buffer, so ignore it.
  1318.          * Similarly, we can only store characters less than NCHARS.
  1319.          * Of course, with a little ingenuity we could store NUL:
  1320.          * NUL could be represented by \n.
  1321.          */
  1322.         if (c == '\0'
  1323. #if NCHARS != UCHAR_ROOF
  1324.         || c >= NCHARS
  1325. #endif
  1326.         )
  1327.             continue;
  1328.  
  1329.         if (c == EOL) {
  1330. #ifdef USE_CRLF
  1331.             /* a CR followed by an EOL is treated as a NL.
  1332.              * Bug: the line-buffer is effectively shortened by one character.
  1333.              */
  1334.             if (bp != base && bp[-1] == '\r') {
  1335.                 bp -= 1;
  1336.                 room += 1;
  1337.             }
  1338. #endif /* USE_CRLF */
  1339.             break;
  1340.         }
  1341.         if (--room < 0)
  1342.             break;    /* no room for this character */
  1343.  
  1344.         if (--nl == 0) {
  1345.             char    *newbp;
  1346.             size_t    nbytes;
  1347.             daddr        old_free_block = blk_chop(DFree);
  1348.  
  1349.             lockblock(old_free_block);
  1350.             DFree = old_free_block + BLK_CHNKS;
  1351.             nbytes = bp - base;
  1352.             newbp = getblock(DFree, YES);
  1353.             nl = nleft;
  1354.             byte_copy(base, newbp, nbytes);
  1355.             bp = newbp + nbytes;
  1356.             base = newbp;
  1357.             unlockblock(old_free_block);
  1358.         }
  1359.         *bp++ = c;
  1360.     }
  1361.     *bp++ = '\0';
  1362.     line->l_dline = DFree;
  1363.     DFree += REQ_CHNKS(bp - base);
  1364.     if (room < 0) {
  1365.         add_mess(" [Line too long]");
  1366.         rbell();
  1367.         return YES;
  1368.     }
  1369.     if (c == EOF) {
  1370.         if (--bp != base)
  1371.             add_mess(" [Incomplete last line]");
  1372.         return YES;
  1373.     }
  1374.     io_lines += 1;
  1375.     return NO;
  1376. }
  1377.  
  1378. typedef struct block {
  1379.     char    b_dirty;    /* (bool) */
  1380.     daddr    b_bno;
  1381.     char    b_buf[JBUFSIZ];
  1382.     struct block
  1383.         *b_LRUnext,
  1384.         *b_LRUprev,
  1385.         *b_HASHnext;
  1386. } Block;
  1387.  
  1388. #define HASHSIZE    7    /* Primes work best (so I'm told) */
  1389. #define B_HASH(bno)    ((bno) % HASHSIZE)
  1390.  
  1391. #ifdef MALLOC_CACHE
  1392. private Block    *b_cache = NULL;
  1393. #else
  1394. private Block    b_cache[NBUF];
  1395. #endif
  1396.  
  1397. private Block
  1398.     *bht[HASHSIZE],        /* Block hash table. Must be zero initially */
  1399.     *f_block = NULL,
  1400.     *l_block = NULL;
  1401.  
  1402. private daddr    next_bno = 0;
  1403.  
  1404. private void    (*blkio) ptrproto((Block *, SSIZE_T (*) ptrproto((int, UnivPtr, size_t))));
  1405.  
  1406. /* Needed to comfort dumb MS Visual C */
  1407. private void real_blkio ptrproto((Block *, SSIZE_T (*) ptrproto((int, UnivPtr, size_t))));
  1408.  
  1409. private void
  1410. real_blkio(b, iofcn)
  1411. register Block    *b;
  1412. register SSIZE_T    (*iofcn) ptrproto((int, UnivPtr, size_t));
  1413. {
  1414.     (void) lseek(tmpfd, (long)b->b_bno << JLGBUFSIZ, 0);
  1415.     if ((*iofcn)(tmpfd, (UnivPtr) b->b_buf, (size_t)JBUFSIZ) != JBUFSIZ)
  1416.         error("[Tmp file %s error: to continue editing would be dangerous]",
  1417.             (iofcn == read) ? "READ" : "WRITE");
  1418. }
  1419.  
  1420. /* Needed to comfort dumb MS Visual C */
  1421. private void fake_blkio ptrproto((Block *, SSIZE_T (*) ptrproto((int, UnivPtr, size_t))));
  1422.  
  1423. private void
  1424. fake_blkio(b, iofcn)
  1425. register Block    *b;
  1426. register SSIZE_T    (*iofcn) ptrproto((int, UnivPtr, size_t));
  1427. {
  1428.     tmpinit();
  1429.     blkio = real_blkio;
  1430.     real_blkio(b, iofcn);
  1431. }
  1432.  
  1433. void
  1434. d_cache_init()
  1435. {
  1436.     register Block    *bp,    /* Block pointer */
  1437.             **hp;    /* Hash pointer */
  1438.     register daddr    bno;
  1439.  
  1440. #ifdef MALLOC_CACHE
  1441.     if (b_cache == NULL) {
  1442.         b_cache = (Block *) calloc((size_t)NBUF,sizeof(Block));
  1443.         if (b_cache == NULL)
  1444.             error("cannot allocate buffer cache");
  1445.     }
  1446. #endif /* MALLOC_CACHE */
  1447.  
  1448.     for (bp = b_cache, bno = NBUF; bno-- > 0; bp++) {
  1449.         bp->b_dirty = NO;
  1450.         bp->b_bno = bno;
  1451.         if (l_block == NULL)
  1452.             l_block = bp;
  1453.         bp->b_LRUprev = NULL;
  1454.         bp->b_LRUnext = f_block;
  1455.         if (f_block != NULL)
  1456.             f_block->b_LRUprev = bp;
  1457.         f_block = bp;
  1458.  
  1459.         bp->b_HASHnext = *(hp = &bht[B_HASH(bno)]);
  1460.         *hp = bp;
  1461.     }
  1462.     blkio = fake_blkio;
  1463. }
  1464.  
  1465. void
  1466. SyncTmp()
  1467. {
  1468.     register Block    *b;
  1469. #ifdef MSDOS
  1470.     register daddr    bno = 0;
  1471.  
  1472.     /* sync the blocks in order, for file systems that don't allow
  1473.        holes (MSDOS).  Perhaps this benefits floppy-based file systems. */
  1474.  
  1475.     for (bno = 0; bno < next_bno; bno++) {
  1476.         if ((b = lookup_block(bno)) != NULL && b->b_dirty) {
  1477.             (*blkio)(b, (SSIZE_T (*) ptrproto((int, UnivPtr, size_t)))write);
  1478.             b->b_dirty = NO;
  1479.         }
  1480.     }
  1481. #else /* !MSDOS */
  1482.     for (b = f_block; b != NULL; b = b->b_LRUnext)
  1483.         if (b->b_dirty) {
  1484.             (*blkio)(b, (SSIZE_T (*) ptrproto((int, UnivPtr, size_t)))write);
  1485.             b->b_dirty = NO;
  1486.         }
  1487. #endif /* !MSDOS */
  1488. }
  1489.  
  1490. /* A prototyped definition is needed because daddr might be affected
  1491.  * by default argument promotions.
  1492.  */
  1493.  
  1494. private Block *
  1495. #ifdef USE_PROTOTYPES
  1496. lookup_block proto((register daddr bno))
  1497. #else
  1498. lookup_block(bno)
  1499. register daddr    bno;
  1500. #endif
  1501. {
  1502.     register Block    *bp;
  1503.  
  1504.     for (bp = bht[B_HASH(bno)]; bp != NULL; bp = bp->b_HASHnext)
  1505.         if (bp->b_bno == bno)
  1506.             break;
  1507.     return bp;
  1508. }
  1509.  
  1510. private void
  1511. LRUunlink(b)
  1512. register Block    *b;
  1513. {
  1514.     if (b->b_LRUprev == NULL)
  1515.         f_block = b->b_LRUnext;
  1516.     else
  1517.         b->b_LRUprev->b_LRUnext = b->b_LRUnext;
  1518.     if (b->b_LRUnext == NULL)
  1519.         l_block = b->b_LRUprev;
  1520.     else
  1521.         b->b_LRUnext->b_LRUprev = b->b_LRUprev;
  1522. }
  1523.  
  1524. private Block *
  1525. b_unlink(bp)
  1526. register Block    *bp;
  1527. {
  1528.     register Block    *hp,
  1529.             *prev = NULL;
  1530.  
  1531.     LRUunlink(bp);
  1532.     /* Now that we have the block, we remove it from its position
  1533.        in the hash table, so we can THEN put it somewhere else with
  1534.        it's new block assignment. */
  1535.  
  1536.     for (hp = bht[B_HASH(bp->b_bno)]; hp != NULL; prev = hp, hp = hp->b_HASHnext)
  1537.         if (hp == bp)
  1538.             break;
  1539.     if (hp == NULL) {
  1540.         writef("\rBlock %ld missing!", (long)bp->b_bno);
  1541.         finish(0);
  1542.     }
  1543.     if (prev)
  1544.         prev->b_HASHnext = hp->b_HASHnext;
  1545.     else
  1546.         bht[B_HASH(bp->b_bno)] = hp->b_HASHnext;
  1547.  
  1548.     if (bp->b_dirty) {    /* do, now, the delayed write */
  1549.         (*blkio)(bp, (SSIZE_T (*) ptrproto((int, UnivPtr, size_t)))write);
  1550.         bp->b_dirty = NO;
  1551.     }
  1552.  
  1553.     return bp;
  1554. }
  1555.  
  1556. /* Get a block which contains at least part of the line with the address
  1557.    atl.  Returns a pointer to the block and sets the global variable
  1558.    nleft (number of good characters left in the buffer). */
  1559.  
  1560. /* A prototyped definition is needed because daddr might be affected
  1561.  * by default argument promotions.
  1562.  */
  1563.  
  1564. private char *
  1565. #ifdef USE_PROTOTYPES
  1566. getblock proto((daddr atl, bool IsWrite))
  1567. #else
  1568. getblock(atl, IsWrite)
  1569. daddr    atl;
  1570. bool    IsWrite;
  1571. #endif
  1572. {
  1573.     register daddr    bno,
  1574.             off;
  1575.     register Block    *bp;
  1576.     static Block    *lastb = NULL;
  1577.  
  1578.     bno = da_to_bno(atl);
  1579.     off = da_to_off(atl);
  1580.     /* We don't allow block number MAX_BLOCKS-1 to be used because
  1581.      * NOWHERE_DADDR and NOTYET_DADDR must not be valid disk references,
  1582.      * and we want to prevent space overflow from being undetected
  1583.      * through arithmetic overflow.
  1584.      */
  1585.     if (bno >=  MAX_BLOCKS-1)
  1586.         error("Tmp file too large.  Get help!");
  1587.     nleft = JBUFSIZ - off;
  1588.     if (lastb != NULL && lastb->b_bno == bno) {
  1589.         bp = lastb;    /* same as last time */
  1590.     } else if ((bp = lookup_block(bno)) != NULL) {
  1591.         /* The requested block already lives in memory, so we move
  1592.            it to the end of the LRU list (making it Most Recently Used)
  1593.            and then return a pointer to it. */
  1594.         if (bp != l_block) {
  1595.             LRUunlink(bp);
  1596.             if (l_block == NULL)
  1597.                 f_block = bp;
  1598.             else
  1599.                 l_block->b_LRUnext = bp;
  1600.             bp->b_LRUprev = l_block;
  1601.             l_block = bp;
  1602.             bp->b_LRUnext = NULL;
  1603.         }
  1604.         if (bno >= next_bno)
  1605.             next_bno = bno + 1;
  1606.     } else {
  1607.         /* The block we want doesn't reside in memory so we take the
  1608.            least recently used clean block (if there is one) and use
  1609.            it.  */
  1610.         bp = f_block;
  1611.         if (bp->b_dirty)    /* The best block is dirty ... */
  1612.             SyncTmp();
  1613.  
  1614.         bp = b_unlink(bp);
  1615.         if (l_block == NULL)
  1616.             f_block = bp;
  1617.         else
  1618.             l_block->b_LRUnext = bp;    /* Place it at the end ... */
  1619.         bp->b_LRUprev = l_block;
  1620.         l_block = bp;
  1621.         bp->b_LRUnext = NULL;        /* so it's Most Recently Used */
  1622.  
  1623.         bp->b_dirty = NO;
  1624.         bp->b_bno = bno;
  1625.         bp->b_HASHnext = bht[B_HASH(bno)];
  1626.         bht[B_HASH(bno)] = bp;
  1627.  
  1628.         /* Get the current contents of the block UNLESS this is a new
  1629.            block that's never been looked at before, i.e., it's past
  1630.            the end of the tmp file. */
  1631.  
  1632.         if (bno < next_bno)
  1633.             (*blkio)(bp, read);
  1634.         else
  1635.             next_bno = bno + 1;
  1636.     }
  1637.     lastb = bp;
  1638.     bp->b_dirty |= IsWrite;
  1639.     return bp->b_buf + off;
  1640. }
  1641.  
  1642. char *
  1643. lbptr(line)
  1644. LinePtr    line;
  1645. {
  1646.     return getblock(line->l_dline, NO);
  1647. }
  1648.  
  1649. /* save the current contents of linebuf, if it has changed */
  1650.  
  1651. void
  1652. lsave()
  1653. {
  1654.     if (curbuf == NULL || !DOLsave)    /* Nothing modified recently */
  1655.         return;
  1656.  
  1657.     if (strcmp(lbptr(curline), linebuf) != 0)
  1658.         SavLine(curline, linebuf);    /* Put linebuf on the disk. */
  1659.     DOLsave = NO;
  1660. }
  1661.  
  1662. #ifdef BACKUPFILES
  1663. private void
  1664. file_backup(fname)
  1665. char *fname;
  1666. {
  1667. # ifndef MSFILESYSTEM
  1668.     SSIZE_T    rr;
  1669.     int
  1670.         ffd,
  1671.         bffd;
  1672.     char
  1673.         buf[JBUFSIZ],
  1674.         bfname[FILESIZE];
  1675.  
  1676.     /* build backup file name */
  1677.     {
  1678.         char    *s = strrchr(fname, '/');
  1679.         size_t    dirlen = (s == NULL)? 0 : s + 1 - fname;
  1680.  
  1681.         strcpy(bfname, fname);
  1682.         swritef(bfname+dirlen, (size_t) (sizeof(bfname) - dirlen), "#%s~",
  1683.             fname+dirlen);
  1684.     }
  1685.  
  1686.     if ((ffd = open(fname, 0)) < 0)
  1687.         return;    /* cannot open original file: nothing to backup, we assume */
  1688.  
  1689.     /* create backup file with same mode as input file */
  1690.     {
  1691. #  ifdef MAC
  1692.         int    mode = CreatMode;    /* dummy */
  1693. #  else
  1694.         struct stat statbuf;
  1695.         int    mode = fstat(ffd, &statbuf) != 0? CreatMode : statbuf.st_mode;
  1696. #  endif
  1697.  
  1698.         if ((bffd = creat(bfname, mode)) < 0) {
  1699.             int    e = errno;
  1700.  
  1701.             (void) close(ffd);
  1702.             complain("[cannot create backup \"%s\": %d %s]",
  1703.                 bfname, e, strerror(e));
  1704.         }
  1705.     }
  1706.  
  1707.     /* copy the contents */
  1708.     while ((rr = read(ffd, (UnivPtr) buf, sizeof(buf))) > 0) {
  1709.         char    *p = buf;
  1710.  
  1711.         while (rr > 0) {
  1712.             SSIZE_T    wr = write(bffd, (UnivPtr) p, (size_t) rr);
  1713.  
  1714.             if (wr < 0) {
  1715.                 int e = errno;
  1716.  
  1717.                 close(bffd);
  1718.                 close(ffd);
  1719.                 complain("[error writing backup: %d %s]", e, strerror(e));
  1720.             }
  1721.             p += wr;
  1722.             rr -= wr;
  1723.         }
  1724.     }
  1725.  
  1726.     if (rr < 0 || close(ffd) != 0)
  1727.         complain("[error reading \"%s\": %d %s]", fname, errno, strerror(errno));
  1728. #  ifdef USE_FSYNC
  1729.     if (fsync(bffd) != 0) {
  1730.         int    e = errno;
  1731.  
  1732.         (void) close(bffd);
  1733.         complain("[error fsyncing backup: %d %s]", e, strerror(e));
  1734.     }
  1735. #  endif /* USE_FSYNC */
  1736.     if (close(bffd) != 0)
  1737.         complain("[error closing backup: %d %s]", errno, strerror(errno));
  1738.  
  1739. # else /* MSFILESYSTEM */
  1740.     /* This code is designed to fit withing the 8.3 limitation of
  1741.      * MSDOS ("FAT" -- huh!) file systems.  Even though newer versions
  1742.      * of these APIs (Win32) may support longer file names, we may still
  1743.      * be dealing with a FAT file system.
  1744.      */
  1745.     char    *dot,
  1746.             *slash,
  1747.             tmp[FILESIZE];
  1748.  
  1749.     strcpy(tmp, fname);
  1750.     slash = basename(tmp);
  1751.     if ((dot = strrchr(slash, '.')) != NULL) {
  1752.         if (stricmp(dot,".bak") != 0)
  1753.             return;
  1754.         *dot = '\0';
  1755.     }
  1756.     strcat(tmp, ".bak");
  1757.     unlink(tmp);
  1758.     if (rename(fname, tmp) != 0)
  1759.         complain("[cannot rename to \"%s\": %s]", tmp, strerror(errno));
  1760. # endif /* MSFILESYSTEM */
  1761. }
  1762. #endif /* BACKUPFILES */
  1763.