home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_300 / 318_01 / redbuf3.c < prev    next >
C/C++ Source or Header  |  1990-06-18  |  17KB  |  882 lines

  1. /*
  2.     RED buffer routines -- Full C version
  3.     Part 3 -- file routines
  4.  
  5.     Source:  redbuf3.c
  6.     Version: August 8, 1986; January 18, 1990.
  7.  
  8.     Written by
  9.     
  10.         Edward K. Ream
  11.         166 N. Prospect
  12.         Madison WI 53705
  13.         (608) 257-0802
  14.  
  15.  
  16.     PUBLIC DOMAIN SOFTWARE
  17.  
  18.     This software is in the public domain.
  19.  
  20.     See red.h for a disclaimer of warranties and other information.
  21. */
  22.  
  23. #include "red.h"
  24.  
  25. /*
  26.     Declare routines local to this file.
  27. */
  28. static void    disk_seek    (void);
  29. static int    read1        (void);
  30. static void    read2        (void);
  31. extern void    write1        (char c);
  32. extern void    wr_flush    (void);
  33.  
  34. /*
  35.     Data buffer used only in this file.
  36. */
  37. static char b_buff [DATA_SIZE];
  38.  
  39. /*
  40.     Kludge to allow more strict checking in swap_out.
  41.     It is usually an internal error if we swap out the current block.
  42.     The exception is in write_file, where we swap out all blocks.
  43. */
  44. static int ok2swap = FALSE;
  45.  
  46. /*
  47.     Open the data file.
  48. */
  49. int
  50. data_open(void)
  51. {
  52.     TICKB("data_open");
  53.  
  54.     /* Erase the data file if it exists. */
  55.     sysunlink(DATA_FILE);
  56.  
  57.     /* Create the data file. */
  58.     b_data_fd = syscreat(DATA_FILE);
  59.     if (b_data_fd == ERROR) {
  60.         disk_error("Can not open swap file.");
  61.     }
  62.  
  63.     RETURN_INT("data_open", b_data_fd);
  64. }
  65.  
  66. /*
  67.     Make the slot the MOST recently used slot.
  68. */
  69. void
  70. do_lru(struct BLOCK *bp)
  71. {
  72.     struct BLOCK *bp1;
  73.     int i, lru;
  74.  
  75.     SL_DISABLE();
  76.     
  77.     /*
  78.         Change the relative ordering of all slots
  79.          which have changed more recently than slot.
  80.      */
  81.     lru = bp -> d_lru;
  82.  
  83.     /* 12/15/89:  do nothing if the current slot is the most recent. */
  84.     if (lru == 0) {
  85.         return;
  86.     }
  87.  
  88.     TRACEP("do_lru", sl_lpout();
  89.         sl_pout(bp); sl_sout(") before: "); dump_slots());
  90.  
  91.     for (i = 0; i < DATA_RES; i++) {
  92.         bp1 = b_bpp [i];
  93.         if (bp1 -> d_lru < lru) {
  94.             bp1 -> d_lru++;
  95.         }
  96.     }
  97.  
  98.     /* The slot is the most recently used. */
  99.     bp -> d_lru = 0;
  100.  
  101.     TRACE("do_lru", sl_sout("after: "); dump_slots(); sl_cout('\n'));
  102. }
  103.  
  104. /*
  105.     Disk error routines
  106. */
  107. void
  108. disk_error(char *message)
  109. {
  110.     TRACEPB("disk_error",  sl_lpout(); sl_sout(message); sl_rpout());
  111.  
  112.     error(message);
  113.  
  114.     /* Clear the buffer if no recovery is possible. */
  115.     if (b_fatal == TRUE) {
  116.         bufnew();
  117.     }
  118.  
  119.     /* Abort the operation that caused the error. */
  120.     longjmp(DISK_ERR, ERROR);
  121.  
  122.     TICKX("disk_error");
  123. }
  124.  
  125. void
  126. disk_full(void)
  127. {
  128.     SL_DISABLE();
  129.  
  130.     disk_error("Disk or directory full?");
  131. }
  132.  
  133. void
  134. disk_rdy(void)
  135. {
  136.     SL_DISABLE();
  137.  
  138.     disk_error("Drive not ready?");
  139. }
  140.  
  141. static void
  142. disk_seek(void)
  143. {
  144.     SL_DISABLE();
  145.  
  146.     disk_error("Bad Seek");
  147. }
  148.  
  149. /*
  150.     Indicate that a slot must be saved on the disk.
  151. */
  152. #if 0 /* a macro now */
  153. void
  154. is_dirty(struct BLOCK *bp)
  155. {
  156.     TRACEPB("is_dirty",  sl_lpout(); sl_pout(bp); sl_rpout());
  157.  
  158.     bp -> d_status = DIRTY;
  159.  
  160.     TICKX("is_dirty");
  161. }
  162. #endif /* 0 */
  163.  
  164. /*
  165.     Put out the block-sized buffer to the disk sector.
  166. */
  167. void
  168. put_block(struct BLOCK *bp, int diskp)
  169. {
  170.     int s;
  171.  
  172.     /* Make sure blocks are written in order. */
  173.  
  174.     TRACEPB("put_block",  sl_lpout();
  175.         sl_pout(bp);    sl_csout();
  176.         sl_iout(diskp); sl_rpout());
  177.  
  178.     if (diskp > b_max_put + 1) {
  179.         swap_sync(b_max_put + 1, diskp - 1);
  180.     }
  181.     b_max_put = max(b_max_put, diskp);
  182.     
  183.     /* Seek to the correct sector of the data file. */
  184.     s = sysseek(b_data_fd, diskp);
  185.     if (s == -1) {
  186.         disk_seek();
  187.     }
  188.  
  189.     /* Write the block to the data file. */
  190.     if (syswrite(b_data_fd, (char *) bp, DATA_SIZE) != DATA_SIZE) {
  191.         disk_full();
  192.     }
  193.  
  194.     TICKX("put_block");
  195. }
  196.  
  197. /*
  198.     Fill in the header fields of the output buffer and
  199.     write it to the disk.
  200.     avail is the number of free characters in the buffer.
  201. */
  202. char *
  203. put_buf(int avail)
  204. {
  205.     struct BLOCK *bp;
  206.  
  207.     /*
  208.         Fill in the back and next links immediately.
  209.         This can be done because we are not waiting
  210.         for the LRU algorithm to allocated disk blocks.
  211.         The last block that put_buf() writes will have
  212.         an incorrect next link.  Read_file() will make
  213.         the correction.
  214.     */
  215.  
  216.     TRACEPB("put_buf", sl_lpout(); sl_iout(avail); sl_rpout());
  217.  
  218.     bp = (struct BLOCK *) b_buff;
  219.     bp -> d_back  = b_max_diskp - 1;
  220.     bp -> d_next  = b_max_diskp + 1;
  221.     bp -> d_lines = b_line - b_start;
  222.  
  223.     if (avail < 0) {
  224.         cant_happen("put_buf");
  225.     }
  226.  
  227.     /* Update block and line counts. */
  228.     b_max_diskp++;
  229.     b_start = b_line;
  230.  
  231.     /* Write the block. */
  232.     put_block( (struct BLOCK *) b_buff, b_max_diskp - 1);
  233.  
  234.     TICKX("put_buf");
  235. }
  236.  
  237. /*
  238.     Write out the slot to the data file.
  239. */
  240. void
  241. put_slot(struct BLOCK *bp)
  242. {
  243.     TRACEPB("put_slot", sl_lpout(); sl_pout(bp); sl_rpout());
  244.  
  245.     if (bp -> d_diskp == ERROR) {
  246.         cant_happen("put_slot");
  247.     }
  248.     put_block(bp, bp -> d_diskp);
  249.  
  250.     TICKX("put_slot");
  251. }
  252.  
  253. /*
  254.     Read a file into the buffer.
  255.  
  256.     This version of read_file puts an index table at
  257.     the end of each block.  The index table's entry
  258.     for each line tells the distance of the LAST character
  259.     of the line from the start of the data buffer.
  260.  
  261.     The global variables br_count, br_bufp, and br_bufc
  262.     are used to communicate with read1().  Using these
  263.     variables speeds the code by a factor of 3!
  264.  
  265.     The "global" variables br_avail and br_out are used
  266.     only by read_file() -- again, purely to speed the code.
  267. */
  268. void
  269. read_file(char file_name [])
  270. {
  271.     struct BLOCK *bp;
  272.  
  273.     /* global:  char * br_bufp   pointer to buffer    */
  274.     /* global:  int    br_bufc   index into buffer    */
  275.     /* global:  int    br_count  number of buffer    */
  276.  
  277.     /* global:  int    br_avail  available chars    */
  278.     /* global:  int    br_out    index into outbuf    */
  279.  
  280.     char    *outbuf;    /* the output buffer    */
  281.     int    out_save;    /* line starts here    */
  282.     int    c, i, j;
  283.  
  284.     /* Clear the swapping buffers and the files. */
  285.  
  286.     TRACEPB("read_file", sl_lpout(); sl_pout(file_name); sl_rpout());
  287.  
  288.     bufnew();
  289.     b_bp -> d_status = FREE;
  290.  
  291.     /* Open the user file. */
  292.     b_user_fd = sysopen(file_name);
  293.     if (b_user_fd == ERROR) {
  294.         disk_error("File not found.");
  295.     }
  296.  
  297.     /* Clear the buffer on any disk error. */
  298.     b_fatal = TRUE;
  299.  
  300.     /* Open the data file. */
  301.     data_open();
  302.  
  303.     /* The file starts with line 1. */
  304.     b_line = 1;
  305.     b_start = 1;
  306.  
  307.     /* There are no blocks in the file yet. */
  308.     b_head = b_tail = ERROR;
  309.     b_max_diskp = 0;
  310.  
  311.     /* Point outbuf to start of the output data area. */
  312.     outbuf = b_buff + HEADER_SIZE;
  313.  
  314.     /* Force an initial read in read1(). */
  315.     br_count = DATA_SIZE;
  316.     br_bufc  = DATA_RES;
  317.  
  318.     /* Zero the pointers into the output buffer. */
  319.     br_out = out_save = 0;
  320.  
  321.     /* Allocate space for the first table entry. */
  322.     br_avail = BUFF_SIZE - sizeof(int);
  323.     
  324.     /* Set the current line counts. */
  325.     b_line = b_start = 1;
  326.  
  327.     for(;;) {
  328.  
  329.         if (br_avail <= 0 && out_save == 0) {
  330.             /* The line is too long. */
  331.             error ("Line split.");
  332.  
  333.             /* End the line. */
  334.             b_settab( (struct BLOCK *) b_buff,
  335.                   b_line - b_start,
  336.                   br_out
  337.                 );
  338.             b_line++;
  339.  
  340.             /* Clear the output buffer. */
  341.             put_buf(br_avail);
  342.             br_out = out_save = 0;
  343.             br_avail = BUFF_SIZE - sizeof(int);
  344.         }
  345.  
  346.         else if (br_avail <= 0) {
  347.  
  348.             /*
  349.                 Deallocate last table entry and
  350.                 reallocate space used by the
  351.                 partial line.
  352.             */
  353.             br_avail += (sizeof(int) + br_out - out_save);
  354.  
  355.             /* Write out the buffer. */
  356.             put_buf(br_avail);
  357.  
  358.             /* Move the remainder to the front. */
  359.             sysmove(outbuf + out_save,
  360.                 outbuf,
  361.                 br_out - out_save);
  362.  
  363.             /* Reset restart point. */
  364.             br_out   = br_out - out_save;
  365.             out_save = 0;
  366.             br_avail = BUFF_SIZE - sizeof(int) - br_out;
  367.         }
  368.  
  369.         c = read1();
  370.  
  371.         if (c == EOF_MARK) {
  372.  
  373.             if (br_out != out_save) {
  374.  
  375.                 /* Finish the last line. */
  376.                 b_settab( (struct BLOCK *) b_buff,
  377.                       b_line-b_start,
  378.                       br_out    /* 3/8/85 */
  379.                     );
  380.                 b_line++;
  381.                 out_save = br_out;
  382.             }
  383.             else {
  384.                 /* No last line after all. */
  385.                 br_avail += sizeof(int);
  386.             }
  387.  
  388.             /* bug fix:  2/20/84, 4/2/84 */
  389.             if (br_avail !=  BUFF_SIZE) {
  390.                 put_buf(br_avail);
  391.             }
  392.             break;
  393.         }
  394.  
  395.         else if (c == '\n') {
  396.  
  397.             /* Finish the line. */
  398.             b_settab( (struct BLOCK *) b_buff,
  399.                   b_line - b_start,
  400.                   br_out
  401.                 );
  402.             br_avail -= sizeof(int);
  403.  
  404.             /* Set restart point. */
  405.             b_line++;
  406.             out_save = br_out;
  407.         }
  408.  
  409.         else if (c == '\r') {
  410.             /* Ignore CP/M's pseudo-newline. */
  411.             continue;
  412.         }
  413.  
  414.         else {
  415.  
  416.             /* Copy normal character. */
  417.             outbuf [br_out++] = c;
  418.             br_avail--;
  419.         }
  420.     }
  421.  
  422.     /* Close the user' file. */
  423.     sysclose(b_user_fd);
  424.  
  425.     /* Special case:  null file. */
  426.     if (b_max_diskp == 0) {
  427.         bufnew();
  428.         RETURN_VOID("read_file");
  429.     }
  430.  
  431.     /* Rewrite the last block with correct next field. */
  432.     bp = (struct BLOCK *) b_buff;
  433.     bp -> d_next = ERROR;
  434.     put_block( (struct BLOCK *) b_buff, b_max_diskp - 1);
  435.  
  436.     /* Set the pointers to the first and last blocks. */
  437.     b_max_diskp--;
  438.     b_head = 0;
  439.     b_tail = b_max_diskp;
  440.  
  441.     /*
  442.         Clear all slots.  This is REQUIRED since
  443.         read_file has just overwritten all slots.
  444.     */
  445.     buf_clr();
  446.  
  447.     /* Move to the start of the file. */
  448.     b_max_line = b_line - 1;
  449.     b_line = 1;
  450.     b_start = 1;
  451.     b_bp = swap_in(b_head);
  452.  
  453.     b_fatal = FALSE;
  454.  
  455.     TICKX("read_file");
  456. }
  457.  
  458. /*
  459.     Get one character from the input file.
  460.  
  461.     This version of read1 uses all slots as an input buffer.
  462.     The slots to not need to be contiguous in memory
  463.     (which they are generally are NOT because of hidden
  464.     header information used only by sysalloc()).
  465.  
  466.     This version uses the globals br_count, br_bufc and
  467.     br_bufp to speed up the code.
  468. */
  469. static int
  470. read1(void)
  471. {
  472.     TICKB("read1");
  473.  
  474.     if (br_count == DATA_SIZE) {
  475.  
  476.         if (br_bufc >= DATA_RES - 1) {
  477.  
  478.             /* Read into buffers. */
  479.             read2();
  480.             br_count = br_bufc = 0;
  481.         }
  482.         else {
  483.  
  484.             /* Switch to next buffer. */
  485.             br_bufc++;
  486.             br_count = 0;
  487.         }
  488.         br_bufp = (char *) b_bpp [br_bufc];
  489.     }
  490.  
  491.     /* Get the character and mask off parity bit. */
  492.  
  493.     RETURN_INT("read1", br_bufp [br_count++] & 0x7f);
  494. }
  495.  
  496. /*
  497.     Read user file into all slots.
  498. */
  499. static void
  500. read2(void)
  501. {
  502.     int i, s;
  503.  
  504.     TICKB("read2");
  505.  
  506.     for (i = 0; i < DATA_RES; i++) {
  507.  
  508.         /* Point at the next slot. */
  509.         br_bufp = (char *) b_bpp [i];
  510.  
  511.         /* Read the next sector. */
  512.         s = sysread(b_user_fd, br_bufp);
  513.  
  514.         if (s == ERROR) {
  515.             disk_rdy();
  516.         }
  517.  
  518.         /* Force a CPM end of file mark. */
  519.         if (s < DATA_SIZE) {
  520.             br_bufp [s] = EOF_MARK;
  521.             break;
  522.         }
  523.     }
  524.  
  525.     TICKX("read2");
  526. }
  527.  
  528. /*
  529.     Swap out all dirty blocks.
  530. */
  531. void
  532. swap_all(void)
  533. {
  534.     struct BLOCK *bp;
  535.     int i;
  536.  
  537.     TICKB("swap_all");
  538.  
  539.     for (i = 0; i < DATA_RES; i++) {
  540.         bp = b_bpp [i];
  541.         if (bp -> d_status == DIRTY) {
  542.             put_slot (bp);
  543.             bp -> d_status = FULL;
  544.         }
  545.     }
  546.  
  547.     TICKX("swap_all");
  548. }
  549.  
  550. /*
  551.     Swap out the first dirty block.   This routine does
  552.     not swap the dirty block since that would waste time.
  553.     This routines is called when nothing else is happening.
  554. */
  555. void
  556. swap_one(void)
  557. {
  558.     struct BLOCK *bp;
  559.     int i;
  560.  
  561.     SL_DISABLE();
  562.  
  563.     for (i = 0; i < DATA_RES; i++) {
  564.         bp = b_bpp [i];
  565.         if (bp != b_bp && bp -> d_status == DIRTY) {
  566.             put_slot (bp);
  567.             bp -> d_status = FULL;
  568.             break;
  569.         }
  570.     }
  571. }
  572.  
  573. /*
  574.     Get the block from the disk into a slot in memory.
  575.     Return a pointer to the block.
  576. */
  577. struct BLOCK *
  578. swap_in(int diskp)
  579. {
  580.     struct BLOCK *bp;
  581.     int i, status;
  582.  
  583.     SL_DISABLE();
  584.  
  585.     if (diskp < 0 || diskp > b_max_diskp) {
  586.         cant_happen("swap_in 1");
  587.     }
  588.  
  589.     /* See whether the block is already in a slot. */
  590.     for (i = 0; i < DATA_RES; i++) {
  591.         bp = b_bpp [i];
  592.         if (bp -> d_status != FREE &&
  593.             bp -> d_diskp  == diskp) {
  594.  
  595.             /* Reference the block. */
  596.             STATB("swap_in");
  597.             do_lru(bp);
  598.             STATX("swap_in");
  599.  
  600.             /* Point to the slot. */
  601.             return bp;
  602.         }
  603.     }
  604.  
  605.     /* Clear a slot for the block. */
  606.     bp = swap_new(diskp);
  607.  
  608.     /* Read from temp file to block. */
  609.     status = sysseek(b_data_fd, diskp);
  610.     if (status == -1) {
  611.         disk_rdy();
  612.     }
  613.  
  614.     /* Read the block into the slot. */
  615.     status = sysread(b_data_fd, (char *) bp);
  616.     if (status == ERROR) {
  617.         disk_rdy();
  618.     }
  619.  
  620.     /* Swap_new() has already called do_lru(). */
  621.     TRACEP("swap_in",  sl_lpout();
  622.         sl_iout(diskp); sl_sout(") ");
  623.         dump_slots();
  624.         sl_sout("returns "); sl_pout(bp); sl_cout('\n'));
  625.  
  626.     /* Return a pointer to the block. */
  627.     return bp;
  628. }
  629.  
  630. /*
  631.     Free a slot for a block located at diskp.
  632.     Swap out the least recently used block if required.
  633.     Return a pointer to the block.
  634. */
  635. struct BLOCK *
  636. swap_new(int diskp)
  637. {
  638.     struct BLOCK *bp;
  639.     int i;
  640.  
  641.     SL_DISABLE();
  642.  
  643.     /* Search for an available slot. */
  644.     for (i = 0; i < DATA_RES; i++) {
  645.         bp = b_bpp [i];
  646.         if (bp -> d_status == FREE) {
  647.             break;
  648.         }
  649.     }
  650.  
  651.     /* Swap out a block if all blocks are full. */
  652.     if (i == DATA_RES) {
  653.         bp = swap_out();
  654.     }
  655.  
  656.     /* Make sure the block will be written. */
  657.     bp -> d_status = FULL;
  658.     bp -> d_diskp  = diskp;
  659.  
  660.     /* Reference the slot. */
  661.     do_lru(bp);
  662.  
  663.     /* Return a pointer to the slot. */
  664.     TRACEP("swap_new",      sl_lpout();
  665.         sl_iout(diskp); sl_sout(") returns ");
  666.         sl_pout(bp);    sl_cout('\n'));
  667.  
  668.     return bp;
  669. }
  670.  
  671. /*
  672.     Swap out the least recently used (LRU) slot.
  673.     Return a pointer to the block.
  674. */
  675. struct BLOCK *
  676. swap_out(void)
  677. {
  678.     struct BLOCK *bp;
  679.     int i;
  680.  
  681.     SL_DISABLE();
  682.  
  683.     /* Open the temp file if it has not been opened. */
  684.     if (b_data_fd == ERROR) {
  685.         b_data_fd = data_open();
  686.     }
  687.  
  688.     /* Find the least recently used slot. */
  689.     for (i = 0; ;i++) {
  690.         bp = b_bpp [i];
  691.         if (bp -> d_lru == DATA_RES - 1) {
  692.             break;
  693.         }
  694.     }
  695.  
  696.     TRACEP("swap_out",
  697.         sl_sout("***** swapping out block ");
  698.         sl_iout(bp -> d_diskp);
  699.         sl_sout(", returns "); sl_pout(bp);
  700.         sl_cout('\n'));
  701.  
  702.     if (bp == b_bp && !ok2swap) {
  703.         cant_happen("swap_out1");
  704.     }
  705.  
  706.     /* Do the actual swapping out if memory is dirty. */
  707.     if (bp -> d_status == DIRTY) {
  708.         put_slot(bp);
  709.         return bp;
  710.     }
  711.  
  712.     /* d_diskp is not ERROR if status is not DIRTY. */
  713.     if (bp -> d_diskp == ERROR) {
  714.         cant_happen("swap_out");
  715.     }
  716.  
  717.     /* Indicate that the slot is available. */
  718.     bp -> d_status = FREE;
  719.     bp -> d_diskp  = ERROR;
  720.  
  721.     /* Return a pointer to the block. */
  722.     return bp;
  723. }
  724.  
  725. /*
  726.     Swap out blocks found between low and high on the disk.
  727. */
  728. void
  729. swap_sync(int low, int high)
  730. {
  731.     struct BLOCK *bp;
  732.     int disk, i;
  733.  
  734.     TRACEPB("swap_sync",   sl_lpout();
  735.         sl_iout(low);  sl_csout();
  736.         sl_iout(high); sl_rpout());
  737.  
  738.     /* Search the slot table for each disk. */
  739.     for (disk = low; disk <= high; disk++) {
  740.         for (i = 0; i < DATA_RES; i++) {
  741.             bp = b_bpp [i];
  742.             if (bp -> d_diskp == disk) {
  743.  
  744.                 /* Write the slot. */
  745.                 put_slot(bp);
  746.                 bp -> d_status = FULL;
  747.                 break;
  748.             }
  749.         }
  750.         if (i == DATA_RES) {
  751.             cant_happen("swap_sync");
  752.         }
  753.     }
  754.  
  755.     TICKX("swap_sync");
  756. }
  757.  
  758. /*
  759.     Write the entire buffer to file.
  760. */
  761. void
  762. write_file(char *file_name)
  763. {
  764.     /* global:  bw_count */
  765.  
  766.     struct BLOCK *bp;
  767.     char *data;
  768.     int slot, line, nlines, length, next, count;
  769.     int c;
  770.         int blocks;
  771.     
  772.     TRACEPB("write_file", sl_lpout(); sl_sout(file_name); sl_rpout());
  773.  
  774.     /* Open the user file.  Erase it if it exists. */
  775.     b_user_fd = syscreat(file_name);
  776.     if (b_user_fd == ERROR) {
  777.         disk_full();
  778.     }
  779.  
  780.     /* Allow the current block to be swapped out. */
  781.     ok2swap = TRUE;
  782.  
  783.     /* Copy each block of the file. */
  784.     bw_count = 0;
  785.     blocks   = 0;
  786.     for (next = b_head; next != ERROR; ) {
  787.  
  788.         /* One more check for file consistency. */
  789.         if (blocks++ > b_max_diskp) {
  790.             cant_happen("write_file1");
  791.         }
  792.  
  793.         /* Swap in the next block. */
  794.         bp = swap_in(next);
  795.  
  796.         /* Get data from the header of the block. */
  797.         next   = bp -> d_next;
  798.         nlines = bp -> d_lines;
  799.         data   = bp -> d_data;
  800.         
  801.         /* Copy each line of the block. */
  802.         count = 0;
  803.         for (line = 0; line < nlines; line++) {
  804.  
  805.             /* Get length of the line. */
  806.             if (line == 0) {
  807.                 length = b_tab(bp, line);
  808.             }
  809.             else {
  810.                 length = b_tab(bp,line) -
  811.                      b_tab(bp,line - 1);
  812.             }
  813.             
  814.             /* Copy each char of the line. */
  815.             for (; length; length--) {
  816.                 c = data [count++];
  817.                 write1(c);
  818.             }
  819.  
  820.             /* Add end-of-line characters at end. */
  821.             write1('\r');
  822.             write1('\n');
  823.         }
  824.     }
  825.  
  826.     /* Force an end of file mark. */
  827. #ifdef CPM
  828.     write1(EOF_MARK);
  829. #endif
  830.  
  831.     /* Flush the buffer and close the file. */
  832.     wr_flush();
  833.     sysclose(b_user_fd);
  834.  
  835.     /* Kludge:  go to line 1 for a reference point. */
  836.     b_bp   = swap_in(b_head);
  837.     b_line = b_start = 1;
  838.  
  839.     ok2swap = FALSE;
  840.  
  841.     TICKX("write_file");
  842. }
  843.  
  844. /*
  845.     Write one character to the user's file.
  846.     bw_count is the current position in the file buffer.
  847. */
  848. static void
  849. write1(char c)
  850. {
  851.     TRACEPB("write1", sl_lpout(); sl_cout(c); sl_rpout());
  852.  
  853.     b_buff [bw_count++] = c;
  854.     if (bw_count == DATA_SIZE) {
  855.         if (syswrite(b_user_fd, b_buff, DATA_SIZE) != DATA_SIZE) {
  856.             disk_full();
  857.         }
  858.         bw_count = 0;
  859.     }
  860.  
  861.     TICKX("write1");
  862. }
  863.  
  864. /*
  865.     Flush b_buff to the user's file.
  866. */
  867. static void
  868. wr_flush(void)
  869. {
  870.     TICKB("wr_flush");
  871.  
  872.     if (bw_count == 0) {
  873.         RETURN_VOID("wr_flush");
  874.     }
  875.     /* 3/28/86 */
  876.     if (sysflush(b_user_fd, b_buff, bw_count) != 1) {
  877.         disk_full();
  878.     }
  879.  
  880.     TICKX("wr_flush");
  881. }
  882.