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

  1. /*
  2. **
  3. ** problem - a problem database manager
  4. **
  5. ** Written in C++ using the termcap\(3\) library for screen management
  6. ** and GDBM as the database library.
  7. **
  8. ** problem.C is made by cating together problem1.C and problem2.C
  9. **
  10. ** problem1.C problem1.C 1.21   Delta\'d: 13:22:00 10/13/92   Mike Lijewski, CNSF
  11. **
  12. ** Copyright \(c\) 1991, 1992 Cornell University
  13. ** All rights reserved.
  14. **
  15. ** Redistribution and use in source and binary forms are permitted
  16. ** provided that: \(1\) source distributions retain this entire copyright
  17. ** notice and comment, and \(2\) distributions including binaries display
  18. ** the following acknowledgement:  ``This product includes software
  19. ** developed by Cornell University\'\' in the documentation or other
  20. ** materials provided with the distribution and in all advertising
  21. ** materials mentioning features or use of this software. Neither the
  22. ** name of the University nor the names of its contributors may be used
  23. ** to endorse or promote products derived from this software without
  24. ** specific prior written permission.
  25. **
  26. ** THIS SOFTWARE IS PROVIDED ``AS IS\'\' AND WITHOUT ANY EXPRESS OR
  27. ** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
  28. ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  29. */
  30.  
  31. #include <ctype.h>
  32.  
  33. #ifndef _IBMR2
  34. #include <libc.h>
  35. #endif
  36.  
  37. #include <fcntl.h>
  38. #include <new.h>
  39. #include <osfcn.h>
  40. #include <signal.h>
  41. #include <stdio.h>
  42. #include <stdlib.h>
  43. #include <string.h>
  44. #include <sys/stat.h>
  45. #include <sys/types.h>
  46. #include <sys/wait.h>
  47. #include <time.h>
  48. #include <unistd.h>
  49.  
  50. #include "classes.h"
  51. #include "display.h"
  52. #include "keys.h"
  53. #include "lister.h"
  54. #include "problem.h"
  55. #include "regexp.h"
  56. #include "utilities.h"
  57. #include "version.h"
  58.  
  59. // a little POSIX stuff
  60. #ifndef SEEK_SET
  61. #define SEEK_SET 0
  62. #endif
  63. #ifndef SEEK_END
  64. #define SEEK_END 2
  65. #endif
  66.  
  67. //
  68. // Where problem\'s maillists and the sequence file reside.
  69. //
  70. #ifdef HOMEBASE
  71. const char *HomeBase = HOMEBASE;
  72. #else
  73. const char *HomeBase = "/staff/mjlx/src/c++/problem";
  74. #endif
  75.  
  76. //
  77. // Name of file containing valid problem areas relative to `HomeBase\'.
  78. //
  79. const char *const ProblemAreas = "AREAS";
  80.  
  81. //
  82. // Definition of our GDMB filehandle  -- only one open GDBM file at a time.
  83. //
  84. GDBM_FILE GdbmFile;
  85.  
  86. //
  87. // Suffix for gdbm files.
  88. //
  89. const char *const GdbmSuffix = ".gdbm";
  90.  
  91. //
  92. // File containing last problem #, relative to `HomeBase\'.
  93. //
  94. const char *const SequenceFile = "SEQUENCE";
  95.  
  96. //
  97. // Prefix for maillist files.
  98. //
  99. const char *const MailListPrefix = "MAILLIST";
  100.  
  101. //
  102. // Our modeline prefix.
  103. //
  104. const char *const ModelinePrefix = "----- Problem: ";
  105.  
  106. //
  107. // Help message for the message window when displaying help.
  108. //
  109. const char *const HELP_MSG[] = {
  110.     "Space scrolls forward.  Other keys quit.",
  111.     "Space forward, Backspace backward.  Other keys quit.",
  112.     "Backspace scrolls backward.  Other keys quit.",
  113.     "Any key quits."
  114. };
  115.  
  116. //
  117. // Our fields.
  118. //
  119. static const char *const Fields[] =
  120. {
  121.     "Area",
  122.     "Logger",
  123.     "Reporter",
  124.     "Logged",
  125.     "Updated",
  126.     "Keywords",
  127.     "Summary",
  128.     "Status",
  129.     "Site",
  130.     "Severity",
  131.     "Problem #"
  132. };
  133.  
  134. inline int NFields() { return sizeof(Fields) / sizeof(Fields[0]); }
  135.  
  136. //
  137. // The current area - used by setCurrentArea\(\) and CurrentArea\(\).
  138. //
  139. static String current_area;
  140.  
  141. void setCurrentArea(const char *area)
  142. {
  143.     current_area    = area;
  144.     const char *tmp = strchr(current_area, '-');
  145.  
  146.     // strip off any comments
  147.     if (tmp)
  148.         current_area[tmp - (const char *)current_area] = 0;
  149.     else
  150.         tmp = (const char *)current_area + current_area.length();
  151.  
  152.     // strip off any trailing blanks
  153.     for (--tmp; *tmp == ' ' || *tmp == '\t'; --tmp)
  154.         current_area[tmp - (const char *)current_area] = 0;
  155.  
  156.     // set any remaining spaces to underscores
  157.     while ((tmp = strchr(current_area, ' ')) != 0)
  158.         current_area[tmp - (const char *)current_area] = '_';
  159. }
  160.  
  161. inline const char *CurrentArea() { return current_area; }
  162.  
  163. //
  164. // our commands - the first character of each string is the unique
  165. //                character identifying that command.
  166. //
  167. static const char *const Commands[] =
  168. {
  169.     "l  -- log new problem",
  170.     "a  -- append to a problem",
  171.     "c  -- close a problem",
  172.     "e  -- examine a problem",
  173.     "v  -- view problem summaries",
  174.     "s  -- subscribe to this problem area",
  175.     "u  -- unsubscribe from this problem area",
  176.     "k  -- keyword search over problem headers",
  177.     "K  -- keyword search over problem headers and data",
  178.     "d  -- delete a problem from the database",
  179.     "r  -- reorganize the database",
  180.     "M  -- modify keyword field",
  181.     "R  -- reopen a closed problem",
  182.     "q  -- quit"
  183. };
  184.  
  185. inline int NCommands() { return sizeof(Commands) / sizeof(Commands[0]); }
  186.  
  187. /*
  188. ** exception handler for new\(\) - called once in main\(\)
  189. */
  190.  
  191. static void free_store_exception()
  192. {
  193.     error("exiting, memory exhausted, sorry");
  194. }
  195.  
  196. /*
  197. ** get_problem_areas - read in the valid problem areas. Exits on error.
  198. */
  199.  
  200. static const char **get_problem_areas()
  201. {
  202.     static char **Areas;
  203.     if (Areas) return (const char **)Areas;
  204.  
  205.     String filename = String(HomeBase) + "/" + ProblemAreas;
  206.     FILE *fp = fopen(filename, "r");
  207.     if (!fp)
  208.         error ("file %s, line %d, couldn't fopen() `%s'",
  209.                __FILE__, __LINE__, (const char *)filename);
  210.  
  211.     const int chunksize = 10;  // average number of problem areas expected
  212.     const int linelen   = 80;  // average length of line in AREAS expected
  213.     Areas = new char*[chunksize];
  214.  
  215.     if (read_file(fp, Areas, chunksize, linelen) < 0)
  216.         error("file %s, line %d, error reading `%s'.",
  217.               __FILE__, __LINE__, (const char *)filename);
  218.  
  219.     (void)fclose(fp);
  220.  
  221.     return (const char **)Areas;
  222. }
  223.  
  224. /*
  225. ** is_area - is `area\' a valid problem area? Returns 1 if true, else 0.
  226. */
  227.  
  228. static int is_area(const char *area)
  229. {
  230.     const char **areas = get_problem_areas();
  231.  
  232.     for (int i = 0; areas[i]; i++)
  233.     {
  234.         const char *space = strchr(areas[i],' ');
  235.         int length = space ? space - areas[i] - 1 : (int)strlen(areas[i]);
  236.         if (strncmp(area, areas[i], length) == 0) return 1;
  237.     }
  238.  
  239.     return 0;
  240. }
  241.  
  242. /*
  243. ** NAreas - the number of areas
  244. */
  245.  
  246. static int NAreas()
  247. {
  248.     static int nAreas;
  249.     if (nAreas) return nAreas;
  250.     const char **areas = get_problem_areas();
  251.     for (int i = 0; areas[i]; i++) ;
  252.     return nAreas = i;
  253. }
  254.  
  255. /*
  256. ** display_area_screen - displays the screen of available areas.
  257. **                       We assume that the caller will display the modeline.
  258. **                       Needs at least four rows to be useful.
  259. */
  260.  
  261. static void display_area_screen()
  262. {
  263.     cursor_home();
  264.     clear_to_end_of_line();
  265.     const char **areas = get_problem_areas();
  266.     enter_standout_mode();
  267.  
  268.     //
  269.     // If we have enough available screen lines, we display the areas
  270.     // one per line.  Otherwise, we display them in columns of up to
  271.     // column_width characters.
  272.     //
  273.     const int column_width = 25;           // space allowed for each area
  274.     int dvd = columns() / column_width;    // # compacted areas per row
  275.     int rws = rows() - 5;
  276.     int compact = NAreas() <= rws ? 0 : 1; // compact the areas?
  277.  
  278.     const char *fmt = NAreas() <= rws * dvd ? "The %d Areas:" :
  279.                                               "The First %d Areas:";
  280.  
  281.     char *msg = new char[strlen(fmt) + 8]; // lots of problem areas
  282.  
  283.     (void)sprintf(msg, fmt, NAreas() <= rws * dvd ? NAreas() : rws * dvd);
  284.  
  285.     display_string(msg);
  286.  
  287.     // maximum # of digits we must display
  288.     int digits = int(strlen(msg) - strlen(fmt)) + 2;
  289.  
  290.     DELETE msg;
  291.  
  292.     end_standout_mode();
  293.     clear_to_end_of_line();
  294.     cursor_wrap();
  295.  
  296.     for (int i = 0, offset = 0; i < NAreas() && i < rws * dvd; i++)
  297.     {
  298.         if (compact)
  299.         {
  300.             offset = (i / rws) * column_width;
  301.             move_cursor((i % rws) + 2, offset);
  302.         }
  303.         clear_to_end_of_line();
  304.         (void)fputs("  ", stdout);  // two spaces for initial indentation
  305.         enter_standout_mode();
  306.         (void)fprintf(stdout, "%*d", digits, i + 1);
  307.         end_standout_mode();
  308.         putchar(' ');               // and one more between number and area
  309.         display_string(areas[i], 0, digits + 3 + offset);
  310.     }
  311.  
  312.     // clear remaining dirty lines
  313.     for (; i < rows() - 4; i++) { clear_to_end_of_line(); cursor_wrap(); }
  314. }
  315.  
  316. /*
  317. ** help - display some help lines.  The first two lines of the output
  318. **        consists of the message `msg\' and then a blank line.
  319. **        This is followed by the contents of `lines\', which has `dim\'
  320. **        elements.
  321. **
  322. */
  323.  
  324. static void help(const char *lines[], int dim, const char *msg)
  325. {
  326.     String old_modeline(current_modeline);
  327.     update_modeline("----- HELP");
  328.     
  329.     int position = 0, offset = 0;
  330.     char key;
  331.     do
  332.     {
  333.         cursor_home();
  334.         if (position == 0)
  335.         {
  336.             offset = 2;
  337.             clear_to_end_of_line();
  338.             display_string(msg);
  339.             clear_to_end_of_line();
  340.             move_cursor(2, 0);
  341.         }
  342.         for (int i = 0; i + offset < rows()-2 && i + position < dim; i++)
  343.         {
  344.             clear_to_end_of_line();
  345.             display_string(lines[i + position]);
  346.         }
  347.         move_cursor(i + offset, 0);
  348.         for (; i + offset < rows() - 2; i++)
  349.         {
  350.             clear_to_end_of_line();
  351.             cursor_down();
  352.         }
  353.         clear_message_line();
  354.  
  355.         if (position == 0 && dim + offset <= rows() - 2)
  356.             // whole help message fits in initial screen
  357.             (void)fputs(HELP_MSG[3], stdout);
  358.         else if (position == 0)
  359.             // the head of the help message -- it all doesn\'t fit
  360.             (void)fputs(HELP_MSG[0], stdout);
  361.         else if (position + rows() - 2 >= dim)
  362.             // the tail of the help message
  363.             (void)fputs(HELP_MSG[2], stdout);
  364.         else
  365.             //  somewhere in between
  366.             (void)fputs(HELP_MSG[1], stdout);
  367.         synch_display();
  368.  
  369.         if (resumingAfterSuspension ||
  370. #ifdef SIGWINCH
  371.             windowSizeChanged       ||
  372. #endif
  373.             read(0, &key, 1) < 0 // assume fails only when errno == EINTR
  374.             )
  375.         {
  376. #ifdef SIGWINCH
  377.             if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
  378. #endif
  379.             resumingAfterSuspension = 0;
  380.             update_modeline();
  381.             continue;
  382.         }
  383.         else if (key == KEY_SPC)
  384.         {
  385.             if (position >= dim - 1) break;
  386.             position += (position == 0 ? rows() - 2 - offset : rows() - 2);
  387.         }
  388.         else if (key == *BC)
  389.         {
  390.             if (position == 0) break;
  391.             position -= rows() - 2;
  392.             if (position < 0) position = 0;
  393.         }
  394.         else 
  395.             break;  // return to the listing
  396.         
  397.         offset = 0;
  398.     }
  399.     while (position < dim + offset);
  400.  
  401.     update_modeline(old_modeline);
  402. }
  403.  
  404. /*
  405. ** redisplay_area_screen - suitable for calling after SIGWINCH and SIGTSTP
  406. */
  407.  
  408. static void redisplay_area_screen()
  409. {
  410.     display_area_screen();
  411.     update_modeline();
  412. }
  413.  
  414. /*
  415. ** choose_problem_area - returns a problem area to examine.
  416. **                       Also gives user option to exit.
  417. */
  418.  
  419. static const char *choose_problem_area()
  420. {
  421.     const char **areas = get_problem_areas();
  422.     display_area_screen();
  423.     update_modeline(ModelinePrefix, "---- q (quit) H (help)");
  424.     const char *helpmsg = "The Available Problem Areas:";
  425.     char key;
  426.     int index;
  427.  
  428.     if (NAreas() < 10)
  429.     {
  430.         //
  431.         // The most usual case.  Read digit as typed.
  432.         //
  433.         message("Your Choice --> ");
  434.  
  435.         while (1)
  436.         {
  437.             if (resumingAfterSuspension ||
  438. #ifdef SIGWINCH
  439.                 windowSizeChanged       ||
  440. #endif
  441.                 read(0, &key, 1) < 0)  // assume only fails when errno==EINTR
  442.             { 
  443. #ifdef SIGWINCH
  444.  
  445.                 if (windowSizeChanged)
  446.                 {
  447.                     windowSizeChanged = 0;
  448.                     adjust_window();
  449.                 }
  450. #endif
  451.                 resumingAfterSuspension = 0;
  452.                 redisplay_area_screen();
  453.                 message("Your Choice --> ");
  454.                 continue;
  455.             }
  456.             else if (key == KEY_q)
  457.             {
  458.                 clear_message_line();
  459.                 term_display();
  460.                 exit(0);
  461.             }
  462.             else if (key == KEY_H || key == KEY_QM)
  463.             {
  464.                 help(areas, NAreas(), helpmsg);
  465.                 redisplay_area_screen();
  466.                 message("Your Choice --> ");
  467.             }
  468.             else
  469.             {
  470.                 index = key - '0';
  471.                 if (index > 0 && index <= NAreas()) return areas[index-1];
  472.                 ding();
  473.             }
  474.         }
  475.     }
  476.     else
  477.     {
  478.         char *response;
  479.         while (1)
  480.         {
  481.             //
  482.             // prompt\(\) takes care of window resizes and resume/suspends
  483.             //
  484.             response = prompt("Your Choice --> ", redisplay_area_screen);
  485.             if (*response == KEY_q)
  486.             {
  487.                 clear_message_line();
  488.                 term_display();
  489.                 exit(0);
  490.             }
  491.             else if (*response == KEY_H || *response == KEY_QM)
  492.             {
  493.                 help(areas, NAreas(), helpmsg);
  494.                 redisplay_area_screen();
  495.                 message("Your Choice --> ");
  496.                 DELETE response;
  497.             }
  498.             else
  499.             {
  500.                 index = atoi(response);
  501.                 DELETE response;
  502.                 if (index > 0 && index <= NAreas()) return areas[index - 1];
  503.                 ding();
  504.             }
  505.         }
  506.     }
  507. }
  508.  
  509. /*
  510. ** max_field_length - returns the length of the longest field
  511. */
  512.  
  513. static int max_field_length()
  514. {
  515.     static int len;
  516.     if (!len) 
  517.         for (int i = 0; i < NFields(); i++)
  518.             if (len < strlen(Fields[i])) len = (int) strlen(Fields[i]);
  519.     return len;
  520. }
  521.  
  522. /*
  523. ** get_severity - prompt for the severity of the problem. Deal with
  524. **                getting SIGTSTP or SIGWINCH.
  525. */
  526.  
  527. static char get_severity(auto void (*redisplay)())  // "auto" needed by g++
  528. {
  529.     const char *msg = "Severity (1 (=highest), 2, 3 or 4 (=lowest)) --> ";
  530.     message(msg);
  531.     char key;
  532.     while (1)
  533.     {
  534.         if (resumingAfterSuspension ||
  535. #ifdef SIGWINCH
  536.             windowSizeChanged       ||
  537. #endif
  538.             read(0, &key, 1) < 0)   // assume only fails when errno == EINTR 
  539.         {
  540. #ifdef SIGWINCH
  541.             if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
  542. #endif
  543.             resumingAfterSuspension = 0;
  544.             redisplay();
  545.             message(msg);
  546.             continue;
  547.         }
  548.         switch (key)
  549.         {
  550.           case '1': case '2': case '3': case '4': return key;
  551.           default: ding(); break;
  552.         }
  553.     }
  554. }
  555.  
  556. /*
  557. ** filesize - returns size of file or MAXFILESIZE,
  558. **            whichever is smaller. Exits on error.
  559. */
  560.  
  561. static int filesize(const char *file)
  562. {
  563. #ifdef MAXFILESIZE
  564.     const int MaxFileSize =  MAXFILESIZE;
  565. #else
  566.     const int MaxFileSize = 16000;
  567. #endif
  568.     struct stat buf;
  569.     if (stat(file, &buf) < 0)
  570.         error("file %s, line %d, fstat() failed", __FILE__, __LINE__);
  571.     return buf.st_size > MaxFileSize ? MaxFileSize : buf.st_size;
  572. }
  573.  
  574. /*
  575. ** sequence_file - returns full pathname of "SequenceFile"
  576. */
  577.  
  578. static const char *sequence_file()
  579. {
  580.     static String filename;
  581.     if (filename == "") filename = String(HomeBase) + "/" + SequenceFile;
  582.     return filename;
  583. }
  584.  
  585. /*
  586. ** update_sequence_file - reads the previous problem number from
  587. **                        `SequenceFile\'; increments that number;
  588. **                        writes it back to the file and returns it.
  589. **                        Exits on error. We should have an exclusive
  590. **                        lock on the sequence file before we get here.
  591. **                        This is to guarantee that we only have one
  592. **                        "writer" to the GDBM file.
  593. */
  594.  
  595. static const char *update_sequence_file(int fd)
  596. {
  597.     static char buf[10];  // won\'t have this many problems for a while
  598.  
  599.     FILE *fp = fdopen(fd, "r+");
  600.     if (!fp)
  601.         error("file %s, line %d, fdopen() on `%s' failed",
  602.               __FILE__, __LINE__, sequence_file());
  603.  
  604.     char *line = fgetline(fp, 10);
  605.  
  606.     if (!fp)
  607.         error("file %s, line %d, fgetline() on `%s' failed",
  608.               __FILE__, __LINE__, sequence_file());
  609.     (void)sprintf(buf, "%d", atoi(line) + 1);
  610.  
  611.     //
  612.     // Truncate the file.  I\'d like to use ftruncate\(2\) here, but that
  613.     // isn\'t as portable as a close\(open\(\)\) scheme.
  614.     //
  615.     if (close(open(sequence_file(), O_RDWR|O_TRUNC)) < 0)
  616.         error("file %s, line %d, close(open()) of `%s' failed",
  617.               __FILE__, __LINE__, sequence_file());
  618.  
  619.     // go to the beginning
  620.     if (lseek(fd, 0, SEEK_SET) < 0)
  621.         error("file %s, line %d, lseek() on `%s' failed",
  622.               __FILE__, __LINE__, sequence_file());
  623.  
  624.     // write the next problem #
  625.     if (write(fd, buf, (unsigned) strlen(buf)) < 0)
  626.         error("file %s, line %d, write() to `%s' failed",
  627.               __FILE__, __LINE__, sequence_file());
  628.  
  629.     DELETE line;
  630.  
  631.     return buf;
  632. }
  633.  
  634. /*
  635. ** mail_list_prefix - returns the full pathname of MailListPrefix
  636. */
  637.  
  638. static const char *mail_list_prefix()
  639. {
  640.     static String filename;
  641.     if (filename == "") filename = String(HomeBase) + "/" + MailListPrefix;
  642.     return filename;
  643. }
  644.  
  645. /*
  646. ** open_maillist_file - open the file containing the interested parties
  647. **                      maillist for the problem area. Exits on error.
  648. **                      If the file didn\'t previously exist, it will be
  649. **                      created.
  650. */
  651.  
  652. static int open_maillist_file()
  653. {
  654.     String file = String(mail_list_prefix()) + "." + CurrentArea();
  655.     int fd = open((const char *)file, O_RDWR|O_CREAT, 0644);
  656.     if (fd < 0)
  657.         error("file %s, line %d, open(%s) failed",
  658.               __FILE__, __LINE__, (const char *)file);
  659.     return fd;
  660. }
  661.  
  662. // The ways that a database can get modified.
  663. static const char *const How[] = {
  664.     "logged",
  665.     "appended",
  666.     "closed",
  667.     "deleted",
  668.     "reorganized",
  669.     "keywords modified",
  670.     "reopened"
  671. };
  672.  
  673. // indices into How\[\]
  674. enum Modified {
  675.     LOGGED,
  676.     APPENDED,
  677.     CLOSED,
  678.     DELETED,
  679.     REORGANIZED,
  680.     KEYWORDMOD,
  681.     REOPENED
  682. };
  683.  
  684. /*
  685. ** update_subscribers - send a mail file about problem to all
  686. **                      those who\'ve subscribed to this `area\'.  If
  687. **                      this is an being called after an append,
  688. **                      `append\' is non-zero.  Otherwise, we assume
  689. **                      it is the initial logging.  If we\'re appending
  690. **                      or closing a problem, we pass the offset of the
  691. **                      new data that needs to be printed.  When logging,
  692. **                      this offset is zero, which we make the default.
  693. */
  694.  
  695. static void update_subscribers(const datum data, const char *number,
  696.                                const Modified how, int offset = 0)
  697. {
  698. #ifdef MAILPROG
  699.     const char *mailprog = MAILPROG;
  700. #else
  701.     const char *mailprog = "/bin/mail";
  702. #endif
  703.  
  704.     // does `mailprog\' really exist?
  705.     if (!read_and_exec_perm(mailprog))
  706.         error("file %s, line %d, `%s' doesn't appear to be executable",
  707.               __FILE__, __LINE__, mailprog);
  708.  
  709.     int mailfd   = open_maillist_file();
  710.     FILE *mailfp = fdopen(mailfd, "r");
  711.     if (!mailfp)
  712.         error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
  713.  
  714.     const int namelen   = 10;  // average length of uid expected
  715.     const int chunksize = 20;  // average number of subscribees expected
  716.     char **args = new char*[chunksize];
  717.     args[0] = "mail";
  718.     char *fmt = "Subject: %s problem # %s %s by %s\n\n";
  719.     char *subject = new char[strlen(fmt) + strlen(CurrentArea()) +
  720.                              strlen(number) + strlen(How[how]) +
  721.                              strlen(username()) - 7];
  722.     (void)sprintf(subject, fmt, CurrentArea(), number, How[how], username());
  723.  
  724.     //
  725.     // Are there any subscribers?
  726.     //
  727.     int nlines = read_file(mailfp, args, chunksize, namelen, 1);
  728.     if (nlines < 0)
  729.         error("file %s, line %d, problem reading from maillist file",
  730.               __FILE__, __LINE__);
  731.     (void)close(mailfd); (void)fclose(mailfp);
  732.     if (nlines == 0)
  733.     {
  734.         //
  735.         // No subscribers.
  736.         //
  737.         DELETE subject;
  738.         for (int i = 1; args[i]; i++) DELETE args[i];
  739.         DELETE args;
  740.  
  741.         return;
  742.     }
  743.  
  744.     int fds[2];
  745.     if (pipe(fds) < 0)
  746.         error("file %s, line %d, pipe() failed", __FILE__, __LINE__);
  747.  
  748.     switch(fork())
  749.     {
  750.       case -1: // error
  751.         error("file %s, line %d, fork() failed", __FILE__, __LINE__);
  752.       case 0:   // in the child
  753.       {
  754.           //
  755.           // Set stdin to the read end of pipe.
  756.           //
  757.           (void)close(0);
  758.           if (dup(fds[0]) < 0)
  759.               error("file %s, line %d, dup() failed", __FILE__, __LINE__);
  760.           (void)close(fds[0]);
  761.           (void)close(fds[1]);
  762.           (void)close(1);
  763.           (void)close(2);
  764.  
  765.           execvp(mailprog, (char *const *)args);
  766.  
  767.           exit(1);  // exec failed -- can\'t use error\(\) as stdout is now closed
  768.       }
  769.         break;
  770.       default:  // in the parent
  771.       {
  772.           (void)close(fds[0]);
  773.           //
  774.           // Write Subject to pipe.
  775.           write_to_pipe(fds[1], subject, strlen(subject));
  776.           //
  777.           // write the mailfile to the pipe
  778.           //
  779.           switch (how)
  780.           {
  781.             case CLOSED:
  782.             case REOPENED:
  783.             case APPENDED:
  784.             case KEYWORDMOD:
  785.             {
  786.                 //
  787.                 // Write the fields and the new data only.
  788.                 //
  789.                 char *tail = data.dptr;
  790.                 for (int i = 0; i < NFields(); i++)
  791.                 {
  792.                     tail = strchr(tail, '\n');
  793.                     tail += 1; // step past the newline
  794.                 }
  795.                 tail += 1;     // step past the second newline to the 
  796.                                // first character past the header
  797.                 // write the header
  798.                 write_to_pipe(fds[1], data.dptr, tail - data.dptr);
  799.                 if (offset <= 0)
  800.                     error("file %s, line %d, offset must be positive",
  801.                           __FILE__, __LINE__);
  802.                 write_to_pipe(fds[1], data.dptr + offset, data.dsize-offset-1);
  803.             }
  804.                 break;
  805.             case LOGGED:
  806.                 write_to_pipe(fds[1], data.dptr, data.dsize - 1);
  807.                 break;
  808.             default: error("file %s, line %d, illegal case in switch()",
  809.                            __FILE__, __LINE__);
  810.           }
  811.           (void)close(fds[1]);
  812.           for (int i = 1; args[i]; i++) DELETE args[i];
  813.           DELETE args;
  814.           DELETE subject;
  815.  
  816.           return;
  817.       }
  818.     }
  819. }
  820.  
  821. /*
  822. ** invoke_editor - invoke users editor on `file\'
  823. */
  824.  
  825. static void invoke_editor(const char *file)
  826. {
  827.     char *editor = getenv("EDITOR");
  828.     if (editor == 0) editor = "vi";
  829.     char *space  = strchr(editor, ' ');
  830.     if (space) *space = 0;  // only use the first arg if they\'re > 1
  831.     const char *args[3];
  832.     args[0] = editor; args[1] = file; args[2] = 0;
  833.  
  834.     //
  835.     // Position cursor in case the editor doesn\'t do this itself.
  836.     // This is primarily for those people who use
  837.     // non-fullscreen editors, such as `ed\', which don\'t do this.
  838.     // We\'ve just typed a message, so we\'re on the last line of the screen.
  839.     //
  840.     cursor_wrap();
  841.     synch_display();
  842.     if (!execute(editor, args) && strstr(editor, "vi") == 0)
  843.         error("file %s, line %d, couldn't exec() your editor `%s'",
  844.               __FILE__, __LINE__, editor);
  845. }
  846.  
  847. /*
  848. ** database_exists - checks to see if a database for the current area exists.
  849. **                   This is important since gdbm_open\(GDBM_READER\)
  850. **                   will fail if the database doesn\'t already exist.
  851. **                   Returns one if a file of the appropriate name
  852. **                   exists, else zero.  There is no guarantee that we
  853. **                   actually have a database, only an appropriately
  854. **                   named file.
  855. */
  856.  
  857. int database_exists()
  858. {
  859.     String filename = String(HomeBase) + "/" + CurrentArea() + GdbmSuffix;
  860.     int rc = open((const char *)filename, O_RDONLY);
  861.     (void)close(rc);
  862.     return rc < 0 ? 0 : 1;
  863. }
  864.  
  865. /*
  866. ** open_database - opens the GDBM database on the current area.
  867. **                 Exits on error.
  868. */
  869.  
  870. void open_database(int mode)
  871. {
  872.     String filename = String(HomeBase) + "/" + CurrentArea() + GdbmSuffix;
  873.  
  874.     if ((GdbmFile = gdbm_open(filename, 0, mode, 00644, 0)) == 0)
  875.         if (gdbm_errno != GDBM_CANT_BE_WRITER)
  876.             error("file %s, line %d, gdbm_open() failed on `%s'",
  877.                   __FILE__, __LINE__, (const char *)filename);
  878.         else
  879.             error("file %s, line %d, gdbm_open() failed on `%s', errno = %d",
  880.                   __FILE__, __LINE__, (const char *)filename, gdbm_errno);
  881. }
  882.  
  883. /*
  884. ** database_directory_exists - does HomeBase exist?
  885. */
  886.  
  887. static int database_directory_exists()
  888. {
  889.     int rc = open(HomeBase, O_RDONLY);
  890.     (void)close(rc);
  891.     return rc < 0 ? 0 : 1;
  892. }
  893.  
  894.  
  895. /*
  896. ** update_database - updates database on the current area with `problem_data\'.
  897. **                   `size\' is total size of data.  This function is
  898. **                   used both to insert new entries and to replace
  899. **                   old entries. If `offset\' is nonzero, it is the
  900. **                   start of problem number field, which we fill in
  901. **                   after getting new problem number.  `offset\' is
  902. **                   nonzero only when initially logging a problem.
  903. */
  904.  
  905. static void update_database(datum &key, const datum &data, const Modified how,
  906.                             int offset = 0)
  907. {
  908.     int fd = open(sequence_file(), O_RDWR);
  909.     if (fd < 0)
  910.         error("file %s, line %d, open() on `%s' failed",
  911.               __FILE__, __LINE__, sequence_file());
  912.  
  913.     block_tstp_and_winch();         // block SIGTSTP and WINCH
  914.     lock_file(fd);                  // lock our sequence file
  915.     open_database(GDBM_WRCREAT);    // open database for writing
  916.     if (how != REORGANIZED)
  917.         data.dptr[data.dsize - 1] = 0;  // make sure data is stringified
  918.  
  919.     switch (how)
  920.     {
  921.       case DELETED:
  922.         if (gdbm_delete(GdbmFile, key))
  923.             error("file %s, line %d, gdbm_delete() failed, errno = %d",
  924.                   __FILE__, __LINE__, gdbm_errno);
  925.         break;
  926.       case REORGANIZED:
  927.         if (gdbm_reorganize(GdbmFile))
  928.             error("file %s, line %d, gdbm_reorganize() failed, errno = %d",
  929.                   __FILE__, __LINE__, gdbm_errno);
  930.         break;
  931.       case LOGGED:
  932.       {
  933.           //
  934.           // Must fill in key values; we\'re doing an initial log of the problem.
  935.           //
  936.           key.dptr  = (char *) update_sequence_file(fd);
  937.           key.dsize = (int)strlen(key.dptr) + 1;
  938.  
  939.           // update problem # field
  940.           for (int i = 0; i < strlen(key.dptr); i++)
  941.               data.dptr[offset + i] = key.dptr[i];
  942.       }
  943.       //
  944.       // Fall through.
  945.       //
  946.       case CLOSED:
  947.       case REOPENED:
  948.       case APPENDED:
  949.       case KEYWORDMOD:
  950.         if (gdbm_store(GdbmFile, key, data, how == LOGGED ?
  951.                        GDBM_INSERT : GDBM_REPLACE))
  952.             error("file %s, line %d, gdbm_store() failed, errno = %d",
  953.                   __FILE__, __LINE__, gdbm_errno);
  954.         break;
  955.       default:
  956.         error("file %s, line %d, illegal case in switch()",
  957.               __FILE__, __LINE__);
  958.     }
  959.  
  960.     gdbm_close(GdbmFile);
  961.     unlock_file(fd);
  962.     (void)close(fd);
  963.     unblock_tstp_and_winch(); // unblock SIGTSTP and WINCH
  964. }
  965.  
  966. //
  967. // These variables are shared by build_log_screen\(\) and log_new_problem\(\)
  968. // so that we can keep track of where we are in case we get a SIGTSTP
  969. // or SIGWINCH.  We have to be careful to nullify them after we\'re done
  970. // with them so that build_log_screen\(\) knows when it needs to prompt
  971. // for fresh data.
  972. //
  973. static char *logged;
  974. static char *reporter;
  975. static char *keywords;
  976. static char *summary;
  977. static char *site;
  978. static char severity;
  979.  
  980. /*
  981. ** build_log_screen - prints the initial screen when logging a problem.
  982. **                    Is also called after a SIGTSTP or SIGWINCH to
  983. **                    redo the screen appropriately.
  984. */
  985.  
  986. // forward declaration
  987. static void redisplay_log_screen();
  988.  
  989. static void build_log_screen()
  990. {
  991.     cursor_home();
  992.     //
  993.     // Print as many of the fields as will fit.
  994.     // This gets done both on a normal call or on a redisplay.
  995.     //
  996.     enter_standout_mode();
  997.     for (int i = 0; i < NFields() && i < rows() - 2; i++)
  998.     {
  999.         clear_to_end_of_line();
  1000.         display_string(Fields[i]);
  1001.     }
  1002.     end_standout_mode();
  1003.  
  1004.     // clear any remaining potentially dirty lines
  1005.     for (; i < rows() - 2; i++) { clear_to_end_of_line(); cursor_wrap(); }
  1006.  
  1007.     int flen = max_field_length() + 1; // plus one accounts for the space
  1008.  
  1009.     int currline = 0;  // keep track of where we are on screen
  1010.  
  1011.     //
  1012.     // Fill in those fields which are obvious.
  1013.     //
  1014.     if (currline < rows() - 2)
  1015.     {
  1016.         move_cursor(currline++, flen);
  1017.         display_string(CurrentArea(), 0, flen);
  1018.     }
  1019.     if (currline < rows() - 2)
  1020.     {
  1021.         move_cursor(currline, flen);
  1022.         display_string(username(), 0, flen);
  1023.         currline += 2;
  1024.     }
  1025.     time_t t = time(0);
  1026.     logged = ctime(&t);
  1027.     if (currline < rows() - 2)
  1028.     {
  1029.         move_cursor(currline++, flen);
  1030.         display_string(logged, 0, flen);
  1031.     }
  1032.     if (currline < rows() - 2)
  1033.     {
  1034.         move_cursor(currline, flen);
  1035.         display_string(logged, 0, flen);
  1036.         currline += 3;
  1037.     }
  1038.     if (currline < rows() - 2)
  1039.     {
  1040.         move_cursor(currline, flen);
  1041.         display_string("open", 0, flen);
  1042.     }
  1043.  
  1044.     //
  1045.     // Prompt for those that aren\'t.
  1046.     //
  1047.     const char *const append = " --> ";
  1048.     static char *line;
  1049.     int recursing = 0;  // is this a recursive call?
  1050.     if (!line)
  1051.         line = new char[flen + strlen(append)];
  1052.     else
  1053.         recursing = 1;
  1054.     (void)strcpy(line, Fields[2]);
  1055.     (void)strcat(line, append);
  1056.     if (!reporter)
  1057.         if (recursing)
  1058.             goto exit;
  1059.         else
  1060.             reporter = prompt(line, redisplay_log_screen);
  1061.     currline = 2;
  1062.     if (currline < rows() - 2)
  1063.     {
  1064.         move_cursor(currline, flen);
  1065.         display_string(reporter, 0, flen);
  1066.         currline += 3;
  1067.     }
  1068.     (void)strcpy(line, Fields[5]);
  1069.     (void)strcat(line, append);
  1070.     if (!keywords)
  1071.         if (recursing)
  1072.             goto exit;
  1073.         else
  1074.             keywords = prompt(line, redisplay_log_screen);
  1075.     if (currline < rows() - 2)
  1076.     {
  1077.         move_cursor(currline++, flen);
  1078.         display_string(keywords, 0, flen);
  1079.     }
  1080.     (void)strcpy(line, Fields[6]);
  1081.     (void)strcat(line, append);
  1082.     if (!summary)
  1083.         if (recursing)
  1084.             goto exit;
  1085.         else
  1086.             summary = prompt(line, redisplay_log_screen);
  1087.     if (currline < rows() - 2)
  1088.     {
  1089.         move_cursor(currline, flen);
  1090.         display_string(summary, 0, flen);
  1091.         currline += 2;
  1092.     }
  1093.     (void)strcpy(line, Fields[8]);
  1094.     (void)strcat(line, append);
  1095.     if (!site)
  1096.         if (recursing)
  1097.             goto exit;
  1098.         else
  1099.             site = prompt(line, redisplay_log_screen);
  1100.     if (currline < rows() - 2)
  1101.     {
  1102.         move_cursor(currline++, flen);
  1103.         display_string(site, 0, flen);
  1104.     }
  1105.     if (!severity)
  1106.         if (recursing)
  1107.             goto exit;
  1108.         else
  1109.             severity = get_severity(redisplay_log_screen);
  1110.     if (currline < rows() - 2)
  1111.     {
  1112.         move_cursor(currline, flen);
  1113.         putchar(severity);
  1114.     }
  1115.     DELETE line;
  1116.     line = 0;  // the nullification is important
  1117.  
  1118.     //
  1119.     // We got here when we\'ve been called recursively due to servicing
  1120.     // a SIGTSTP or SIGWINCH.  We don\'t delete `line\' as we\'ll shortly be
  1121.     // back in our original self where we\'ll continue to use it.
  1122.     //
  1123.   exit:
  1124.     return;
  1125. }
  1126.  
  1127. /*
  1128. ** redisplay_log_screen - redisplay the log screen.  Can be called at any
  1129. **                        point during our building of the log screen to
  1130. **                        service a SIGTSTP or SIGWINCH.
  1131. */
  1132.  
  1133. static void redisplay_log_screen() { update_modeline(); build_log_screen(); }
  1134.  
  1135. /*
  1136. ** log_new_problem - need at least 4 rows to be useful
  1137. */
  1138.  
  1139. static void log_new_problem()
  1140. {
  1141.     const char *fmt = "%s (logging)";
  1142.     char *suffix = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
  1143.     (void)sprintf(suffix, fmt, CurrentArea());
  1144.     update_modeline(ModelinePrefix, suffix);
  1145.     DELETE suffix;
  1146.     build_log_screen();
  1147.  
  1148.     message("Invoking your editor ...");
  1149.  
  1150.     //
  1151.     // Build  tmp file into which the problem will be edited by user.
  1152.     //
  1153.     const char *file = temporary_file();
  1154.  
  1155.     invoke_editor(file);
  1156.  
  1157.     //
  1158.     // Generate string just large enough to hold the problem.
  1159.     // Don\'t forget to add space for the newlines.
  1160.     //
  1161.     int flen       = max_field_length() + 1; // plus one accounts for the space
  1162.     int fsize      = filesize(file);
  1163.     int totalsize  = fsize + NFields() * flen;
  1164.     const int PDim = 10;   // spaces reserved for `Problem #\' field
  1165.     const int StatDim = 6; // big enough for "open" or "closed"
  1166.     totalsize += int (strlen(CurrentArea()) + strlen(username()) + 2);
  1167.     totalsize += 50;       // strlen\(ctime\(\)\) == 25 && already contains newline
  1168.     totalsize += StatDim+1;// "open" or "closed"
  1169.     totalsize += int (strlen(reporter) + strlen(keywords) + 2);
  1170.     totalsize += int (strlen(summary) + strlen(site) + 2);
  1171.     totalsize += 2;        // the severity field and it\'s newline
  1172.     totalsize += PDim + 2; // space reserved for the problem number
  1173.                            // and two newlines
  1174.  
  1175.     datum data;
  1176.     data.dsize = totalsize + 1; // don\'t forget about the null
  1177.     data.dptr  = new char[data.dsize];
  1178.  
  1179.     //
  1180.     // Write the header info to `data\'.
  1181.     //
  1182.     int pos = 0;  // our position in `data\'
  1183.     (void)sprintf(data.dptr, "%-*.*s%s\n", flen, flen, Fields[0], CurrentArea());
  1184.     pos += int (flen+strlen(CurrentArea())+1);
  1185.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[1],
  1186.                   username());
  1187.     pos += int (flen+strlen(username())+1);
  1188.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[2],
  1189.                   reporter);
  1190.     pos += int (flen+strlen(reporter)+1);
  1191.     (void)sprintf(&data.dptr[pos], "%-*.*s%s", flen, flen, Fields[3], logged);
  1192.     pos += flen+25;
  1193.     (void)sprintf(&data.dptr[pos], "%-*.*s%s", flen, flen, Fields[4], logged);
  1194.     pos += flen+25;
  1195.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[5],
  1196.                   keywords);
  1197.     pos += int (flen+strlen(keywords)+1);
  1198.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[6],
  1199.                   summary);
  1200.     pos += int (flen+strlen(summary)+1);
  1201.     (void)sprintf(&data.dptr[pos], "%-*.*s%-*.*s\n", flen, flen, Fields[7],
  1202.                   StatDim, StatDim, "open");
  1203.     pos += flen+StatDim+1;
  1204.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[8], site);
  1205.     pos += int (flen+strlen(site)+1);
  1206.     (void)sprintf(&data.dptr[pos], "%-*.*s%c\n", flen, flen, Fields[9],
  1207.                   severity);
  1208.     pos += flen+2;
  1209.     (void)sprintf(&data.dptr[pos], "%-*.*s          \n\n", flen, flen,
  1210.                   Fields[10]);
  1211.     int offset = pos + flen; // data.dptr\[offset\] is where `Problem #\' goes
  1212.     pos += flen+PDim+2;      // we output two newlines here as separator
  1213.  
  1214.     //
  1215.     // Now for the problem itself.  Make sure this read only fails on a
  1216.     // real error -- block SIGTSTP and SIGWINCH
  1217.     //
  1218.     block_tstp_and_winch();
  1219.  
  1220.     int fd;
  1221.     if ((fd = open(file, O_RDONLY)) < 0) 
  1222.         error("file %s, line %d, open(%s, O_RDONLY) failed",
  1223.               __FILE__, __LINE__, file);
  1224.  
  1225.     if (read(fd, &data.dptr[pos], fsize) != fsize)
  1226.         error("file %s, line %d, read() failed", __FILE__, __LINE__);
  1227.  
  1228.     unblock_tstp_and_winch(); // unblock SIGTSTP and WINCH
  1229.     (void)close(fd);
  1230.     (void)unlink(file);
  1231.  
  1232.     if (yes_or_no("Really log this problem (y|n)? ", redisplay_log_screen, Yes, 1))
  1233.     {
  1234.         datum key;  // empty key to be filled in by update_database\(\)
  1235.         update_database(key, data, LOGGED, offset);
  1236.         update_subscribers(data, key.dptr, LOGGED);
  1237.     }
  1238.  
  1239.     update_modeline();  // redisplay last modeline
  1240.     DELETE data.dptr;
  1241.  
  1242.     //
  1243.     // We have to both delete these and nullify them so that the next
  1244.     // time we call build_log_screen\(\), we prompt for new data.
  1245.     //
  1246.     DELETE reporter;
  1247.     DELETE keywords;
  1248.     DELETE summary;
  1249.     DELETE site;
  1250.     reporter = 0;
  1251.     keywords = 0;
  1252.     summary  = 0;
  1253.     site     = 0;
  1254.     severity = 0;  // Just nullify this; it\'s a static char.
  1255. }
  1256.