home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / misc / volume33 / problem / part06 / problem2.C
Encoding:
C/C++ Source or Header  |  1992-10-19  |  41.6 KB  |  1,480 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. ** problem2.C problem2.C 1.17   Delta\'d: 17:43:06 10/8/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. /*
  32. ** commands_screen - display screen of problem commands. We assume
  33. **                   that the caller will be setting the modeline.
  34. **                   Needs at least five before anything useful is displayed.
  35. */
  36.  
  37. static void commands_screen()
  38. {
  39.     cursor_home();
  40.     clear_to_end_of_line();
  41.     enter_standout_mode();
  42.     display_string("Commands");
  43.     end_standout_mode();
  44.     clear_to_end_of_line();
  45.     cursor_wrap();
  46.  
  47.     //
  48.     // Display as many commands as will fit on screen starting in third row.
  49.     //
  50.     for (int i = 0; i < NCommands() && i < rows() - 4; i++)
  51.     {
  52.         clear_to_end_of_line();
  53.         (void)fputs("  ", stdout);
  54.         enter_standout_mode();
  55.         putchar(Commands[i][0]);  // first char of command in bold
  56.         end_standout_mode();
  57.         display_string(&Commands[i][1], 0, 3);
  58.     }
  59.  
  60.     // clear any dirty lines
  61.     for (; i < rows() - 4; i++) {
  62.         clear_to_end_of_line();
  63.         cursor_wrap();
  64.     }
  65. }
  66.  
  67. /*
  68. ** redisplay_commands - redisplay the commands screen and modeline.
  69. **                      This gets called on a SIGTSTP or SIGWINCH.
  70. */
  71.  
  72. static void redisplay_commands() { commands_screen(); update_modeline(); }
  73.     
  74. /*
  75. ** update_existing_problem - update an existing problem entry.
  76. */
  77.  
  78. static void update_existing_problem(datum &data, datum &key, Modified how)
  79. {
  80.     message("Invoking your editor ...");
  81.  
  82.     //
  83.     // Build tmp file into which the data will be edited by user.
  84.     //
  85.     const char *file = temporary_file();
  86.  
  87.     invoke_editor(file);
  88.  
  89.     //
  90.     // Merge old data with the new.
  91.     //
  92.     time_t t = time(0);
  93.     char *updated = ctime(&t);
  94.     char *fmt = "\n****** %s by `%s' on %s\n";
  95.     char *separator = new char[strlen(fmt) + strlen(How[how]) +
  96.                                strlen(username()) + strlen(updated) - 5];
  97.  
  98.     (void)sprintf(separator, fmt, How[how], username(), updated);
  99.     int fsize = filesize(file);
  100.     datum newdata;
  101.     newdata.dsize = int (strlen(separator) + data.dsize + fsize);
  102.     newdata.dptr  = new char[newdata.dsize];
  103.     (void)strcpy(newdata.dptr, data.dptr);  // the old data
  104.     (void)strcat(newdata.dptr, separator);  // the separator
  105.  
  106.     //
  107.     // The new data.  Make sure this read only fails on a real
  108.     // error -- block SIGTSTP and SIGWINCH.
  109.     //
  110.     block_tstp_and_winch();
  111.  
  112.     int fd;
  113.     if ((fd = open(file, O_RDONLY)) < 0) 
  114.         error("file %s, line %d, open(%s, O_RDONLY) failed",
  115.               __FILE__, __LINE__, file);
  116.  
  117.     if (read(fd,&newdata.dptr[strlen(separator)+data.dsize-1],fsize) != fsize)
  118.         error("file %s, line %d, read() failed", __FILE__, __LINE__);
  119.  
  120.     unblock_tstp_and_winch();
  121.     (void)close(fd);
  122.     (void)unlink(file);
  123.  
  124.     //
  125.     // Always update the Updated field -- Fields\[4\].
  126.     //
  127.     char *head = newdata.dptr;
  128.     for (int i = 0; i < 4; i++) 
  129.     {
  130.         // want to find head of fifth line
  131.         head = strchr(head, '\n');
  132.         head += 1; // step past the newline
  133.     }
  134.     int flen = max_field_length() + 1;
  135.     head += flen;  // skip to the data in Fields\[4\]
  136.     for (i = 0; i < 25; i++) head[i] = updated[i];
  137.  
  138.     //
  139.     // Update the Status field only on closes and reopens.
  140.     //
  141.     if (how == CLOSED || how == REOPENED)
  142.     {
  143.         char *field = (how == CLOSED ? "closed" : "open  ");
  144.         for (i = 0; i < 3; i++)
  145.         {
  146.             // skip three more lines
  147.             head = strchr(head, '\n');
  148.             head += 1; // step past the newline
  149.         }
  150.         head += flen;  // step to the data in Fields\[7\]
  151.         for (i = 0; i < 6; i++)  // StatDim == 6 in log_new_problem\(\)
  152.             head[i] = field[i];
  153.     }
  154.  
  155.     fmt = "Really do the %s (y|n)?";
  156.     char *str;
  157.     switch (how)
  158.     {
  159.       case CLOSED:     str = "close" ; break;
  160.       case REOPENED:   str = "reopen"; break;
  161.       case APPENDED:   str = "append"; break;
  162.       case KEYWORDMOD: str = "keyword modification"; break;
  163.       default:         error("file %s, line %d, illegal case in switch()",
  164.                              __FILE__, __LINE__);
  165.     }
  166.  
  167.     char *buf = new char[strlen(fmt) + strlen(str) - 1];
  168.     (void)sprintf(buf, fmt, str);
  169.     if (yes_or_no(buf, redisplay_commands, Yes, 1))
  170.     {
  171.         update_database(key, newdata, how);
  172.         update_subscribers(newdata, key.dptr, how, (int)strlen(separator) +
  173.                            data.dsize - 1);
  174.     }
  175.  
  176.     DELETE buf;
  177.     DELETE separator;
  178.     DELETE newdata.dptr;
  179. }
  180.  
  181. /*
  182. ** append_to_problem - append to an already existing problem.  Returns
  183. **                     false if the problem doesn\'t exist.  This indicates
  184. **                     that we don\'t need to redisplay the command list.
  185. **                     Returns true if we need to redisplay the command
  186. **                     list.  If `number\', which defaults to zero, is
  187. **                     nonzero, we use that number instead of prompting.
  188. */
  189.  
  190. int append_to_problem(const char *number)
  191. {
  192.     datum key;
  193.     key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  194.                                                   redisplay_commands);
  195.     key.dsize = int (strlen(key.dptr) + 1);
  196.     if (!database_exists())
  197.     {
  198.         ding();
  199.         message("There is no database for problem area `%' ", CurrentArea());
  200.         if (!number)
  201.         {
  202.             DELETE key.dptr;
  203.             sleep(2);
  204.         }
  205.         return 0;
  206.     }
  207.  
  208.     open_database(GDBM_READER);
  209.     datum data = gdbm_fetch(GdbmFile, key);
  210.     gdbm_close(GdbmFile);
  211.     if (!data.dptr)
  212.     {
  213.         ding();
  214.         message("There is no problem # `%' ", key.dptr);
  215.         if (!number)
  216.         {
  217.             DELETE key.dptr;
  218.             sleep(2);
  219.         }
  220.         return 0;  // only the message area has been corrupted
  221.     }
  222.  
  223.     //
  224.     // The problem exists.
  225.     //
  226.     update_existing_problem(data, key, APPENDED);
  227.     update_modeline();  // redisplay the previous modeline
  228.     free(data.dptr);
  229.     if (!number) DELETE key.dptr;
  230.  
  231.     return 1;  // must refresh the screen
  232. }
  233.  
  234. /*
  235. ** subscribe_to_area - put user on interested parties list for current area.
  236. **                     Exits on error
  237. */
  238.  
  239. static void subscribe_to_area()
  240. {
  241.     int mailfd = open_maillist_file();
  242.     const int chunksize = 20;
  243.     const int linelen   = 10;
  244.     char **users = new char*[chunksize];
  245.     FILE *mailfp = fdopen(mailfd, "r+");
  246.     if (!mailfp)
  247.         error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
  248.     int nusers = read_file(mailfp, users, chunksize, linelen);
  249.     if (nusers < 0)
  250.         error("file %s, line %d, error reading %s maillist",
  251.               __FILE__, __LINE__, CurrentArea());
  252.  
  253.     //
  254.     // Is user already subscribed?
  255.     //
  256.     const char *user = username();
  257.     for (int i = 0, subscribed = 0; i < nusers; i++)
  258.         if (strcmp(users[i], user) == 0)
  259.         {
  260.             subscribed = 1; break;
  261.         }
  262.  
  263.     for (i = 0; i < nusers; i++) DELETE users[i];
  264.     DELETE users;
  265.  
  266.     if (subscribed)
  267.     {
  268.         (void)close(mailfd); (void)fclose(mailfp);
  269.         ding();
  270.         message("You already subscribe to the `%' area ", CurrentArea());
  271.         sleep(2);
  272.         return;
  273.     }
  274.  
  275.     //
  276.     // Lock on sequence file when updating the maillist.
  277.     //
  278.     int seqfd = open(sequence_file(), O_RDWR);
  279.     if (seqfd < 0) error("file %s, line %d, open() on `%s' failed",
  280.                          __FILE__, __LINE__, sequence_file());
  281.  
  282.     block_tstp_and_winch(); // block SIGTSTP and WINCH
  283.     lock_file(seqfd);       // lock our sequence file
  284.  
  285.     // seek to end of maillist file
  286.     if (fseek(mailfp, 0, SEEK_END))
  287.         error("file %s, line %d, fseek() failed", __FILE__, __LINE__);
  288.     (void)fprintf(mailfp, "%s\n", user); // add\'em
  289.  
  290.     (void)fclose(mailfp);
  291.     (void) close(mailfd);
  292.     unlock_file(seqfd);
  293.     (void)close(seqfd);
  294.  
  295.     unblock_tstp_and_winch();
  296.  
  297.     message("You now subscribe to the `%' area ", CurrentArea());
  298.     sleep(2);
  299. }
  300.  
  301. /*
  302. ** unsubscribe_from_area - unsubscribe from the current area.  Exits on error.
  303. */
  304.  
  305. static void unsubscribe_from_area()
  306. {
  307.     int mailfd   = open_maillist_file();
  308.     FILE *mailfp = fdopen(mailfd, "r+");
  309.     if (!mailfp)
  310.         error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
  311.  
  312.     const int chunksize = 20;
  313.     const int linelen   = 10;
  314.     char **users = new char*[chunksize];
  315.     int nusers   = read_file(mailfp, users, chunksize, linelen);
  316.     if (nusers < 0)
  317.         error("file %s, line %d, error reading %s maillist",
  318.               __FILE__, __LINE__, CurrentArea());
  319.  
  320.     //
  321.     // Are they already subscribed?
  322.     //
  323.     const char *user = username();
  324.     for (int i = 0, subscribed = 0; i < nusers; i++)
  325.         if (strcmp(users[i], user) == 0) { subscribed = 1; break; }
  326.  
  327.     for (i = 0; i < nusers; i++) DELETE users[i];
  328.     DELETE users;
  329.  
  330.     if (!subscribed)
  331.     {
  332.         (void)fclose(mailfp);
  333.         (void)close(mailfd);
  334.         ding();
  335.         message("You're not a subscribee of the `%' area ", CurrentArea());
  336.         sleep(2);
  337.         return;
  338.     }
  339.  
  340.     //
  341.     // They subscribe - remove them.
  342.     //
  343.     if (fseek(mailfp, 0, SEEK_SET))  // seek to beginning of file
  344.         error("file %s, line %d, fseek() failed", __FILE__, __LINE__);
  345.  
  346.     //
  347.     // First, build  a tmp file for new maillist.
  348.     //
  349.     char *tmpfile = new char[strlen(mail_list_prefix()) + 12];
  350.     (void)sprintf(tmpfile, "%s.%d", mail_list_prefix(), getpid());
  351.     int tmpfd = open(tmpfile, O_RDWR|O_CREAT, 0644);
  352.     if (tmpfd < 0)
  353.         error("file %s, line %d, open() failed", __FILE__, __LINE__);
  354.     FILE *tmpfp = fdopen(tmpfd, "w+");
  355.     if (!tmpfp)
  356.         error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
  357.  
  358.     for (char *line = fgetline(tmpfp, 10); line; line = fgetline(tmpfp, 10))
  359.     {
  360.         if (strcmp(line, user) == 0)
  361.         {
  362.             DELETE line;
  363.             continue;
  364.         }
  365.         (void)fprintf(tmpfp, "%s\n", line);
  366.         DELETE line;
  367.     }
  368.  
  369.     if (feof(tmpfp) && !ferror(tmpfp))
  370.     {
  371.         //
  372.         // Alternate maillist correctly constructed.
  373.         //
  374.         (void)fclose(tmpfp); (void)fclose(mailfp);
  375.         (void) close(tmpfd); (void) close(mailfd);
  376.         //
  377.         // Remove old maillist.
  378.         //
  379.         String maillist = String(mail_list_prefix()) + "." + CurrentArea();
  380.         int seqfd = open(sequence_file(), O_RDWR);
  381.         if (seqfd < 0)
  382.             error("file %s, line %d, open() on `%s' failed",
  383.                   __FILE__, __LINE__, sequence_file());
  384.  
  385.         block_tstp_and_winch(); // block SIGTSTP and WINCH
  386.         lock_file(seqfd);       // lock our sequence file
  387.  
  388.         if (unlink((const char *)maillist) < 0)
  389.             error("file %s, line %d, unlink(%s) failed",
  390.                   __FILE__, __LINE__, (const char *)maillist);
  391.         if (rename((const char *)tmpfile, (const char *)maillist) < 0)
  392.             error("file %s, line %d, rename(%s, %s) failed", __FILE__,
  393.                   __LINE__, (const char *)tmpfile, (const char *)maillist);
  394.  
  395.         unlock_file(seqfd);
  396.         (void)close(seqfd);
  397.         unblock_tstp_and_winch();
  398.  
  399.         DELETE tmpfile;
  400.  
  401.         message("You've been unsubscribed from the `%' area ", CurrentArea());
  402.     }
  403.     else
  404.     {
  405.         (void)fclose(tmpfp);
  406.         (void)fclose(mailfp);
  407.         (void) close(tmpfd);
  408.         (void) close(mailfd);
  409.         ding();
  410.         message("Problem updating maillist -- update not committed ");
  411.     }
  412.     sleep(2);
  413. }
  414.  
  415. /*
  416. ** examine_problem - pages through a problem in current area.  Returns one
  417. **                   if the problem exists, otherwise zero.  If `number\' is
  418. **                   nonzero \(it defaults to zero\), we use that number
  419. **                   instead of prompting for one.
  420. */
  421.  
  422. int examine_problem(const char *number)
  423. {
  424.     if (!database_exists())
  425.     {
  426.         ding();
  427.         message("There is no database for problem area `%' ", CurrentArea());
  428.         if (!number) sleep(2);
  429.         return 0;
  430.     }
  431.  
  432.     datum key;
  433.     key.dptr = number ? (char *) number : prompt("Problem # --> ",
  434.                                                  redisplay_commands); 
  435.     key.dsize = int(strlen(key.dptr)) + 1;
  436.     open_database(GDBM_READER);
  437.     datum data = gdbm_fetch(GdbmFile, key);
  438.     gdbm_close(GdbmFile);
  439.  
  440.     if (!data.dptr)
  441.     {
  442.         ding();
  443.         message("There is no problem # % ", key.dptr);
  444.         if (!number)
  445.         {
  446.             DELETE key.dptr;
  447.             sleep(2);
  448.         }
  449.         return 0;
  450.     }
  451.  
  452.     //
  453.     // Build tmp file to pass to pager.
  454.     //
  455.     const char *file = temporary_file();
  456.  
  457.     //
  458.     // Write the problem to the file.
  459.     //
  460.     int fd;
  461.     if ((fd = open(file, O_RDWR)) < 0) 
  462.         error("file %s, line %d, open(%s, O_RDONLY) failed",
  463.               __FILE__, __LINE__, file);
  464.     if (write(fd, data.dptr, data.dsize - 1) != data.dsize - 1)
  465.         error("file %s, line %d, write() failed", __FILE__, __LINE__);
  466.  
  467.     const char *prefix = "-PProblem # ";
  468.     const char *suffix = " -- line %lb of %L ?e(END) .-- q (quit) H (help)";
  469.     String prompt = String(prefix) + key.dptr + suffix;
  470.     const char *less = "less";
  471.     const char *args[5];
  472.     args[0] = less;   args[1] = "-d";
  473.     args[2] = prompt; args[3] = file; args[4] = 0;
  474.  
  475.     clear_display();
  476.     synch_display();
  477.  
  478.     if (!execute(less, args))
  479.     {
  480.         (void)unlink(file);
  481.         error("file %s, line %d, problem running `less'", __FILE__, __LINE__);
  482.     }
  483.  
  484.     (void)close(fd);
  485.     (void)unlink(file);
  486.     update_modeline();  // redisplay previous modeline
  487.     free(data.dptr);
  488.     if (!number) DELETE key.dptr;
  489.  
  490.     return 1;  // must update screen
  491. }
  492.  
  493. /*
  494. ** summary_info - returns a string in new\'d space of the form:
  495. **
  496. **                     prob# status severity last-activity-date summary
  497. **
  498. **                sort_by_date\(\) "knows" the format of the lines output
  499. **                by this function, so if you change the format, you need
  500. **                to change sort_by_date\(\) also.
  501. */
  502.  
  503. char *summary_info(datum &data)
  504. {
  505.     const char *tail = data.dptr;
  506.  
  507.     //
  508.     // Updated is Fields\[4\]
  509.     //
  510.     for (int i = 0; i < 4; i++)
  511.     {
  512.         tail = strchr(tail, '\n');
  513.         tail += 1; // step past the newline
  514.     }
  515.     tail += max_field_length() + 1; // this is the Updated line
  516.     const char *updated = tail + 4; // don\'t output the day of the week
  517.     tail = strchr(tail, '\n');      // end of Updated line
  518.     int updatedLen = tail - updated;
  519.  
  520.     //
  521.     // Keywords is Fields\[5\] - just skip over it for now
  522.     //
  523.     tail += 1; // step past the newline
  524.     tail = strchr(tail, '\n');      // end of Keywords line
  525.  
  526.     //
  527.     // Summary is Fields\[6\]
  528.     //
  529.     tail += 1; // step past the newline
  530.     tail += max_field_length() + 1; // this is the Summary line
  531.     const char *summary = tail;
  532.     tail = strchr(tail, '\n');      // end of Summary line
  533.     int summaryLen = tail - summary;
  534.  
  535.     //
  536.     // Status Field\[7\]
  537.     //
  538.     tail += 1; // step past the newline
  539.     tail += max_field_length() + 1; // this is the Status line
  540.     const char *status = tail;
  541.     tail = strchr(tail, '\n');      // end of Status line
  542.     int statusLen = tail - status;
  543.  
  544.     //
  545.     // Severity is Field\[9\]
  546.     //
  547.     tail += 1; // step past the newline
  548.     tail = strchr(tail, '\n');      // end of Site line
  549.     tail += 1;                      // step past the newline
  550.     tail += max_field_length() + 1; // this is the Severity line
  551.     const char severity = *tail;
  552.     tail = strchr(tail, '\n');      // end of Severity line
  553.  
  554.     //
  555.     // Problem # is Fields\[10\]
  556.     //
  557.     tail += 1; // step past the newline
  558.     tail += max_field_length() + 1; // this is the prob # line
  559.     const char *number = tail;
  560.     //
  561.     // Find the end of the problem # field - probably contains a space.
  562.     //
  563.     tail = strchr(tail, '\n');
  564.     while (*(tail - 1) == ' ') tail--; // strip off trailing spaces
  565.     int numberLen = tail - number;
  566.  
  567.     const int numberFieldLen = 5;  // min width of Prob # field in summary
  568.     int len = numberLen > numberFieldLen ? numberLen : numberFieldLen;
  569.     char *line = new char[updatedLen + summaryLen + statusLen + len + 4];
  570.  
  571.  
  572.     *line = 0; // stringify
  573.  
  574.     if (numberLen < numberFieldLen) 
  575.     {
  576.         //
  577.         // Pad the length to numberFieldLen by prepending spaces.
  578.         //
  579.         int nSpaces = numberFieldLen - numberLen;
  580.         for (int i = 0; i < nSpaces; i++)
  581.             *(line + i) = ' ';
  582.         *(line + nSpaces) = 0;  // restringify
  583.     }
  584.         
  585.     (void)strncat(line, number, numberLen);
  586.     line[len] = 0;
  587.     (void)strcat(line, " ");
  588.     (void)strncat(line, status, statusLen);
  589.     line[len += statusLen + 1] = 0;
  590.  
  591.     // if status == "open", output the severity also
  592.     if (line[len - 1] == ' ') line[len - 1] = severity;
  593.  
  594.     (void)strcat(line, " ");
  595.     (void)strncat(line, updated, updatedLen);
  596.     line[len += updatedLen + 1] = 0;
  597.     (void)strcat(line, " ");
  598.     (void)strncat(line, summary, summaryLen);
  599.     line[len += summaryLen + 1] = 0;
  600.  
  601.     return line;
  602. }
  603.  
  604. /*
  605. ** lsign - returns -1, 0 or 1 depending on the sign of the argument
  606. */
  607.  
  608. inline int lsign(long arg)
  609. {
  610.     return arg == 0 ? 0 : (arg > 0 ? 1 : -1);
  611. }
  612.  
  613. /*
  614. ** sort_by_date - the comparison function passed to qsort\(3\) used when
  615. **                sorting listing lines by date.
  616. */
  617.  
  618. static int sort_by_date(const void *a, const void *b)
  619. {
  620.     char *tmpa = *(char **)a;
  621.     char *tmpb = *(char **)b;
  622.  
  623.     while (*tmpa == ' ') tmpa++;   // step over any spaces preceding Prob #
  624.     while (*tmpb == ' ') tmpb++;   // ditto
  625.  
  626.     tmpa = strchr(tmpa, ' ') + 1;  // step onto first character of Status
  627.     tmpb = strchr(tmpb, ' ') + 1;  // ditto
  628.  
  629.     if (strncmp(tmpa, tmpb, 4))
  630.         return -strncmp(tmpa, tmpb, 4); // sort "open" before "closed"
  631.     else if (strncmp(tmpa, tmpb, 6))
  632.         return strncmp(tmpa, tmpb, 6);  // sort by severity
  633.     else
  634.     {
  635.         // lastly, sort by most recent first
  636.         tmpa += 7;
  637.         tmpb += 7;
  638.         return lsign(seconds_in_date(tmpb) - seconds_in_date(tmpa));
  639.     }
  640. }
  641.  
  642. /*
  643. ** view_summary_lines - page through data from problem headers in current area.
  644. **                      Returns one if the database exists, else zero.
  645. */
  646.  
  647. static int view_summary_lines()
  648. {
  649.     if (!database_exists())
  650.     {
  651.         ding();
  652.         message("There is no database for problem area `%' ", CurrentArea());
  653.         sleep(2);
  654.         return 0;
  655.     }
  656.  
  657.     open_database(GDBM_READER);
  658.     datum key = gdbm_firstkey(GdbmFile);
  659.     if (!key.dptr)
  660.     {
  661.         const char *fmt = "Area `%s' appears to be empty ";
  662.         char *msg = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
  663.         (void)sprintf(msg, fmt, CurrentArea());
  664.         ding();
  665.         message(msg);
  666.         sleep(2);
  667.         DELETE msg;
  668.         gdbm_close(GdbmFile);
  669.         return 0;
  670.     }
  671.  
  672.     DList summaryLines;  // listing of problem summaries
  673.     datum data, tmp;
  674.     const int chunksize = 100;
  675.     int size = chunksize;
  676.     int pos = 0;
  677.     char **lines = new char*[size];
  678.  
  679.     message("Reading problems ... ");
  680.     while (1)
  681.     {
  682.         data = gdbm_fetch(GdbmFile, key);
  683.         lines[pos++] = summary_info(data);
  684.         if (pos == size)
  685.         {
  686.             // grow lines
  687.             char **newspace = new char*[size += chunksize];
  688.             for (int i = 0; i < pos; i++) newspace[i] = lines[i];
  689.             DELETE lines;
  690.             lines = newspace;
  691.         }
  692.         free(data.dptr);
  693.         tmp = gdbm_nextkey(GdbmFile, key);
  694.         free(key.dptr);
  695.         key = tmp;
  696.         if (!key.dptr) break;
  697.     }
  698.     gdbm_close(GdbmFile);
  699.     message("Reading problems ... done");
  700.  
  701.     //
  702.     // Sort lines "most recently updated" first.
  703.     //
  704.     qsort(lines, pos, sizeof(char**), sort_by_date);
  705.     for (int i = 0; i < pos; i++)
  706.         summaryLines.add(new DLink((char **)&lines[i]));
  707.  
  708.     DELETE lines;
  709.  
  710.     initialize_lister(&summaryLines);
  711.     initial_listing(&summaryLines);
  712.  
  713.     const char *fmt = "%s ---- q (quit) H (help)";
  714.     char *suffix = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
  715.     (void)sprintf(suffix, fmt, CurrentArea());
  716.     update_modeline(ModelinePrefix, suffix);
  717.  
  718.     DELETE suffix;
  719.  
  720.     summaryLines.saveYXPos(0, goal_column(&summaryLines));
  721.     if (summaryLines.currLine()->length() > columns())    
  722.         leftshift_current_line(&summaryLines);
  723.     else
  724.         move_cursor(summaryLines.savedYPos(), summaryLines.savedXPos());
  725.     synch_display();
  726.  
  727.     lister_cmd_loop(&summaryLines);
  728.  
  729.     return 1;
  730. }
  731.  
  732. /*
  733. ** close_problem - close an existing problem in current area.  Returns one if
  734. **                 we did the close, zero otherwise.  Only a database
  735. **                 administrator or the original logger can close a problem.
  736. **                 `number\', which defaults to null, is used if not null,
  737. **                 instead of prompting for the number.  Also, only an open
  738. **                 problem can be closed.
  739. */
  740.  
  741. int close_problem(const char *number)
  742. {
  743.     if (!database_exists())
  744.     {
  745.         ding();
  746.         message("There is no database for problem area `%' ", CurrentArea());
  747.         if (!number) sleep(2);
  748.         return 0;
  749.     }
  750.     datum key;
  751.     key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  752.                                                   redisplay_commands);
  753.     key.dsize = int(strlen(key.dptr)) + 1;
  754.  
  755.     open_database(GDBM_READER);
  756.     datum data = gdbm_fetch(GdbmFile, key);
  757.     gdbm_close(GdbmFile);
  758.  
  759.     if (!data.dptr)
  760.     {
  761.         ding();
  762.         message("There is no problem # % ", key.dptr);
  763.         if (!number)
  764.         {
  765.             DELETE key.dptr;
  766.             sleep(2);
  767.         }
  768.         return 0;
  769.     }
  770.  
  771.     //
  772.     // Are we authorized to close the problem?
  773.     //
  774.     char *tmp = strchr(data.dptr, '\n');  // logger is Fields\[1\]
  775.     tmp += 1; // step past the newline
  776.     tmp += max_field_length() + 1;        // the logger
  777.     int llen  = strchr(tmp, '\n') - tmp;  // # of chars in logger
  778.     const char *user = username();
  779.  
  780.     if ((llen != strlen(user) || strncmp(user, tmp, llen)) &&
  781.         getuid() != geteuid())
  782.     {
  783.         ding();
  784.         message("You're not authorized to close problem # % ", key.dptr);
  785.         if (!number)
  786.         {
  787.             DELETE key.dptr;
  788.             sleep(2);
  789.         }
  790.         free(data.dptr);
  791.         return 0;
  792.     }
  793.  
  794.     //
  795.     // Is the problem open?
  796.     //
  797.     for (int i = 0; i < 6; i++)
  798.     {
  799.         // status is Fields\[7\]
  800.         tmp = strchr(tmp, '\n');
  801.         tmp += 1;  // step past newline
  802.     }
  803.     if (strncmp(tmp + max_field_length() + 1, "open", 4))
  804.     {
  805.         ding();
  806.         message("Only open problems can be closed ");
  807.         if (!number)
  808.         {
  809.             DELETE key.dptr;
  810.             sleep(2);
  811.         }
  812.         free(data.dptr);
  813.         return 0;
  814.     }
  815.  
  816.     update_existing_problem(data, key, CLOSED);
  817.     update_modeline();  // redisplay previous modeline
  818.     free(data.dptr);
  819.     if (!number) DELETE key.dptr;
  820.  
  821.     return 1;
  822. }
  823.  
  824. /*
  825. ** reopen_problem - reopen a closed problem in current area.  Returns one if
  826. **                  we did the close, zero otherwise.  Only a database
  827. **                  administrator or the original logger can reopen a problem.
  828. **                  `number\', which defaults to null, is used if not null,
  829. **                  instead of prompting for the number.
  830. */
  831.  
  832. int reopen_problem(const char *number)
  833. {
  834.     if (!database_exists())
  835.     {
  836.         ding();
  837.         message("There is no database for problem area `%' ", CurrentArea());
  838.         if (!number) sleep(2);
  839.         return 0;
  840.     }
  841.  
  842.     datum key;
  843.     key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  844.                                                   redisplay_commands);
  845.     key.dsize = int(strlen(key.dptr)) + 1;
  846.  
  847.     open_database(GDBM_READER);
  848.     datum data = gdbm_fetch(GdbmFile, key);
  849.     gdbm_close(GdbmFile);
  850.  
  851.     if (!data.dptr)
  852.     {
  853.         ding();
  854.         message("There is no problem # % ", key.dptr);
  855.         if (!number)
  856.         {
  857.             DELETE key.dptr;
  858.             sleep(2);
  859.         }
  860.         return 0;
  861.     }
  862.  
  863.     //
  864.     // Are we authorized to reopen the problem?
  865.     //
  866.     char *tmp = strchr(data.dptr, '\n');  // logger is Fields\[1\]
  867.     tmp += 1; // step past the newline
  868.     tmp += max_field_length() + 1;        // the logger
  869.     int llen  = strchr(tmp, '\n') - tmp;  // # of chars in logger
  870.     const char *user = username();
  871.  
  872.     if ((llen != strlen(user) || strncmp(user, tmp, llen)) &&
  873.         getuid() != geteuid())
  874.     {
  875.         ding();
  876.         message("You're not authorized to close problem # % ", key.dptr);
  877.         if (!number)
  878.         {
  879.             DELETE key.dptr;
  880.             sleep(2);
  881.         }
  882.         free(data.dptr);
  883.         return 0;
  884.     }
  885.  
  886.     //
  887.     // Only closed problems can be opened.
  888.     //
  889.     for (int i = 0; i < 6; i++)
  890.     {
  891.         // status is Fields\[7\]
  892.         tmp = strchr(tmp, '\n');
  893.         tmp += 1;  // step past newline
  894.     }
  895.     if (strncmp(tmp + max_field_length() + 1, "closed", 6))
  896.     {
  897.         ding();
  898.         message("Only closed problems can be reopened ");
  899.         if (!number)
  900.         {
  901.             DELETE key.dptr;
  902.             sleep(2);
  903.         }
  904.         free(data.dptr);
  905.         return 0;
  906.     }
  907.  
  908.     update_existing_problem(data, key, REOPENED);
  909.     update_modeline();  // redisplay previous modeline
  910.     free(data.dptr);
  911.     if (!number) DELETE key.dptr;
  912.  
  913.     return 1;
  914. }
  915.  
  916. /*
  917. ** delete_problem - delete the problem from current area.
  918. **                  This is strictly for the database administrators.
  919. **                  If number is non-null, use it instead of prompting
  920. **                  for the problem number.  Returns 1 if the problem
  921. **                  was deleted, 0 otherwise.
  922. */
  923.  
  924. int delete_problem(const char *number)
  925. {
  926.     int del = 0;  // was delete successful?
  927.  
  928.     if (getuid() != geteuid())
  929.     {
  930.         ding();
  931.         message("Only database administrators can to delete problems ");
  932.         if (!number) sleep(2);
  933.         return 0;
  934.     }
  935.  
  936.     if (!database_exists())
  937.     {
  938.         ding();
  939.         message("There is no database for problem area `%' ", CurrentArea());
  940.         if (!number) sleep(2);
  941.         return 0;
  942.     }
  943.  
  944.     datum key;
  945.     key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  946.                                                   redisplay_commands);
  947.     key.dsize = int(strlen(key.dptr)) + 1;
  948.  
  949.     open_database(GDBM_READER);
  950.     datum data = gdbm_fetch(GdbmFile, key);
  951.     gdbm_close(GdbmFile);
  952.  
  953.     if (!data.dptr)
  954.     {
  955.         ding();
  956.         message("There is no problem # % ", key.dptr);
  957.         if (!number)
  958.         {
  959.             DELETE key.dptr;
  960.             sleep(2);
  961.         }
  962.         return 0;
  963.     }
  964.  
  965.     //
  966.     // The problem exists; delete it.
  967.     //
  968.     const char *msg = "Do you really want to delete this problem (n|y)? ";
  969.     if (del = yes_or_no(msg, redisplay_commands, No, 0))
  970.         update_database(key, data, DELETED);
  971.     free(data.dptr);
  972.     if (!number) DELETE key.dptr;
  973.  
  974.     return del;
  975. }
  976.  
  977. /*
  978. ** reorganize_database - reorganize the database for current area
  979. */
  980.  
  981. void reorganize_database(int dodelay)
  982. {
  983.     if (getuid() != geteuid())
  984.     {
  985.         ding();
  986.         message("Only database administrators can reorganize the database ");
  987.         if (dodelay) sleep(2);
  988.         return;
  989.     }
  990.     if (!database_exists())
  991.     {
  992.         ding();
  993.         message("There is no database for problem area `%' ", CurrentArea());
  994.         if (dodelay) sleep(2);
  995.         return;
  996.     }
  997.  
  998.     const char *msg = "Do you really want to reorganize this database (n|y)? ";
  999.     if (yes_or_no(msg, redisplay_commands, No, 0))
  1000.     {
  1001.         datum key, data;  // just placeholders here
  1002.         update_database(key, data, REORGANIZED);
  1003.     }
  1004. }
  1005.  
  1006. /*
  1007. ** search - prompt for pattern \(regexp\) and display the matches.
  1008. **          Returns zero if we only need to redisplay
  1009. **          the prompt, else returns one.
  1010. */
  1011.  
  1012. //
  1013. // Ways to search -- over full problem text or just the header
  1014. //
  1015. enum SearchMethod { FULLTEXT, HEADER };
  1016.  
  1017. static int search(const SearchMethod how)
  1018. {
  1019.     if (!database_exists())
  1020.     {
  1021.         ding();
  1022.         message("There is no database for problem area `%' ", CurrentArea());
  1023.         sleep(2);
  1024.         return 0;
  1025.     }
  1026.  
  1027.     char *keywords = prompt("Search for --> ", redisplay_commands);
  1028.     const char *separators = " ,\t";
  1029.     const char **list = tokenize(keywords, separators);
  1030.     DELETE keywords;
  1031.  
  1032.     if (!list[0]) return 0;  // no words to search for
  1033.  
  1034.     //
  1035.     // Build a regular expression to search for.
  1036.     // We want to build a pattern of the form:
  1037.     //
  1038.     //   word1|word2|word3|word4 ...
  1039.     //
  1040.     int len = 0;  // total length of all words in `list\'
  1041.     for (int i = 0; list[i]; i++)
  1042.         len += (int) strlen(list[i]) + 1; // plus one for the `|\'
  1043.     char *line = new char[len + 1];
  1044.     line[0] = 0;  // make it into a valid string
  1045.  
  1046.     //
  1047.     // Are each of the words individually a valid regexp?
  1048.     //
  1049.     regexp *regex;
  1050.     for (i = 0; list[i]; i++)
  1051.     {
  1052.         if ((regex = regcomp(list[i])) == 0)
  1053.         {
  1054.             ding();
  1055.             const char *fmt = "`%s' isn't a valid regex: %s , skipping ... ";
  1056.             char *msg = new char[strlen(fmt) + strlen(list[i]) +
  1057.                                  strlen(REerror) - 3];
  1058.             (void)sprintf(msg, fmt, list[i], REerror);
  1059.             message(msg);
  1060.             sleep(2);
  1061.             DELETE msg;
  1062.             DELETE line;
  1063.         }
  1064.         else
  1065.         {
  1066.             (void)strcat(line, list[i]);
  1067.             (void)strcat(line, "|");
  1068.         }
  1069.         DELETE (char *) regex;
  1070.     }
  1071.  
  1072.     //
  1073.     // Remove any trailing `|\'s.
  1074.     //
  1075.     if (line[strlen(line) - 1] == '|') line[strlen(line) - 1] = 0;
  1076.     if (strlen(line) == 0)
  1077.     {
  1078.         ding();
  1079.         message("No valid regular expressions among your keywords ");
  1080.         sleep(2);
  1081.         DELETE line;
  1082.         return 0;
  1083.     }
  1084.     if ((regex = regcomp(line)) == 0)
  1085.     {
  1086.         ding();
  1087.         message("regcomp() failed: %s", REerror);
  1088.         sleep(2);
  1089.         DELETE line;
  1090.         return 0;
  1091.     }
  1092.  
  1093.     open_database(GDBM_READER);
  1094.     datum key = gdbm_firstkey(GdbmFile);
  1095.     if (!key.dptr)
  1096.     {
  1097.         const char *fmt = "Area `%s' appears to be empty ";
  1098.         char *msg = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
  1099.         (void)sprintf(msg, fmt, CurrentArea());
  1100.         ding();
  1101.         message(msg);
  1102.         sleep(2);
  1103.         DELETE msg;
  1104.         DELETE line;
  1105.         DELETE (char *) regex;
  1106.         return 0;
  1107.     }
  1108.  
  1109.     DList summaryLines;  // listing of problem summaries
  1110.     datum data, tmp;
  1111.     const int chunksize = 100;
  1112.     int size = chunksize, pos = 0;
  1113.     char **lines = new char*[size];
  1114.  
  1115.     message("Reading problems ... ");
  1116.  
  1117.     while (1)
  1118.     {
  1119.         data = gdbm_fetch(GdbmFile, key);
  1120.         switch (how)
  1121.         {
  1122.           case HEADER:
  1123.           {
  1124.               //
  1125.               // Search only the problem header.
  1126.               //
  1127.               char *tail = data.dptr;
  1128.               for (int i = 0; i < NFields(); i++)
  1129.               {
  1130.                   tail = strchr(tail, '\n');
  1131.                   tail += 1; // step past the newline
  1132.               }
  1133.               *tail = 0;     // treat the header as one long string
  1134.               if (regexec(regex, data.dptr)) lines[pos++] = summary_info(data);
  1135.           }
  1136.               break;
  1137.           case FULLTEXT:
  1138.               //
  1139.               // Search over full problem text.
  1140.               //
  1141.               if (regexec(regex, data.dptr)) lines[pos++] = summary_info(data);
  1142.               break;
  1143.           default: error("file %s, line %d, illegal case in switch()",
  1144.                            __FILE__, __LINE__);
  1145.         }
  1146.         if (pos == size)
  1147.         {
  1148.             // grow \'lines\'
  1149.             char **newspace = new char*[size += chunksize];
  1150.             for (int i = 0; i < pos; i++) newspace[i] = lines[i];
  1151.             DELETE lines;
  1152.             lines = newspace;
  1153.         }
  1154.         free(data.dptr);
  1155.         tmp = gdbm_nextkey(GdbmFile, key);
  1156.         free(key.dptr);
  1157.         key = tmp;
  1158.         if (!key.dptr) break;
  1159.     }
  1160.     gdbm_close(GdbmFile);
  1161.     message("Reading problems ... done");
  1162.     DELETE (char *) regex;
  1163.  
  1164.     //
  1165.     // sort lines "most recently updated" first
  1166.     //
  1167.     qsort(lines, pos, sizeof(char**), sort_by_date);
  1168.     for (i = 0; i < pos; i++)
  1169.         summaryLines.add(new DLink((char **)&lines[i]));
  1170.     delete lines;
  1171.  
  1172.     //
  1173.     // Are there any problem summaries to peruse?
  1174.     //
  1175.     if (!summaryLines.nelems())
  1176.     {
  1177.          ding();
  1178.          message("No matches for regex `%' ", line);
  1179.          sleep(2);
  1180.          delete line;
  1181.          return 0;
  1182.      }
  1183.  
  1184.     initialize_lister(&summaryLines);
  1185.     initial_listing(&summaryLines);
  1186.  
  1187.     char *fmt    = "%s (regex: %s) ---- q (quit) H (help)";
  1188.     char *suffix = new char[strlen(fmt) + strlen(CurrentArea()) +
  1189.                             strlen(line) - 3];
  1190.  
  1191.     (void)sprintf(suffix, fmt, CurrentArea(), line);
  1192.     update_modeline(ModelinePrefix, suffix);
  1193.     delete suffix;
  1194.     delete line;
  1195.     summaryLines.saveYXPos(0, goal_column(&summaryLines));
  1196.     if (summaryLines.currLine()->length() > columns())    
  1197.         leftshift_current_line(&summaryLines);
  1198.     else
  1199.         move_cursor(summaryLines.savedYPos(), summaryLines.savedXPos());
  1200.     synch_display();
  1201.     lister_cmd_loop(&summaryLines);
  1202.  
  1203.     return 1;
  1204. }
  1205.  
  1206. //
  1207. // the header data -- needs to be shared between display_header\(\) and 
  1208. //                    modify_keywords\(\) so that we can call prompt\(\)
  1209. //                    using display_header as its second argument.
  1210. static datum header_data;
  1211.  
  1212. /*
  1213. ** display_header - put up the header of the problem in `data\'.
  1214. */
  1215.  
  1216. static void display_header()
  1217. {
  1218.     cursor_home();
  1219.     enter_standout_mode();
  1220.     for (int i = 0; i < NFields() && i < rows() - 2; i++)
  1221.     {
  1222.         clear_to_end_of_line();
  1223.         display_string(Fields[i]);
  1224.     }
  1225.     end_standout_mode();
  1226.  
  1227.     int flen   = max_field_length() + 1;
  1228.     char *tmp1 = header_data.dptr, *tmp2;
  1229.  
  1230.     for (i = 0; i < NFields() && i < rows() - 2; i++)
  1231.     {
  1232.         tmp2  = strchr(tmp1, '\n');
  1233.         *tmp2 = 0;        // stringify
  1234.         move_cursor(i, flen);
  1235.         display_string(tmp1 + flen);
  1236.         *tmp2 = '\n';     // un-stringify
  1237.         tmp1  = tmp2 + 1; // step past newline to next Field
  1238.     }
  1239.  
  1240.     // clear any remaining potentially dirty lines
  1241.     for (; i < rows() - 2; i++) { clear_to_end_of_line(); cursor_wrap(); }
  1242. }
  1243.  
  1244. /*
  1245. ** modify_keywords - allows the problem owner or the administrator
  1246. **                   to change the Keyword field.
  1247. */
  1248.  
  1249. int modify_keywords(const char *number)
  1250. {
  1251.     if (!database_exists())
  1252.     {
  1253.         ding();
  1254.         message("There is no database for problem area `%' ", CurrentArea());
  1255.         if (!number) sleep(2);
  1256.         return 0;
  1257.     }
  1258.     datum key;
  1259.     key.dptr  = number ? (char *) number : prompt("Problem # --> ",
  1260.                                                   redisplay_commands);
  1261.     key.dsize = int(strlen(key.dptr)) + 1;
  1262.  
  1263.     open_database(GDBM_READER);
  1264.     datum data = gdbm_fetch(GdbmFile, key);
  1265.     gdbm_close(GdbmFile);
  1266.  
  1267.     if (!data.dptr)
  1268.     {
  1269.         ding();
  1270.         message("There is no problem # % ", key.dptr);
  1271.         if (!number) { delete key.dptr; sleep(2); }
  1272.         return 0;
  1273.     }
  1274.     header_data = data;
  1275.  
  1276.     //
  1277.     // Are we authorized to modify the problem\'s keywords?
  1278.     //
  1279.     char *tmp = strchr(data.dptr, '\n');  // logger is Fields\[1\]
  1280.     tmp += 1; // step past the newline
  1281.     tmp += max_field_length() + 1;        // the logger
  1282.     int llen  = strchr(tmp, '\n') - tmp;  // # of chars in logger
  1283.     const char *user = username();
  1284.  
  1285.     if ((llen != strlen(user) || strncmp(user, tmp, llen)) &&
  1286.         getuid() != geteuid())
  1287.     {
  1288.         ding();
  1289.         message("You're not authorized to modify problem # %'s keywords ",
  1290.                 key.dptr);
  1291.         if (!number) { delete key.dptr; sleep(2); }
  1292.         return 0;
  1293.     }
  1294.  
  1295.     //
  1296.     // Put up problem header.
  1297.     //
  1298.     const char *fmt = "%s  # %s (modifying keywords)";
  1299.     String suffix = String(CurrentArea()) + "  # " + key.dptr +
  1300.                     " (modifying keywords)";
  1301.     String old_modeline(current_modeline);
  1302.  
  1303.     update_modeline(ModelinePrefix, suffix);
  1304.     display_header();
  1305.  
  1306.     char *newkeywords = prompt("New keyword field --> ", display_header);
  1307.  
  1308.     //
  1309.     // Make new data datum.
  1310.     //
  1311.     datum newdata;
  1312.     tmp = data.dptr;
  1313.  
  1314.     //
  1315.     // Find old Keyword field -- Fields\[5\].
  1316.     //
  1317.     for (int i = 0; i < 5; i++)
  1318.     {
  1319.         tmp  = strchr(tmp, '\n');
  1320.         tmp += 1;  // step past newline
  1321.     }
  1322.     tmp += max_field_length() + 1;  // the Keywords field data
  1323.     newdata.dsize = data.dsize + (int)strlen(newkeywords) -
  1324.                     (strchr(tmp, '\n') - tmp);
  1325.     newdata.dptr = new char[newdata.dsize];
  1326.     *tmp++ = 0;  // stringify data.dptr
  1327.     (void)strcpy(newdata.dptr, data.dptr);    // data preceding Keywords field
  1328.     (void)strcat(newdata.dptr, newkeywords);  // new keywords
  1329.     (void)strcat(newdata.dptr, strchr(tmp, '\n'));  // following data
  1330.     free(data.dptr);
  1331.  
  1332.     update_existing_problem(newdata, key, KEYWORDMOD);
  1333.     update_modeline(old_modeline);  // redisplay previous modeline
  1334.     if (!number) delete key.dptr;
  1335.     delete newdata.dptr;
  1336.     return 1;
  1337. }
  1338.  
  1339. /*
  1340. ** problem - examine the `area\' in problem
  1341. */
  1342.  
  1343. static void problem(const char *area)
  1344. {
  1345.     const char *the_area = is_area(area) ? area : choose_problem_area();
  1346.     const char *helpmsg  = "The Available Commands:";
  1347.     setCurrentArea(the_area);
  1348.     commands_screen();
  1349.     String suffix = String(CurrentArea()) + " ---- q (quit) H (help)";
  1350.  
  1351.     update_modeline(ModelinePrefix, suffix);
  1352.     message("Your Choice --> ");
  1353.  
  1354.     char key;
  1355.     char refresh = 1; // need to redisplay command list?
  1356.     char redomsg = 1; // need to redisplay the message?
  1357.     while (1)
  1358.     {
  1359.         if (resumingAfterSuspension ||
  1360. #ifdef SIGWINCH
  1361.             windowSizeChanged       ||
  1362. #endif
  1363.             read(0, &key, 1) < 0    || // assume only fails when errno==EINTR 
  1364.             key == KEY_CTL_L)
  1365.         {
  1366. #ifdef SIGWINCH
  1367.             if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
  1368. #endif
  1369.             resumingAfterSuspension = 0;
  1370.             redisplay_commands();
  1371.             refresh = 0;
  1372.         }
  1373.         else
  1374.             switch(key)
  1375.             {
  1376.               case KEY_l:
  1377.                 log_new_problem(); refresh = 1; break;
  1378.               case KEY_e:
  1379.                 refresh = examine_problem(); break;
  1380.               case KEY_v:
  1381.                 refresh = view_summary_lines(); break;
  1382.               case KEY_a:
  1383.                 refresh = append_to_problem(); break;
  1384.               case KEY_s:
  1385.                 subscribe_to_area(); refresh = 0; break;
  1386.               case KEY_u:
  1387.                 unsubscribe_from_area(); refresh = 0; break;
  1388.               case KEY_c:
  1389.                 refresh = close_problem(); break;
  1390.               case KEY_k:
  1391.                 refresh = search(HEADER); break;
  1392.               case KEY_K:
  1393.                 refresh = search(FULLTEXT); break;
  1394.               case KEY_M:
  1395.                 refresh = modify_keywords(); break;
  1396.               case KEY_R:
  1397.                 refresh = reopen_problem(); break;
  1398.               case KEY_d:
  1399.                 (void)delete_problem(); refresh = 0; break;
  1400.               case KEY_r:
  1401.                 reorganize_database(); refresh = 0; break;
  1402.               case KEY_q:
  1403.                 return;
  1404.               case KEY_H: case KEY_QM:
  1405.                 help((const char **)&Commands[0], NCommands(), helpmsg);
  1406.                 refresh = 1; break;
  1407.               default:
  1408.                 ding(); redomsg = refresh = 0; break;
  1409.             }
  1410.         if (refresh)
  1411.         {
  1412.             commands_screen();
  1413.             update_modeline(ModelinePrefix, suffix);
  1414.         }
  1415.         if (redomsg) message("Your Choice --> ");
  1416.         redomsg = 1;
  1417.     }
  1418. }
  1419.  
  1420. main(int argc, char *argv[])
  1421. {
  1422.     if (!isatty(0) || !isatty(1))
  1423.     {
  1424.         (void)fputs("stdin & stdout must be terminals\n", stderr);
  1425.         exit(1);
  1426.     }
  1427.  
  1428.     //
  1429.     // Usage: problem \[-v\] \[-d\] \[area1\] \[area2\] \[...\]
  1430.     //
  1431.     // We only accept the -d flag if it is the first argument.
  1432.     // It directs us to use a different directory as our HomeBase.
  1433.     // Regarding problem areas, we just ignore invalid ones.
  1434.     // The \'-v\' option just prints out the version string and exits.
  1435.     //
  1436.     argc--; argv++;
  1437.     if (argc && strcmp(*argv, "-v") == 0)
  1438.     {
  1439.         fputs(Version, stdout);
  1440.         exit(0);
  1441.     }
  1442.     else if (argc && strcmp(*argv, "-d") == 0)
  1443.     {
  1444.         argc--; argv++;
  1445.         if (!argc)
  1446.         {
  1447.             fputs("Ignoring `-d' flag - no corresponding directory.", stdout);
  1448.             sleep(2);
  1449.         }
  1450.         else
  1451.         {
  1452.             HomeBase = *argv;
  1453.             argc--; argv++;
  1454.         }
  1455.     }
  1456.  
  1457.     //
  1458.     // If you don\'t have SIGINTERRUPT then signals almost surely interrupt
  1459.     // read\(2\).  If you do have it, you\'ll need this to ensure that signals
  1460.     // interrupt slow system calls -- we\'re only interested in read.
  1461.     //
  1462. #ifdef SIGINTERRUPT
  1463.     if (siginterrupt(SIGTSTP, 1) < 0 || siginterrupt(SIGWINCH, 1) < 0)
  1464.     {
  1465.         perror("siginterrupt()"); exit(1);
  1466.     }
  1467. #endif
  1468.  
  1469.     if (umask(022) < 0) { perror("umask()"); exit(1); }
  1470.  
  1471.     set_new_handler(free_store_exception);
  1472.     init_display();
  1473.     set_signals();
  1474.  
  1475.     if (!database_directory_exists())
  1476.         error("Database directory `%s' isn't accessible.", HomeBase);
  1477.  
  1478.     while (1) problem(*argv ? *argv++ : choose_problem_area());
  1479. }
  1480.