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

  1. /*
  2. **
  3. ** problem - a problem database manager
  4. **
  5. ** Written in C++ using the termcap 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.33   Delta\'d: 17:24:36 11/9/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. #error "must define HOMEBASE"
  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. String current_area;
  140.  
  141. void setCurrentArea(const char *area)
  142. {
  143.     current_area    = area;
  144.     const char *tmp = strchr(current_area, '-');
  145.  
  146.     //
  147.     // strip off any comments
  148.     //
  149.     if (tmp)
  150.         current_area[tmp - (const char *)current_area] = 0;
  151.     else
  152.         tmp = (const char *)current_area + current_area.length();
  153.  
  154.     //
  155.     // strip off any trailing blanks
  156.     //
  157.     for (--tmp; *tmp == ' ' || *tmp == '\t'; --tmp)
  158.         current_area[tmp - (const char *)current_area] = 0;
  159.  
  160.     //
  161.     // set any remaining spaces to underscores
  162.     //
  163.     while ((tmp = strchr(current_area, ' ')) != 0)
  164.         current_area[tmp - (const char *)current_area] = '_';
  165. }
  166.  
  167. //
  168. // our commands - the first character of each string is the unique
  169. //                character identifying that command.
  170. //
  171. static const char *const Commands[] =
  172. {
  173.     "l  -- log new problem",
  174.     "a  -- append to a problem",
  175.     "c  -- close a problem",
  176.     "e  -- examine a problem",
  177.     "v  -- view problem summaries",
  178.     "s  -- subscribe to this problem area",
  179.     "u  -- unsubscribe from this problem area",
  180.     "k  -- keyword search over problem headers",
  181.     "K  -- keyword search over problem headers and data",
  182.     "d  -- delete a problem from the database",
  183.     "r  -- reorganize the database",
  184.     "M  -- modify keyword field",
  185.     "P  -- change priority (severity) of problem",
  186.     "R  -- reopen a closed problem",
  187.     "T  -- transfer problem to another area",
  188.     "q  -- quit"
  189. };
  190.  
  191. inline int NCommands() { return sizeof(Commands) / sizeof(Commands[0]); }
  192.  
  193. /*
  194. ** exception handler for new - called once in main
  195. */
  196.  
  197. static void free_store_exception()
  198. {
  199.     error("exiting, memory exhausted, sorry");
  200. }
  201.  
  202. /*
  203. ** get_problem_areas - read in the valid problem areas. Exits on error.
  204. */
  205.  
  206. static const char **get_problem_areas()
  207. {
  208.     static char **Areas;
  209.     if (Areas) return (const char **)Areas;
  210.  
  211.     String filename(HomeBase);
  212.     filename += "/";
  213.     filename += ProblemAreas;
  214.  
  215.     FILE *fp = fopen(filename, "r");
  216.     if (!fp)
  217.         error ("file %s, line %d, couldn't fopen() `%s'",
  218.                __FILE__, __LINE__, (const char *)filename);
  219.  
  220.     const int chunksize = 10;  // average number of problem areas expected
  221.     const int linelen   = 80;  // average length of line in AREAS expected
  222.     Areas = new char*[chunksize];
  223.  
  224.     if (read_file(fp, Areas, chunksize, linelen) < 0)
  225.         error("file %s, line %d, error reading `%s'.",
  226.               __FILE__, __LINE__, (const char *)filename);
  227.     (void)fclose(fp);
  228.  
  229.     return (const char **)Areas;
  230. }
  231.  
  232. /*
  233. ** is_area - is area a valid problem area? Returns 1 if true, else 0.
  234. */
  235.  
  236. int is_area(const char *area)
  237. {
  238.     const char **areas = get_problem_areas();
  239.  
  240.     for (int i = 0; areas[i]; i++)
  241.     {
  242.         const char *space = strchr(areas[i],' ');
  243.         int length = space ? space - areas[i] - 1 : (int)strlen(areas[i]);
  244.         if (strncmp(area, areas[i], length) == 0) return 1;
  245.     }
  246.  
  247.     return 0;
  248. }
  249.  
  250. /*
  251. ** NAreas - the number of areas
  252. */
  253.  
  254. static int NAreas()
  255. {
  256.     static int nAreas;
  257.     if (!nAreas)
  258.     {
  259.         const char **areas = get_problem_areas();
  260.         for (int i = 0; areas[i]; i++) ;
  261.         nAreas = i;
  262.     }
  263.     return nAreas;
  264. }
  265.  
  266. /*
  267. ** display_area_screen - displays the screen of available areas.
  268. **                       We assume that the caller will display the modeline.
  269. **                       Needs at least four rows to be useful.
  270. */
  271.  
  272. static void display_area_screen()
  273. {
  274.     clear_display_area();
  275.  
  276.     const char **areas = get_problem_areas();
  277.     enter_standout_mode();
  278.  
  279.     //
  280.     // If we have enough available screen lines, we display the areas
  281.     // one per line.  Otherwise, we display them in columns of up to
  282.     // column_width characters.
  283.     //
  284.     const int column_width = 25;           // space allowed for each area
  285.     int dvd = columns() / column_width;    // # compacted areas per row
  286.     int rws = rows() - 5;
  287.     int compact = NAreas() <= rws ? 0 : 1; // compact the areas?
  288.  
  289.     const char *fmt = NAreas() <= rws * dvd ? "The %d Areas:" :
  290.                                               "The First %d Areas:";
  291.  
  292.     char *msg = new char[strlen(fmt) + 8]; // lots of problem areas
  293.  
  294.     (void)sprintf(msg, fmt, NAreas() <= rws * dvd ? NAreas() : rws * dvd);
  295.  
  296.     display_string(msg);
  297.  
  298.     //
  299.     // Maximum # of digits we must display.
  300.     //
  301.     int digits = int(strlen(msg) - strlen(fmt)) + 2;
  302.  
  303.     DELETE msg;
  304.  
  305.     end_standout_mode();
  306.     cursor_wrap();
  307.  
  308.     for (int i = 0, offset = 0; i < NAreas() && i < rws * dvd; i++)
  309.     {
  310.         if (compact)
  311.         {
  312.             offset = (i / rws) * column_width;
  313.             move_cursor((i % rws) + 2, offset);
  314.         }
  315.         (void)fputs("  ", stdout);  // two spaces for initial indentation
  316.         enter_standout_mode();
  317.         (void)fprintf(stdout, "%*d", digits, i + 1);
  318.         end_standout_mode();
  319.         putchar(' ');               // and one more between number and area
  320.         display_string(areas[i], 0, digits + 3 + offset);
  321.     }
  322. }
  323.  
  324. /*
  325. ** help - display some help lines.  The first two lines of the output
  326. **        consists of the message msg and then a blank line.
  327. **        This is followed by the contents of lines, which has dim
  328. **        elements.
  329. **
  330. */
  331.  
  332. static void help(const char *lines[], int dim, const char *msg)
  333. {
  334.     String old_modeline(current_modeline);
  335.  
  336.     update_modeline("----- HELP");
  337.     clear_display_area();
  338.     
  339.     int position = 0, offset = 0;
  340.     char key;
  341.     do
  342.     {
  343.         cursor_home();
  344.         if (position == 0)
  345.         {
  346.             offset = 2;
  347.             display_string(msg);
  348.             move_cursor(2, 0);
  349.         }
  350.         for (int i = 0; i + offset < rows()-2 && i + position < dim; i++)
  351.             display_string(lines[i + position]);
  352.  
  353.         clear_message_line();
  354.  
  355.         if (position == 0 && dim + offset <= rows() - 2)
  356.             //
  357.             // whole help message fits in initial screen
  358.             //
  359.             (void)fputs(HELP_MSG[3], stdout);
  360.         else if (position == 0)
  361.             //
  362.             // the head of the help message -- it all does not fit
  363.             //
  364.             (void)fputs(HELP_MSG[0], stdout);
  365.         else if (position + rows() - 2 >= dim)
  366.             //
  367.             // the tail of the help message
  368.             //
  369.             (void)fputs(HELP_MSG[2], stdout);
  370.         else
  371.             //
  372.             //  somewhere in between
  373.             //
  374.             (void)fputs(HELP_MSG[1], stdout);
  375.         synch_display();
  376.  
  377.         if (resumingAfterSuspension ||
  378. #ifdef SIGWINCH
  379.             windowSizeChanged       ||
  380. #endif
  381.             read(0, &key, 1) < 0 // assume fails only when errno == EINTR
  382.             )
  383.         {
  384. #ifdef SIGWINCH
  385.             if (windowSizeChanged)
  386.             {
  387.                 windowSizeChanged = 0;
  388.                 adjust_window();
  389.             }
  390. #endif
  391.             resumingAfterSuspension = 0;
  392.             update_modeline();
  393.             continue;
  394.         }
  395.         else if (key == KEY_SPC)
  396.         {
  397.             if (position >= dim - 1) break;
  398.             position += (position == 0 ? rows() - 2 - offset : rows() - 2);
  399.         }
  400.         else if (key == *BC)
  401.         {
  402.             if (position == 0) break;
  403.             position -= rows() - 2;
  404.             if (position < 0) position = 0;
  405.         }
  406.         else 
  407.             break;  // return to the listing
  408.         
  409.         offset = 0;
  410.     }
  411.     while (position < dim + offset);
  412.  
  413.     update_modeline(old_modeline);
  414. }
  415.  
  416. /*
  417. ** redisplay_area_screen - suitable for calling after SIGWINCH and SIGTSTP
  418. */
  419.  
  420. static void redisplay_area_screen()
  421. {
  422.     display_area_screen();
  423.     update_modeline();
  424. }
  425.  
  426. /*
  427. ** choose_problem_area - returns a problem area to examine.
  428. **                       Also gives user option to exit.
  429. */
  430.  
  431. static const char *choose_problem_area()
  432. {
  433.     const char **areas = get_problem_areas();
  434.     display_area_screen();
  435.     update_modeline(ModelinePrefix, "---- q (quit) H (help)");
  436.     const char *helpmsg = "The Available Problem Areas:";
  437.     char key;
  438.     int index;
  439.  
  440.     if (NAreas() < 10)
  441.     {
  442.         //
  443.         // The most usual case.  Read digit as typed.
  444.         //
  445.         message("Your Choice --> ");
  446.  
  447.         while (1)
  448.         {
  449.             if (resumingAfterSuspension ||
  450. #ifdef SIGWINCH
  451.                 windowSizeChanged       ||
  452. #endif
  453.                 read(0, &key, 1) < 0)  // assume only fails when errno==EINTR
  454.             { 
  455. #ifdef SIGWINCH
  456.  
  457.                 if (windowSizeChanged)
  458.                 {
  459.                     windowSizeChanged = 0;
  460.                     adjust_window();
  461.                 }
  462. #endif
  463.                 resumingAfterSuspension = 0;
  464.                 redisplay_area_screen();
  465.                 message("Your Choice --> ");
  466.                 continue;
  467.             }
  468.             else if (key == KEY_q)
  469.                 quit();
  470.             else if (key == KEY_H || key == KEY_QM)
  471.             {
  472.                 help(areas, NAreas(), helpmsg);
  473.                 redisplay_area_screen();
  474.                 message("Your Choice --> ");
  475.             }
  476.             else
  477.             {
  478.                 index = key - '0';
  479.                 if (index > 0 && index <= NAreas()) return areas[index-1];
  480.                 ding();
  481.             }
  482.         }
  483.     }
  484.     else
  485.     {
  486.         char *response;
  487.         while (1)
  488.         {
  489.             //
  490.             // prompt takes care of window resizes and resume/suspends
  491.             //
  492.             response = prompt("Your Choice --> ", redisplay_area_screen);
  493.  
  494.             if (*response == KEY_q)
  495.                 quit();
  496.             else if (*response == KEY_H || *response == KEY_QM)
  497.             {
  498.                 help(areas, NAreas(), helpmsg);
  499.                 redisplay_area_screen();
  500.                 message("Your Choice --> ");
  501.                 DELETE response;
  502.             }
  503.             else
  504.             {
  505.                 index = atoi(response);
  506.                 DELETE response;
  507.                 if (index > 0 && index <= NAreas()) return areas[index - 1];
  508.                 ding();
  509.             }
  510.         }
  511.     }
  512. }
  513.  
  514. /*
  515. ** max_field_length - returns the length of the longest field
  516. */
  517.  
  518. static int max_field_length()
  519. {
  520.     static int len;
  521.     if (!len) 
  522.         for (int i = 0; i < NFields(); i++)
  523.             if (len < strlen(Fields[i])) len = (int) strlen(Fields[i]);
  524.     return len;
  525. }
  526.  
  527. /*
  528. ** get_severity - prompt for the severity of the problem. Deal with
  529. **                getting SIGTSTP or SIGWINCH.
  530. */
  531.  
  532. static char get_severity(void (*redisplay)())
  533. {
  534.     //
  535.     // Compile with -DSEVLEVEL3 if you only want three severity levels.
  536.     //
  537. #ifdef SEVLEVEL3
  538.     const char *msg = "Severity (1 (=highest), 2, or 3 (=lowest)) --> ";
  539. #else
  540.     const char *msg = "Severity (1 (=highest), 2, 3 or 4 (=lowest)) --> ";
  541. #endif
  542.  
  543.     message(msg);
  544.  
  545.     char key;
  546.     while (1)
  547.     {
  548.         if (resumingAfterSuspension ||
  549. #ifdef SIGWINCH
  550.             windowSizeChanged       ||
  551. #endif
  552.             read(0, &key, 1) < 0)   // assume only fails when errno == EINTR 
  553.         {
  554. #ifdef SIGWINCH
  555.             if (windowSizeChanged)
  556.             {
  557.                 windowSizeChanged = 0;
  558.                 adjust_window();
  559.             }
  560. #endif
  561.             resumingAfterSuspension = 0;
  562.             redisplay();
  563.             message(msg);
  564.             continue;
  565.         }
  566.         switch (key)
  567.         {
  568. #ifdef SEVLEVEL3
  569.           case '1': case '2': case '3': return key;
  570. #else
  571.           case '1': case '2': case '3': case '4': return key;
  572. #endif
  573.           default: ding(); break;
  574.         }
  575.     }
  576. }
  577.  
  578. /*
  579. ** filesize - returns size of file or MAXFILESIZE,
  580. **            whichever is smaller. Exits on error.
  581. */
  582.  
  583. static int filesize(const char *file)
  584. {
  585. #ifdef MAXFILESIZE
  586.     const int MaxFileSize =  MAXFILESIZE;
  587. #else
  588.     const int MaxFileSize = 16000;
  589. #endif
  590.     struct stat buf;
  591.     if (stat(file, &buf) < 0)
  592.         error("file %s, line %d, fstat() failed", __FILE__, __LINE__);
  593.     return buf.st_size > MaxFileSize ? MaxFileSize : buf.st_size;
  594. }
  595.  
  596. /*
  597. ** sequence_file - returns full pathname of "SequenceFile"
  598. */
  599.  
  600. static const char *sequence_file()
  601. {
  602.     static String filename;
  603.     if (filename == "")
  604.     {
  605.         filename  = HomeBase;
  606.         filename += "/";
  607.         filename += SequenceFile;
  608.     }
  609.     return filename;
  610. }
  611.  
  612. /*
  613. ** update_sequence_file - reads the previous problem number from
  614. **                        SequenceFile; increments that number;
  615. **                        writes it back to the file and returns it.
  616. **                        Exits on error. We should have an exclusive
  617. **                        lock on the sequence file before we get here.
  618. **                        This is to guarantee that we only have one
  619. **                        "writer" to the GDBM file.
  620. */
  621.  
  622. static const char *update_sequence_file(int fd)
  623. {
  624.     static char buf[10];  // will not have this many problems for a while
  625.  
  626.     FILE *fp = fdopen(fd, "r+");
  627.     if (fp == 0)
  628.         error("file %s, line %d, fdopen() on `%s' failed",
  629.               __FILE__, __LINE__, sequence_file());
  630.  
  631.     char *line = fgetline(fp, 10);
  632.  
  633.     if (fp == 0)
  634.         error("file %s, line %d, fgetline() on `%s' failed",
  635.               __FILE__, __LINE__, sequence_file());
  636.     (void)sprintf(buf, "%d", atoi(line) + 1);
  637.  
  638.     //
  639.     // Truncate the file.  I would like to use ftruncate here, but that
  640.     // is not as portable as a close\(open\(\)\) scheme.
  641.     //
  642.     if (close(open(sequence_file(), O_RDWR|O_TRUNC)) < 0)
  643.         error("file %s, line %d, close(open()) of `%s' failed",
  644.               __FILE__, __LINE__, sequence_file());
  645.  
  646.     //
  647.     // Go to the beginning.
  648.     //
  649.     if (lseek(fd, 0, SEEK_SET) < 0)
  650.         error("file %s, line %d, lseek() on `%s' failed",
  651.               __FILE__, __LINE__, sequence_file());
  652.  
  653.     //
  654.     // Write the next problem #.
  655.     //
  656.     if (write(fd, buf, (unsigned) strlen(buf)) < 0)
  657.         error("file %s, line %d, write() to `%s' failed",
  658.               __FILE__, __LINE__, sequence_file());
  659.  
  660.     DELETE line;
  661.  
  662.     return buf;
  663. }
  664.  
  665. /*
  666. ** mail_list_prefix - returns the full pathname of MailListPrefix
  667. */
  668.  
  669. static const char *mail_list_prefix()
  670. {
  671.     static String filename;
  672.     if (filename == "")
  673.     {
  674.         filename  = HomeBase;
  675.         filename += "/";
  676.         filename += MailListPrefix;
  677.     }
  678.     return filename;
  679. }
  680.  
  681. /*
  682. ** open_maillist_file - open the file containing the interested parties
  683. **                      maillist for the problem area. Exits on error.
  684. **                      If the file did not previously exist, it will be
  685. **                      created.
  686. */
  687.  
  688. static int open_maillist_file()
  689. {
  690.     String file(mail_list_prefix());
  691.     file += ".";
  692.     file += CurrentArea();
  693.  
  694.     int fd = open((const char *)file, O_RDWR|O_CREAT, 0644);
  695.     if (fd < 0)
  696.         error("file %s, line %d, open(%s) failed",
  697.               __FILE__, __LINE__, (const char *)file);
  698.     return fd;
  699. }
  700.  
  701. //
  702. // The ways that a database can get modified.
  703. //
  704. static const char *const How[] = {
  705.     "logged",
  706.     "appended",
  707.     "closed",
  708.     "deleted",
  709.     "reorganized",
  710.     "keywords modified",
  711.     "reopened",
  712.     "severity modified",
  713.     "transferred"        
  714. };
  715.  
  716. //
  717. // indices into How
  718. //
  719. enum Modified {
  720.     LOGGED,
  721.     APPENDED,
  722.     CLOSED,
  723.     DELETED,
  724.     REORGANIZED,
  725.     KEYWORDMOD,
  726.     REOPENED,
  727.     SEVERITYMOD,
  728.     TRANSFER
  729. };
  730.  
  731. /*
  732. ** update_subscribers - send a mail file about problem to all
  733. **                      those who have subscribed to this area.  If
  734. **                      this is being called after an append,
  735. **                      append is non-zero.  Otherwise, we assume
  736. **                      it is the initial logging.  If we are appending
  737. **                      or closing a problem, we pass the offset of the
  738. **                      new data that needs to be printed.  When logging,
  739. **                      this offset is zero, which we make the default.
  740. */
  741.  
  742. static void update_subscribers(const datum data, const char *number,
  743.                                const Modified how, int offset = 0)
  744. {
  745. #ifdef MAILPROG
  746.     const char *mailprog = MAILPROG;
  747. #else
  748.     const char *mailprog = "/bin/mail";
  749. #endif
  750.  
  751.     //
  752.     // Does mailprog really exist?
  753.     //
  754.     if (!read_and_exec_perm(mailprog))
  755.         error("file %s, line %d, `%s' doesn't appear to be executable",
  756.               __FILE__, __LINE__, mailprog);
  757.  
  758.     int mailfd   = open_maillist_file();
  759.     FILE *mailfp = fdopen(mailfd, "r");
  760.     if (!mailfp)
  761.         error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
  762.  
  763.     const int namelen   = 10;  // average length of uid expected
  764.     const int chunksize = 20;  // average number of subscribees expected
  765.     char **args = new char*[chunksize];
  766.     args[0] = "mail";
  767.  
  768. #ifdef NOSUBJECT
  769.     String subject("Subject: Problem: ");
  770. #else
  771.     args[1] = "-s";
  772.     String subject("Problem: ");
  773. #endif
  774.  
  775.     subject += CurrentArea();
  776.     subject += " # ";
  777.     subject += number;
  778.     subject += " ";
  779.     subject += How[how];
  780.     subject += " by `";
  781.     subject += username();
  782.     subject += "'";
  783.  
  784. #ifdef NOSUBJECT
  785.     subject += "\n\n";
  786. #endif
  787.  
  788. #ifndef NOSUBJECT
  789.     args[2] = subject;
  790. #endif
  791.  
  792.     //
  793.     // Are there any subscribers?
  794.     //
  795. #ifdef NOSUBJECT
  796.     int nlines = read_file(mailfp, args, chunksize, namelen, 1);
  797. #else
  798.     int nlines = read_file(mailfp, args, chunksize, namelen, 3);
  799. #endif
  800.     if (nlines < 0)
  801.         error("file %s, line %d, problem reading from maillist file",
  802.               __FILE__, __LINE__);
  803.     (void)close(mailfd);
  804.     (void)fclose(mailfp);
  805.     if (nlines == 0)
  806.     {
  807.         //
  808.         // No subscribers.
  809.         //
  810. #ifdef NOSUBJECT
  811.         for (int i = 1; args[i]; i++) DELETE args[i];
  812. #else
  813.         for (int i = 3; args[i]; i++) DELETE args[i];
  814. #endif
  815.         DELETE args;
  816.         return;
  817.     }
  818.  
  819.     int fds[2];
  820.     if (pipe(fds) < 0)
  821.         error("file %s, line %d, pipe() failed", __FILE__, __LINE__);
  822.  
  823.     switch(fork())
  824.     {
  825.       case -1: // error
  826.         error("file %s, line %d, fork() failed", __FILE__, __LINE__);
  827.       case 0: // in the child
  828.         //
  829.         // Set stdin to the read end of pipe.
  830.         //
  831.         (void)close(0);
  832.         if (dup(fds[0]) < 0)
  833.             error("file %s, line %d, dup() failed", __FILE__, __LINE__);
  834.         (void)close(fds[0]);
  835.         (void)close(fds[1]);
  836.         (void)close(1);
  837.         (void)close(2);
  838.         
  839.         //
  840.         // We fork again and let the grandchild do the actual
  841.         // mailing.  This way our parent will not have to wait
  842.         // for the mail to be sent.
  843.         //
  844.         switch(fork())
  845.         {
  846.           case -1:
  847.             exit(1);
  848.           case 0:
  849.             execv(mailprog, (char *const *)args);
  850.             exit(1);  // exec failed
  851.           default:
  852.             exit(0);
  853.         }
  854.         break;
  855.       default:  // in the parent
  856.       {
  857.           (void)close(fds[0]);
  858.  
  859. #ifdef NOSUBJECT
  860.           //
  861.           // Write Subject to pipe.
  862.           //
  863.           write_to_pipe(fds[1], subject, subject.length());
  864. #endif
  865.  
  866.           //
  867.           // write the mailfile to the pipe
  868.           //
  869.           switch (how)
  870.           {
  871.             case CLOSED:
  872.             case REOPENED:
  873.             case APPENDED:
  874.             case KEYWORDMOD:
  875.             case SEVERITYMOD:
  876.             case TRANSFER:
  877.             {
  878.                 //
  879.                 // Write the fields and the new data only.
  880.                 //
  881.                 char *tail = data.dptr;
  882.                 for (int i = 0; i < NFields(); i++)
  883.                 {
  884.                     tail = strchr(tail, '\n');
  885.                     tail += 1; // step past the newline
  886.                 }
  887.                 tail += 1;     // step past the second newline to the 
  888.                                // first character past the header
  889.                 //
  890.                 // write the header
  891.                 //
  892.                 write_to_pipe(fds[1], data.dptr, tail - data.dptr);
  893.                 if (offset <= 0)
  894.                     error("file %s, line %d, offset must be positive",
  895.                           __FILE__, __LINE__);
  896.                 write_to_pipe(fds[1], data.dptr + offset, data.dsize-offset-1);
  897.             }
  898.                 break;
  899.             case LOGGED:
  900.                 write_to_pipe(fds[1], data.dptr, data.dsize - 1);
  901.                 break;
  902.             default: error("file %s, line %d, illegal case in switch()",
  903.                            __FILE__, __LINE__);
  904.           }
  905.           (void)close(fds[1]);
  906.           
  907. #ifdef NOSUBJECT
  908.           for (int i = 1; args[i]; i++) DELETE args[i];
  909. #else
  910.           for (int i = 3; args[i]; i++) DELETE args[i];
  911. #endif
  912.           DELETE args;
  913.  
  914.           //
  915.           // We are not interested in the return value.  The assumption
  916.           // here is that if something goes wrong with the mailing that
  917.           // the error will eventually get to the problem administrator.
  918.           //
  919.           (void)wait(0);
  920.  
  921.           return;
  922.       }
  923.     }
  924. }
  925.  
  926. /*
  927. ** invoke_editor - invoke users editor on file
  928. */
  929.  
  930. static void invoke_editor(const char *file)
  931. {
  932.     char *editor = getenv("EDITOR");
  933.     if (editor == 0) editor = "vi";
  934.  
  935.     String argstring(editor);
  936.     argstring += " ";
  937.     argstring += file;
  938.  
  939.     //
  940.     // We tokenize because the user could have EDITOR
  941.     // set to "emacs -q -nw", or some such thing, which execvp
  942.     // would not recognize as a legal filename.
  943.     //
  944.     const char **args = tokenize(argstring, " \t");
  945.  
  946.     //
  947.     // We must be careful of that venerable old editor "vi",
  948.     // which has a habit of returning error codes, when nothing
  949.     // went wrong.
  950.     //
  951.     if (!execute(args[0], args) && strcmp(args[0], "vi"))
  952.         error("file %s, line %d, couldn't exec() your editor `%s'",
  953.               __FILE__, __LINE__, editor);
  954. }
  955.  
  956. /*
  957. ** database_exists - checks to see if a database for the current area exists.
  958. **                   This is important since gdbm_open\(GDBM_READER\)
  959. **                   will fail if the database does not already exist.
  960. **                   Returns one if a file of the appropriate name
  961. **                   exists, else zero.  There is no guarantee that we
  962. **                   actually have a database, only an appropriately
  963. **                   named file.
  964. */
  965.  
  966. int database_exists()
  967. {
  968.     String filename(HomeBase);
  969.     filename += "/";
  970.     filename += CurrentArea();
  971.     filename += GdbmSuffix;
  972.  
  973.     int rc = open((const char *)filename, O_RDONLY);
  974.     (void)close(rc);
  975.     return rc < 0 ? 0 : 1;
  976. }
  977.  
  978. /*
  979. ** open_database - opens the GDBM database on the current area.
  980. **                 Exits on error.
  981. */
  982.  
  983. void open_database(int mode)
  984. {
  985.     String filename(HomeBase);
  986.     filename += "/";
  987.     filename += CurrentArea();
  988.     filename += GdbmSuffix;
  989.  
  990.     if ((GdbmFile = gdbm_open(filename, 0, mode, 00644, 0)) == 0)
  991.         error("file %s, line %d, gdbm_open() failed on `%s', errno = %d",
  992.               __FILE__, __LINE__, (const char *)filename, gdbm_errno);
  993. }
  994.  
  995. /*
  996. ** database_directory_exists - does HomeBase exist?
  997. */
  998.  
  999. static int database_directory_exists()
  1000. {
  1001.     int rc = open(HomeBase, O_RDONLY);
  1002.     (void)close(rc);
  1003.     return rc < 0 ? 0 : 1;
  1004. }
  1005.  
  1006.  
  1007. /*
  1008. ** update_database - updates database on the current area with problem_data.
  1009. **                   size is total size of data.  This function is
  1010. **                   used both to insert new entries and to replace
  1011. **                   old entries. If offset is nonzero, it is the
  1012. **                   start of problem number field, which we fill in
  1013. **                   after getting new problem number.  offset is
  1014. **                   nonzero only when initially logging a problem.
  1015. */
  1016.  
  1017. static void update_database(datum &key, datum &data, const Modified how,
  1018.                             int offset = 0)
  1019. {
  1020.     int fd = open(sequence_file(), O_RDWR);
  1021.     if (fd < 0)
  1022.         error("file %s, line %d, open() on `%s' failed",
  1023.               __FILE__, __LINE__, sequence_file());
  1024.  
  1025.     block_tstp_and_winch();             // block SIGTSTP and WINCH
  1026.     lock_file(fd);                      // lock our sequence file
  1027.     open_database(GDBM_WRCREAT);        // open database for writing
  1028.     if (how != REORGANIZED)
  1029.         data.dptr[data.dsize - 1] = 0;  // make sure data is stringified
  1030.  
  1031.     switch (how)
  1032.     {
  1033.       case DELETED:
  1034.         if (gdbm_delete(GdbmFile, key))
  1035.             error("file %s, line %d, gdbm_delete() failed, errno = %d",
  1036.                   __FILE__, __LINE__, gdbm_errno);
  1037.         break;
  1038.       case REORGANIZED:
  1039.         if (gdbm_reorganize(GdbmFile))
  1040.             error("file %s, line %d, gdbm_reorganize() failed, errno = %d",
  1041.                   __FILE__, __LINE__, gdbm_errno);
  1042.         break;
  1043.       case TRANSFER:
  1044.       {
  1045.         //
  1046.         // Close original database.
  1047.         //
  1048.         gdbm_close(GdbmFile);
  1049.  
  1050.         //
  1051.         // Remember current area before switching to new one.
  1052.         //
  1053.         String oldArea(CurrentArea());
  1054.         char *newArea = data.dptr + max_field_length() + 1;
  1055.         char *tmp = strchr(newArea, '\n');
  1056.         *tmp = 0;
  1057.         setCurrentArea(newArea);
  1058.         *tmp = '\n';
  1059.         
  1060.         //
  1061.         // Open database on the new area and insert the problem.
  1062.         //
  1063.         open_database(GDBM_WRCREAT);
  1064.  
  1065.         if (gdbm_store(GdbmFile, key, data, GDBM_INSERT))
  1066.             error("file %s, line %d, gdbm_store() failed, errno = %d",
  1067.                   __FILE__, __LINE__, gdbm_errno);
  1068.  
  1069.         //
  1070.         // Now go back to previous database and delete problem from there.
  1071.         //
  1072.         gdbm_close(GdbmFile);
  1073.         setCurrentArea(oldArea);
  1074.         open_database(GDBM_WRCREAT);
  1075.  
  1076.         if (gdbm_delete(GdbmFile, key))
  1077.             error("file %s, line %d, gdbm_delete() failed, errno = %d",
  1078.                   __FILE__, __LINE__, gdbm_errno);
  1079.         break;
  1080.       }
  1081.       case LOGGED:
  1082.       {
  1083.           //
  1084.           // Must fill in key values; we are doing an initial log
  1085.           // of the problem.
  1086.           //
  1087.           key.dptr  = (char *) update_sequence_file(fd);
  1088.           key.dsize = (int)strlen(key.dptr) + 1;
  1089.  
  1090.           //
  1091.           // update problem # field
  1092.           //
  1093.           for (int i = 0; i < strlen(key.dptr); i++)
  1094.               data.dptr[offset + i] = key.dptr[i];
  1095.       }
  1096.       //
  1097.       // Fall through.
  1098.       //
  1099.       case CLOSED:
  1100.       case REOPENED:
  1101.       case APPENDED:
  1102.       case KEYWORDMOD:
  1103.       case SEVERITYMOD:
  1104.         if (gdbm_store(GdbmFile, key, data, how == LOGGED ?
  1105.                        GDBM_INSERT : GDBM_REPLACE))
  1106.             error("file %s, line %d, gdbm_store() failed, errno = %d",
  1107.                   __FILE__, __LINE__, gdbm_errno);
  1108.         break;
  1109.       default:
  1110.         error("file %s, line %d, illegal case in switch()",
  1111.               __FILE__, __LINE__);
  1112.     }
  1113.  
  1114.     gdbm_close(GdbmFile);
  1115.     unlock_file(fd);
  1116.     (void)close(fd);
  1117.     unblock_tstp_and_winch(); // unblock SIGTSTP and WINCH
  1118. }
  1119.  
  1120. //
  1121. // These variables are shared by build_log_screen and log_new_problem
  1122. // so that we can keep track of where we are in case we get a SIGTSTP
  1123. // or SIGWINCH.  We have to be careful to nullify them after we are done
  1124. // with them so that build_log_screen knows when it needs to prompt
  1125. // for fresh data.
  1126. //
  1127. static char *logged;
  1128. static char *reporter;
  1129. static char *keywords;
  1130. static char *summary;
  1131. static char *site;
  1132. static char severity;
  1133.  
  1134. /*
  1135. ** build_log_screen - prints the initial screen when logging a problem.
  1136. **                    Is also called after a SIGTSTP or SIGWINCH to
  1137. **                    redo the screen appropriately.
  1138. */
  1139.  
  1140. // forward declaration
  1141. static void redisplay_log_screen();
  1142.  
  1143. static void build_log_screen()
  1144. {
  1145.     clear_display_area();
  1146.  
  1147.     //
  1148.     // Print as many of the fields as will fit.
  1149.     // This gets done both on a normal call or on a redisplay.
  1150.     //
  1151.     enter_standout_mode();
  1152.     for (int i = 0; i < NFields() && i < rows() - 2; i++)
  1153.         display_string(Fields[i]);
  1154.     end_standout_mode();
  1155.  
  1156.     int fieldlen = max_field_length() + 1; // plus one accounts for the space
  1157.  
  1158.     int currline = 0;  // keep track of where we are on screen
  1159.  
  1160.     //
  1161.     // Fill in those fields which are obvious.
  1162.     //
  1163.     if (currline < rows() - 2)
  1164.     {
  1165.         move_cursor(currline++, fieldlen);
  1166.         display_string(CurrentArea(), 0, fieldlen);
  1167.     }
  1168.     if (currline < rows() - 2)
  1169.     {
  1170.         move_cursor(currline, fieldlen);
  1171.         display_string(username(), 0, fieldlen);
  1172.         currline += 2;
  1173.     }
  1174.     time_t t = time(0);
  1175.     logged = ctime(&t);
  1176.     if (currline < rows() - 2)
  1177.     {
  1178.         move_cursor(currline++, fieldlen);
  1179.         display_string(logged, 0, fieldlen);
  1180.     }
  1181.     if (currline < rows() - 2)
  1182.     {
  1183.         move_cursor(currline, fieldlen);
  1184.         display_string(logged, 0, fieldlen);
  1185.         currline += 3;
  1186.     }
  1187.     if (currline < rows() - 2)
  1188.     {
  1189.         move_cursor(currline, fieldlen);
  1190.         display_string("open", 0, fieldlen);
  1191.     }
  1192.  
  1193.     //
  1194.     // Prompt for those that are not.
  1195.     //
  1196.     const char *const suffix = " --> ";
  1197.     static char *line;
  1198.     int recursing = 0;  // is this a recursive call?
  1199.     if (!line)
  1200.         line = new char[fieldlen + strlen(suffix)];
  1201.     else
  1202.         recursing = 1;
  1203.     (void)strcpy(line, Fields[2]);
  1204.     (void)strcat(line, suffix);
  1205.     if (!reporter)
  1206.         if (recursing)
  1207.             goto exit;
  1208.         else
  1209.             reporter = prompt(line, redisplay_log_screen);
  1210.     currline = 2;
  1211.     if (currline < rows() - 2)
  1212.     {
  1213.         move_cursor(currline, fieldlen);
  1214.         display_string(reporter, 0, fieldlen);
  1215.         currline += 3;
  1216.     }
  1217.     (void)strcpy(line, Fields[5]);
  1218.     (void)strcat(line, suffix);
  1219.     if (!keywords)
  1220.         if (recursing)
  1221.             goto exit;
  1222.         else
  1223.             keywords = prompt(line, redisplay_log_screen);
  1224.     if (currline < rows() - 2)
  1225.     {
  1226.         move_cursor(currline++, fieldlen);
  1227.         display_string(keywords, 0, fieldlen);
  1228.     }
  1229.     (void)strcpy(line, Fields[6]);
  1230.     (void)strcat(line, suffix);
  1231.     if (!summary)
  1232.         if (recursing)
  1233.             goto exit;
  1234.         else
  1235.             summary = prompt(line, redisplay_log_screen);
  1236.     if (currline < rows() - 2)
  1237.     {
  1238.         move_cursor(currline, fieldlen);
  1239.         display_string(summary, 0, fieldlen);
  1240.         currline += 2;
  1241.     }
  1242.     (void)strcpy(line, Fields[8]);
  1243.     (void)strcat(line, suffix);
  1244.     if (!site)
  1245.         if (recursing)
  1246.             goto exit;
  1247.         else
  1248.             site = prompt(line, redisplay_log_screen);
  1249.     if (currline < rows() - 2)
  1250.     {
  1251.         move_cursor(currline++, fieldlen);
  1252.         display_string(site, 0, fieldlen);
  1253.     }
  1254.     if (!severity)
  1255.         if (recursing)
  1256.             goto exit;
  1257.         else
  1258.             severity = get_severity(redisplay_log_screen);
  1259.     if (currline < rows() - 2)
  1260.     {
  1261.         move_cursor(currline, fieldlen);
  1262.         putchar(severity);
  1263.     }
  1264.     DELETE line;
  1265.     line = 0;  // the nullification is important
  1266.  
  1267.     //
  1268.     // We got here when we have been called recursively due to servicing
  1269.     // a SIGTSTP or SIGWINCH.  We do not delete line as we will shortly be
  1270.     // back in our original self where we will continue to use it.
  1271.     //
  1272.   exit:
  1273.     return;
  1274. }
  1275.  
  1276. /*
  1277. ** redisplay_log_screen - redisplay the log screen.  Can be called at any
  1278. **                        point during our building of the log screen to
  1279. **                        service a SIGTSTP or SIGWINCH.
  1280. */
  1281.  
  1282. static void redisplay_log_screen() { update_modeline(); build_log_screen(); }
  1283.  
  1284. /*
  1285. ** log_new_problem - need at least 4 rows to be useful
  1286. */
  1287.  
  1288. static void log_new_problem()
  1289. {
  1290.     String suffix(CurrentArea());
  1291.     suffix += " (logging)";
  1292.     
  1293.     update_modeline(ModelinePrefix, suffix);
  1294.  
  1295.     build_log_screen();
  1296.  
  1297.     message("Invoking your editor ...");
  1298.  
  1299.     //
  1300.     // Build  tmp file into which the problem will be edited by user.
  1301.     //
  1302.     const char *file = temporary_file();
  1303.  
  1304.     invoke_editor(file);
  1305.  
  1306.     //
  1307.     // Generate string just large enough to hold the problem.
  1308.     // Do not forget to add space for the newlines.
  1309.     //
  1310.     int flen       = max_field_length() + 1; // plus one accounts for the space
  1311.     int fsize      = filesize(file);
  1312.     int totalsize  = fsize + NFields() * flen;
  1313.  
  1314.     const int PDim = 10;   // spaces reserved for Problem # field
  1315.     const int StatDim = 6; // big enough for "open" or "closed"
  1316.  
  1317.  
  1318.     totalsize += int(strlen(CurrentArea())) + 1;
  1319.     totalsize += int(strlen(username())) + 1;
  1320.     totalsize += 50;       // strlen\(ctime\)==25 && already contains newline
  1321.     totalsize += StatDim + 1;  // "open" or "closed"
  1322.     totalsize += int(strlen(reporter)) + 1;
  1323.     totalsize += int(strlen(keywords)) + 1;
  1324.     totalsize += int(strlen(summary)) + 1;
  1325.     totalsize += int(strlen(site)) + 1;
  1326.     totalsize += 2;        // the severity field
  1327.     totalsize += PDim + 2; // space reserved for the problem number, its
  1328.                            // newline, and an extra one
  1329.  
  1330.     datum data;
  1331.     data.dsize = totalsize + 1; // do not forget about the null
  1332.     data.dptr  = new char[data.dsize];
  1333.  
  1334.     //
  1335.     // Write the header info to data.
  1336.     //
  1337.     int pos = 0;  // our position in data
  1338.     (void)sprintf(data.dptr, "%-*.*s%s\n", flen, flen, Fields[0], CurrentArea());
  1339.     pos += int (flen+strlen(CurrentArea())+1);
  1340.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[1],
  1341.                   username());
  1342.     pos += int (flen+strlen(username())+1);
  1343.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[2],
  1344.                   reporter);
  1345.     pos += int (flen+strlen(reporter)+1);
  1346.     (void)sprintf(&data.dptr[pos], "%-*.*s%s", flen, flen, Fields[3], logged);
  1347.     pos += flen+25;
  1348.     (void)sprintf(&data.dptr[pos], "%-*.*s%s", flen, flen, Fields[4], logged);
  1349.     pos += flen+25;
  1350.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[5],
  1351.                   keywords);
  1352.     pos += int (flen+strlen(keywords)+1);
  1353.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[6],
  1354.                   summary);
  1355.     pos += int (flen+strlen(summary)+1);
  1356.     (void)sprintf(&data.dptr[pos], "%-*.*s%-*.*s\n", flen, flen, Fields[7],
  1357.                   StatDim, StatDim, "open");
  1358.     pos += flen+StatDim+1;
  1359.     (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[8], site);
  1360.     pos += int (flen+strlen(site)+1);
  1361.     (void)sprintf(&data.dptr[pos], "%-*.*s%c\n", flen, flen, Fields[9],
  1362.                   severity);
  1363.     pos += flen+2;
  1364.     (void)sprintf(&data.dptr[pos], "%-*.*s          \n\n", flen, flen,
  1365.                   Fields[10]);
  1366.     int offset = pos + flen; // data.dptr\[offset\] is where Problem # goes
  1367.     pos += flen+PDim+2;      // we output two newlines here as separator
  1368.  
  1369.     //
  1370.     // Now for the problem itself.  Make sure this read only fails on a
  1371.     // real error -- block SIGTSTP and SIGWINCH
  1372.     //
  1373.     block_tstp_and_winch();
  1374.  
  1375.     int fd;
  1376.     if ((fd = open(file, O_RDONLY)) < 0) 
  1377.         error("file %s, line %d, open(%s, O_RDONLY) failed",
  1378.               __FILE__, __LINE__, file);
  1379.  
  1380.     if (read(fd, &data.dptr[pos], fsize) != fsize)
  1381.         error("file %s, line %d, read() failed", __FILE__, __LINE__);
  1382.  
  1383.     unblock_tstp_and_winch(); // unblock SIGTSTP and WINCH
  1384.     (void)close(fd);
  1385.     (void)unlink(file);
  1386.  
  1387.     if (yes_or_no("Really log this problem (y|n)? ",
  1388.                   redisplay_log_screen, Yes, 1))
  1389.     {
  1390.         datum key;  // empty key to be filled in by update_database
  1391.         update_database(key, data, LOGGED, offset);
  1392.         update_subscribers(data, key.dptr, LOGGED);
  1393.     }
  1394.  
  1395.     update_modeline();  // redisplay last modeline
  1396.     DELETE data.dptr;
  1397.  
  1398.     //
  1399.     // We have to both delete these and nullify them so that the next
  1400.     // time we call build_log_screen, we prompt for new data.
  1401.     //
  1402.     DELETE reporter;
  1403.     DELETE keywords;
  1404.     DELETE summary;
  1405.     DELETE site;
  1406.     reporter = 0;
  1407.     keywords = 0;
  1408.     summary  = 0;
  1409.     site     = 0;
  1410.     severity = 0;  // Just nullify this; it is a static char.
  1411. }
  1412.  
  1413. /*
  1414. ** commands_screen - display screen of problem commands. We assume
  1415. **                   that the caller will be setting the modeline.
  1416. **                   Needs at least five before anything useful is displayed.
  1417. */
  1418.  
  1419. static void commands_screen()
  1420. {
  1421.     clear_display_area();
  1422.  
  1423.     enter_standout_mode();
  1424.     display_string("Commands");
  1425.     end_standout_mode();
  1426.     cursor_wrap();
  1427.  
  1428.     //
  1429.     // Display as many commands as will fit on screen starting in third row.
  1430.     //
  1431.     for (int i = 0; i < NCommands() && i < rows() - 4; i++)
  1432.     {
  1433.         (void)fputs("  ", stdout);
  1434.         enter_standout_mode();
  1435.         putchar(Commands[i][0]);  // first char of command in bold
  1436.         end_standout_mode();
  1437.         display_string(&Commands[i][1], 0, 3);
  1438.     }
  1439. }
  1440.  
  1441. /*
  1442. ** redisplay_commands - redisplay the commands screen and modeline.
  1443. **                      This gets called on a SIGTSTP or SIGWINCH.
  1444. */
  1445.  
  1446. static void redisplay_commands() { commands_screen(); update_modeline(); }
  1447.     
  1448. /*
  1449. ** update_existing_problem - update an existing problem entry.
  1450. */
  1451.  
  1452. static void update_existing_problem(datum &data, datum &key, Modified how)
  1453. {
  1454.     message("Invoking your editor ...");
  1455.  
  1456.     //
  1457.     // Build tmp file into which the data will be edited by user.
  1458.     //
  1459.     const char *file = temporary_file();
  1460.  
  1461.     invoke_editor(file);
  1462.  
  1463.     //
  1464.     // Merge old data with the new.
  1465.     //
  1466.     time_t t = time(0);
  1467.     char *updated = ctime(&t);
  1468.  
  1469.     String separator("\n****** ");
  1470.     separator += How[how];
  1471.     separator += " by `";
  1472.     separator += username();
  1473.     separator += "' on ";
  1474.     separator += updated;
  1475.     separator += "\n";
  1476.     
  1477.     int fsize = filesize(file);
  1478.  
  1479.     //
  1480.     // Is last character in problem a newline?
  1481.     //
  1482.     int add_newline = data.dptr[data.dsize-2] != '\n';
  1483.  
  1484.     datum newdata;
  1485.  
  1486.     newdata.dsize = int(separator.length() + data.dsize + fsize + add_newline);
  1487.     newdata.dptr  = new char[newdata.dsize];
  1488.     (void)strcpy(newdata.dptr, data.dptr);            // the old data
  1489.     if (add_newline) (void)strcat(newdata.dptr, "\n"); // add terminal newline 
  1490.     (void)strcat(newdata.dptr, separator);            // the separator
  1491.  
  1492.     //
  1493.     // The new data.  Make sure this read only fails on a real
  1494.     // error -- block SIGTSTP and SIGWINCH.
  1495.     //
  1496.     block_tstp_and_winch();
  1497.  
  1498.     int fd;
  1499.     if ((fd = open(file, O_RDONLY)) < 0) 
  1500.         error("file %s, line %d, open(%s, O_RDONLY) failed",
  1501.               __FILE__, __LINE__, file);
  1502.  
  1503.     if (read(fd,&newdata.dptr[separator.length()+data.dsize-1],fsize) != fsize)
  1504.         error("file %s, line %d, read() failed", __FILE__, __LINE__);
  1505.  
  1506.     unblock_tstp_and_winch();
  1507.     (void)close(fd);
  1508.     (void)unlink(file);
  1509.  
  1510.     //
  1511.     // Always update the Updated field -- Fields\[4\].
  1512.     //
  1513.     char *head = newdata.dptr;
  1514.     for (int i = 0; i < 4; i++) 
  1515.     {
  1516.         // want to find head of fifth line
  1517.         head = strchr(head, '\n');
  1518.         head += 1; // step past the newline
  1519.     }
  1520.     int flen = max_field_length() + 1;
  1521.     head += flen;  // skip to the data in Fields\[4\]
  1522.     for (i = 0; i < 25; i++) head[i] = updated[i];
  1523.  
  1524.     //
  1525.     // Update the Status field only on closes and reopens.
  1526.     //
  1527.     if (how == CLOSED || how == REOPENED)
  1528.     {
  1529.         char *field = (how == CLOSED ? "closed" : "open  ");
  1530.         for (i = 0; i < 3; i++)
  1531.         {
  1532.             //
  1533.             // Skip three more lines.
  1534.             //
  1535.             head = strchr(head, '\n');
  1536.             head += 1;           // step past the newline
  1537.         }
  1538.         head += flen;            // step to the data in Fields\[7\]
  1539.         for (i = 0; i < 6; i++)  // StatDim == 6 in log_new_problem
  1540.             head[i] = field[i];
  1541.     }
  1542.  
  1543.     String msg("Really do the ");
  1544.     switch (how)
  1545.     {
  1546.       case CLOSED:      msg += "close (y|n)?" ; break;
  1547.       case REOPENED:    msg += "reopen (y|n)?"; break;
  1548.       case APPENDED:    msg += "append (y|n)?"; break;
  1549.       case KEYWORDMOD:  msg += "keyword modification (y|n)?"; break;
  1550.       case SEVERITYMOD: msg += "severity modification (y|n)?"; break;
  1551.       case TRANSFER:    msg += "problem transfer (y|n)?"; break;
  1552.       default:          error("file %s, line %d, illegal case in switch()",
  1553.                               __FILE__, __LINE__);
  1554.     }
  1555.  
  1556.     if (yes_or_no(msg, redisplay_commands, Yes, 1))
  1557.     {
  1558.         update_database(key, newdata, how);
  1559.         update_subscribers(newdata, key.dptr, how, separator.length() +
  1560.                            data.dsize - 1);
  1561.     }
  1562.  
  1563.     DELETE newdata.dptr;
  1564. }
  1565.