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
- **
- ** problem2.C problem2.C 1.30 Delta\'d: 17:54:17 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.
- */
-
- /*
- ** append_to_problem - append to an already existing problem. Returns
- ** false if the problem does not exist. This indicates
- ** that we do not 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 them
-
- (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(%s) failed",
- __FILE__, __LINE__, tmpfile);
-
- FILE *tmpfp = fdopen(tmpfd, "w+");
- if (!tmpfp)
- error("file %s, line %d, fdopen() failed", __FILE__, __LINE__);
-
- //
- // We use the sequence_file for locking.
- //
- 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
-
- for (char *line = fgetline(mailfp, 10); line; line = fgetline(mailfp, 10))
- {
- if (strcmp(line, user) == 0)
- {
- DELETE line;
- continue;
- }
- (void)fprintf(tmpfp, "%s\n", line);
- DELETE line;
- }
-
- if (feof(mailfp) && !ferror(mailfp))
- {
- //
- // Alternate maillist correctly constructed.
- //
- (void)fclose(tmpfp);
- (void)fclose(mailfp);
- (void) close(tmpfd);
- (void) close(mailfd);
-
- //
- // Remove old maillist.
- //
- String maillist(mail_list_prefix());
- maillist += ".";
- maillist += CurrentArea();
-
- 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);
-
- 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 ");
- }
-
- unlock_file(seqfd);
- (void)close(seqfd);
- unblock_tstp_and_winch();
-
- DELETE tmpfile;
-
- 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. Remember that the problem is
- // stored in the database as a string, with the null character counted
- // as part of its length.
- //
- int fd;
- if ((fd = open(file, O_RDWR)) < 0)
- error("file %s, line %d, open(%s, O_RDWR) failed",
- __FILE__, __LINE__, file);
- if (write(fd, data.dptr, data.dsize - 1) != data.dsize - 1)
- error("file %s, line %d, write() failed", __FILE__, __LINE__);
-
- #ifdef NOLESS
- const char *pager = getenv("PAGER");
- if (pager == 0) pager = "more";
-
- String argstring(pager);
- argstring += " ";
- argstring += file;
-
- //
- // We tokenize because the user could have PAGER
- // set to "less -i -Q -M", which execvp would not
- // recognize as a legal filename.
- //
- const char **args = tokenize(argstring, " \t");
- pager = args[0];
- #else
- //
- // Use "less".
- //
- const char *prefix = "-PProblem #";
- const char *suffix = " -- line %lb?L/%L. byte %bB?s/%s. ?e(END) :?pB%pB\\% ..-- q (quit) H (help)";
-
- String prompt(prefix);
- prompt += key.dptr;
- prompt += suffix;
-
- const char *pager = "less";
- const char *args[5];
- args[0] = pager;
- args[1] = "-d";
- args[2] = prompt;
- args[3] = file;
- args[4] = 0;
- #endif /*NOLESS*/
-
- #ifdef NOLESS
- //
- // We prompt for a keypress before returning from the pager, so
- // that we do not immediately return when we hit last page.
- // This is because some pagers -- "more" being one -- exit when
- // you get to the last page, which makes perusal of the last page
- // difficult. We do not have to do this with "less", since it is exited
- // deliberately by typing a q.
- //
- if (!execute(pager, args, 1))
- #else
- if (!execute(pager, args))
- #endif /*NOLESS*/
- {
- (void)unlink(file);
- error("file %s, line %d, problem running pager: `%s'",
- __FILE__, __LINE__, pager);
- }
-
- (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 newd 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; // do not 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 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))
- //
- // Sort "open" before "closed".
- //
- return -strncmp(tmpa, tmpb, 4);
- else if (strncmp(tmpa, tmpb, 6))
- //
- // Sort by severity.
- //
- return strncmp(tmpa, tmpb, 6);
- 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)
- {
- String msg("Area `");
- msg += CurrentArea();
- msg += "' appears to be empty ";
-
- ding();
- message(msg);
- sleep(2);
-
- 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);
-
- String suffix(CurrentArea());
- suffix += " ---- q (quit) H (help)";
-
- update_modeline(ModelinePrefix, 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 reopen, 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 **list = tokenize(keywords, " ,\t");
- 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)
- {
- String msg("`");
- msg += list[i];
- msg += "' isn't a valid regex: ";
- msg += REerror;
- msg += ", skipping ... ";
-
- ding();
- message(msg);
- sleep(2);
- 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: %", REerror);
- sleep(2);
- DELETE line;
- return 0;
- }
-
- open_database(GDBM_READER);
- datum key = gdbm_firstkey(GdbmFile);
- if (!key.dptr)
- {
- String msg("Area `");
- msg += CurrentArea();
- msg += "' appears to be empty ";
-
- ding();
- message(msg);
- sleep(2);
- 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.
- //
- 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);
-
- String suffix(CurrentArea());
- suffix += " (regex: ";
- suffix += line;
- suffix += ") ---- q (quit) H (help)";
-
- update_modeline(ModelinePrefix, 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()
- {
- clear_display_area();
-
- enter_standout_mode();
- for (int i = 0; i < NFields() && i < rows() - 2; i++)
- 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'; // unstringify
- tmp1 = tmp2 + 1; // step past newline to next Field
- }
- }
-
- /*
- ** 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.
- //
- String suffix(CurrentArea());
- suffix += " # ";
- suffix += key.dptr;
- suffix += " (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];
-
- char overwritten = *tmp;
- *tmp = 0; // stringify data.dptr
- (void)strcpy(newdata.dptr, data.dptr); // data preceding Keywords field
- *tmp = overwritten; // replace overwritten character
- (void)strcat(newdata.dptr, newkeywords); // new keywords
- (void)strcat(newdata.dptr, strchr(tmp, '\n')); // following data
-
- DELETE newkeywords;
- free(data.dptr);
-
- update_existing_problem(newdata, key, KEYWORDMOD);
- update_modeline(); // force comlete redisplay
- update_modeline(old_modeline); // update to old version
-
- if (!number) DELETE key.dptr;
- DELETE newdata.dptr;
-
- return 1;
- }
-
- /*
- ** modify_severity - allows the problem owner or the administrator
- ** to change the Severity field.
- */
-
- int modify_severity(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 modify the problem\'s severity?
- //
- 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 severity ",
- key.dptr);
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- return 0;
- }
-
- char newSeverity = get_severity(display_header);
-
- //
- // Find old Severity field -- Fields\[9\].
- //
- tmp = data.dptr;
- for (int i = 0; i < 9; i++)
- {
- tmp = strchr(tmp, '\n');
- tmp += 1; // step past newline
- }
- tmp += max_field_length() + 1; // the Severity field data
-
-
- //
- // Are the severities actually different?
- //
- if (*tmp == newSeverity)
- {
- ding();
- message("The old and new severities are the same");
- if (!number)
- {
- DELETE key.dptr;
- sleep(2);
- }
- return 0;
- }
-
- *tmp = newSeverity; // set new Severity
-
- update_existing_problem(data, key, SEVERITYMOD);
- update_modeline();
-
- if (!number) DELETE key.dptr;
- free(data.dptr);
-
- return 1;
- }
-
- /*
- ** transfer_problem - allows the problem owner or the administrator
- ** to move the problem to another area.
- */
-
- int transfer_problem(const char *number, char *area)
- {
- 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 transfer the problem to another area?
- //
- 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 transfer problem # %", key.dptr);
- if (!number)
- {
- DELETE key.dptr;
- free(data.dptr);
- sleep(2);
- }
- return 0;
- }
-
- //
- // Area is null when called from the command display.
- //
- if (!area) area = prompt("New Area --> ", redisplay_commands);
-
- //
- // Is area a valid area?
- //
- // This is guaranteed to be true if we get here when called
- // from the view window.
- //
- if (!is_area(area))
- {
- ding();
- message("`%' isn't a valid problem area", area);
- if (!number)
- {
- DELETE key.dptr;
- DELETE area;
- free(data.dptr);
- sleep(2);
- }
- return 0;
- }
-
- //
- // Is area really a new problem area?
- //
- // This is guaranteed to be true if we get here when called
- // from the view window.
- //
- if (strcmp(area, CurrentArea()) == 0)
- {
- ding();
- message("`%' is the same as the current area", area);
- if (!number)
- {
- DELETE key.dptr;
- DELETE area;
- free(data.dptr);
- sleep(2);
- }
- return 0;
- }
-
- //
- // Build new problem header -- must update Area field.
- //
- if (strlen(area) <= strlen(CurrentArea()))
- {
- //
- // We can just overwrite the old area with the new one.
- // We blank out any extra characters in the old area.
- //
- tmp = data.dptr + max_field_length() + 1;
- strncpy(tmp, area, strlen(area));
- int i = int (strlen(CurrentArea()) - strlen(area));
- tmp += strlen(area);
- for (int j = 0; j < i; j++) *tmp++ = ' ';
-
- update_existing_problem(data, key, TRANSFER);
-
- free(data.dptr);
- }
- else
- {
- //
- // We must build a new problem entry.
- //
- int i = int (strlen(area) - strlen(CurrentArea()));
- datum newdata;
- newdata.dsize = data.dsize + i;
- newdata.dptr = new char[newdata.dsize];
- strncpy(newdata.dptr, data.dptr, max_field_length() + 1);
- *(newdata.dptr + max_field_length() + 1) = 0;
- strcat(newdata.dptr, area);
- strcat(newdata.dptr, strchr(data.dptr, '\n'));
- free(data.dptr);
-
- update_existing_problem(newdata, key, TRANSFER);
-
- DELETE newdata.dptr;
- }
-
- update_modeline(); // completely redisplay modeline
-
- if (!number)
- {
- DELETE key.dptr;
- DELETE area;
- }
-
- 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(CurrentArea());
- suffix += " ---- 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_P:
- refresh = modify_severity(); break;
- case KEY_T:
- refresh = transfer_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 do not have SIGINTERRUPT then signals almost surely interrupt
- // read. If you do have it, you will need this to ensure that signals
- // interrupt slow system calls -- we are only interested in read.
- //
- #ifdef SIGINTERRUPT
- #ifdef SIGTSTP
- if (siginterrupt(SIGTSTP, 1) < 0)
- {
- perror("siginterrupt(SIGTSTP)");
- exit(1);
- }
- #endif
- #ifdef SIGWINCH
- if (siginterrupt(SIGWINCH, 1) < 0)
- {
- perror("siginterrupt(SIGWINCH)");
- exit(1);
- }
- #endif
- #endif
-
- set_new_handler(free_store_exception);
- initialize();
- set_signals();
-
- if (!database_directory_exists())
- error("Database directory `%s' isn't accessible.", HomeBase);
-
- while (1) problem(*argv ? *argv++ : choose_problem_area());
- }
-