home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / misc / volume33 / problem / part07 / utilities.C
Encoding:
C/C++ Source or Header  |  1992-10-19  |  35.2 KB  |  1,241 lines

  1. /*
  2. ** utilities.C - utility functions
  3. **
  4. ** utilities.C utilities.C 1.51   Delta\'d: 15:10:14 10/9/92   Mike Lijewski, CNSF
  5. **
  6. ** Copyright \(c\) 1991, 1992 Cornell University
  7. ** All rights reserved.
  8. **
  9. ** Redistribution and use in source and binary forms are permitted
  10. ** provided that: \(1\) source distributions retain this entire copyright
  11. ** notice and comment, and \(2\) distributions including binaries display
  12. ** the following acknowledgement:  ``This product includes software
  13. ** developed by Cornell University\'\' in the documentation or other
  14. ** materials provided with the distribution and in all advertising
  15. ** materials mentioning features or use of this software. Neither the
  16. ** name of the University nor the names of its contributors may be used
  17. ** to endorse or promote products derived from this software without
  18. ** specific prior written permission.
  19. **
  20. ** THIS SOFTWARE IS PROVIDED ``AS IS\'\' AND WITHOUT ANY EXPRESS OR
  21. ** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
  22. ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  23. */
  24.  
  25. #include <ctype.h>
  26.  
  27. #ifndef _IBMR2
  28. #include <libc.h>
  29. #endif
  30.  
  31. #include <fcntl.h>
  32. #include <osfcn.h>
  33. #include <pwd.h>
  34. #include <signal.h>
  35. #include <stdarg.h>
  36. #include <stdlib.h>
  37. #include <stdio.h>
  38.  
  39. #ifndef _IBMR2
  40. #include <unistd.h>
  41. #endif
  42.  
  43. // access\(2\) is prototyped in <sys/access.h> on RS/6000s
  44. #ifdef _IBMR2
  45. #include <sys/access.h>
  46. #include <sys/lockf.h>
  47. #endif
  48.  
  49. #include <sys/stat.h>
  50. #include <sys/types.h>
  51. #ifdef ESIX
  52. typedef int pid_t;
  53. #endif /*ESIX*/
  54. #include <sys/wait.h>
  55. #include <string.h>
  56. #include <sys/errno.h>
  57.  
  58. // this header file is badly busted on RS/6000s
  59. #ifndef _IBMR2
  60. #include <unistd.h>
  61. #endif
  62.  
  63. #include "classes.h"
  64. #include "display.h"
  65. #include "lister.h"
  66. #include "problem.h"
  67. #include "utilities.h"
  68.  
  69. /* remove this once GNU gets it fixed -- this stuff should be in <unistd.h> */
  70. #ifdef __GNUG__
  71. extern "C" int lockf(int, int, long);
  72. extern "C" int flock(int, int);
  73. #ifndef LOCK_UN
  74. #define LOCK_UN 8
  75. #endif
  76. #ifndef LOCK_EX
  77. #define LOCK_EX 2
  78. #endif
  79. #ifndef F_LOCK
  80. #define F_LOCK   1
  81. #endif
  82. #ifndef F_ULOCK
  83. #define F_ULOCK  0
  84. #endif
  85. #endif /*__GNUG__*/
  86.  
  87. const int KEY_CR    = '\r';  // carriage return
  88. const int KEY_BKSP  = '\b';  // backslash
  89. const int KEY_CTL_L = '\f';  // repaint screen -- CTR-L
  90.  
  91. /*
  92. ** fgetline - returns a pointer to the start of a line read from fp,
  93. **            or the null pointer if we hit eof or get an error from
  94. **            fgets\(\). Exits if new\(\) fails. Strips the newline from
  95. **            the line. Caller should free memory if desired. `size\'
  96. **            is the expected length of the line to be read.
  97. */
  98.  
  99. char *fgetline(FILE *fp, int size)
  100. {
  101.     char *buffer = new char[size];
  102.  
  103.     char *result= fgets(buffer, size, fp);
  104.     if (result == 0)
  105.     {
  106.         //
  107.         // Either error or at eof.
  108.         //
  109.         DELETE buffer;
  110.         return 0;
  111.     }
  112.  
  113.     if (buffer[strlen(buffer) - 1] != '\n' && !feof(fp))
  114.     {
  115.         //
  116.         // Longer line than buffer can hold.
  117.         //
  118.         char *restofline = fgetline(fp, size);
  119.  
  120.         if (restofline == 0) return 0; // eof or error
  121.  
  122.         char *longline = new char[strlen(buffer) + strlen(restofline) + 1];
  123.         (void)strcat(strcpy(longline, buffer), restofline);
  124.  
  125.         DELETE restofline;
  126.         DELETE buffer;
  127.  
  128.         if (longline[strlen(longline) - 1] == '\n')
  129.             longline[strlen(longline) - 1] = '\0';
  130.  
  131.         return longline;
  132.     }
  133.     else
  134.     {
  135.         if (buffer[strlen(buffer) - 1] == '\n')
  136.             buffer[strlen(buffer) - 1] = '\0';
  137.         return buffer;
  138.     }
  139. }
  140.  
  141. /*
  142. ** display_string - prints a string to the given the display, guaranteeing not
  143. **                  to print more than columns\(\) characters.  If the string
  144. **                  exceeds the width of the window, a `!\' is placed in
  145. **                  the final column.  `len\' is the length of the string,
  146. **                  if known, which defaults to zero. `offset\', which
  147. **                  defaults to zero, is non-zero in those cases where we\'ve
  148. **                  already printed `offset\' characters to the screen line.
  149. **                  We never call this when trying to write to the last
  150. **                  row on the screen.  That is the dominion of message\(\).
  151. */
  152.  
  153. void display_string(const char *str, int length, int offset)
  154. {
  155.     int len = (length == 0 ? (int) strlen(str) : length) + offset;
  156.  
  157.     if (len < columns())
  158.     {
  159.         (void)fputs(str, stdout);
  160.         cursor_wrap();
  161.     }
  162.     else if (len > columns())
  163.     {
  164.         (void)printf("%*.*s%c", columns() - offset - 1, columns() - offset - 1,
  165.                      str, '!');
  166.         if (!AM || XN) cursor_wrap();
  167.     }
  168.     else
  169.     {
  170.         (void)fputs(str, stdout);
  171.         if (!AM || XN) cursor_wrap();
  172.     }
  173. }
  174.  
  175. /*
  176. ** error - Prints error message so it can be read.  This is the error
  177. **         function we call once we\'ve initialized the display.
  178. */
  179.  
  180. void error(const char *format, ...)
  181. {
  182.     va_list ap;
  183.     va_start(ap, format);
  184.     clear_message_line();
  185.     (void) vfprintf(stdout, format, ap);
  186.     cursor_wrap();
  187.     va_end(ap);
  188.     synch_display();
  189.     term_display();
  190.     exit(1);
  191. }
  192.  
  193. /*
  194. ** eat_a_character - prints a message and then reads and
  195. **                   discards a character from the keyboard.
  196. **                   signals interrupt read\(2\); in fact we rely on this to
  197. **                   correctly deal with SIGTSTP and SIGWINCH.  If we get
  198. **                   an EINTR, we reprint the message and keep trying to
  199. **                   read a character.  if `standout\' is true, the message
  200. **                   is printed in standout mode; it defaults to false.
  201. */
  202.  
  203. void eat_a_character(const char *msg, void (*redisplay)(), int standout)
  204. {
  205.     char c;
  206.     while (1)
  207.     {
  208.         // read a character dealing with interruption
  209.         if (standout) enter_standout_mode();
  210.         message(msg);
  211.         if (standout) end_standout_mode();
  212.         if (resumingAfterSuspension ||
  213. #ifdef SIGWINCH
  214.             windowSizeChanged       ||
  215. #endif
  216.             read(0, &c, 1) < 0      || // assume only fails when errno==EINTR
  217.             c == KEY_CTL_L)
  218.         {
  219. #ifdef SIGWINCH
  220.             if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
  221. #endif
  222.             resumingAfterSuspension = 0;
  223.             redisplay();
  224.             if (standout) enter_standout_mode();
  225.             message(msg);
  226.             if (standout) end_standout_mode();
  227.         }
  228.         else
  229.             return;
  230.     }
  231. }
  232.  
  233. /*
  234. ** execute - executes command using exec\(2\).  Returns 1 if the exec
  235. **           went OK, otherwise it returns 0.  Assumes that the exec\'d
  236. **           program wants our open file descriptors.
  237. **           Forces the effective uid to the real uid in the child.
  238. */
  239.  
  240. int execute(const char *file, const char *argv[])
  241. {
  242.     unsetraw();
  243.     unset_signals();
  244.     int status;
  245.     pid_t pid = fork();
  246.     switch(pid)
  247.     {
  248.       case -1: // error
  249.         return 0;
  250.       case 0:  // in the child
  251.         if (setuid(getuid()) < 0)
  252.             error("file %s, line %d, setuid() failed" __FILE__, __LINE__);
  253.         execvp(file, (char *const *)argv);
  254.         // exec failed
  255.         exit(1);
  256.       default: // in the parent
  257.         waitpid(pid, &status, 0);
  258.         set_signals();
  259.         setraw();
  260.         return status == 0 ? 1 : 0;
  261.     }
  262. }
  263.  
  264. #ifdef SIGWINCH
  265.  
  266. /*
  267. ** winch - set flag indicating window size changed.
  268. */
  269.  
  270. int windowSizeChanged;  // should be a sig_atomic_t
  271.  
  272. void winch(int)
  273. {
  274.     (void)signal(SIGWINCH, SIG_IGN);
  275.     windowSizeChanged = 1;
  276.     (void)signal(SIGWINCH, winch);
  277. }
  278.  
  279. #include <sys/ioctl.h>
  280.  
  281. /*
  282. ** adjust_window - called to adjust our window after getting a SIGWINCH
  283. */
  284.  
  285. void adjust_window()
  286. {
  287. #ifdef TIOCGWINSZ
  288.     struct winsize w;
  289.     if (ioctl(2, TIOCGWINSZ, (char *)&w) == 0 && w.ws_row > 0) LI = w.ws_row;
  290.     if (ioctl(2, TIOCGWINSZ, (char *)&w) == 0 && w.ws_col > 0) CO = w.ws_col;
  291. #endif
  292.     if (LI < 5 || CO < 20)
  293.         error("screen too small to be useful");
  294. }
  295.  
  296. #endif
  297.  
  298. /*
  299. ** prompt - displays `msg\' prompt and then collects the response.
  300. **          The keys of the response are echoed as they\'re collected.
  301. **          The response should be delete\'d when no longer needed.
  302. **          A response can contain any graphical character. Backspace
  303. **          works as expected. Carriage return indicates the end of
  304. **          response. Non-graphical characters are ignored.  If we
  305. **          get suspended and resumed in prompt\(\), `redisplay\' is
  306. **          the function to call to fixed up all but the message lines
  307. **          of the display.  We rely on signals interrupting read\(2\).
  308. */
  309.  
  310. char *prompt(const char *msg, void (*redisplay)())
  311. {
  312.     size_t written = 0;  // number of characters written to message line
  313.     size_t len = strlen(msg);
  314.     String nmsg(msg);
  315.  
  316.     clear_message_line();
  317.  
  318.     if (len < columns())
  319.     {
  320.         (void)fputs(nmsg, stdout);
  321.         written = len;
  322.     }
  323.     else
  324.     {
  325.         // Leave space for columns\(\)/2 + 1 characters.
  326.         (void)fputs((const char *)nmsg + (len-columns()/2+1), stdout);
  327.         written = columns()/2 - 1;
  328.     }
  329.     synch_display();
  330.  
  331.     //
  332.     // We never echo into the last position in the message window.
  333.     //
  334.     size_t space_available = columns() - written; // available spaces in line
  335.     char *response = new char[space_available + 1];
  336.     size_t pos = 0;  // index of next character in `response\'
  337.  
  338.     char key;
  339.     for (;;)
  340.     {
  341.         if (resumingAfterSuspension ||
  342. #ifdef SIGWINCH
  343.             windowSizeChanged       ||
  344. #endif
  345.             read(0, &key, 1) < 0    || // assume only fails when errno == EINTR
  346.             key == KEY_CTL_L)
  347.         {
  348. #ifdef SIGWINCH
  349.             if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
  350. #endif
  351.             resumingAfterSuspension = 0;
  352.             redisplay();
  353.             clear_message_line();  // make sure we\'re on the message line
  354.             response[pos] = 0;
  355.             if (pos + len < columns())
  356.             {
  357.                 //
  358.                 // Output message and response-to-date.
  359.                 //
  360.                 (void)fputs(nmsg, stdout);
  361.                 (void)fputs(response, stdout);
  362.                 space_available = columns() - pos - len;
  363.             }
  364.             else if (pos < columns())
  365.             {
  366.                 //
  367.                 // Display the response.
  368.                 //
  369.                 (void)fputs(response, stdout);
  370.                 space_available = columns() - strlen(response);
  371.             }
  372.             else
  373.             {
  374.                 //
  375.                 // Display the backend of the response.
  376.                 //
  377.                 (void)fputs(&response[pos - columns()/2 + 1], stdout);
  378.                 space_available = columns()/2 + 1;
  379.             }
  380.             synch_display();
  381.         }
  382.         else if (isprint(key))
  383.         {
  384.             //
  385.             // echo character to message window and wait for another
  386.             //
  387.             response[pos++] = key;
  388.             space_available--;
  389.             if (!space_available)
  390.             {
  391.                 //
  392.                 // Need to allocate more room for the response.
  393.                 // Note that strlen\(response\) == pos
  394.                 //
  395.                 space_available = columns()/2 + 1;
  396.                 char *nresponse = new char[pos + space_available + 1];
  397.                 response[pos] = 0;  // stringify response
  398.                 (void)strcpy(nresponse, response);
  399.  
  400.                 DELETE response;
  401.                 response = nresponse;
  402.  
  403.                 //
  404.                 // Shift prompt in message window so we
  405.                 // always have the end in view to which we\'re
  406.                 // adding characters as they\'re typed.
  407.                 //
  408.                 clear_message_line();
  409.                 (void)fputs(&response[pos - columns()/2 + 1], stdout);
  410.                 key = 0;  // nullify key
  411.             }
  412.             else
  413.             {
  414.                 putchar(key);
  415.                 key = 0;  // nullify key
  416.             }
  417.             synch_display();
  418.         }
  419.         else
  420.             switch (key)
  421.             {
  422.               case KEY_CR: // we have the complete response
  423.                 response[pos] = 0;
  424.                 clear_message_line();
  425.                 synch_display();
  426.                 return response;
  427.               case KEY_BKSP: // back up one character
  428.                 if (pos == 0) { ding(); break; }
  429.                 backspace();
  430.                 DC ? delete_char_at_cursor() : clear_to_end_of_line();
  431.                 --pos;
  432.                 ++space_available;
  433.                 if (space_available == columns())
  434.                 {
  435.                     //
  436.                     // The only way this can happen is if we
  437.                     // had previously shifted the response to the left.
  438.                     // Now we must shift the response to the right.
  439.                     //
  440.                     clear_message_line();
  441.                     response[pos] = 0;
  442.                     if (pos + len < columns())
  443.                     {
  444.                         //
  445.                         // Output message and response-to-date.
  446.                         //
  447.                         (void)fputs(nmsg, stdout);
  448.                         (void)fputs(response, stdout);
  449.                         space_available = columns() - pos - len;
  450.                     }
  451.                     else if (pos < columns())
  452.                     {
  453.                         //
  454.                         // Display the response.
  455.                         //
  456.                         (void)fputs(response, stdout);
  457.                         space_available = columns() - strlen(response);
  458.                     }
  459.                     else
  460.                     {
  461.                         //
  462.                         // Display the backend of the response
  463.                         //
  464.                         (void)fputs(&response[pos - columns()/2 + 1], stdout);
  465.                         space_available = columns()/2 + 1;
  466.                     }
  467.                 }
  468.                 synch_display();
  469.                 break;
  470.               default: ding(); break; // ignore other characters
  471.             }
  472.     }
  473. }
  474.  
  475. /*
  476. ** message - prints a message on the last line of the screen.
  477. **           It is up to the calling process to put the cursor
  478. **           back where it belongs.  Synchs the display.  It can
  479. **           be called as either:
  480. **
  481. **                message\(msg\);
  482. **           or
  483. **                message\(fmt, str\);
  484. **
  485. **           In the later case it must be the case that the format `fmt\'
  486. **           has exactly one `%\' into which the `str\' will be substituted
  487. **           as in the ?printf\(\) functions.  Prints in standout mode.
  488. */
  489.  
  490. // the definition -- declared in utilities.h
  491. int message_window_dirty = 0;
  492.  
  493. void message(const char *fmt, const char *str)
  494. {
  495.     char *msg;          // the complete message to be output
  496.     int allocated = 0;  // was `msg\' allocated in new\(\) space?
  497.  
  498.     clear_message_line();
  499.  
  500.     if (str)
  501.     {
  502.         msg = new char[strlen(fmt) + strlen(str) + 1];
  503.         const char *token = strchr(fmt, '%');
  504.         if (token == 0)
  505.             //
  506.             // This shouldn\'t happen.  But if it does, let\'s
  507.             // just print the format `fmt\'.
  508.             //
  509.             msg = (char *)fmt;
  510.         else
  511.         {
  512.             (void)strncpy(msg, fmt, token - fmt);
  513.             msg[token - fmt] = 0;  // strncpy doesn\'t nullify the string
  514.             (void)strcat(msg, str);
  515.             (void)strcat(msg, token + 1);
  516.             allocated = 1;
  517.         }
  518.     }
  519.     else
  520.         msg = (char *)fmt;
  521.  
  522.     if (strlen(msg) < columns())
  523.         (void)fputs(msg, stdout);
  524.     else
  525.         (void)printf("%*.*s", columns() - 1, columns() - 1, msg);
  526.  
  527.     synch_display();
  528.     message_window_dirty = 1;
  529.     if (allocated) DELETE msg;
  530. }
  531.  
  532. /*
  533. ** yes_or_no - returns true if a \'y\' or \'Y\' is typed in response to
  534. **             the msg. We deal with being suspended and resumed.
  535. **
  536. **             defResponse is the assumed default response.
  537. **
  538. **                 defResponse == Yes ==> that return value is true unless
  539. **                                        \'n\' or \'N\' is typed; else it
  540. **                                        returns false.
  541. **
  542. **                 defResponse == No  ==> that return value is true only if
  543. **                                        \'y\' or \'Y\' is typed; else it
  544. **                                        return false.
  545. **
  546. **             If standout is true the message is displayed in standout mode.
  547. **
  548. **             It is assumed that the message that is printed somehow indicates
  549. **             whether the default response is true or false.
  550. */
  551.  
  552. int yes_or_no(const char *msg,
  553.               void (*redisplay)(),
  554.               Response defResponse,
  555.               int standout)
  556. {
  557.     if (standout) enter_standout_mode();
  558.     message(msg);
  559.     if (standout) end_standout_mode();
  560.  
  561.     char key;
  562.     while (1)
  563.         //
  564.         // read a character dealing with interruption
  565.         //
  566.         if (resumingAfterSuspension ||
  567. #ifdef SIGWINCH
  568.             windowSizeChanged       ||
  569. #endif
  570.             read(0, &key, 1) < 0    || // assume only fails when errno==EINTR 
  571.             key == KEY_CTL_L)
  572.         {
  573. #ifdef SIGWINCH
  574.             if (windowSizeChanged)
  575.             {
  576.                 windowSizeChanged = 0;
  577.                 adjust_window();
  578.             }
  579. #endif
  580.             resumingAfterSuspension = 0;
  581.             redisplay();
  582.             if (standout) enter_standout_mode();
  583.             message(msg);
  584.             if (standout) end_standout_mode();
  585.         }
  586.         else
  587.             break;
  588.     clear_message_line();
  589.     synch_display();
  590.  
  591.     switch (defResponse)
  592.     {
  593.       case Yes:
  594.         return !(key == 'n' || key == 'N');
  595.       case No:
  596.         return   key == 'y' || key == 'Y';
  597.     }
  598. }
  599.  
  600. /*
  601. ** lock_file - lock the file opened with file descriptor `fd\'.
  602. **             Exits on error.
  603. */
  604.  
  605. void lock_file(int fd)
  606. {
  607.     if (lseek(fd, 0, 0) < 0) error("lseek() failed");
  608. #ifdef FLOCK
  609.     if (flock(fd, LOCK_EX) < 0) error("flock - LOCK_EX");
  610. #else
  611.     if (lockf(fd, F_LOCK, 0) < 0) error("lockf - F_LOCK");
  612. #endif
  613. }
  614.  
  615. /*
  616. ** unlock_file - unlock file with file descriptor `fd\'.
  617. **               Exits on error.
  618. */
  619.  
  620. void unlock_file(int fd)
  621. {
  622.     if (lseek(fd, 0, 0) < 0) error("lseek() failed");
  623. #ifdef FLOCK
  624.     if (flock(fd, LOCK_UN) < 0) error("flock - LOCK_UN");
  625. #else
  626.     if (lockf(fd, F_ULOCK, 0) < 0) error("lockf - F_ULOCK");
  627. #endif
  628. }
  629.  
  630. /*
  631. ** cleanup - cleanup and exit after a SIGHUP, SIGTERM, SIGQUIT or SIGINT
  632. */
  633.  
  634. void cleanup(int) { term_display(); exit(0); }
  635.  
  636. /*
  637. ** username - returns the username pertaining to the real uid.
  638. **            Exits on error;
  639. */
  640.  
  641. const char *username()
  642. {
  643.     static String user;
  644.     if (user == "")
  645.     {
  646.         struct passwd *entry = getpwuid(getuid());
  647.         if (!entry)
  648.             error("file %s, line %d, getpwuid() failed", __FILE__, __LINE__);
  649.         user =  entry->pw_name;
  650.     }
  651.     return user;
  652. }
  653.  
  654. /*
  655. ** write_to_pipe - writes the data to the pipe on the file descriptor.
  656. **                 Exits on error. 
  657. */
  658.  
  659. void write_to_pipe(int fd, const char *data, int size)
  660. {
  661.     int nwritten = 0;
  662.     while (size > 0)
  663.     {
  664.         nwritten = write(fd, data, size);
  665.         if (nwritten <= 0)
  666.             error("file %s, line %d, write() failed", __FILE__, __LINE__);
  667.         size -= nwritten;
  668.         data += nwritten;
  669.     }
  670.  
  671. }
  672.  
  673. /*
  674. ** read_file - reads the file pointed to by `fp\' into the array `lines\'.
  675. **             `lines\' must have been previously allocated in the caller,
  676. **             to size `size\'. `len\' is the expected length of the lines
  677. **             in the file. `pos\' is the position in `lines\'
  678. **             to which we start adding the lines; it defaults to zero.
  679. **             In general, this will be zero, but in one case when I\'m
  680. **             building an argv for an execvp\(\), I want a non-zero offset.
  681. **             Returns the number of lines in `lines\' or -1 on error.
  682. **             `lines\' is null-terminated.
  683. **
  684. **             This is used only for reading files containing 
  685. **             relatively few lines.
  686. */
  687.  
  688. int read_file(FILE *fp, char** &lines, int size, int linelen, int pos)
  689. {
  690.     const int chunksize = 20;  // chunksize to `grow\' by
  691.     int nlines = 0;            // number of lines added to `lines\'
  692.  
  693.     char *line = fgetline(fp, linelen);
  694.     for (; line; line = fgetline(fp, linelen))
  695.     {
  696.         // skip comment lines
  697.         if (*line == '#') { DELETE line; continue; }
  698.         // strip off newline
  699.         if (line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = 0;
  700.         lines[pos++] = line;
  701.         nlines++;
  702.         if (pos == size)
  703.         {
  704.             // grow `Areas\'
  705.             char **newspace = new char*[size += chunksize];
  706.             for (int i = 0; i < pos; i++) newspace[i] = lines[i];
  707.             DELETE lines;
  708.             lines = newspace;
  709.         }
  710.     }
  711.     if (feof(fp) && !ferror(fp))
  712.     {
  713.         // null terminate `lines\'
  714.         if (pos == size)
  715.         {
  716.             // got to grow one more
  717.             char **newspace = new char*[size + 1];
  718.             for (int i = 0; i < pos; i++) newspace[i] = lines[i];
  719.             DELETE lines;
  720.             lines = newspace;
  721.         }
  722.         lines[pos] = 0;
  723.         return nlines;
  724.     }
  725.     else
  726.         return -1;
  727. }
  728.  
  729. /*
  730. ** tokenize - returns a null-terminated vector of the words in `line\'
  731. **            The vector and its elements are in volatile storage
  732. **            which we manage here.
  733. */
  734.  
  735. const char **tokenize(const char *line, const char *separators)
  736. {
  737.     //
  738.     // Since strtok modifies it\'s argument, we use a copy of `line\'.
  739.     //
  740.     static char *newline;     // volatile storage of vector elements
  741.     DELETE newline;
  742.     newline = new char[strlen(line) + 1];
  743.     (void)strcpy(newline, line);
  744.  
  745.     const int chunksize = 5;  // chunksize to `grow\' by
  746.     int size   = chunksize;   // total size of vector
  747.     int nwords = 0;           // number of words in vector
  748.     static char **words;      // volatile storage for the word pointers
  749.     DELETE words;
  750.     words = new char*[chunksize];
  751.  
  752.     if ((words[nwords++] = strtok(newline, separators)) == 0)
  753.         return (const char **)words;
  754.  
  755.     while (words[nwords++] = strtok(0, separators))
  756.         if (nwords == size)
  757.         {
  758.             // Grow "words".
  759.             char **newspace = new char*[size += chunksize];
  760.             for (int i = 0; i < nwords; i++) newspace[i] = words[i];
  761.             DELETE words;
  762.             words = newspace;
  763.         }
  764.     return (const char **)words;
  765. }
  766.  
  767. /*
  768. ** read_and_exec_perm - returns non-zero if we have read and execute
  769. **                      permission on the file, otherwise 0.
  770. **                      Returns 0 on error.
  771. */
  772.  
  773. int read_and_exec_perm(const char *file)
  774. {
  775.     return access(file, R_OK | X_OK) == -1 ? 0 : 1;
  776. }
  777.  
  778. /*
  779. ** expand_tilde - expects a string of the form "~ ...".
  780. **                Returns a new string in volatile storage
  781. **                with the user\'s home directory in place of the `~\'.
  782. **                The user\'s home directory is always appended
  783. **                in the form: "/usr/staff/mjlx"; a slash is not added to
  784. **                the end of the home directory string.  Returns the original
  785. **                string if we cannot get the user\'s home directory.
  786. */
  787.  
  788. const char *expand_tilde(char *str)
  789. {
  790.     static char *home = getenv("HOME");
  791.     if (home == NULL)
  792.     {
  793.         struct passwd *user = getpwuid(getuid());
  794.         if (user == NULL) return str;
  795.         home = user->pw_dir;
  796.     }
  797.     if (*str != '~') return str;
  798.     static String expansion;
  799.     expansion = String(home) + (str + 1);
  800.     return expansion;
  801. }
  802.  
  803. /*
  804. ** update_screen_line
  805. **
  806. **     `oldline\' is what is currently on the screen in row `y\'
  807. **     `newline\' is what we want on the screen in row `y\'
  808. **
  809. **     We make a good attempt to optimize the output of characters to
  810. **     the screen.  We want to display `newline\' on the screen,
  811. **     assuming `oldline\' is what is currently displayed.  This
  812. **     will be "good" if `oldline\' and `newline\' are quite similar.
  813. **     That is to say, this should only be called when there is an
  814. **     expectation that `oldline\' and `newline\' are "almost" the same.
  815. */
  816.  
  817. void update_screen_line(const char *oldline, const char *newline, int y)
  818. {
  819.     if (strcmp(oldline, newline) == 0) return;
  820.  
  821.     size_t olen = strlen(oldline);
  822.     size_t nlen = strlen(newline);
  823.     size_t  len = olen < nlen ? olen : nlen;
  824.  
  825.     //
  826.     // Never display more than columns\(\) characters.
  827.     //
  828.     int chop = 0;  // do we need to chop off the tail?
  829.     if (len > columns()) { chop = 1; len = columns(); }
  830.  
  831.     char *equal = new char[len];
  832.  
  833.     //
  834.     // How similar are the two strings?
  835.     //
  836.     int differences = 0;
  837.     for (int i = 0; i < len; i++) equal[i] = 1;
  838.     for (i = 0; i < len; i++)
  839.         if (oldline[i] != newline[i]) { differences++; equal[i] = 0; }
  840.  
  841.     if (differences > columns()/2)
  842.     {
  843.         //
  844.         // We just display the new line.
  845.         //
  846.         clear_to_end_of_line();
  847.         (void)fputs(newline, stdout);
  848.         DELETE equal;
  849.         return;
  850.     }
  851.  
  852.     if (!OS)
  853.     {
  854.         //
  855.         // We can just overwrite the old with the new.
  856.         //
  857.         int last = -2;  // position of last character written
  858.         for (i = 0; i < len; i++)
  859.         {
  860.             if (equal[i]) continue;
  861.             if (i - 1 != last) move_cursor(y, i);
  862.             (i == len - 1 && chop) ? putchar('!') : putchar(newline[i]);
  863.             last = i;
  864.         }
  865.         if (nlen > olen)
  866.         {
  867.             //
  868.             // Have more characters to output.
  869.             //
  870.             chop = len > columns();
  871.             move_cursor(y, i);
  872.             for (i = (int)len; i < nlen && i < columns(); i++)
  873.                 (i == columns()-1 && chop) ? putchar('!') : putchar(newline[i]);
  874.         }
  875.         else if (nlen < olen)
  876.         {
  877.             move_cursor(y, i);
  878.             clear_to_end_of_line();
  879.         }
  880.     }
  881.     else
  882.     {
  883.         //
  884.         // We can\'t overwrite.  Truncate at first difference.
  885.         //
  886.         int first = 0;
  887.         for (i = 0; i < len; i++)
  888.             if (!equal[i]) { first = i; break; }
  889.         move_cursor(y, i);
  890.         clear_to_end_of_line();
  891.         for (; i < nlen && i < columns(); i++)
  892.             (i == columns() - 1) ? putchar('!') : putchar(newline[i]);
  893.     }
  894.     DELETE equal;
  895. }
  896.  
  897. /*
  898. ** update_modeline - this routine concatenates the two strings
  899. **                   into the modeline.  The modeline
  900. **                   is displayed in standout mode if possible.
  901. **                   We never put more than columns\(\) characters into
  902. **                   the modeline.  The modeline is the penultimate
  903. **                   line on the terminal screen.  It does not
  904. **                   synch the display.  If head == tail == 0, we
  905. **                   just display the old modeline.  This happens
  906. **                   if for some reason we had to clear the screen.
  907. */
  908.  
  909. // the current modeline
  910. char *current_modeline;
  911.  
  912. void update_modeline(const char *head, const char *tail)
  913. {
  914.     move_to_modeline();
  915.     enter_standout_mode();
  916.  
  917.     if (head == 0)   // actually, head == tail == 0
  918.     {
  919.         // Redisplay old modeline.
  920.         (void)fputs(current_modeline, stdout);
  921.         end_standout_mode();
  922.         return;
  923.     }
  924.  
  925.     int len = (int) strlen(head);
  926.     char *new_modeline = new char[columns() + 1];
  927.     (void)strncpy(new_modeline, head, columns());
  928.     new_modeline[columns()] = 0;  // ensure it\'s null-terminated
  929.     
  930.     if (len < columns())
  931.     {
  932.         //
  933.         // Write exactly columns\(\) characters to modeline.
  934.         //
  935.         for (int i = len; i < columns() - 1 && tail && *tail; i++, tail++)
  936.             new_modeline[i] = *tail;
  937.         if (i < columns() - 1)
  938.         {
  939.             new_modeline[i++] = ' ';
  940.             for (; i < columns(); i++) new_modeline[i] = '-';
  941.         }
  942.         else if (tail && *tail)
  943.             //
  944.             // The tail was overly long.  Put a \'!\' in the last space
  945.             // on the modeline to signify truncation.
  946.             //
  947.             new_modeline[columns() - 1] = '!';
  948.         else
  949.             //
  950.             // Here len == columns\(\)-1 && there is nothing else in tail.
  951.             //
  952.             new_modeline[columns() - 1] = ' ';
  953.     }
  954.     else if (len > columns())
  955.         new_modeline[columns() - 1] = '!';
  956.  
  957.     if (current_modeline)
  958.     {
  959.         update_screen_line(current_modeline, new_modeline, rows() - 2);
  960.         DELETE current_modeline;
  961.     }
  962.     else
  963.         (void)fputs(new_modeline, stdout);
  964.  
  965.     current_modeline = new_modeline;
  966.     end_standout_mode();
  967. }
  968.  
  969. /*
  970. ** lines_displayed - returns the number of lines in the DList
  971. **                   currently displayed on the screen.
  972. */
  973.  
  974. int lines_displayed(DList *dl)
  975. {
  976.     DLink *ln = dl->firstLine();
  977.     for (int i = 1; ln != dl->lastLine(); i++, ln = ln->next()) ;
  978.     return i;
  979. }
  980.  
  981. /*
  982. ** get_prob_number - returns the prob# of the current line of the DList
  983. **                   in volatile storage.  The line is of the form:
  984. **
  985. **                       prob# ...
  986. **
  987. **                   We must know INTIMATELY how summary_lines\(\)
  988. **                   formats the output.
  989. */
  990.  
  991. char *get_prob_number(const DList *dl)
  992. {
  993.     static char* number = 0; // our volatile storage
  994.     DELETE number;           // release our volatile storage
  995.     const char* begin = dl->currLine()->line();
  996.  
  997.     // step over any spaces preceding Prob #
  998.     while (*begin == ' ') begin++;
  999.  
  1000.     const char* end = strchr(begin, ' ');
  1001.     int len = end - begin;
  1002.     number  = new char[len + 1];
  1003.     (void)strncpy(number, begin, len);
  1004.     number[len] = 0; // stringify it
  1005.  
  1006.     return number;
  1007. }
  1008.  
  1009. /*
  1010. ** leftshift_current_line - shifts the current line in DList left until
  1011. **                          its tail is visible.
  1012. */
  1013.  
  1014. void leftshift_current_line(DList *dl)
  1015. {
  1016.     int inc = dl->currLine()->length()-columns()+1;
  1017.     move_cursor(dl->savedYPos(), 0);
  1018.     clear_to_end_of_line();
  1019.     display_string(&(dl->currLine()->line())[inc],columns()-1);
  1020.     dl->saveYXPos(dl->savedYPos(), max(goal_column(dl)-inc, 0));
  1021.     move_cursor(dl->savedYPos(), dl->savedXPos());
  1022. }
  1023.  
  1024. /*
  1025. ** rightshift_current_line - rightshifts current line to "natural" position.
  1026. */
  1027.  
  1028. void rightshift_current_line(DList *dl)
  1029. {
  1030.     move_cursor(dl->savedYPos(), 0);
  1031.     clear_to_end_of_line();
  1032.     display_string(dl->currLine()->line(), dl->currLine()->length());
  1033.     dl->saveYXPos(dl->savedYPos(), goal_column(dl));
  1034.     move_cursor(dl->savedYPos(), dl->savedXPos());
  1035. }
  1036.  
  1037. /*
  1038. ** initial_listing - prints the initial listing screen.
  1039. **                   Adjusts firstLine\(\), lastLine\(\) and currLine\(\).
  1040. */
  1041.  
  1042. void initial_listing(DList *dl)
  1043. {
  1044.     DLink *ln = dl->head();
  1045.     dl->setFirst(ln);
  1046.     dl->setCurrLine(ln);
  1047.     cursor_home();
  1048.     for (int i = 0; i < rows() - 2 && ln; ln = ln->next(), i++)
  1049.     {
  1050.         clear_to_end_of_line();
  1051.         display_string(ln->line(), ln->length());
  1052.     }
  1053.  
  1054.     ln ? dl->setLast(ln->prev()) : dl->setLast(dl->tail());
  1055.  
  1056.     //
  1057.     // Don\'t forget to clear any remaining lines in those
  1058.     // cases when the screen hasn\'t already been cleared and
  1059.     // there are potentially dirty lines remaining.
  1060.     //
  1061.     move_cursor(i, 0);
  1062.     for (; i < rows() - 2; i++) { clear_to_end_of_line(); cursor_down(); }
  1063. }
  1064.  
  1065. /*
  1066. ** seconds_in_date - returns the number of seconds in a date string
  1067. **                   of the form:
  1068. **
  1069. **                       "mmm dd mm:hh:ss yyyy"
  1070. **
  1071. **                   This is the format returned by ctime\(3\), with
  1072. **                   the leading "day" name chopped off.  According to ANSI C,
  1073. **                   a long must be able to hold values up to at least
  1074. **                   2147483647.  This will keep this function working for
  1075. **                   many years into the future.  Eventually, CurrentYear will
  1076. **                   have to be incremented.
  1077. */
  1078.  
  1079. long seconds_in_date(const char *date)
  1080. {
  1081.     //
  1082.     // days\[i\] is the number of days preceding month `i\',
  1083.     // where 0 <= i <= 11
  1084.     //
  1085.     static const int days[] = { 0,31,59,90,120,151,181,212,243,273,304,334 };
  1086.  
  1087.     //
  1088.     // mhash\[*date + *\(date+1\) + *\(date+2\) - 268\] hashes to an integer
  1089.     // in the range 0-11 signifying the month.
  1090.     //
  1091.     static char mhash[] = { 11, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1092.                             0, 0, 7, 0, 0, 2, 0, 0, 3, 0, 0, 9, 4, 8, 0,
  1093.                             0, 6, 0, 5, 0, 0, 0, 0, 0, 0, 10 };
  1094.  
  1095.     const long SecondsPerDay  = 86400L;
  1096.     const long SecondsPerYear = 31536000L;
  1097.     const int CurrentYear = 1992;
  1098.     const char *const fmt = "%d";
  1099.     long total = 0;
  1100.     int tmp, mon = mhash[*date + *(date + 1) + *(date + 2) - 268];
  1101.  
  1102.     (void)sscanf(date + 13, fmt, &tmp);   // seconds
  1103.     total += tmp;
  1104.     (void)sscanf(date + 10, fmt, &tmp);   // minutes
  1105.     total += tmp * 60;
  1106.     (void)sscanf(date + 7 , fmt, &tmp);   // hours
  1107.     total += tmp * 3600;
  1108.     (void)sscanf(date + 4 , fmt, &tmp);   // day of the month
  1109.     total += (tmp - 1) * SecondsPerDay;
  1110.     // days in months preceding this one
  1111.     total += days[mon] * SecondsPerDay;
  1112.     (void)sscanf(date + 16, fmt, &tmp);   // the year
  1113.     // leap year adjustment
  1114.     if (tmp % 4 == 0 && tmp % 100 != 0 && mon >= 2) total += SecondsPerDay;
  1115.     total += (tmp - CurrentYear) * SecondsPerYear;
  1116.     return total;
  1117. }
  1118.  
  1119. /*
  1120. ** temporary_file - returns the name of a temporary file.  The temporary
  1121. **                  is forced to have permission 666.  Note that
  1122. **                  we force tmpnam\(3\) to store the name for us in
  1123. **                  volatile storage.
  1124. */
  1125.  
  1126. const char *temporary_file()
  1127. {
  1128.     char *file = tmpnam(0);
  1129.     if (file == 0)
  1130.         error("file %s, line %d, tmpnam() failed", __FILE__, __LINE__);
  1131.     int fd;
  1132.     if ((fd = open(file, O_RDWR|O_CREAT, 0666)) < 0)
  1133.         error("file %s, line %d, open(%s) failed",
  1134.               __FILE__, __LINE__, file);
  1135.     (void)close(fd);
  1136.     return file;
  1137. }
  1138.  
  1139. /*
  1140. ** set_signals - set up our signal handlers
  1141. */
  1142.  
  1143. void set_signals()
  1144. {
  1145.     (void)signal(SIGHUP,  cleanup);
  1146.     (void)signal(SIGINT,  cleanup);
  1147.     (void)signal(SIGQUIT, cleanup);
  1148.     (void)signal(SIGTERM, cleanup);
  1149. #ifdef SIGTSTP
  1150.     (void)signal(SIGTSTP, termstop);
  1151. #endif
  1152. #ifdef SIGWINCH
  1153.     (void)signal(SIGWINCH, winch);
  1154. #endif
  1155. }
  1156.  
  1157. /*
  1158. ** unset_signals - set signals back to defaults
  1159. */
  1160.  
  1161. void unset_signals()
  1162. {
  1163.     (void)signal(SIGHUP,  SIG_DFL);
  1164.     (void)signal(SIGINT,  SIG_DFL);
  1165.     (void)signal(SIGQUIT, SIG_DFL);
  1166.     (void)signal(SIGTERM, SIG_DFL);
  1167. #ifdef SIGTSTP
  1168.     (void)signal(SIGTSTP, SIG_DFL);
  1169. #endif
  1170. #ifdef SIGWINCH
  1171.     (void)signal(SIGWINCH, SIG_DFL);
  1172. #endif
  1173. }
  1174.  
  1175. /*
  1176. ** block_tstp_and_winch - block SIGTSTP and SIGWINCH
  1177. */
  1178.  
  1179. #ifdef BSDSIGNALS
  1180. static int oldmask;
  1181. #elif POSIXSIGNALS
  1182. static sigset_t oldset;
  1183. #endif
  1184.  
  1185. void block_tstp_and_winch()
  1186. {
  1187. #ifdef BSDSIGNALS
  1188.     int oldmask = sigblock(sigmask(SIGTSTP)
  1189. #ifdef SIGWINCH
  1190.                            | sigmask(SIGWINCH)
  1191. #endif
  1192.                            );
  1193. #elif POSIXSIGNALS
  1194.     sigset_t newset;
  1195.     sigemptyset(&newset);
  1196. #ifdef SIGTSTP
  1197.     sigaddset(&newset, SIGTSTP);
  1198. #endif
  1199. #ifdef SIGWINCH
  1200.     sigaddset(&newset, SIGWINCH);
  1201. #endif
  1202.     if (sigprocmask(SIG_BLOCK, &newset, &oldset) < 0)
  1203.         error("file %s, line %d, sigprocmask(SIG_BLOCK) failed\n",
  1204.               __FILE__, __LINE__);
  1205. #else
  1206.     //
  1207.     // We use ANSI C signals.  These can be "lost" but it\'s the
  1208.     // best we can do.
  1209.     //
  1210. #ifdef SIGTSTP
  1211.     (void)signal(SIGTSTP, SIG_IGN);
  1212. #endif
  1213. #ifdef SIGWINCH
  1214.     (void)signal(SIGWINCH, SIG_IGN);
  1215. #endif
  1216. #endif
  1217. }
  1218.  
  1219. /*
  1220. ** unblock_tstp_and_winch - unblock SIGTSTP and SIGWINCH
  1221. */
  1222.  
  1223. void unblock_tstp_and_winch()
  1224. {
  1225. #ifdef BSDSIGNALS
  1226.     (void)sigsetmask(oldmask);
  1227. #elif POSIXSIGNALS
  1228.     if (sigprocmask(SIG_SETMASK, &oldset, 0) < 0)
  1229.         error("file %s, line %d, sigprocmask(SIG_SETMASK) failed\n",
  1230.               __FILE__, __LINE__);
  1231. #else
  1232. #ifdef SIGTSTP
  1233.     (void)signal(SIGTSTP, termstop);
  1234. #endif
  1235. #ifdef SIGWINCH
  1236.     (void)signal(SIGWINCH, winch);
  1237. #endif
  1238. #endif
  1239. }
  1240.  
  1241.