home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume33 / problem1 / part05 < prev    next >
Encoding:
Text File  |  1992-11-12  |  45.6 KB  |  1,619 lines

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