home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume33 / problem1.1 / part07 / utilities.C
Encoding:
C/C++ Source or Header  |  1992-11-13  |  34.5 KB  |  1,276 lines

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