home *** CD-ROM | disk | FTP | other *** search
- /*
- **
- ** problem - a problem database manager
- **
- ** Written in C++ using the termcap\(3\) library for screen management
- ** and GDBM as the database library.
- **
- ** problem.C is made by cating together problem1.C and problem2.C
- **
- ** problem1.C problem1.C 1.21 Delta\'d: 13:22:00 10/13/92 Mike Lijewski, CNSF
- **
- ** Copyright \(c\) 1991, 1992 Cornell University
- ** All rights reserved.
- **
- ** Redistribution and use in source and binary forms are permitted
- ** provided that: \(1\) source distributions retain this entire copyright
- ** notice and comment, and \(2\) distributions including binaries display
- ** the following acknowledgement: ``This product includes software
- ** developed by Cornell University\'\' in the documentation or other
- ** materials provided with the distribution and in all advertising
- ** materials mentioning features or use of this software. Neither the
- ** name of the University nor the names of its contributors may be used
- ** to endorse or promote products derived from this software without
- ** specific prior written permission.
- **
- ** THIS SOFTWARE IS PROVIDED ``AS IS\'\' AND WITHOUT ANY EXPRESS OR
- ** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
- */
-
- #include <ctype.h>
-
- #ifndef _IBMR2
- #include <libc.h>
- #endif
-
- #include <fcntl.h>
- #include <new.h>
- #include <osfcn.h>
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <time.h>
- #include <unistd.h>
-
- #include "classes.h"
- #include "display.h"
- #include "keys.h"
- #include "lister.h"
- #include "problem.h"
- #include "regexp.h"
- #include "utilities.h"
- #include "version.h"
-
- // a little POSIX stuff
- #ifndef SEEK_SET
- #define SEEK_SET 0
- #endif
- #ifndef SEEK_END
- #define SEEK_END 2
- #endif
-
- //
- // Where problem\'s maillists and the sequence file reside.
- //
- #ifdef HOMEBASE
- const char *HomeBase = HOMEBASE;
- #else
- const char *HomeBase = "/staff/mjlx/src/c++/problem";
- #endif
-
- //
- // Name of file containing valid problem areas relative to `HomeBase\'.
- //
- const char *const ProblemAreas = "AREAS";
-
- //
- // Definition of our GDMB filehandle -- only one open GDBM file at a time.
- //
- GDBM_FILE GdbmFile;
-
- //
- // Suffix for gdbm files.
- //
- const char *const GdbmSuffix = ".gdbm";
-
- //
- // File containing last problem #, relative to `HomeBase\'.
- //
- const char *const SequenceFile = "SEQUENCE";
-
- //
- // Prefix for maillist files.
- //
- const char *const MailListPrefix = "MAILLIST";
-
- //
- // Our modeline prefix.
- //
- const char *const ModelinePrefix = "----- Problem: ";
-
- //
- // Help message for the message window when displaying help.
- //
- const char *const HELP_MSG[] = {
- "Space scrolls forward. Other keys quit.",
- "Space forward, Backspace backward. Other keys quit.",
- "Backspace scrolls backward. Other keys quit.",
- "Any key quits."
- };
-
- //
- // Our fields.
- //
- static const char *const Fields[] =
- {
- "Area",
- "Logger",
- "Reporter",
- "Logged",
- "Updated",
- "Keywords",
- "Summary",
- "Status",
- "Site",
- "Severity",
- "Problem #"
- };
-
- inline int NFields() { return sizeof(Fields) / sizeof(Fields[0]); }
-
- //
- // The current area - used by setCurrentArea\(\) and CurrentArea\(\).
- //
- static String current_area;
-
- void setCurrentArea(const char *area)
- {
- current_area = area;
- const char *tmp = strchr(current_area, '-');
-
- // strip off any comments
- if (tmp)
- current_area[tmp - (const char *)current_area] = 0;
- else
- tmp = (const char *)current_area + current_area.length();
-
- // strip off any trailing blanks
- for (--tmp; *tmp == ' ' || *tmp == '\t'; --tmp)
- current_area[tmp - (const char *)current_area] = 0;
-
- // set any remaining spaces to underscores
- while ((tmp = strchr(current_area, ' ')) != 0)
- current_area[tmp - (const char *)current_area] = '_';
- }
-
- inline const char *CurrentArea() { return current_area; }
-
- //
- // our commands - the first character of each string is the unique
- // character identifying that command.
- //
- static const char *const Commands[] =
- {
- "l -- log new problem",
- "a -- append to a problem",
- "c -- close a problem",
- "e -- examine a problem",
- "v -- view problem summaries",
- "s -- subscribe to this problem area",
- "u -- unsubscribe from this problem area",
- "k -- keyword search over problem headers",
- "K -- keyword search over problem headers and data",
- "d -- delete a problem from the database",
- "r -- reorganize the database",
- "M -- modify keyword field",
- "R -- reopen a closed problem",
- "q -- quit"
- };
-
- inline int NCommands() { return sizeof(Commands) / sizeof(Commands[0]); }
-
- /*
- ** exception handler for new\(\) - called once in main\(\)
- */
-
- static void free_store_exception()
- {
- error("exiting, memory exhausted, sorry");
- }
-
- /*
- ** get_problem_areas - read in the valid problem areas. Exits on error.
- */
-
- static const char **get_problem_areas()
- {
- static char **Areas;
- if (Areas) return (const char **)Areas;
-
- String filename = String(HomeBase) + "/" + ProblemAreas;
- FILE *fp = fopen(filename, "r");
- if (!fp)
- error ("file %s, line %d, couldn't fopen() `%s'",
- __FILE__, __LINE__, (const char *)filename);
-
- const int chunksize = 10; // average number of problem areas expected
- const int linelen = 80; // average length of line in AREAS expected
- Areas = new char*[chunksize];
-
- if (read_file(fp, Areas, chunksize, linelen) < 0)
- error("file %s, line %d, error reading `%s'.",
- __FILE__, __LINE__, (const char *)filename);
-
- (void)fclose(fp);
-
- return (const char **)Areas;
- }
-
- /*
- ** is_area - is `area\' a valid problem area? Returns 1 if true, else 0.
- */
-
- static int is_area(const char *area)
- {
- const char **areas = get_problem_areas();
-
- for (int i = 0; areas[i]; i++)
- {
- const char *space = strchr(areas[i],' ');
- int length = space ? space - areas[i] - 1 : (int)strlen(areas[i]);
- if (strncmp(area, areas[i], length) == 0) return 1;
- }
-
- return 0;
- }
-
- /*
- ** NAreas - the number of areas
- */
-
- static int NAreas()
- {
- static int nAreas;
- if (nAreas) return nAreas;
- const char **areas = get_problem_areas();
- for (int i = 0; areas[i]; i++) ;
- return nAreas = i;
- }
-
- /*
- ** display_area_screen - displays the screen of available areas.
- ** We assume that the caller will display the modeline.
- ** Needs at least four rows to be useful.
- */
-
- static void display_area_screen()
- {
- cursor_home();
- clear_to_end_of_line();
- const char **areas = get_problem_areas();
- enter_standout_mode();
-
- //
- // If we have enough available screen lines, we display the areas
- // one per line. Otherwise, we display them in columns of up to
- // column_width characters.
- //
- const int column_width = 25; // space allowed for each area
- int dvd = columns() / column_width; // # compacted areas per row
- int rws = rows() - 5;
- int compact = NAreas() <= rws ? 0 : 1; // compact the areas?
-
- const char *fmt = NAreas() <= rws * dvd ? "The %d Areas:" :
- "The First %d Areas:";
-
- char *msg = new char[strlen(fmt) + 8]; // lots of problem areas
-
- (void)sprintf(msg, fmt, NAreas() <= rws * dvd ? NAreas() : rws * dvd);
-
- display_string(msg);
-
- // maximum # of digits we must display
- int digits = int(strlen(msg) - strlen(fmt)) + 2;
-
- DELETE msg;
-
- end_standout_mode();
- clear_to_end_of_line();
- cursor_wrap();
-
- for (int i = 0, offset = 0; i < NAreas() && i < rws * dvd; i++)
- {
- if (compact)
- {
- offset = (i / rws) * column_width;
- move_cursor((i % rws) + 2, offset);
- }
- clear_to_end_of_line();
- (void)fputs(" ", stdout); // two spaces for initial indentation
- enter_standout_mode();
- (void)fprintf(stdout, "%*d", digits, i + 1);
- end_standout_mode();
- putchar(' '); // and one more between number and area
- display_string(areas[i], 0, digits + 3 + offset);
- }
-
- // clear remaining dirty lines
- for (; i < rows() - 4; i++) { clear_to_end_of_line(); cursor_wrap(); }
- }
-
- /*
- ** help - display some help lines. The first two lines of the output
- ** consists of the message `msg\' and then a blank line.
- ** This is followed by the contents of `lines\', which has `dim\'
- ** elements.
- **
- */
-
- static void help(const char *lines[], int dim, const char *msg)
- {
- String old_modeline(current_modeline);
- update_modeline("----- HELP");
-
- int position = 0, offset = 0;
- char key;
- do
- {
- cursor_home();
- if (position == 0)
- {
- offset = 2;
- clear_to_end_of_line();
- display_string(msg);
- clear_to_end_of_line();
- move_cursor(2, 0);
- }
- for (int i = 0; i + offset < rows()-2 && i + position < dim; i++)
- {
- clear_to_end_of_line();
- display_string(lines[i + position]);
- }
- move_cursor(i + offset, 0);
- for (; i + offset < rows() - 2; i++)
- {
- clear_to_end_of_line();
- cursor_down();
- }
- clear_message_line();
-
- if (position == 0 && dim + offset <= rows() - 2)
- // whole help message fits in initial screen
- (void)fputs(HELP_MSG[3], stdout);
- else if (position == 0)
- // the head of the help message -- it all doesn\'t fit
- (void)fputs(HELP_MSG[0], stdout);
- else if (position + rows() - 2 >= dim)
- // the tail of the help message
- (void)fputs(HELP_MSG[2], stdout);
- else
- // somewhere in between
- (void)fputs(HELP_MSG[1], stdout);
- synch_display();
-
- if (resumingAfterSuspension ||
- #ifdef SIGWINCH
- windowSizeChanged ||
- #endif
- read(0, &key, 1) < 0 // assume fails only when errno == EINTR
- )
- {
- #ifdef SIGWINCH
- if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
- #endif
- resumingAfterSuspension = 0;
- update_modeline();
- continue;
- }
- else if (key == KEY_SPC)
- {
- if (position >= dim - 1) break;
- position += (position == 0 ? rows() - 2 - offset : rows() - 2);
- }
- else if (key == *BC)
- {
- if (position == 0) break;
- position -= rows() - 2;
- if (position < 0) position = 0;
- }
- else
- break; // return to the listing
-
- offset = 0;
- }
- while (position < dim + offset);
-
- update_modeline(old_modeline);
- }
-
- /*
- ** redisplay_area_screen - suitable for calling after SIGWINCH and SIGTSTP
- */
-
- static void redisplay_area_screen()
- {
- display_area_screen();
- update_modeline();
- }
-
- /*
- ** choose_problem_area - returns a problem area to examine.
- ** Also gives user option to exit.
- */
-
- static const char *choose_problem_area()
- {
- const char **areas = get_problem_areas();
- display_area_screen();
- update_modeline(ModelinePrefix, "---- q (quit) H (help)");
- const char *helpmsg = "The Available Problem Areas:";
- char key;
- int index;
-
- if (NAreas() < 10)
- {
- //
- // The most usual case. Read digit as typed.
- //
- message("Your Choice --> ");
-
- while (1)
- {
- if (resumingAfterSuspension ||
- #ifdef SIGWINCH
- windowSizeChanged ||
- #endif
- read(0, &key, 1) < 0) // assume only fails when errno==EINTR
- {
- #ifdef SIGWINCH
-
- if (windowSizeChanged)
- {
- windowSizeChanged = 0;
- adjust_window();
- }
- #endif
- resumingAfterSuspension = 0;
- redisplay_area_screen();
- message("Your Choice --> ");
- continue;
- }
- else if (key == KEY_q)
- {
- clear_message_line();
- term_display();
- exit(0);
- }
- else if (key == KEY_H || key == KEY_QM)
- {
- help(areas, NAreas(), helpmsg);
- redisplay_area_screen();
- message("Your Choice --> ");
- }
- else
- {
- index = key - '0';
- if (index > 0 && index <= NAreas()) return areas[index-1];
- ding();
- }
- }
- }
- else
- {
- char *response;
- while (1)
- {
- //
- // prompt\(\) takes care of window resizes and resume/suspends
- //
- response = prompt("Your Choice --> ", redisplay_area_screen);
- if (*response == KEY_q)
- {
- clear_message_line();
- term_display();
- exit(0);
- }
- else if (*response == KEY_H || *response == KEY_QM)
- {
- help(areas, NAreas(), helpmsg);
- redisplay_area_screen();
- message("Your Choice --> ");
- DELETE response;
- }
- else
- {
- index = atoi(response);
- DELETE response;
- if (index > 0 && index <= NAreas()) return areas[index - 1];
- ding();
- }
- }
- }
- }
-
- /*
- ** max_field_length - returns the length of the longest field
- */
-
- static int max_field_length()
- {
- static int len;
- if (!len)
- for (int i = 0; i < NFields(); i++)
- if (len < strlen(Fields[i])) len = (int) strlen(Fields[i]);
- return len;
- }
-
- /*
- ** get_severity - prompt for the severity of the problem. Deal with
- ** getting SIGTSTP or SIGWINCH.
- */
-
- static char get_severity(auto void (*redisplay)()) // "auto" needed by g++
- {
- const char *msg = "Severity (1 (=highest), 2, 3 or 4 (=lowest)) --> ";
- message(msg);
- char key;
- while (1)
- {
- if (resumingAfterSuspension ||
- #ifdef SIGWINCH
- windowSizeChanged ||
- #endif
- read(0, &key, 1) < 0) // assume only fails when errno == EINTR
- {
- #ifdef SIGWINCH
- if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
- #endif
- resumingAfterSuspension = 0;
- redisplay();
- message(msg);
- continue;
- }
- switch (key)
- {
- case '1': case '2': case '3': case '4': return key;
- default: ding(); break;
- }
- }
- }
-
- /*
- ** filesize - returns size of file or MAXFILESIZE,
- ** whichever is smaller. Exits on error.
- */
-
- static int filesize(const char *file)
- {
- #ifdef MAXFILESIZE
- const int MaxFileSize = MAXFILESIZE;
- #else
- const int MaxFileSize = 16000;
- #endif
- struct stat buf;
- if (stat(file, &buf) < 0)
- error("file %s, line %d, fstat() failed", __FILE__, __LINE__);
- return buf.st_size > MaxFileSize ? MaxFileSize : buf.st_size;
- }
-
- /*
- ** sequence_file - returns full pathname of "SequenceFile"
- */
-
- static const char *sequence_file()
- {
- static String filename;
- if (filename == "") filename = String(HomeBase) + "/" + SequenceFile;
- return filename;
- }
-
- /*
- ** update_sequence_file - reads the previous problem number from
- ** `SequenceFile\'; increments that number;
- ** writes it back to the file and returns it.
- ** Exits on error. We should have an exclusive
- ** lock on the sequence file before we get here.
- ** This is to guarantee that we only have one
- ** "writer" to the GDBM file.
- */
-
- static const char *update_sequence_file(int fd)
- {
- static char buf[10]; // won\'t have this many problems for a while
-
- FILE *fp = fdopen(fd, "r+");
- if (!fp)
- error("file %s, line %d, fdopen() on `%s' failed",
- __FILE__, __LINE__, sequence_file());
-
- char *line = fgetline(fp, 10);
-
- if (!fp)
- error("file %s, line %d, fgetline() on `%s' failed",
- __FILE__, __LINE__, sequence_file());
- (void)sprintf(buf, "%d", atoi(line) + 1);
-
- //
- // Truncate the file. I\'d like to use ftruncate\(2\) here, but that
- // isn\'t as portable as a close\(open\(\)\) scheme.
- //
- if (close(open(sequence_file(), O_RDWR|O_TRUNC)) < 0)
- error("file %s, line %d, close(open()) of `%s' failed",
- __FILE__, __LINE__, sequence_file());
-
- // go to the beginning
- if (lseek(fd, 0, SEEK_SET) < 0)
- error("file %s, line %d, lseek() on `%s' failed",
- __FILE__, __LINE__, sequence_file());
-
- // write the next problem #
- if (write(fd, buf, (unsigned) strlen(buf)) < 0)
- error("file %s, line %d, write() to `%s' failed",
- __FILE__, __LINE__, sequence_file());
-
- DELETE line;
-
- return buf;
- }
-
- /*
- ** mail_list_prefix - returns the full pathname of MailListPrefix
- */
-
- static const char *mail_list_prefix()
- {
- static String filename;
- if (filename == "") filename = String(HomeBase) + "/" + MailListPrefix;
- return filename;
- }
-
- /*
- ** open_maillist_file - open the file containing the interested parties
- ** maillist for the problem area. Exits on error.
- ** If the file didn\'t previously exist, it will be
- ** created.
- */
-
- static int open_maillist_file()
- {
- String file = String(mail_list_prefix()) + "." + CurrentArea();
- int fd = open((const char *)file, O_RDWR|O_CREAT, 0644);
- if (fd < 0)
- error("file %s, line %d, open(%s) failed",
- __FILE__, __LINE__, (const char *)file);
- return fd;
- }
-
- // The ways that a database can get modified.
- static const char *const How[] = {
- "logged",
- "appended",
- "closed",
- "deleted",
- "reorganized",
- "keywords modified",
- "reopened"
- };
-
- // indices into How\[\]
- enum Modified {
- LOGGED,
- APPENDED,
- CLOSED,
- DELETED,
- REORGANIZED,
- KEYWORDMOD,
- REOPENED
- };
-
- /*
- ** update_subscribers - send a mail file about problem to all
- ** those who\'ve subscribed to this `area\'. If
- ** this is an being called after an append,
- ** `append\' is non-zero. Otherwise, we assume
- ** it is the initial logging. If we\'re appending
- ** or closing a problem, we pass the offset of the
- ** new data that needs to be printed. When logging,
- ** this offset is zero, which we make the default.
- */
-
- static void update_subscribers(const datum data, const char *number,
- const Modified how, int offset = 0)
- {
- #ifdef MAILPROG
- const char *mailprog = MAILPROG;
- #else
- const char *mailprog = "/bin/mail";
- #endif
-
- // does `mailprog\' really exist?
- if (!read_and_exec_perm(mailprog))
- error("file %s, line %d, `%s' doesn't appear to be executable",
- __FILE__, __LINE__, mailprog);
-
- int mailfd = open_maillist_file();
- FILE *mailfp = fdopen(mailfd, "r");
- if (!mailfp)
- error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
-
- const int namelen = 10; // average length of uid expected
- const int chunksize = 20; // average number of subscribees expected
- char **args = new char*[chunksize];
- args[0] = "mail";
- char *fmt = "Subject: %s problem # %s %s by %s\n\n";
- char *subject = new char[strlen(fmt) + strlen(CurrentArea()) +
- strlen(number) + strlen(How[how]) +
- strlen(username()) - 7];
- (void)sprintf(subject, fmt, CurrentArea(), number, How[how], username());
-
- //
- // Are there any subscribers?
- //
- int nlines = read_file(mailfp, args, chunksize, namelen, 1);
- if (nlines < 0)
- error("file %s, line %d, problem reading from maillist file",
- __FILE__, __LINE__);
- (void)close(mailfd); (void)fclose(mailfp);
- if (nlines == 0)
- {
- //
- // No subscribers.
- //
- DELETE subject;
- for (int i = 1; args[i]; i++) DELETE args[i];
- DELETE args;
-
- return;
- }
-
- int fds[2];
- if (pipe(fds) < 0)
- error("file %s, line %d, pipe() failed", __FILE__, __LINE__);
-
- switch(fork())
- {
- case -1: // error
- error("file %s, line %d, fork() failed", __FILE__, __LINE__);
- case 0: // in the child
- {
- //
- // Set stdin to the read end of pipe.
- //
- (void)close(0);
- if (dup(fds[0]) < 0)
- error("file %s, line %d, dup() failed", __FILE__, __LINE__);
- (void)close(fds[0]);
- (void)close(fds[1]);
- (void)close(1);
- (void)close(2);
-
- execvp(mailprog, (char *const *)args);
-
- exit(1); // exec failed -- can\'t use error\(\) as stdout is now closed
- }
- break;
- default: // in the parent
- {
- (void)close(fds[0]);
- //
- // Write Subject to pipe.
- write_to_pipe(fds[1], subject, strlen(subject));
- //
- // write the mailfile to the pipe
- //
- switch (how)
- {
- case CLOSED:
- case REOPENED:
- case APPENDED:
- case KEYWORDMOD:
- {
- //
- // Write the fields and the new data only.
- //
- char *tail = data.dptr;
- for (int i = 0; i < NFields(); i++)
- {
- tail = strchr(tail, '\n');
- tail += 1; // step past the newline
- }
- tail += 1; // step past the second newline to the
- // first character past the header
- // write the header
- write_to_pipe(fds[1], data.dptr, tail - data.dptr);
- if (offset <= 0)
- error("file %s, line %d, offset must be positive",
- __FILE__, __LINE__);
- write_to_pipe(fds[1], data.dptr + offset, data.dsize-offset-1);
- }
- break;
- case LOGGED:
- write_to_pipe(fds[1], data.dptr, data.dsize - 1);
- break;
- default: error("file %s, line %d, illegal case in switch()",
- __FILE__, __LINE__);
- }
- (void)close(fds[1]);
- for (int i = 1; args[i]; i++) DELETE args[i];
- DELETE args;
- DELETE subject;
-
- return;
- }
- }
- }
-
- /*
- ** invoke_editor - invoke users editor on `file\'
- */
-
- static void invoke_editor(const char *file)
- {
- char *editor = getenv("EDITOR");
- if (editor == 0) editor = "vi";
- char *space = strchr(editor, ' ');
- if (space) *space = 0; // only use the first arg if they\'re > 1
- const char *args[3];
- args[0] = editor; args[1] = file; args[2] = 0;
-
- //
- // Position cursor in case the editor doesn\'t do this itself.
- // This is primarily for those people who use
- // non-fullscreen editors, such as `ed\', which don\'t do this.
- // We\'ve just typed a message, so we\'re on the last line of the screen.
- //
- cursor_wrap();
- synch_display();
- if (!execute(editor, args) && strstr(editor, "vi") == 0)
- error("file %s, line %d, couldn't exec() your editor `%s'",
- __FILE__, __LINE__, editor);
- }
-
- /*
- ** database_exists - checks to see if a database for the current area exists.
- ** This is important since gdbm_open\(GDBM_READER\)
- ** will fail if the database doesn\'t already exist.
- ** Returns one if a file of the appropriate name
- ** exists, else zero. There is no guarantee that we
- ** actually have a database, only an appropriately
- ** named file.
- */
-
- int database_exists()
- {
- String filename = String(HomeBase) + "/" + CurrentArea() + GdbmSuffix;
- int rc = open((const char *)filename, O_RDONLY);
- (void)close(rc);
- return rc < 0 ? 0 : 1;
- }
-
- /*
- ** open_database - opens the GDBM database on the current area.
- ** Exits on error.
- */
-
- void open_database(int mode)
- {
- String filename = String(HomeBase) + "/" + CurrentArea() + GdbmSuffix;
-
- if ((GdbmFile = gdbm_open(filename, 0, mode, 00644, 0)) == 0)
- if (gdbm_errno != GDBM_CANT_BE_WRITER)
- error("file %s, line %d, gdbm_open() failed on `%s'",
- __FILE__, __LINE__, (const char *)filename);
- else
- error("file %s, line %d, gdbm_open() failed on `%s', errno = %d",
- __FILE__, __LINE__, (const char *)filename, gdbm_errno);
- }
-
- /*
- ** database_directory_exists - does HomeBase exist?
- */
-
- static int database_directory_exists()
- {
- int rc = open(HomeBase, O_RDONLY);
- (void)close(rc);
- return rc < 0 ? 0 : 1;
- }
-
-
- /*
- ** update_database - updates database on the current area with `problem_data\'.
- ** `size\' is total size of data. This function is
- ** used both to insert new entries and to replace
- ** old entries. If `offset\' is nonzero, it is the
- ** start of problem number field, which we fill in
- ** after getting new problem number. `offset\' is
- ** nonzero only when initially logging a problem.
- */
-
- static void update_database(datum &key, const datum &data, const Modified how,
- int offset = 0)
- {
- int fd = open(sequence_file(), O_RDWR);
- if (fd < 0)
- error("file %s, line %d, open() on `%s' failed",
- __FILE__, __LINE__, sequence_file());
-
- block_tstp_and_winch(); // block SIGTSTP and WINCH
- lock_file(fd); // lock our sequence file
- open_database(GDBM_WRCREAT); // open database for writing
- if (how != REORGANIZED)
- data.dptr[data.dsize - 1] = 0; // make sure data is stringified
-
- switch (how)
- {
- case DELETED:
- if (gdbm_delete(GdbmFile, key))
- error("file %s, line %d, gdbm_delete() failed, errno = %d",
- __FILE__, __LINE__, gdbm_errno);
- break;
- case REORGANIZED:
- if (gdbm_reorganize(GdbmFile))
- error("file %s, line %d, gdbm_reorganize() failed, errno = %d",
- __FILE__, __LINE__, gdbm_errno);
- break;
- case LOGGED:
- {
- //
- // Must fill in key values; we\'re doing an initial log of the problem.
- //
- key.dptr = (char *) update_sequence_file(fd);
- key.dsize = (int)strlen(key.dptr) + 1;
-
- // update problem # field
- for (int i = 0; i < strlen(key.dptr); i++)
- data.dptr[offset + i] = key.dptr[i];
- }
- //
- // Fall through.
- //
- case CLOSED:
- case REOPENED:
- case APPENDED:
- case KEYWORDMOD:
- if (gdbm_store(GdbmFile, key, data, how == LOGGED ?
- GDBM_INSERT : GDBM_REPLACE))
- error("file %s, line %d, gdbm_store() failed, errno = %d",
- __FILE__, __LINE__, gdbm_errno);
- break;
- default:
- error("file %s, line %d, illegal case in switch()",
- __FILE__, __LINE__);
- }
-
- gdbm_close(GdbmFile);
- unlock_file(fd);
- (void)close(fd);
- unblock_tstp_and_winch(); // unblock SIGTSTP and WINCH
- }
-
- //
- // These variables are shared by build_log_screen\(\) and log_new_problem\(\)
- // so that we can keep track of where we are in case we get a SIGTSTP
- // or SIGWINCH. We have to be careful to nullify them after we\'re done
- // with them so that build_log_screen\(\) knows when it needs to prompt
- // for fresh data.
- //
- static char *logged;
- static char *reporter;
- static char *keywords;
- static char *summary;
- static char *site;
- static char severity;
-
- /*
- ** build_log_screen - prints the initial screen when logging a problem.
- ** Is also called after a SIGTSTP or SIGWINCH to
- ** redo the screen appropriately.
- */
-
- // forward declaration
- static void redisplay_log_screen();
-
- static void build_log_screen()
- {
- cursor_home();
- //
- // Print as many of the fields as will fit.
- // This gets done both on a normal call or on a redisplay.
- //
- enter_standout_mode();
- for (int i = 0; i < NFields() && i < rows() - 2; i++)
- {
- clear_to_end_of_line();
- display_string(Fields[i]);
- }
- end_standout_mode();
-
- // clear any remaining potentially dirty lines
- for (; i < rows() - 2; i++) { clear_to_end_of_line(); cursor_wrap(); }
-
- int flen = max_field_length() + 1; // plus one accounts for the space
-
- int currline = 0; // keep track of where we are on screen
-
- //
- // Fill in those fields which are obvious.
- //
- if (currline < rows() - 2)
- {
- move_cursor(currline++, flen);
- display_string(CurrentArea(), 0, flen);
- }
- if (currline < rows() - 2)
- {
- move_cursor(currline, flen);
- display_string(username(), 0, flen);
- currline += 2;
- }
- time_t t = time(0);
- logged = ctime(&t);
- if (currline < rows() - 2)
- {
- move_cursor(currline++, flen);
- display_string(logged, 0, flen);
- }
- if (currline < rows() - 2)
- {
- move_cursor(currline, flen);
- display_string(logged, 0, flen);
- currline += 3;
- }
- if (currline < rows() - 2)
- {
- move_cursor(currline, flen);
- display_string("open", 0, flen);
- }
-
- //
- // Prompt for those that aren\'t.
- //
- const char *const append = " --> ";
- static char *line;
- int recursing = 0; // is this a recursive call?
- if (!line)
- line = new char[flen + strlen(append)];
- else
- recursing = 1;
- (void)strcpy(line, Fields[2]);
- (void)strcat(line, append);
- if (!reporter)
- if (recursing)
- goto exit;
- else
- reporter = prompt(line, redisplay_log_screen);
- currline = 2;
- if (currline < rows() - 2)
- {
- move_cursor(currline, flen);
- display_string(reporter, 0, flen);
- currline += 3;
- }
- (void)strcpy(line, Fields[5]);
- (void)strcat(line, append);
- if (!keywords)
- if (recursing)
- goto exit;
- else
- keywords = prompt(line, redisplay_log_screen);
- if (currline < rows() - 2)
- {
- move_cursor(currline++, flen);
- display_string(keywords, 0, flen);
- }
- (void)strcpy(line, Fields[6]);
- (void)strcat(line, append);
- if (!summary)
- if (recursing)
- goto exit;
- else
- summary = prompt(line, redisplay_log_screen);
- if (currline < rows() - 2)
- {
- move_cursor(currline, flen);
- display_string(summary, 0, flen);
- currline += 2;
- }
- (void)strcpy(line, Fields[8]);
- (void)strcat(line, append);
- if (!site)
- if (recursing)
- goto exit;
- else
- site = prompt(line, redisplay_log_screen);
- if (currline < rows() - 2)
- {
- move_cursor(currline++, flen);
- display_string(site, 0, flen);
- }
- if (!severity)
- if (recursing)
- goto exit;
- else
- severity = get_severity(redisplay_log_screen);
- if (currline < rows() - 2)
- {
- move_cursor(currline, flen);
- putchar(severity);
- }
- DELETE line;
- line = 0; // the nullification is important
-
- //
- // We got here when we\'ve been called recursively due to servicing
- // a SIGTSTP or SIGWINCH. We don\'t delete `line\' as we\'ll shortly be
- // back in our original self where we\'ll continue to use it.
- //
- exit:
- return;
- }
-
- /*
- ** redisplay_log_screen - redisplay the log screen. Can be called at any
- ** point during our building of the log screen to
- ** service a SIGTSTP or SIGWINCH.
- */
-
- static void redisplay_log_screen() { update_modeline(); build_log_screen(); }
-
- /*
- ** log_new_problem - need at least 4 rows to be useful
- */
-
- static void log_new_problem()
- {
- const char *fmt = "%s (logging)";
- char *suffix = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
- (void)sprintf(suffix, fmt, CurrentArea());
- update_modeline(ModelinePrefix, suffix);
- DELETE suffix;
- build_log_screen();
-
- message("Invoking your editor ...");
-
- //
- // Build tmp file into which the problem will be edited by user.
- //
- const char *file = temporary_file();
-
- invoke_editor(file);
-
- //
- // Generate string just large enough to hold the problem.
- // Don\'t forget to add space for the newlines.
- //
- int flen = max_field_length() + 1; // plus one accounts for the space
- int fsize = filesize(file);
- int totalsize = fsize + NFields() * flen;
- const int PDim = 10; // spaces reserved for `Problem #\' field
- const int StatDim = 6; // big enough for "open" or "closed"
- totalsize += int (strlen(CurrentArea()) + strlen(username()) + 2);
- totalsize += 50; // strlen\(ctime\(\)\) == 25 && already contains newline
- totalsize += StatDim+1;// "open" or "closed"
- totalsize += int (strlen(reporter) + strlen(keywords) + 2);
- totalsize += int (strlen(summary) + strlen(site) + 2);
- totalsize += 2; // the severity field and it\'s newline
- totalsize += PDim + 2; // space reserved for the problem number
- // and two newlines
-
- datum data;
- data.dsize = totalsize + 1; // don\'t forget about the null
- data.dptr = new char[data.dsize];
-
- //
- // Write the header info to `data\'.
- //
- int pos = 0; // our position in `data\'
- (void)sprintf(data.dptr, "%-*.*s%s\n", flen, flen, Fields[0], CurrentArea());
- pos += int (flen+strlen(CurrentArea())+1);
- (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[1],
- username());
- pos += int (flen+strlen(username())+1);
- (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[2],
- reporter);
- pos += int (flen+strlen(reporter)+1);
- (void)sprintf(&data.dptr[pos], "%-*.*s%s", flen, flen, Fields[3], logged);
- pos += flen+25;
- (void)sprintf(&data.dptr[pos], "%-*.*s%s", flen, flen, Fields[4], logged);
- pos += flen+25;
- (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[5],
- keywords);
- pos += int (flen+strlen(keywords)+1);
- (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[6],
- summary);
- pos += int (flen+strlen(summary)+1);
- (void)sprintf(&data.dptr[pos], "%-*.*s%-*.*s\n", flen, flen, Fields[7],
- StatDim, StatDim, "open");
- pos += flen+StatDim+1;
- (void)sprintf(&data.dptr[pos], "%-*.*s%s\n", flen, flen, Fields[8], site);
- pos += int (flen+strlen(site)+1);
- (void)sprintf(&data.dptr[pos], "%-*.*s%c\n", flen, flen, Fields[9],
- severity);
- pos += flen+2;
- (void)sprintf(&data.dptr[pos], "%-*.*s \n\n", flen, flen,
- Fields[10]);
- int offset = pos + flen; // data.dptr\[offset\] is where `Problem #\' goes
- pos += flen+PDim+2; // we output two newlines here as separator
-
- //
- // Now for the problem itself. Make sure this read only fails on a
- // real error -- block SIGTSTP and SIGWINCH
- //
- block_tstp_and_winch();
-
- int fd;
- if ((fd = open(file, O_RDONLY)) < 0)
- error("file %s, line %d, open(%s, O_RDONLY) failed",
- __FILE__, __LINE__, file);
-
- if (read(fd, &data.dptr[pos], fsize) != fsize)
- error("file %s, line %d, read() failed", __FILE__, __LINE__);
-
- unblock_tstp_and_winch(); // unblock SIGTSTP and WINCH
- (void)close(fd);
- (void)unlink(file);
-
- if (yes_or_no("Really log this problem (y|n)? ", redisplay_log_screen, Yes, 1))
- {
- datum key; // empty key to be filled in by update_database\(\)
- update_database(key, data, LOGGED, offset);
- update_subscribers(data, key.dptr, LOGGED);
- }
-
- update_modeline(); // redisplay last modeline
- DELETE data.dptr;
-
- //
- // We have to both delete these and nullify them so that the next
- // time we call build_log_screen\(\), we prompt for new data.
- //
- DELETE reporter;
- DELETE keywords;
- DELETE summary;
- DELETE site;
- reporter = 0;
- keywords = 0;
- summary = 0;
- site = 0;
- severity = 0; // Just nullify this; it\'s a static char.
- }
-