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
- **
- ** problem2.C problem2.C 1.17 Delta\'d: 17:43:06 10/8/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.
- */
-
- /*
- ** 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()
- {
- cursor_home();
- clear_to_end_of_line();
- enter_standout_mode();
- display_string("Commands");
- end_standout_mode();
- clear_to_end_of_line();
- 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++)
- {
- clear_to_end_of_line();
- (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);
- }
-
- // clear any dirty lines
- for (; i < rows() - 4; i++) {
- clear_to_end_of_line();
- cursor_wrap();
- }
- }
-
- /*
- ** 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);
- char *fmt = "\n****** %s by `%s' on %s\n";
- char *separator = new char[strlen(fmt) + strlen(How[how]) +
- strlen(username()) + strlen(updated) - 5];
-
- (void)sprintf(separator, fmt, How[how], username(), updated);
- int fsize = filesize(file);
- datum newdata;
- newdata.dsize = int (strlen(separator) + data.dsize + fsize);
- newdata.dptr = new char[newdata.dsize];
- (void)strcpy(newdata.dptr, data.dptr); // the old data
- (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[strlen(separator)+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];
- }
-
- fmt = "Really do the %s (y|n)?";
- char *str;
- switch (how)
- {
- case CLOSED: str = "close" ; break;
- case REOPENED: str = "reopen"; break;
- case APPENDED: str = "append"; break;
- case KEYWORDMOD: str = "keyword modification"; break;
- default: error("file %s, line %d, illegal case in switch()",
- __FILE__, __LINE__);
- }
-
- char *buf = new char[strlen(fmt) + strlen(str) - 1];
- (void)sprintf(buf, fmt, str);
- if (yes_or_no(buf, redisplay_commands, Yes, 1))
- {
- update_database(key, newdata, how);
- update_subscribers(newdata, key.dptr, how, (int)strlen(separator) +
- data.dsize - 1);
- }
-
- DELETE buf;
- DELETE separator;
- DELETE newdata.dptr;
- }
-
- /*
- ** append_to_problem - append to an already existing problem. Returns
- ** false if the problem doesn\'t exist. This indicates
- ** that we don\'t need to redisplay the command list.
- ** Returns true if we need to redisplay the command
- ** list. If `number\', which defaults to zero, is
- ** nonzero, we use that number instead of prompting.
- */
-
- int append_to_problem(const char *number)
- {
- datum key;
- key.dptr = number ? (char *) number : prompt("Problem # --> ",
- redisplay_commands);
- key.dsize = int (strlen(key.dptr) + 1);
- if (!database_exists())
- {
- ding();
- message("There is no database for problem area `%' ", CurrentArea());
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- return 0;
- }
-
- open_database(GDBM_READER);
- datum data = gdbm_fetch(GdbmFile, key);
- gdbm_close(GdbmFile);
- if (!data.dptr)
- {
- ding();
- message("There is no problem # `%' ", key.dptr);
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- return 0; // only the message area has been corrupted
- }
-
- //
- // The problem exists.
- //
- update_existing_problem(data, key, APPENDED);
- update_modeline(); // redisplay the previous modeline
- free(data.dptr);
- if (!number) DELETE key.dptr;
-
- return 1; // must refresh the screen
- }
-
- /*
- ** subscribe_to_area - put user on interested parties list for current area.
- ** Exits on error
- */
-
- static void subscribe_to_area()
- {
- int mailfd = open_maillist_file();
- const int chunksize = 20;
- const int linelen = 10;
- char **users = new char*[chunksize];
- FILE *mailfp = fdopen(mailfd, "r+");
- if (!mailfp)
- error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
- int nusers = read_file(mailfp, users, chunksize, linelen);
- if (nusers < 0)
- error("file %s, line %d, error reading %s maillist",
- __FILE__, __LINE__, CurrentArea());
-
- //
- // Is user already subscribed?
- //
- const char *user = username();
- for (int i = 0, subscribed = 0; i < nusers; i++)
- if (strcmp(users[i], user) == 0)
- {
- subscribed = 1; break;
- }
-
- for (i = 0; i < nusers; i++) DELETE users[i];
- DELETE users;
-
- if (subscribed)
- {
- (void)close(mailfd); (void)fclose(mailfp);
- ding();
- message("You already subscribe to the `%' area ", CurrentArea());
- sleep(2);
- return;
- }
-
- //
- // Lock on sequence file when updating the maillist.
- //
- int seqfd = open(sequence_file(), O_RDWR);
- if (seqfd < 0) error("file %s, line %d, open() on `%s' failed",
- __FILE__, __LINE__, sequence_file());
-
- block_tstp_and_winch(); // block SIGTSTP and WINCH
- lock_file(seqfd); // lock our sequence file
-
- // seek to end of maillist file
- if (fseek(mailfp, 0, SEEK_END))
- error("file %s, line %d, fseek() failed", __FILE__, __LINE__);
- (void)fprintf(mailfp, "%s\n", user); // add\'em
-
- (void)fclose(mailfp);
- (void) close(mailfd);
- unlock_file(seqfd);
- (void)close(seqfd);
-
- unblock_tstp_and_winch();
-
- message("You now subscribe to the `%' area ", CurrentArea());
- sleep(2);
- }
-
- /*
- ** unsubscribe_from_area - unsubscribe from the current area. Exits on error.
- */
-
- static void unsubscribe_from_area()
- {
- int mailfd = open_maillist_file();
- FILE *mailfp = fdopen(mailfd, "r+");
- if (!mailfp)
- error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
-
- const int chunksize = 20;
- const int linelen = 10;
- char **users = new char*[chunksize];
- int nusers = read_file(mailfp, users, chunksize, linelen);
- if (nusers < 0)
- error("file %s, line %d, error reading %s maillist",
- __FILE__, __LINE__, CurrentArea());
-
- //
- // Are they already subscribed?
- //
- const char *user = username();
- for (int i = 0, subscribed = 0; i < nusers; i++)
- if (strcmp(users[i], user) == 0) { subscribed = 1; break; }
-
- for (i = 0; i < nusers; i++) DELETE users[i];
- DELETE users;
-
- if (!subscribed)
- {
- (void)fclose(mailfp);
- (void)close(mailfd);
- ding();
- message("You're not a subscribee of the `%' area ", CurrentArea());
- sleep(2);
- return;
- }
-
- //
- // They subscribe - remove them.
- //
- if (fseek(mailfp, 0, SEEK_SET)) // seek to beginning of file
- error("file %s, line %d, fseek() failed", __FILE__, __LINE__);
-
- //
- // First, build a tmp file for new maillist.
- //
- char *tmpfile = new char[strlen(mail_list_prefix()) + 12];
- (void)sprintf(tmpfile, "%s.%d", mail_list_prefix(), getpid());
- int tmpfd = open(tmpfile, O_RDWR|O_CREAT, 0644);
- if (tmpfd < 0)
- error("file %s, line %d, open() failed", __FILE__, __LINE__);
- FILE *tmpfp = fdopen(tmpfd, "w+");
- if (!tmpfp)
- error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
-
- for (char *line = fgetline(tmpfp, 10); line; line = fgetline(tmpfp, 10))
- {
- if (strcmp(line, user) == 0)
- {
- DELETE line;
- continue;
- }
- (void)fprintf(tmpfp, "%s\n", line);
- DELETE line;
- }
-
- if (feof(tmpfp) && !ferror(tmpfp))
- {
- //
- // Alternate maillist correctly constructed.
- //
- (void)fclose(tmpfp); (void)fclose(mailfp);
- (void) close(tmpfd); (void) close(mailfd);
- //
- // Remove old maillist.
- //
- String maillist = String(mail_list_prefix()) + "." + CurrentArea();
- int seqfd = open(sequence_file(), O_RDWR);
- if (seqfd < 0)
- error("file %s, line %d, open() on `%s' failed",
- __FILE__, __LINE__, sequence_file());
-
- block_tstp_and_winch(); // block SIGTSTP and WINCH
- lock_file(seqfd); // lock our sequence file
-
- if (unlink((const char *)maillist) < 0)
- error("file %s, line %d, unlink(%s) failed",
- __FILE__, __LINE__, (const char *)maillist);
- if (rename((const char *)tmpfile, (const char *)maillist) < 0)
- error("file %s, line %d, rename(%s, %s) failed", __FILE__,
- __LINE__, (const char *)tmpfile, (const char *)maillist);
-
- unlock_file(seqfd);
- (void)close(seqfd);
- unblock_tstp_and_winch();
-
- DELETE tmpfile;
-
- message("You've been unsubscribed from the `%' area ", CurrentArea());
- }
- else
- {
- (void)fclose(tmpfp);
- (void)fclose(mailfp);
- (void) close(tmpfd);
- (void) close(mailfd);
- ding();
- message("Problem updating maillist -- update not committed ");
- }
- sleep(2);
- }
-
- /*
- ** examine_problem - pages through a problem in current area. Returns one
- ** if the problem exists, otherwise zero. If `number\' is
- ** nonzero \(it defaults to zero\), we use that number
- ** instead of prompting for one.
- */
-
- int examine_problem(const char *number)
- {
- if (!database_exists())
- {
- ding();
- message("There is no database for problem area `%' ", CurrentArea());
- if (!number) sleep(2);
- return 0;
- }
-
- datum key;
- key.dptr = number ? (char *) number : prompt("Problem # --> ",
- redisplay_commands);
- key.dsize = int(strlen(key.dptr)) + 1;
- open_database(GDBM_READER);
- datum data = gdbm_fetch(GdbmFile, key);
- gdbm_close(GdbmFile);
-
- if (!data.dptr)
- {
- ding();
- message("There is no problem # % ", key.dptr);
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- return 0;
- }
-
- //
- // Build tmp file to pass to pager.
- //
- const char *file = temporary_file();
-
- //
- // Write the problem to the file.
- //
- int fd;
- if ((fd = open(file, O_RDWR)) < 0)
- error("file %s, line %d, open(%s, O_RDONLY) failed",
- __FILE__, __LINE__, file);
- if (write(fd, data.dptr, data.dsize - 1) != data.dsize - 1)
- error("file %s, line %d, write() failed", __FILE__, __LINE__);
-
- const char *prefix = "-PProblem # ";
- const char *suffix = " -- line %lb of %L ?e(END) .-- q (quit) H (help)";
- String prompt = String(prefix) + key.dptr + suffix;
- const char *less = "less";
- const char *args[5];
- args[0] = less; args[1] = "-d";
- args[2] = prompt; args[3] = file; args[4] = 0;
-
- clear_display();
- synch_display();
-
- if (!execute(less, args))
- {
- (void)unlink(file);
- error("file %s, line %d, problem running `less'", __FILE__, __LINE__);
- }
-
- (void)close(fd);
- (void)unlink(file);
- update_modeline(); // redisplay previous modeline
- free(data.dptr);
- if (!number) DELETE key.dptr;
-
- return 1; // must update screen
- }
-
- /*
- ** summary_info - returns a string in new\'d space of the form:
- **
- ** prob# status severity last-activity-date summary
- **
- ** sort_by_date\(\) "knows" the format of the lines output
- ** by this function, so if you change the format, you need
- ** to change sort_by_date\(\) also.
- */
-
- char *summary_info(datum &data)
- {
- const char *tail = data.dptr;
-
- //
- // Updated is Fields\[4\]
- //
- for (int i = 0; i < 4; i++)
- {
- tail = strchr(tail, '\n');
- tail += 1; // step past the newline
- }
- tail += max_field_length() + 1; // this is the Updated line
- const char *updated = tail + 4; // don\'t output the day of the week
- tail = strchr(tail, '\n'); // end of Updated line
- int updatedLen = tail - updated;
-
- //
- // Keywords is Fields\[5\] - just skip over it for now
- //
- tail += 1; // step past the newline
- tail = strchr(tail, '\n'); // end of Keywords line
-
- //
- // Summary is Fields\[6\]
- //
- tail += 1; // step past the newline
- tail += max_field_length() + 1; // this is the Summary line
- const char *summary = tail;
- tail = strchr(tail, '\n'); // end of Summary line
- int summaryLen = tail - summary;
-
- //
- // Status Field\[7\]
- //
- tail += 1; // step past the newline
- tail += max_field_length() + 1; // this is the Status line
- const char *status = tail;
- tail = strchr(tail, '\n'); // end of Status line
- int statusLen = tail - status;
-
- //
- // Severity is Field\[9\]
- //
- tail += 1; // step past the newline
- tail = strchr(tail, '\n'); // end of Site line
- tail += 1; // step past the newline
- tail += max_field_length() + 1; // this is the Severity line
- const char severity = *tail;
- tail = strchr(tail, '\n'); // end of Severity line
-
- //
- // Problem # is Fields\[10\]
- //
- tail += 1; // step past the newline
- tail += max_field_length() + 1; // this is the prob # line
- const char *number = tail;
- //
- // Find the end of the problem # field - probably contains a space.
- //
- tail = strchr(tail, '\n');
- while (*(tail - 1) == ' ') tail--; // strip off trailing spaces
- int numberLen = tail - number;
-
- const int numberFieldLen = 5; // min width of Prob # field in summary
- int len = numberLen > numberFieldLen ? numberLen : numberFieldLen;
- char *line = new char[updatedLen + summaryLen + statusLen + len + 4];
-
-
- *line = 0; // stringify
-
- if (numberLen < numberFieldLen)
- {
- //
- // Pad the length to numberFieldLen by prepending spaces.
- //
- int nSpaces = numberFieldLen - numberLen;
- for (int i = 0; i < nSpaces; i++)
- *(line + i) = ' ';
- *(line + nSpaces) = 0; // restringify
- }
-
- (void)strncat(line, number, numberLen);
- line[len] = 0;
- (void)strcat(line, " ");
- (void)strncat(line, status, statusLen);
- line[len += statusLen + 1] = 0;
-
- // if status == "open", output the severity also
- if (line[len - 1] == ' ') line[len - 1] = severity;
-
- (void)strcat(line, " ");
- (void)strncat(line, updated, updatedLen);
- line[len += updatedLen + 1] = 0;
- (void)strcat(line, " ");
- (void)strncat(line, summary, summaryLen);
- line[len += summaryLen + 1] = 0;
-
- return line;
- }
-
- /*
- ** lsign - returns -1, 0 or 1 depending on the sign of the argument
- */
-
- inline int lsign(long arg)
- {
- return arg == 0 ? 0 : (arg > 0 ? 1 : -1);
- }
-
- /*
- ** sort_by_date - the comparison function passed to qsort\(3\) used when
- ** sorting listing lines by date.
- */
-
- static int sort_by_date(const void *a, const void *b)
- {
- char *tmpa = *(char **)a;
- char *tmpb = *(char **)b;
-
- while (*tmpa == ' ') tmpa++; // step over any spaces preceding Prob #
- while (*tmpb == ' ') tmpb++; // ditto
-
- tmpa = strchr(tmpa, ' ') + 1; // step onto first character of Status
- tmpb = strchr(tmpb, ' ') + 1; // ditto
-
- if (strncmp(tmpa, tmpb, 4))
- return -strncmp(tmpa, tmpb, 4); // sort "open" before "closed"
- else if (strncmp(tmpa, tmpb, 6))
- return strncmp(tmpa, tmpb, 6); // sort by severity
- else
- {
- // lastly, sort by most recent first
- tmpa += 7;
- tmpb += 7;
- return lsign(seconds_in_date(tmpb) - seconds_in_date(tmpa));
- }
- }
-
- /*
- ** view_summary_lines - page through data from problem headers in current area.
- ** Returns one if the database exists, else zero.
- */
-
- static int view_summary_lines()
- {
- if (!database_exists())
- {
- ding();
- message("There is no database for problem area `%' ", CurrentArea());
- sleep(2);
- return 0;
- }
-
- open_database(GDBM_READER);
- datum key = gdbm_firstkey(GdbmFile);
- if (!key.dptr)
- {
- const char *fmt = "Area `%s' appears to be empty ";
- char *msg = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
- (void)sprintf(msg, fmt, CurrentArea());
- ding();
- message(msg);
- sleep(2);
- DELETE msg;
- gdbm_close(GdbmFile);
- return 0;
- }
-
- DList summaryLines; // listing of problem summaries
- datum data, tmp;
- const int chunksize = 100;
- int size = chunksize;
- int pos = 0;
- char **lines = new char*[size];
-
- message("Reading problems ... ");
- while (1)
- {
- data = gdbm_fetch(GdbmFile, key);
- lines[pos++] = summary_info(data);
- if (pos == size)
- {
- // grow lines
- char **newspace = new char*[size += chunksize];
- for (int i = 0; i < pos; i++) newspace[i] = lines[i];
- DELETE lines;
- lines = newspace;
- }
- free(data.dptr);
- tmp = gdbm_nextkey(GdbmFile, key);
- free(key.dptr);
- key = tmp;
- if (!key.dptr) break;
- }
- gdbm_close(GdbmFile);
- message("Reading problems ... done");
-
- //
- // Sort lines "most recently updated" first.
- //
- qsort(lines, pos, sizeof(char**), sort_by_date);
- for (int i = 0; i < pos; i++)
- summaryLines.add(new DLink((char **)&lines[i]));
-
- DELETE lines;
-
- initialize_lister(&summaryLines);
- initial_listing(&summaryLines);
-
- const char *fmt = "%s ---- q (quit) H (help)";
- char *suffix = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
- (void)sprintf(suffix, fmt, CurrentArea());
- update_modeline(ModelinePrefix, suffix);
-
- DELETE suffix;
-
- summaryLines.saveYXPos(0, goal_column(&summaryLines));
- if (summaryLines.currLine()->length() > columns())
- leftshift_current_line(&summaryLines);
- else
- move_cursor(summaryLines.savedYPos(), summaryLines.savedXPos());
- synch_display();
-
- lister_cmd_loop(&summaryLines);
-
- return 1;
- }
-
- /*
- ** close_problem - close an existing problem in current area. Returns one if
- ** we did the close, zero otherwise. Only a database
- ** administrator or the original logger can close a problem.
- ** `number\', which defaults to null, is used if not null,
- ** instead of prompting for the number. Also, only an open
- ** problem can be closed.
- */
-
- int close_problem(const char *number)
- {
- if (!database_exists())
- {
- ding();
- message("There is no database for problem area `%' ", CurrentArea());
- if (!number) sleep(2);
- return 0;
- }
- datum key;
- key.dptr = number ? (char *) number : prompt("Problem # --> ",
- redisplay_commands);
- key.dsize = int(strlen(key.dptr)) + 1;
-
- open_database(GDBM_READER);
- datum data = gdbm_fetch(GdbmFile, key);
- gdbm_close(GdbmFile);
-
- if (!data.dptr)
- {
- ding();
- message("There is no problem # % ", key.dptr);
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- return 0;
- }
-
- //
- // Are we authorized to close the problem?
- //
- char *tmp = strchr(data.dptr, '\n'); // logger is Fields\[1\]
- tmp += 1; // step past the newline
- tmp += max_field_length() + 1; // the logger
- int llen = strchr(tmp, '\n') - tmp; // # of chars in logger
- const char *user = username();
-
- if ((llen != strlen(user) || strncmp(user, tmp, llen)) &&
- getuid() != geteuid())
- {
- ding();
- message("You're not authorized to close problem # % ", key.dptr);
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- free(data.dptr);
- return 0;
- }
-
- //
- // Is the problem open?
- //
- for (int i = 0; i < 6; i++)
- {
- // status is Fields\[7\]
- tmp = strchr(tmp, '\n');
- tmp += 1; // step past newline
- }
- if (strncmp(tmp + max_field_length() + 1, "open", 4))
- {
- ding();
- message("Only open problems can be closed ");
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- free(data.dptr);
- return 0;
- }
-
- update_existing_problem(data, key, CLOSED);
- update_modeline(); // redisplay previous modeline
- free(data.dptr);
- if (!number) DELETE key.dptr;
-
- return 1;
- }
-
- /*
- ** reopen_problem - reopen a closed problem in current area. Returns one if
- ** we did the close, zero otherwise. Only a database
- ** administrator or the original logger can reopen a problem.
- ** `number\', which defaults to null, is used if not null,
- ** instead of prompting for the number.
- */
-
- int reopen_problem(const char *number)
- {
- if (!database_exists())
- {
- ding();
- message("There is no database for problem area `%' ", CurrentArea());
- if (!number) sleep(2);
- return 0;
- }
-
- datum key;
- key.dptr = number ? (char *) number : prompt("Problem # --> ",
- redisplay_commands);
- key.dsize = int(strlen(key.dptr)) + 1;
-
- open_database(GDBM_READER);
- datum data = gdbm_fetch(GdbmFile, key);
- gdbm_close(GdbmFile);
-
- if (!data.dptr)
- {
- ding();
- message("There is no problem # % ", key.dptr);
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- return 0;
- }
-
- //
- // Are we authorized to reopen the problem?
- //
- char *tmp = strchr(data.dptr, '\n'); // logger is Fields\[1\]
- tmp += 1; // step past the newline
- tmp += max_field_length() + 1; // the logger
- int llen = strchr(tmp, '\n') - tmp; // # of chars in logger
- const char *user = username();
-
- if ((llen != strlen(user) || strncmp(user, tmp, llen)) &&
- getuid() != geteuid())
- {
- ding();
- message("You're not authorized to close problem # % ", key.dptr);
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- free(data.dptr);
- return 0;
- }
-
- //
- // Only closed problems can be opened.
- //
- for (int i = 0; i < 6; i++)
- {
- // status is Fields\[7\]
- tmp = strchr(tmp, '\n');
- tmp += 1; // step past newline
- }
- if (strncmp(tmp + max_field_length() + 1, "closed", 6))
- {
- ding();
- message("Only closed problems can be reopened ");
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- free(data.dptr);
- return 0;
- }
-
- update_existing_problem(data, key, REOPENED);
- update_modeline(); // redisplay previous modeline
- free(data.dptr);
- if (!number) DELETE key.dptr;
-
- return 1;
- }
-
- /*
- ** delete_problem - delete the problem from current area.
- ** This is strictly for the database administrators.
- ** If number is non-null, use it instead of prompting
- ** for the problem number. Returns 1 if the problem
- ** was deleted, 0 otherwise.
- */
-
- int delete_problem(const char *number)
- {
- int del = 0; // was delete successful?
-
- if (getuid() != geteuid())
- {
- ding();
- message("Only database administrators can to delete problems ");
- if (!number) sleep(2);
- return 0;
- }
-
- if (!database_exists())
- {
- ding();
- message("There is no database for problem area `%' ", CurrentArea());
- if (!number) sleep(2);
- return 0;
- }
-
- datum key;
- key.dptr = number ? (char *) number : prompt("Problem # --> ",
- redisplay_commands);
- key.dsize = int(strlen(key.dptr)) + 1;
-
- open_database(GDBM_READER);
- datum data = gdbm_fetch(GdbmFile, key);
- gdbm_close(GdbmFile);
-
- if (!data.dptr)
- {
- ding();
- message("There is no problem # % ", key.dptr);
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- return 0;
- }
-
- //
- // The problem exists; delete it.
- //
- const char *msg = "Do you really want to delete this problem (n|y)? ";
- if (del = yes_or_no(msg, redisplay_commands, No, 0))
- update_database(key, data, DELETED);
- free(data.dptr);
- if (!number) DELETE key.dptr;
-
- return del;
- }
-
- /*
- ** reorganize_database - reorganize the database for current area
- */
-
- void reorganize_database(int dodelay)
- {
- if (getuid() != geteuid())
- {
- ding();
- message("Only database administrators can reorganize the database ");
- if (dodelay) sleep(2);
- return;
- }
- if (!database_exists())
- {
- ding();
- message("There is no database for problem area `%' ", CurrentArea());
- if (dodelay) sleep(2);
- return;
- }
-
- const char *msg = "Do you really want to reorganize this database (n|y)? ";
- if (yes_or_no(msg, redisplay_commands, No, 0))
- {
- datum key, data; // just placeholders here
- update_database(key, data, REORGANIZED);
- }
- }
-
- /*
- ** search - prompt for pattern \(regexp\) and display the matches.
- ** Returns zero if we only need to redisplay
- ** the prompt, else returns one.
- */
-
- //
- // Ways to search -- over full problem text or just the header
- //
- enum SearchMethod { FULLTEXT, HEADER };
-
- static int search(const SearchMethod how)
- {
- if (!database_exists())
- {
- ding();
- message("There is no database for problem area `%' ", CurrentArea());
- sleep(2);
- return 0;
- }
-
- char *keywords = prompt("Search for --> ", redisplay_commands);
- const char *separators = " ,\t";
- const char **list = tokenize(keywords, separators);
- DELETE keywords;
-
- if (!list[0]) return 0; // no words to search for
-
- //
- // Build a regular expression to search for.
- // We want to build a pattern of the form:
- //
- // word1|word2|word3|word4 ...
- //
- int len = 0; // total length of all words in `list\'
- for (int i = 0; list[i]; i++)
- len += (int) strlen(list[i]) + 1; // plus one for the `|\'
- char *line = new char[len + 1];
- line[0] = 0; // make it into a valid string
-
- //
- // Are each of the words individually a valid regexp?
- //
- regexp *regex;
- for (i = 0; list[i]; i++)
- {
- if ((regex = regcomp(list[i])) == 0)
- {
- ding();
- const char *fmt = "`%s' isn't a valid regex: %s , skipping ... ";
- char *msg = new char[strlen(fmt) + strlen(list[i]) +
- strlen(REerror) - 3];
- (void)sprintf(msg, fmt, list[i], REerror);
- message(msg);
- sleep(2);
- DELETE msg;
- DELETE line;
- }
- else
- {
- (void)strcat(line, list[i]);
- (void)strcat(line, "|");
- }
- DELETE (char *) regex;
- }
-
- //
- // Remove any trailing `|\'s.
- //
- if (line[strlen(line) - 1] == '|') line[strlen(line) - 1] = 0;
- if (strlen(line) == 0)
- {
- ding();
- message("No valid regular expressions among your keywords ");
- sleep(2);
- DELETE line;
- return 0;
- }
- if ((regex = regcomp(line)) == 0)
- {
- ding();
- message("regcomp() failed: %s", REerror);
- sleep(2);
- DELETE line;
- return 0;
- }
-
- open_database(GDBM_READER);
- datum key = gdbm_firstkey(GdbmFile);
- if (!key.dptr)
- {
- const char *fmt = "Area `%s' appears to be empty ";
- char *msg = new char[strlen(fmt) + strlen(CurrentArea()) - 1];
- (void)sprintf(msg, fmt, CurrentArea());
- ding();
- message(msg);
- sleep(2);
- DELETE msg;
- DELETE line;
- DELETE (char *) regex;
- return 0;
- }
-
- DList summaryLines; // listing of problem summaries
- datum data, tmp;
- const int chunksize = 100;
- int size = chunksize, pos = 0;
- char **lines = new char*[size];
-
- message("Reading problems ... ");
-
- while (1)
- {
- data = gdbm_fetch(GdbmFile, key);
- switch (how)
- {
- case HEADER:
- {
- //
- // Search only the problem header.
- //
- char *tail = data.dptr;
- for (int i = 0; i < NFields(); i++)
- {
- tail = strchr(tail, '\n');
- tail += 1; // step past the newline
- }
- *tail = 0; // treat the header as one long string
- if (regexec(regex, data.dptr)) lines[pos++] = summary_info(data);
- }
- break;
- case FULLTEXT:
- //
- // Search over full problem text.
- //
- if (regexec(regex, data.dptr)) lines[pos++] = summary_info(data);
- break;
- default: error("file %s, line %d, illegal case in switch()",
- __FILE__, __LINE__);
- }
- if (pos == size)
- {
- // grow \'lines\'
- char **newspace = new char*[size += chunksize];
- for (int i = 0; i < pos; i++) newspace[i] = lines[i];
- DELETE lines;
- lines = newspace;
- }
- free(data.dptr);
- tmp = gdbm_nextkey(GdbmFile, key);
- free(key.dptr);
- key = tmp;
- if (!key.dptr) break;
- }
- gdbm_close(GdbmFile);
- message("Reading problems ... done");
- DELETE (char *) regex;
-
- //
- // sort lines "most recently updated" first
- //
- qsort(lines, pos, sizeof(char**), sort_by_date);
- for (i = 0; i < pos; i++)
- summaryLines.add(new DLink((char **)&lines[i]));
- delete lines;
-
- //
- // Are there any problem summaries to peruse?
- //
- if (!summaryLines.nelems())
- {
- ding();
- message("No matches for regex `%' ", line);
- sleep(2);
- delete line;
- return 0;
- }
-
- initialize_lister(&summaryLines);
- initial_listing(&summaryLines);
-
- char *fmt = "%s (regex: %s) ---- q (quit) H (help)";
- char *suffix = new char[strlen(fmt) + strlen(CurrentArea()) +
- strlen(line) - 3];
-
- (void)sprintf(suffix, fmt, CurrentArea(), line);
- update_modeline(ModelinePrefix, suffix);
- delete suffix;
- delete line;
- summaryLines.saveYXPos(0, goal_column(&summaryLines));
- if (summaryLines.currLine()->length() > columns())
- leftshift_current_line(&summaryLines);
- else
- move_cursor(summaryLines.savedYPos(), summaryLines.savedXPos());
- synch_display();
- lister_cmd_loop(&summaryLines);
-
- return 1;
- }
-
- //
- // the header data -- needs to be shared between display_header\(\) and
- // modify_keywords\(\) so that we can call prompt\(\)
- // using display_header as its second argument.
- static datum header_data;
-
- /*
- ** display_header - put up the header of the problem in `data\'.
- */
-
- static void display_header()
- {
- cursor_home();
- 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();
-
- int flen = max_field_length() + 1;
- char *tmp1 = header_data.dptr, *tmp2;
-
- for (i = 0; i < NFields() && i < rows() - 2; i++)
- {
- tmp2 = strchr(tmp1, '\n');
- *tmp2 = 0; // stringify
- move_cursor(i, flen);
- display_string(tmp1 + flen);
- *tmp2 = '\n'; // un-stringify
- tmp1 = tmp2 + 1; // step past newline to next Field
- }
-
- // clear any remaining potentially dirty lines
- for (; i < rows() - 2; i++) { clear_to_end_of_line(); cursor_wrap(); }
- }
-
- /*
- ** modify_keywords - allows the problem owner or the administrator
- ** to change the Keyword field.
- */
-
- int modify_keywords(const char *number)
- {
- if (!database_exists())
- {
- ding();
- message("There is no database for problem area `%' ", CurrentArea());
- if (!number) sleep(2);
- return 0;
- }
- datum key;
- key.dptr = number ? (char *) number : prompt("Problem # --> ",
- redisplay_commands);
- key.dsize = int(strlen(key.dptr)) + 1;
-
- open_database(GDBM_READER);
- datum data = gdbm_fetch(GdbmFile, key);
- gdbm_close(GdbmFile);
-
- if (!data.dptr)
- {
- ding();
- message("There is no problem # % ", key.dptr);
- if (!number) { delete key.dptr; sleep(2); }
- return 0;
- }
- header_data = data;
-
- //
- // Are we authorized to modify the problem\'s keywords?
- //
- char *tmp = strchr(data.dptr, '\n'); // logger is Fields\[1\]
- tmp += 1; // step past the newline
- tmp += max_field_length() + 1; // the logger
- int llen = strchr(tmp, '\n') - tmp; // # of chars in logger
- const char *user = username();
-
- if ((llen != strlen(user) || strncmp(user, tmp, llen)) &&
- getuid() != geteuid())
- {
- ding();
- message("You're not authorized to modify problem # %'s keywords ",
- key.dptr);
- if (!number) { delete key.dptr; sleep(2); }
- return 0;
- }
-
- //
- // Put up problem header.
- //
- const char *fmt = "%s # %s (modifying keywords)";
- String suffix = String(CurrentArea()) + " # " + key.dptr +
- " (modifying keywords)";
- String old_modeline(current_modeline);
-
- update_modeline(ModelinePrefix, suffix);
- display_header();
-
- char *newkeywords = prompt("New keyword field --> ", display_header);
-
- //
- // Make new data datum.
- //
- datum newdata;
- tmp = data.dptr;
-
- //
- // Find old Keyword field -- Fields\[5\].
- //
- for (int i = 0; i < 5; i++)
- {
- tmp = strchr(tmp, '\n');
- tmp += 1; // step past newline
- }
- tmp += max_field_length() + 1; // the Keywords field data
- newdata.dsize = data.dsize + (int)strlen(newkeywords) -
- (strchr(tmp, '\n') - tmp);
- newdata.dptr = new char[newdata.dsize];
- *tmp++ = 0; // stringify data.dptr
- (void)strcpy(newdata.dptr, data.dptr); // data preceding Keywords field
- (void)strcat(newdata.dptr, newkeywords); // new keywords
- (void)strcat(newdata.dptr, strchr(tmp, '\n')); // following data
- free(data.dptr);
-
- update_existing_problem(newdata, key, KEYWORDMOD);
- update_modeline(old_modeline); // redisplay previous modeline
- if (!number) delete key.dptr;
- delete newdata.dptr;
- return 1;
- }
-
- /*
- ** problem - examine the `area\' in problem
- */
-
- static void problem(const char *area)
- {
- const char *the_area = is_area(area) ? area : choose_problem_area();
- const char *helpmsg = "The Available Commands:";
- setCurrentArea(the_area);
- commands_screen();
- String suffix = String(CurrentArea()) + " ---- q (quit) H (help)";
-
- update_modeline(ModelinePrefix, suffix);
- message("Your Choice --> ");
-
- char key;
- char refresh = 1; // need to redisplay command list?
- char redomsg = 1; // need to redisplay the message?
- while (1)
- {
- if (resumingAfterSuspension ||
- #ifdef SIGWINCH
- windowSizeChanged ||
- #endif
- read(0, &key, 1) < 0 || // assume only fails when errno==EINTR
- key == KEY_CTL_L)
- {
- #ifdef SIGWINCH
- if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
- #endif
- resumingAfterSuspension = 0;
- redisplay_commands();
- refresh = 0;
- }
- else
- switch(key)
- {
- case KEY_l:
- log_new_problem(); refresh = 1; break;
- case KEY_e:
- refresh = examine_problem(); break;
- case KEY_v:
- refresh = view_summary_lines(); break;
- case KEY_a:
- refresh = append_to_problem(); break;
- case KEY_s:
- subscribe_to_area(); refresh = 0; break;
- case KEY_u:
- unsubscribe_from_area(); refresh = 0; break;
- case KEY_c:
- refresh = close_problem(); break;
- case KEY_k:
- refresh = search(HEADER); break;
- case KEY_K:
- refresh = search(FULLTEXT); break;
- case KEY_M:
- refresh = modify_keywords(); break;
- case KEY_R:
- refresh = reopen_problem(); break;
- case KEY_d:
- (void)delete_problem(); refresh = 0; break;
- case KEY_r:
- reorganize_database(); refresh = 0; break;
- case KEY_q:
- return;
- case KEY_H: case KEY_QM:
- help((const char **)&Commands[0], NCommands(), helpmsg);
- refresh = 1; break;
- default:
- ding(); redomsg = refresh = 0; break;
- }
- if (refresh)
- {
- commands_screen();
- update_modeline(ModelinePrefix, suffix);
- }
- if (redomsg) message("Your Choice --> ");
- redomsg = 1;
- }
- }
-
- main(int argc, char *argv[])
- {
- if (!isatty(0) || !isatty(1))
- {
- (void)fputs("stdin & stdout must be terminals\n", stderr);
- exit(1);
- }
-
- //
- // Usage: problem \[-v\] \[-d\] \[area1\] \[area2\] \[...\]
- //
- // We only accept the -d flag if it is the first argument.
- // It directs us to use a different directory as our HomeBase.
- // Regarding problem areas, we just ignore invalid ones.
- // The \'-v\' option just prints out the version string and exits.
- //
- argc--; argv++;
- if (argc && strcmp(*argv, "-v") == 0)
- {
- fputs(Version, stdout);
- exit(0);
- }
- else if (argc && strcmp(*argv, "-d") == 0)
- {
- argc--; argv++;
- if (!argc)
- {
- fputs("Ignoring `-d' flag - no corresponding directory.", stdout);
- sleep(2);
- }
- else
- {
- HomeBase = *argv;
- argc--; argv++;
- }
- }
-
- //
- // If you don\'t have SIGINTERRUPT then signals almost surely interrupt
- // read\(2\). If you do have it, you\'ll need this to ensure that signals
- // interrupt slow system calls -- we\'re only interested in read.
- //
- #ifdef SIGINTERRUPT
- if (siginterrupt(SIGTSTP, 1) < 0 || siginterrupt(SIGWINCH, 1) < 0)
- {
- perror("siginterrupt()"); exit(1);
- }
- #endif
-
- if (umask(022) < 0) { perror("umask()"); exit(1); }
-
- set_new_handler(free_store_exception);
- init_display();
- set_signals();
-
- if (!database_directory_exists())
- error("Database directory `%s' isn't accessible.", HomeBase);
-
- while (1) problem(*argv ? *argv++ : choose_problem_area());
- }
-