2 * CDE - Common Desktop Environment
4 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
6 * These libraries and programs are free software; you can
7 * redistribute them and/or modify them under the terms of the GNU
8 * Lesser General Public License as published by the Free Software
9 * Foundation; either version 2 of the License, or (at your option)
12 * These libraries and programs are distributed in the hope that
13 * they will be useful, but WITHOUT ANY WARRANTY; without even the
14 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU Lesser General Public License for more
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with these librararies and programs; if not, write
20 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21 * Floor, Boston, MA 02110-1301 USA
24 * COMPONENT_NAME: austext
38 * (C) COPYRIGHT International Business Machines Corp. 1992,1995
40 * US Government Users Restricted Rights - Use, duplication or
41 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
43 /******************* TOMITA.C *******************
44 * $TOG: tomita.c /main/9 1998/04/17 11:23:38 mgreess $
46 * Replaces original tomita but removes curses dependencies
47 * and uses Opera Engine for deletes rather than hard
50 * In effect, tomita is two programs.
51 * Program #1 browses an input list of opera record ids and
52 * prompts user to confirm that they should be deleted from opera.
53 * The confirmed list is written to an output file that is identical in format.
54 * Program 1 can be run anytime because it only reads the database.
56 * Program #2 deletes record from an input list, presumably
57 * the output of program #1. Program #2 writes to vista and changes it.
58 * It MUST be run offline when no users are logged into opera,
59 * in order to prevent database corruption.
60 * Currently password is passed on command line.
61 * The password is maintained in an encrypted flat file.
62 * It can be changed from an undocumented Opera Engine function
63 * available in tuiopera.
65 * RECORD ID FILE FORMAT (shdscrd.lst format):
66 * One record to be deleted per line.
67 * 3 words per line separated by whitespace, everything thereafter is comments.
68 * (These programs only use the first 2 words).
69 * All words may be optionally enclosed in double quotes
70 * to capture embedded blanks ("xxx xxx").
72 * databasename recordid userid comments...\n
76 * Revision 2.2 1995/10/25 15:21:36 miker
79 * Revision 2.1 1995/09/22 22:17:11 miker
80 * Freeze DtSearch 0.1, AusText 2.1.8
82 * Revision 1.11 1995/09/05 19:14:39 miker
83 * Removed password requirement. DtSearch header file and function
84 * name changes. Made usrblk universal global.
93 #define PRINT_MESSAGES \
94 { puts (DtSearchGetMessages()); DtSearchFreeMessages(); }
95 #define TOKEN_DELIM " \t\n"
98 #define PROGNAME "TOMITA"
102 /*------------------ GLOBALS ------------------*/
103 static int debug_mode = FALSE;
104 static int prog = 'B'; /* 'D' = deleting, 'B' = browsing */
105 static int shutdown_now = FALSE;
106 static int yesarg = FALSE;
107 static int retncode = 0;
108 static int max_dbacount = DBACOUNT;
109 static FILE *inf, *outf;
110 static char *infname;
111 static char *outfname;
113 static long records_read;
114 static char parsed_dbname [24];
115 static DBLK *parsed_dblk;
117 char parsed_recid [2048];
120 /************************************************/
122 /* print_exit_code */
124 /************************************************/
125 /* Called from inside DtSearchExit() at austext_exit_last */
126 static void print_exit_code (int exit_code)
128 printf ( catgets(dtsearch_catd, MS_tomita, 3,
129 "%s: Exit Code = %d.\n") ,
130 aa_argv0, exit_code);
132 } /* print_exit_code() */
135 /************************************************/
139 /************************************************/
140 /* Interrupt handler for all termination signals
141 * in Delete mode. Just sets global flag so we
142 * can come down gracefully between deletions.
144 static void kill_delete (int sig)
147 printf ( catgets(dtsearch_catd, MS_tomita, 1,
148 "\n%s Received interrupt %d.\n"
149 " Program will stop after current batch of deletions.\n") ,
152 } /* kill_delete() */
155 /************************************************/
159 /************************************************/
160 static void retncode_abort (int location)
162 fputc ('\n', aa_stderr);
163 if (DtSearchHasMessages ())
164 fprintf (aa_stderr, "%s\n", DtSearchGetMessages ());
166 PROGNAME "%d Program abort. usrblk.retncode = %d. Exit code = 3.\n",
167 location, usrblk.retncode);
169 } /* retncode_abort() */
172 /****************************************/
174 /* change_database */
176 /****************************************/
177 /* Changes usrblk.dblk to point to passed database name.
178 * Returns TRUE if successful.
180 static int change_database (char *newname)
184 for (db = usrblk.dblist; db != NULL; db = db->link)
185 if (strcmp (db->name, newname) == 0) {
190 /* Invalid newname. If deleting, just say which database is invalid. */
192 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 4,
193 "%s Database '%s' not found.\n") ,
194 PROGNAME"114", newname);
198 /* If browsing, tell user his options */
199 fprintf (aa_stderr, "%s", catgets(dtsearch_catd, MS_tomita, 5,
200 "Available choices are:") );
201 for (db = usrblk.dblist; db != NULL; db = db->link)
202 fprintf (aa_stderr, " '%s'", db->name);
203 fputc ('\n', aa_stderr);
205 } /* change_database() */
208 /****************************************/
212 /****************************************/
213 /* Parses a line from a standard formatted discard file.
214 * If first word indicates different database from usrblk.dblk,
215 * changes it. First token loaded into parsed_dbname
216 * (and parsed_dblk will be made to track it), and
217 * second token is loaded into parsed_recid.
218 * Tokens are separated by blanks and/or tabs,
219 * except 2nd token may have embedded spaces if it is
220 * surrounded by double quotes. Returns TRUE unless
221 * database couldn't change or other error, then returns FALSE.
223 static int parse_infbuf (char *infbuf)
228 /* Do all parsing in my own buf so infbuf not peppered with \0's */
229 strncpy (mybuf, infbuf, sizeof (mybuf));
230 mybuf[sizeof (mybuf) - 1] = 0;
232 /* Parse first token (database name) */
233 if ((ptr = strtok (mybuf, " \t")) == NULL) {
234 /* Msg #8 is used in two places */
235 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 8,
236 "%s Invalid input format: %.30s...\n") ,
237 PROGNAME"152", infbuf);
242 /* Change database if necessary */
243 if (strcmp (ptr, usrblk.dblk->name) != 0)
244 if (!change_database (ptr)) {
249 strcpy (parsed_dbname, ptr);
250 parsed_dblk = usrblk.dblk;
252 /* Hop over to beginning of 2nd token */
253 for (ptr += strlen (ptr) + 1; *ptr == ' ' || *ptr == '\t'; ptr++);
255 /* Get 2nd token (record id). Token delimiters depend
256 * on whether token begins with a double quote.
258 ptr = strtok (ptr, (*ptr == '\"') ? "\"" : " \t");
260 /* Msg #8 is used in two places */
261 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 8,
262 "%s Invalid input format: %.30s...\n") ,
263 PROGNAME"176", infbuf);
267 strncpy (parsed_recid, ptr, sizeof (parsed_recid));
268 parsed_recid[sizeof (parsed_recid) - 1] = 0;
271 } /* parse_infbuf() */
274 /****************************************/
278 /****************************************/
279 /* Program 1: displays records in input file,
280 * or user selected records, and if confirmed,
281 * writes their record ids to output file.
283 static int browser (void)
287 int redisplay_rec = FALSE;
292 char datestr[32]; /* "1946/04/17 13:03" */
296 /* All writes to output file will have same date string in comment */
298 strftime (datestr, sizeof (datestr), "%Y/%m/%d %H:%M", localtime (&stamp));
302 if (DtSearchHasMessages ()) {
307 /* Write main menu prompt */
308 printf ( catgets(dtsearch_catd, MS_tomita, 10,
309 "\n---------- SHOW NEXT RECORD ----------- Database = '%s'\n"
310 "q QUIT. Current Record Count = %ld\n"
311 "p Toggle PAUSE from %s.\n"
312 "n NEXT INPUT file record.\n"
313 "+ NEXT SEQUENTIAL database record.\n"
314 "- PRIOR SEQUENTIAL database record.\n"
315 "r REDISPLAY current record '%s'.\n"
316 "x CONFIRM DELETION of current record.\n"
317 "dxxx Change DATABASE to xxx.\n"
318 "\"xxx GET record id xxx (embedded blanks are ok).\n"
321 usrblk.dblk->dbrec.or_reccount,
322 (pausing) ? "on to OFF" : "off to ON",
323 usrblk.objrec.or_objkey
326 /* Read user's response. Remove user's \n. */
328 if ((fgets (userbuf, sizeof (userbuf), stdin)) == NULL) break;
329 if (strlen(userbuf) && userbuf[strlen(userbuf)-1] == '\n')
330 userbuf[strlen(userbuf)-1] = '\0';
334 /* depending on response, get database address into usrblk */
335 redisplay_rec = FALSE;
336 switch (tolower (*userbuf)) {
342 change_database (userbuf + 1);
352 if (usrblk.objrec.or_objkey[0] == 0) {
354 catgets(dtsearch_catd, MS_tomita, 11,
355 "%s Record buffer empty.\n"),
359 redisplay_rec = FALSE;
364 usrblk.request = (*userbuf == '+') ? OE_NEXT_DBA : OE_PREV_DBA;
371 catgets(dtsearch_catd, MS_tomita, 12,
372 "%s Input file unavailable.\n"),
377 if ((fgets (infbuf, sizeof (infbuf), inf)) == NULL)
380 catgets(dtsearch_catd, MS_tomita, 13,
381 "%s No more records in input file.\n"),
388 if (strlen(infbuf) && infbuf[strlen(infbuf)-1] == '\n')
389 infbuf[strlen(infbuf)-1] = '\0';
391 if (!parse_infbuf (infbuf))
393 usrblk.request = OE_RECKEY2DBA;
394 usrblk.query = parsed_recid;
399 ptr = strtok (userbuf, "\"");
400 if (ptr == NULL || *ptr == 0) {
402 catgets(dtsearch_catd, MS_tomita, 14,
403 "%s Invalid Record ID.\n"),
407 usrblk.request = OE_RECKEY2DBA;
414 * Write record id to output file. Format:
415 * dbasename "recid" userid comments(date)...
417 fprintf (outf, DISCARD_FORMAT, usrblk.dblk->name,
418 usrblk.objrec.or_objkey, usrblk.userid, datestr);
419 printf ( catgets(dtsearch_catd, MS_tomita, 15,
420 "%s '%s' appended to file of confirmed deletions.\n") ,
421 PROGNAME"317", usrblk.objrec.or_objkey);
425 printf ("%s", catgets(dtsearch_catd, MS_tomita, 16, "...what?\n"));
432 /* if user requested redisplay, skip the following OE code */
437 * check return code from attempt to get opera database
440 if (usrblk.retncode == OE_WRAPPED)
441 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 17,
442 "%s %s Engine wrapped to next record.\n") ,
443 PROGNAME"333", OE_prodname);
444 else if (usrblk.retncode != OE_OK)
445 retncode_abort (334);
447 /* retrieve the record and uncompress it */
448 usrblk.request = OE_GETREC;
450 if (usrblk.retncode != OE_OK)
451 retncode_abort (339);
454 /* display the record's cleartext, character by character */
455 printf ( catgets(dtsearch_catd, MS_tomita, 18,
459 "--------------------------------------\n") ,
460 usrblk.objrec.or_objkey,
461 (usrblk.abstrbufsz > 0) ? usrblk.abstrbuf :
462 catgets (dtsearch_catd, MS_misc, 1, "<null>"));
465 for (ptr = usrblk.cleartext; *ptr != 0; ptr++) {
468 * pause every so many lines so user can browse the
471 if (pausing && *ptr == '\n') {
472 if (++pause_counter >= PAUSE_ROWS) {
473 /* Msg 21 is used in two places */
474 printf ( "%s", catgets(dtsearch_catd, MS_tomita, 21,
475 "\n...push ENTER to continue... ") );
478 fgets (userbuf, sizeof (userbuf), stdin);
479 if (strlen(userbuf) && userbuf[strlen(userbuf)-1] == '\n')
480 userbuf[strlen(userbuf)-1] = '\0';
486 } /* end of cleartext printing */
488 /* display the user notes if any, character by character */
489 if (usrblk.notes != NULL) {
490 printf ( catgets(dtsearch_catd, MS_tomita, 20,
491 "--------------------------------------\n"
492 "End of Text Blob for '%s':\n\n"
494 "--------------------------------------\n") ,
495 usrblk.objrec.or_objkey);
497 for (llptr = usrblk.notes; llptr != NULL; llptr = llptr->link) {
498 for (ptr = llptr->data; *ptr != '\0'; ptr++) {
500 if (pausing && *ptr == '\n')
501 if (++pause_counter >= PAUSE_ROWS) {
502 /* Msg 21 is used in two places */
503 printf ( "%s", catgets(dtsearch_catd, MS_tomita, 21,
504 "\n...push ENTER to continue... ") );
507 fgets (userbuf, sizeof (userbuf), stdin);
508 if (strlen(userbuf) &&
509 userbuf[strlen(userbuf)-1] == '\n')
510 userbuf[strlen(userbuf)-1] = '\0';
517 } /* end of user notes printing */
519 printf ("--------------------------------------\n"
520 "End of Record '%s'.\n", usrblk.objrec.or_objkey);
522 } /* end of main menu loop */
527 /****************************************/
531 /****************************************/
532 /* Subroutine of deleter(). Reads discard file containing
533 * record ids to be deleted, converts to database addresses,
534 * loads usrblk.dbatab up to max batch size.
535 * Returns number of dba's added to table.
536 * Returns 0 when file is empty after last batch.
538 static int load_dbatab (void)
540 static int read_next_rec = TRUE;
541 static char last_dbname[24] = "";
542 static DBLK *last_dblk;
545 int first_err = TRUE;
551 next_dba = usrblk.dbatab;
554 /* MAIN LOOP - break it at EOF, max count, or dbname change */
555 while (usrblk.dbacount < max_dbacount) {
557 * Skip the read of the first record if the reason we left
558 * main loop the last time was because of a database name
559 * change, and the data from the last read is still in
560 * parsed_dbname, _dblk, and _recid. Update usrblk.dblk
561 * because it's based on the last table's database.
563 if (!read_next_rec) {
564 read_next_rec = TRUE;
565 usrblk.dblk = parsed_dblk;
569 if (fgets (buf, sizeof (buf), inf) == NULL)
576 buf[sizeof (buf) - 1] = 0; /* guarantee termination */
577 if (strlen(buf) && buf[strlen(buf)-1] == '\n')
578 buf[strlen(buf)-1] = '\0';
582 * Parse line into dbname and recid. Skip line if
585 if (!parse_infbuf (buf))
588 /* on very first read, save the database name */
589 if (last_dbname[0] == 0) {
590 strcpy (last_dbname, parsed_dbname);
591 last_dblk = parsed_dblk;
593 } /* finished reading next rec in input file */
596 * Test for change of database name. Restore usrblk.dblk
597 * to reflect all the records on the dba table so far. Then
598 * save the new dblk for when we are again called.
600 if (strcmp (last_dbname, parsed_dbname) != 0) {
601 read_next_rec = FALSE;
602 strcpy (last_dbname, parsed_dbname);
603 usrblk.dblk = last_dblk;
604 last_dblk = parsed_dblk;
609 * Call OE to get record's db address. Turn off debug
610 * temporarily so won't flood output with messages.
612 usrblk.query = parsed_recid;
613 usrblk.debug &= ~USRDBG_DELETE;
614 usrblk.request = OE_RECKEY2DBA;
616 if (debug_mode) /* restore */
617 usrblk.debug |= USRDBG_DELETE;
618 if (DtSearchHasMessages ()) {
623 if (usrblk.retncode == OE_WRAPPED) {
626 fputc ('\n', aa_stderr);
628 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 24,
629 "%s Database %s, '%s' not found.\n") ,
630 PROGNAME"482", parsed_dbname, parsed_recid);
633 else if (usrblk.retncode != OE_OK)
634 retncode_abort (486);
636 /* add db address to growing table */
637 *next_dba = usrblk.dba;
641 } /* end of main record read loop */
643 /* It is possible to exit the main loop, because database changed
644 * or whatever, but no records were added to usrblk.dbatab.
645 * If there are still records to be read from the input file,
646 * go back and try another pass.
648 if (inf != NULL && usrblk.dbacount == 0)
651 return usrblk.dbacount;
652 } /* load_dbatab() */
655 /****************************************/
659 /****************************************/
660 /* Program 2: deletes records specified in input file.
661 * Must be run offline when all online users have logged off.
663 static void deleter (char *infname)
666 long records_deleted;
667 time_t start_time, minutes, hours, seconds, elapsed;
671 printf ( catgets(dtsearch_catd, MS_tomita, 25,
672 "\nDO NOT CONTINUE under any of the following circumstances:\n"
673 "-> If the input file which lists record ids to be deleted is not\n"
675 "-> If any users are still accessing the affected database(s).\n"
676 "-> If any database files have not been backed up.\n\n"
677 "If you are sure you are ready to start deleting, enter 'y' now... ") ,
678 infname, OE_prodname);
680 fgets (buf, sizeof(buf)-1, stdin);
681 if (tolower (*buf) != 'y')
685 /* Make sure engine doesn't abort because of
686 * recurring changes to d99 files.
688 OE_sitecnfg_mtime = 0L;
690 /* Init table of db addrs */
691 usrblk.dbatab = austext_malloc
692 (sizeof (DB_ADDR) * (max_dbacount + 2), PROGNAME "531", NULL);
693 usrblk.dbacount = 0; /* number of recs currently in table */
695 /* Init status msg stuff */
697 records_deleted = 0L;
700 signal (SIGINT, kill_delete);
701 signal (SIGQUIT, kill_delete);
702 signal (SIGTRAP, kill_delete);
703 signal (SIGTERM, kill_delete);
705 signal (SIGPWR, kill_delete);
708 signal (SIGXCPU, kill_delete); /* cpu time limit exceeded */
709 signal (SIGDANGER, kill_delete); /* imminent paging space
714 while (load_dbatab ()) {
716 * Stop now if we have exceeded user specified time limit
717 * or if user sent termination or interrupt signal.
721 elapsed = time (NULL) - start_time;
722 if (maxtime > 0L && elapsed >= maxtime)
725 /* echo status for humans who might be watching */
726 hours = elapsed / 3600L;
727 seconds = elapsed - (3600L * hours); /* remaining after hours */
728 minutes = seconds / 60L;
729 seconds = seconds - (60L * minutes);
730 printf ( catgets(dtsearch_catd, MS_tomita, 26,
731 "%s %ld read, %ld deleted, %ldh %2ldm %2lds elapsed.\n"
732 " Database '%s': Current record count = %ld, Batch size = %d.\n") ,
733 aa_argv0, records_read, records_deleted,
734 hours, minutes, seconds,
735 usrblk.dblk->name, usrblk.dblk->dbrec.or_reccount, usrblk.dbacount);
736 /*****fflush (stdout);*****/
738 /* call OE to delete batch of records */
739 usrblk.request = OE_DELETE_BATCH;
741 if (DtSearchHasMessages ()) {
745 if (usrblk.retncode != OE_OK)
746 retncode_abort (572);
747 records_deleted += usrblk.dbacount;
749 } /* end main loop */
752 /* Print final status messages */
753 elapsed = time (NULL) - start_time; /* total elapsed time */
755 hours = elapsed / 3600L;
756 seconds = elapsed - (3600L * hours); /* remaining after hours */
757 minutes = seconds / 60L;
758 seconds = seconds - (60L * minutes); /* remaining after hours
760 printf ( catgets(dtsearch_catd, MS_tomita, 27,
761 "%s %ld records read from input file. %ld were deleted and\n"
762 " %ld were not found in %ld hours, %ld minutes, %ld seconds,\n") ,
763 aa_argv0, records_read, records_deleted,
764 records_read - records_deleted,
765 hours, minutes, seconds);
767 /* Figure average time for a deletion */
768 elapsed = (records_deleted) ? elapsed / records_deleted : 0L;
769 minutes = elapsed / 60L;
770 seconds = elapsed - (60L * minutes);
771 printf ( catgets(dtsearch_catd, MS_tomita, 28,
772 " or an average of %ld minutes, %ld seconds per record deleted.\n"),
778 /****************************************/
782 /****************************************/
783 int main (int argc, char *argv[])
790 setlocale (LC_ALL, "");
791 dtsearch_catd = catopen (FNAME_DTSRCAT, 0);
793 strftime (timebuf, sizeof (timebuf),
794 catgets(dtsearch_catd, MS_misc, 22, "%A, %b %d %Y, %I:%M %p"),
795 localtime (&mytime));
796 printf (catgets(dtsearch_catd, MS_tomita, 29,
799 austext_exit_last = print_exit_code;
801 signal (SIGINT, DtSearchExit);
802 signal (SIGTERM, DtSearchExit);
803 /****memset (&usrblk, 0, sizeof(USRBLK));****/
805 /* Validate program number argument */
808 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 30,
809 "\nUSAGE: %s [options]\n"
810 " -i Input file name. If not specified, defaults to %s.\n"
811 " -d[v] Print debug statements.\n"
812 " -dv turns on verbose (record-by-record) debugging.\n"
813 " -t<N> Max desired number of seconds of run time.\n"
814 " Ctrl-C/Break will also stop deletion at next record.\n"
815 " -n<N> Change number of records in a batch from %d to <N>.\n"
816 " -y Automatically answers 'yes' to Delete mode confirm prompt.\n"
817 " -d trace deletion operations.\n") ,
818 aa_argv0, FNAME_DISCARD_DATA,
819 FNAME_CONFIRM_LIST, FNAME_CONFIRM_LIST, DBACOUNT);
822 prog = toupper (argv[1][0]);
823 if (prog != 'B' && prog != 'D')
826 /* Initialize defaults depending on program mode */
828 infname = FNAME_DISCARD_DATA;
829 outfname = FNAME_CONFIRM_LIST;
832 infname = FNAME_CONFIRM_LIST;
833 outfname = PROGNAME "654";
837 /* Save rest of command line arguments */
838 for (argc -= 2, argv += 2; argc > 0; argc--, argv++) {
840 switch (tolower (arg[1])) {
851 usrblk.debug |= USRDBG_DELETE;
853 usrblk.debug |= USRDBG_VERBOSE;
861 maxtime = atol (arg + 2);
865 max_dbacount = atol (arg + 2);
869 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 31,
870 "\n%s Unknown argument '%s'.\n") ,
875 } /* end arg parsing */
877 /* Open input file to test for its existence.
878 * For the Browse program, file ptr 'inf' == NULL
879 * means the file is not open.
881 if ((inf = fopen (infname, "r")) == NULL) {
883 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 32,
884 "%s Unable to open input file '%s'.\n") ,
885 PROGNAME"710", infname);
890 /* If browsing, get output file name and
891 * open it to test for write permission.
894 if ((outf = fopen (outfname, "a ")) == NULL)
895 /* the blank in "a " works around old aix bug */
897 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 33,
898 "\n%s Unable to open output file '%s'.\n") ,
899 PROGNAME"721", outfname);
904 /* Initialize the opera engine, i.e. open the database */
905 printf ( catgets(dtsearch_catd, MS_tomita, 34,
906 "Initializing %s engine...\n"),
908 strcpy (usrblk.userid, "ToMiTa");
909 usrblk.request = OE_INITIALIZE;
910 usrblk.query = AUSAPI_VERSION;
912 if (usrblk.retncode != OE_OK)
913 retncode_abort (733);
921 usrblk.request = OE_SHUTDOWN;
923 printf ( "%s", catgets(dtsearch_catd, MS_tomita, 36,
924 "Normal engine shutdown.\n") );
928 /******************* TOMITA.C *******************/