home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #26 / NN_1992_26.iso / spool / comp / sources / misc / 4080 < prev    next >
Encoding:
Text File  |  1992-11-12  |  45.8 KB  |  1,626 lines

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