home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume33 / problem / part05 < prev    next >
Encoding:
Text File  |  1992-10-18  |  39.0 KB  |  1,309 lines

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