home *** CD-ROM | disk | FTP | other *** search
- /*
- **
- ** problem - a problem database manager
- **
- ** Written in C++ using the termcap 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.33 Delta\'d: 17:24:36 11/9/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
- #error "must define HOMEBASE"
- #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.
- //
- 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] = '_';
- }
-
- //
- // 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",
- "P -- change priority (severity) of problem",
- "R -- reopen a closed problem",
- "T -- transfer problem to another area",
- "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(HomeBase);
- filename += "/";
- filename += 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.
- */
-
- 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)
- {
- const char **areas = get_problem_areas();
- for (int i = 0; areas[i]; i++) ;
- nAreas = i;
- }
- return nAreas;
- }
-
- /*
- ** 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()
- {
- clear_display_area();
-
- 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();
- 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);
- }
- (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);
- }
- }
-
- /*
- ** 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");
- clear_display_area();
-
- int position = 0, offset = 0;
- char key;
- do
- {
- cursor_home();
- if (position == 0)
- {
- offset = 2;
- display_string(msg);
- move_cursor(2, 0);
- }
- for (int i = 0; i + offset < rows()-2 && i + position < dim; i++)
- display_string(lines[i + position]);
-
- 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 does not 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)
- quit();
- 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)
- quit();
- 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(void (*redisplay)())
- {
- //
- // Compile with -DSEVLEVEL3 if you only want three severity levels.
- //
- #ifdef SEVLEVEL3
- const char *msg = "Severity (1 (=highest), 2, or 3 (=lowest)) --> ";
- #else
- const char *msg = "Severity (1 (=highest), 2, 3 or 4 (=lowest)) --> ";
- #endif
-
- 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)
- {
- #ifdef SEVLEVEL3
- case '1': case '2': case '3': return key;
- #else
- case '1': case '2': case '3': case '4': return key;
- #endif
- 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 = HomeBase;
- filename += "/";
- filename += 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]; // will not have this many problems for a while
-
- FILE *fp = fdopen(fd, "r+");
- if (fp == 0)
- error("file %s, line %d, fdopen() on `%s' failed",
- __FILE__, __LINE__, sequence_file());
-
- char *line = fgetline(fp, 10);
-
- if (fp == 0)
- error("file %s, line %d, fgetline() on `%s' failed",
- __FILE__, __LINE__, sequence_file());
- (void)sprintf(buf, "%d", atoi(line) + 1);
-
- //
- // Truncate the file. I would like to use ftruncate here, but that
- // is not 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 = HomeBase;
- filename += "/";
- filename += MailListPrefix;
- }
- return filename;
- }
-
- /*
- ** open_maillist_file - open the file containing the interested parties
- ** maillist for the problem area. Exits on error.
- ** If the file did not previously exist, it will be
- ** created.
- */
-
- static int open_maillist_file()
- {
- String file(mail_list_prefix());
- file += ".";
- file += 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",
- "severity modified",
- "transferred"
- };
-
- //
- // indices into How
- //
- enum Modified {
- LOGGED,
- APPENDED,
- CLOSED,
- DELETED,
- REORGANIZED,
- KEYWORDMOD,
- REOPENED,
- SEVERITYMOD,
- TRANSFER
- };
-
- /*
- ** update_subscribers - send a mail file about problem to all
- ** those who have subscribed to this area. If
- ** this is being called after an append,
- ** append is non-zero. Otherwise, we assume
- ** it is the initial logging. If we are 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";
-
- #ifdef NOSUBJECT
- String subject("Subject: Problem: ");
- #else
- args[1] = "-s";
- String subject("Problem: ");
- #endif
-
- subject += CurrentArea();
- subject += " # ";
- subject += number;
- subject += " ";
- subject += How[how];
- subject += " by `";
- subject += username();
- subject += "'";
-
- #ifdef NOSUBJECT
- subject += "\n\n";
- #endif
-
- #ifndef NOSUBJECT
- args[2] = subject;
- #endif
-
- //
- // Are there any subscribers?
- //
- #ifdef NOSUBJECT
- int nlines = read_file(mailfp, args, chunksize, namelen, 1);
- #else
- int nlines = read_file(mailfp, args, chunksize, namelen, 3);
- #endif
- 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.
- //
- #ifdef NOSUBJECT
- for (int i = 1; args[i]; i++) DELETE args[i];
- #else
- for (int i = 3; args[i]; i++) DELETE args[i];
- #endif
- 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);
-
- //
- // We fork again and let the grandchild do the actual
- // mailing. This way our parent will not have to wait
- // for the mail to be sent.
- //
- switch(fork())
- {
- case -1:
- exit(1);
- case 0:
- execv(mailprog, (char *const *)args);
- exit(1); // exec failed
- default:
- exit(0);
- }
- break;
- default: // in the parent
- {
- (void)close(fds[0]);
-
- #ifdef NOSUBJECT
- //
- // Write Subject to pipe.
- //
- write_to_pipe(fds[1], subject, subject.length());
- #endif
-
- //
- // write the mailfile to the pipe
- //
- switch (how)
- {
- case CLOSED:
- case REOPENED:
- case APPENDED:
- case KEYWORDMOD:
- case SEVERITYMOD:
- case TRANSFER:
- {
- //
- // 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]);
-
- #ifdef NOSUBJECT
- for (int i = 1; args[i]; i++) DELETE args[i];
- #else
- for (int i = 3; args[i]; i++) DELETE args[i];
- #endif
- DELETE args;
-
- //
- // We are not interested in the return value. The assumption
- // here is that if something goes wrong with the mailing that
- // the error will eventually get to the problem administrator.
- //
- (void)wait(0);
-
- return;
- }
- }
- }
-
- /*
- ** invoke_editor - invoke users editor on file
- */
-
- static void invoke_editor(const char *file)
- {
- char *editor = getenv("EDITOR");
- if (editor == 0) editor = "vi";
-
- String argstring(editor);
- argstring += " ";
- argstring += file;
-
- //
- // We tokenize because the user could have EDITOR
- // set to "emacs -q -nw", or some such thing, which execvp
- // would not recognize as a legal filename.
- //
- const char **args = tokenize(argstring, " \t");
-
- //
- // We must be careful of that venerable old editor "vi",
- // which has a habit of returning error codes, when nothing
- // went wrong.
- //
- if (!execute(args[0], args) && strcmp(args[0], "vi"))
- 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 does not 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(HomeBase);
- filename += "/";
- filename += CurrentArea();
- filename += 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(HomeBase);
- filename += "/";
- filename += CurrentArea();
- filename += GdbmSuffix;
-
- if ((GdbmFile = gdbm_open(filename, 0, mode, 00644, 0)) == 0)
- 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, 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 TRANSFER:
- {
- //
- // Close original database.
- //
- gdbm_close(GdbmFile);
-
- //
- // Remember current area before switching to new one.
- //
- String oldArea(CurrentArea());
- char *newArea = data.dptr + max_field_length() + 1;
- char *tmp = strchr(newArea, '\n');
- *tmp = 0;
- setCurrentArea(newArea);
- *tmp = '\n';
-
- //
- // Open database on the new area and insert the problem.
- //
- open_database(GDBM_WRCREAT);
-
- if (gdbm_store(GdbmFile, key, data, GDBM_INSERT))
- error("file %s, line %d, gdbm_store() failed, errno = %d",
- __FILE__, __LINE__, gdbm_errno);
-
- //
- // Now go back to previous database and delete problem from there.
- //
- gdbm_close(GdbmFile);
- setCurrentArea(oldArea);
- open_database(GDBM_WRCREAT);
-
- if (gdbm_delete(GdbmFile, key))
- error("file %s, line %d, gdbm_delete() failed, errno = %d",
- __FILE__, __LINE__, gdbm_errno);
- break;
- }
- case LOGGED:
- {
- //
- // Must fill in key values; we are 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:
- case SEVERITYMOD:
- 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 are 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()
- {
- clear_display_area();
-
- //
- // 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++)
- display_string(Fields[i]);
- end_standout_mode();
-
- int fieldlen = 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++, fieldlen);
- display_string(CurrentArea(), 0, fieldlen);
- }
- if (currline < rows() - 2)
- {
- move_cursor(currline, fieldlen);
- display_string(username(), 0, fieldlen);
- currline += 2;
- }
- time_t t = time(0);
- logged = ctime(&t);
- if (currline < rows() - 2)
- {
- move_cursor(currline++, fieldlen);
- display_string(logged, 0, fieldlen);
- }
- if (currline < rows() - 2)
- {
- move_cursor(currline, fieldlen);
- display_string(logged, 0, fieldlen);
- currline += 3;
- }
- if (currline < rows() - 2)
- {
- move_cursor(currline, fieldlen);
- display_string("open", 0, fieldlen);
- }
-
- //
- // Prompt for those that are not.
- //
- const char *const suffix = " --> ";
- static char *line;
- int recursing = 0; // is this a recursive call?
- if (!line)
- line = new char[fieldlen + strlen(suffix)];
- else
- recursing = 1;
- (void)strcpy(line, Fields[2]);
- (void)strcat(line, suffix);
- if (!reporter)
- if (recursing)
- goto exit;
- else
- reporter = prompt(line, redisplay_log_screen);
- currline = 2;
- if (currline < rows() - 2)
- {
- move_cursor(currline, fieldlen);
- display_string(reporter, 0, fieldlen);
- currline += 3;
- }
- (void)strcpy(line, Fields[5]);
- (void)strcat(line, suffix);
- if (!keywords)
- if (recursing)
- goto exit;
- else
- keywords = prompt(line, redisplay_log_screen);
- if (currline < rows() - 2)
- {
- move_cursor(currline++, fieldlen);
- display_string(keywords, 0, fieldlen);
- }
- (void)strcpy(line, Fields[6]);
- (void)strcat(line, suffix);
- if (!summary)
- if (recursing)
- goto exit;
- else
- summary = prompt(line, redisplay_log_screen);
- if (currline < rows() - 2)
- {
- move_cursor(currline, fieldlen);
- display_string(summary, 0, fieldlen);
- currline += 2;
- }
- (void)strcpy(line, Fields[8]);
- (void)strcat(line, suffix);
- if (!site)
- if (recursing)
- goto exit;
- else
- site = prompt(line, redisplay_log_screen);
- if (currline < rows() - 2)
- {
- move_cursor(currline++, fieldlen);
- display_string(site, 0, fieldlen);
- }
- if (!severity)
- if (recursing)
- goto exit;
- else
- severity = get_severity(redisplay_log_screen);
- if (currline < rows() - 2)
- {
- move_cursor(currline, fieldlen);
- putchar(severity);
- }
- DELETE line;
- line = 0; // the nullification is important
-
- //
- // We got here when we have been called recursively due to servicing
- // a SIGTSTP or SIGWINCH. We do not delete line as we will shortly be
- // back in our original self where we will 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()
- {
- String suffix(CurrentArea());
- suffix += " (logging)";
-
- update_modeline(ModelinePrefix, 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.
- // Do not 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())) + 1;
- totalsize += int(strlen(username())) + 1;
- totalsize += 50; // strlen\(ctime\)==25 && already contains newline
- totalsize += StatDim + 1; // "open" or "closed"
- totalsize += int(strlen(reporter)) + 1;
- totalsize += int(strlen(keywords)) + 1;
- totalsize += int(strlen(summary)) + 1;
- totalsize += int(strlen(site)) + 1;
- totalsize += 2; // the severity field
- totalsize += PDim + 2; // space reserved for the problem number, its
- // newline, and an extra one
-
- datum data;
- data.dsize = totalsize + 1; // do not 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 is a static char.
- }
-
- /*
- ** commands_screen - display screen of problem commands. We assume
- ** that the caller will be setting the modeline.
- ** Needs at least five before anything useful is displayed.
- */
-
- static void commands_screen()
- {
- clear_display_area();
-
- enter_standout_mode();
- display_string("Commands");
- end_standout_mode();
- cursor_wrap();
-
- //
- // Display as many commands as will fit on screen starting in third row.
- //
- for (int i = 0; i < NCommands() && i < rows() - 4; i++)
- {
- (void)fputs(" ", stdout);
- enter_standout_mode();
- putchar(Commands[i][0]); // first char of command in bold
- end_standout_mode();
- display_string(&Commands[i][1], 0, 3);
- }
- }
-
- /*
- ** redisplay_commands - redisplay the commands screen and modeline.
- ** This gets called on a SIGTSTP or SIGWINCH.
- */
-
- static void redisplay_commands() { commands_screen(); update_modeline(); }
-
- /*
- ** update_existing_problem - update an existing problem entry.
- */
-
- static void update_existing_problem(datum &data, datum &key, Modified how)
- {
- message("Invoking your editor ...");
-
- //
- // Build tmp file into which the data will be edited by user.
- //
- const char *file = temporary_file();
-
- invoke_editor(file);
-
- //
- // Merge old data with the new.
- //
- time_t t = time(0);
- char *updated = ctime(&t);
-
- String separator("\n****** ");
- separator += How[how];
- separator += " by `";
- separator += username();
- separator += "' on ";
- separator += updated;
- separator += "\n";
-
- int fsize = filesize(file);
-
- //
- // Is last character in problem a newline?
- //
- int add_newline = data.dptr[data.dsize-2] != '\n';
-
- datum newdata;
-
- newdata.dsize = int(separator.length() + data.dsize + fsize + add_newline);
- newdata.dptr = new char[newdata.dsize];
- (void)strcpy(newdata.dptr, data.dptr); // the old data
- if (add_newline) (void)strcat(newdata.dptr, "\n"); // add terminal newline
- (void)strcat(newdata.dptr, separator); // the separator
-
- //
- // The new data. 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,&newdata.dptr[separator.length()+data.dsize-1],fsize) != fsize)
- error("file %s, line %d, read() failed", __FILE__, __LINE__);
-
- unblock_tstp_and_winch();
- (void)close(fd);
- (void)unlink(file);
-
- //
- // Always update the Updated field -- Fields\[4\].
- //
- char *head = newdata.dptr;
- for (int i = 0; i < 4; i++)
- {
- // want to find head of fifth line
- head = strchr(head, '\n');
- head += 1; // step past the newline
- }
- int flen = max_field_length() + 1;
- head += flen; // skip to the data in Fields\[4\]
- for (i = 0; i < 25; i++) head[i] = updated[i];
-
- //
- // Update the Status field only on closes and reopens.
- //
- if (how == CLOSED || how == REOPENED)
- {
- char *field = (how == CLOSED ? "closed" : "open ");
- for (i = 0; i < 3; i++)
- {
- //
- // Skip three more lines.
- //
- head = strchr(head, '\n');
- head += 1; // step past the newline
- }
- head += flen; // step to the data in Fields\[7\]
- for (i = 0; i < 6; i++) // StatDim == 6 in log_new_problem
- head[i] = field[i];
- }
-
- String msg("Really do the ");
- switch (how)
- {
- case CLOSED: msg += "close (y|n)?" ; break;
- case REOPENED: msg += "reopen (y|n)?"; break;
- case APPENDED: msg += "append (y|n)?"; break;
- case KEYWORDMOD: msg += "keyword modification (y|n)?"; break;
- case SEVERITYMOD: msg += "severity modification (y|n)?"; break;
- case TRANSFER: msg += "problem transfer (y|n)?"; break;
- default: error("file %s, line %d, illegal case in switch()",
- __FILE__, __LINE__);
- }
-
- if (yes_or_no(msg, redisplay_commands, Yes, 1))
- {
- update_database(key, newdata, how);
- update_subscribers(newdata, key.dptr, how, separator.length() +
- data.dsize - 1);
- }
-
- DELETE newdata.dptr;
- }
-