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