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.
94 #define PRINT_MESSAGES \
95 { puts (DtSearchGetMessages()); DtSearchFreeMessages(); }
96 #define TOKEN_DELIM " \t\n"
99 #define PROGNAME "TOMITA"
103 /*------------------ GLOBALS ------------------*/
104 static int debug_mode = FALSE;
105 static int prog = 'B'; /* 'D' = deleting, 'B' = browsing */
106 static int shutdown_now = FALSE;
107 static int yesarg = FALSE;
108 static int retncode = 0;
109 static int max_dbacount = DBACOUNT;
110 static FILE *inf, *outf;
111 static char *infname;
112 static char *outfname;
114 static long records_read;
115 static char parsed_dbname [24];
116 static DBLK *parsed_dblk;
118 char parsed_recid [2048];
121 /************************************************/
123 /* print_exit_code */
125 /************************************************/
126 /* Called from inside DtSearchExit() at austext_exit_last */
127 static void print_exit_code (int exit_code)
129 printf ( catgets(dtsearch_catd, MS_tomita, 3,
130 "%s: Exit Code = %d.\n") ,
131 aa_argv0, exit_code);
133 } /* print_exit_code() */
136 /************************************************/
140 /************************************************/
141 /* Interrupt handler for all termination signals
142 * in Delete mode. Just sets global flag so we
143 * can come down gracefully between deletions.
145 static void kill_delete (int sig)
148 printf ( catgets(dtsearch_catd, MS_tomita, 1,
149 "\n%s Received interrupt %d.\n"
150 " Program will stop after current batch of deletions.\n") ,
153 } /* kill_delete() */
156 /************************************************/
160 /************************************************/
161 static void retncode_abort (int location)
163 fputc ('\n', aa_stderr);
164 if (DtSearchHasMessages ())
165 fprintf (aa_stderr, "%s\n", DtSearchGetMessages ());
167 PROGNAME "%d Program abort. usrblk.retncode = %d. Exit code = 3.\n",
168 location, usrblk.retncode);
170 } /* retncode_abort() */
173 /****************************************/
175 /* change_database */
177 /****************************************/
178 /* Changes usrblk.dblk to point to passed database name.
179 * Returns TRUE if successful.
181 static int change_database (char *newname)
185 for (db = usrblk.dblist; db != NULL; db = db->link)
186 if (strcmp (db->name, newname) == 0) {
191 /* Invalid newname. If deleting, just say which database is invalid. */
193 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 4,
194 "%s Database '%s' not found.\n") ,
195 PROGNAME"114", newname);
199 /* If browsing, tell user his options */
200 fprintf (aa_stderr, "%s", catgets(dtsearch_catd, MS_tomita, 5,
201 "Available choices are:") );
202 for (db = usrblk.dblist; db != NULL; db = db->link)
203 fprintf (aa_stderr, " '%s'", db->name);
204 fputc ('\n', aa_stderr);
206 } /* change_database() */
209 /****************************************/
213 /****************************************/
214 /* Parses a line from a standard formatted discard file.
215 * If first word indicates different database from usrblk.dblk,
216 * changes it. First token loaded into parsed_dbname
217 * (and parsed_dblk will be made to track it), and
218 * second token is loaded into parsed_recid.
219 * Tokens are separated by blanks and/or tabs,
220 * except 2nd token may have embedded spaces if it is
221 * surrounded by double quotes. Returns TRUE unless
222 * database couldn't change or other error, then returns FALSE.
224 static int parse_infbuf (char *infbuf)
229 /* Do all parsing in my own buf so infbuf not peppered with \0's */
230 strncpy (mybuf, infbuf, sizeof (mybuf));
231 mybuf[sizeof (mybuf) - 1] = 0;
233 /* Parse first token (database name) */
234 if ((ptr = strtok (mybuf, " \t")) == NULL) {
235 /* Msg #8 is used in two places */
236 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 8,
237 "%s Invalid input format: %.30s...\n") ,
238 PROGNAME"152", infbuf);
243 /* Change database if necessary */
244 if (strcmp (ptr, usrblk.dblk->name) != 0)
245 if (!change_database (ptr)) {
250 strcpy (parsed_dbname, ptr);
251 parsed_dblk = usrblk.dblk;
253 /* Hop over to beginning of 2nd token */
254 for (ptr += strlen (ptr) + 1; *ptr == ' ' || *ptr == '\t'; ptr++);
256 /* Get 2nd token (record id). Token delimiters depend
257 * on whether token begins with a double quote.
259 ptr = strtok (ptr, (*ptr == '\"') ? "\"" : " \t");
261 /* Msg #8 is used in two places */
262 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 8,
263 "%s Invalid input format: %.30s...\n") ,
264 PROGNAME"176", infbuf);
268 strncpy (parsed_recid, ptr, sizeof (parsed_recid));
269 parsed_recid[sizeof (parsed_recid) - 1] = 0;
272 } /* parse_infbuf() */
275 /****************************************/
279 /****************************************/
280 /* Program 1: displays records in input file,
281 * or user selected records, and if confirmed,
282 * writes their record ids to output file.
284 static int browser (void)
288 int redisplay_rec = FALSE;
293 char datestr[32]; /* "1946/04/17 13:03" */
297 /* All writes to output file will have same date string in comment */
299 strftime (datestr, sizeof (datestr), "%Y/%m/%d %H:%M", localtime (&stamp));
303 if (DtSearchHasMessages ()) {
308 /* Write main menu prompt */
309 printf ( catgets(dtsearch_catd, MS_tomita, 10,
310 "\n---------- SHOW NEXT RECORD ----------- Database = '%s'\n"
311 "q QUIT. Current Record Count = %ld\n"
312 "p Toggle PAUSE from %s.\n"
313 "n NEXT INPUT file record.\n"
314 "+ NEXT SEQUENTIAL database record.\n"
315 "- PRIOR SEQUENTIAL database record.\n"
316 "r REDISPLAY current record '%s'.\n"
317 "x CONFIRM DELETION of current record.\n"
318 "dxxx Change DATABASE to xxx.\n"
319 "\"xxx GET record id xxx (embedded blanks are ok).\n"
322 usrblk.dblk->dbrec.or_reccount,
323 (pausing) ? "on to OFF" : "off to ON",
324 usrblk.objrec.or_objkey
327 /* Read user's response. Remove user's \n. */
329 if ((fgets (userbuf, sizeof (userbuf), stdin)) == NULL) break;
330 if (strlen(userbuf) && userbuf[strlen(userbuf)-1] == '\n')
331 userbuf[strlen(userbuf)-1] = '\0';
335 /* depending on response, get database address into usrblk */
336 redisplay_rec = FALSE;
337 switch (tolower (*userbuf)) {
343 change_database (userbuf + 1);
353 if (usrblk.objrec.or_objkey[0] == 0) {
355 catgets(dtsearch_catd, MS_tomita, 11,
356 "%s Record buffer empty.\n"),
360 redisplay_rec = FALSE;
365 usrblk.request = (*userbuf == '+') ? OE_NEXT_DBA : OE_PREV_DBA;
372 catgets(dtsearch_catd, MS_tomita, 12,
373 "%s Input file unavailable.\n"),
378 if ((fgets (infbuf, sizeof (infbuf), inf)) == NULL)
381 catgets(dtsearch_catd, MS_tomita, 13,
382 "%s No more records in input file.\n"),
389 if (strlen(infbuf) && infbuf[strlen(infbuf)-1] == '\n')
390 infbuf[strlen(infbuf)-1] = '\0';
392 if (!parse_infbuf (infbuf))
394 usrblk.request = OE_RECKEY2DBA;
395 usrblk.query = parsed_recid;
400 ptr = strtok (userbuf, "\"");
401 if (ptr == NULL || *ptr == 0) {
403 catgets(dtsearch_catd, MS_tomita, 14,
404 "%s Invalid Record ID.\n"),
408 usrblk.request = OE_RECKEY2DBA;
415 * Write record id to output file. Format:
416 * dbasename "recid" userid comments(date)...
418 fprintf (outf, DISCARD_FORMAT, usrblk.dblk->name,
419 usrblk.objrec.or_objkey, usrblk.userid, datestr);
420 printf ( catgets(dtsearch_catd, MS_tomita, 15,
421 "%s '%s' appended to file of confirmed deletions.\n") ,
422 PROGNAME"317", usrblk.objrec.or_objkey);
426 printf ("%s", catgets(dtsearch_catd, MS_tomita, 16, "...what?\n"));
433 /* if user requested redisplay, skip the following OE code */
438 * check return code from attempt to get opera database
441 if (usrblk.retncode == OE_WRAPPED)
442 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 17,
443 "%s %s Engine wrapped to next record.\n") ,
444 PROGNAME"333", OE_prodname);
445 else if (usrblk.retncode != OE_OK)
446 retncode_abort (334);
448 /* retrieve the record and uncompress it */
449 usrblk.request = OE_GETREC;
451 if (usrblk.retncode != OE_OK)
452 retncode_abort (339);
455 /* display the record's cleartext, character by character */
456 printf ( catgets(dtsearch_catd, MS_tomita, 18,
460 "--------------------------------------\n") ,
461 usrblk.objrec.or_objkey,
462 (usrblk.abstrbufsz > 0) ? usrblk.abstrbuf :
463 catgets (dtsearch_catd, MS_misc, 1, "<null>"));
466 for (ptr = usrblk.cleartext; *ptr != 0; ptr++) {
469 * pause every so many lines so user can browse the
472 if (pausing && *ptr == '\n') {
473 if (++pause_counter >= PAUSE_ROWS) {
474 /* Msg 21 is used in two places */
475 printf ( "%s", catgets(dtsearch_catd, MS_tomita, 21,
476 "\n...push ENTER to continue... ") );
479 if(NULL == fgets (userbuf, sizeof (userbuf), stdin)) {
480 fprintf(stderr, "Failed to read from stdin\n");
483 if (strlen(userbuf) && userbuf[strlen(userbuf)-1] == '\n')
484 userbuf[strlen(userbuf)-1] = '\0';
490 } /* end of cleartext printing */
492 /* display the user notes if any, character by character */
493 if (usrblk.notes != NULL) {
494 printf ( catgets(dtsearch_catd, MS_tomita, 20,
495 "--------------------------------------\n"
496 "End of Text Blob for '%s':\n\n"
498 "--------------------------------------\n") ,
499 usrblk.objrec.or_objkey);
501 for (llptr = usrblk.notes; llptr != NULL; llptr = llptr->link) {
502 for (ptr = llptr->data; *ptr != '\0'; ptr++) {
504 if (pausing && *ptr == '\n')
505 if (++pause_counter >= PAUSE_ROWS) {
506 /* Msg 21 is used in two places */
507 printf ( "%s", catgets(dtsearch_catd, MS_tomita, 21,
508 "\n...push ENTER to continue... ") );
511 if(NULL == fgets (userbuf, sizeof (userbuf), stdin)) {
512 fprintf(stderr, "Failed to read from stdin 2\n");
516 if (strlen(userbuf) &&
517 userbuf[strlen(userbuf)-1] == '\n')
518 userbuf[strlen(userbuf)-1] = '\0';
525 } /* end of user notes printing */
527 printf ("--------------------------------------\n"
528 "End of Record '%s'.\n", usrblk.objrec.or_objkey);
530 } /* end of main menu loop */
535 /****************************************/
539 /****************************************/
540 /* Subroutine of deleter(). Reads discard file containing
541 * record ids to be deleted, converts to database addresses,
542 * loads usrblk.dbatab up to max batch size.
543 * Returns number of dba's added to table.
544 * Returns 0 when file is empty after last batch.
546 static int load_dbatab (void)
548 static int read_next_rec = TRUE;
549 static char last_dbname[24] = "";
550 static DBLK *last_dblk;
553 int first_err = TRUE;
559 next_dba = usrblk.dbatab;
562 /* MAIN LOOP - break it at EOF, max count, or dbname change */
563 while (usrblk.dbacount < max_dbacount) {
565 * Skip the read of the first record if the reason we left
566 * main loop the last time was because of a database name
567 * change, and the data from the last read is still in
568 * parsed_dbname, _dblk, and _recid. Update usrblk.dblk
569 * because it's based on the last table's database.
571 if (!read_next_rec) {
572 read_next_rec = TRUE;
573 usrblk.dblk = parsed_dblk;
577 if (fgets (buf, sizeof (buf), inf) == NULL)
584 buf[sizeof (buf) - 1] = 0; /* guarantee termination */
585 if (strlen(buf) && buf[strlen(buf)-1] == '\n')
586 buf[strlen(buf)-1] = '\0';
590 * Parse line into dbname and recid. Skip line if
593 if (!parse_infbuf (buf))
596 /* on very first read, save the database name */
597 if (last_dbname[0] == 0) {
598 strcpy (last_dbname, parsed_dbname);
599 last_dblk = parsed_dblk;
601 } /* finished reading next rec in input file */
604 * Test for change of database name. Restore usrblk.dblk
605 * to reflect all the records on the dba table so far. Then
606 * save the new dblk for when we are again called.
608 if (strcmp (last_dbname, parsed_dbname) != 0) {
609 read_next_rec = FALSE;
610 strcpy (last_dbname, parsed_dbname);
611 usrblk.dblk = last_dblk;
612 last_dblk = parsed_dblk;
617 * Call OE to get record's db address. Turn off debug
618 * temporarily so won't flood output with messages.
620 usrblk.query = parsed_recid;
621 usrblk.debug &= ~USRDBG_DELETE;
622 usrblk.request = OE_RECKEY2DBA;
624 if (debug_mode) /* restore */
625 usrblk.debug |= USRDBG_DELETE;
626 if (DtSearchHasMessages ()) {
631 if (usrblk.retncode == OE_WRAPPED) {
634 fputc ('\n', aa_stderr);
636 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 24,
637 "%s Database %s, '%s' not found.\n") ,
638 PROGNAME"482", parsed_dbname, parsed_recid);
641 else if (usrblk.retncode != OE_OK)
642 retncode_abort (486);
644 /* add db address to growing table */
645 *next_dba = usrblk.dba;
649 } /* end of main record read loop */
651 /* It is possible to exit the main loop, because database changed
652 * or whatever, but no records were added to usrblk.dbatab.
653 * If there are still records to be read from the input file,
654 * go back and try another pass.
656 if (inf != NULL && usrblk.dbacount == 0)
659 return usrblk.dbacount;
660 } /* load_dbatab() */
663 /****************************************/
667 /****************************************/
668 /* Program 2: deletes records specified in input file.
669 * Must be run offline when all online users have logged off.
671 static void deleter (char *infname)
674 long records_deleted;
675 time_t start_time, minutes, hours, seconds, elapsed;
679 printf ( catgets(dtsearch_catd, MS_tomita, 25,
680 "\nDO NOT CONTINUE under any of the following circumstances:\n"
681 "-> If the input file which lists record ids to be deleted is not\n"
683 "-> If any users are still accessing the affected database(s).\n"
684 "-> If any database files have not been backed up.\n\n"
685 "If you are sure you are ready to start deleting, enter 'y' now... ") ,
686 infname, OE_prodname);
688 if(NULL == fgets (buf, sizeof(buf)-1, stdin)) {
689 fprintf(stderr, "no input\n");
692 if (tolower (*buf) != 'y')
696 /* Make sure engine doesn't abort because of
697 * recurring changes to d99 files.
699 OE_sitecnfg_mtime = 0L;
701 /* Init table of db addrs */
702 usrblk.dbatab = austext_malloc
703 (sizeof (DB_ADDR) * (max_dbacount + 2), PROGNAME "531", NULL);
704 usrblk.dbacount = 0; /* number of recs currently in table */
706 /* Init status msg stuff */
708 records_deleted = 0L;
711 signal (SIGINT, kill_delete);
712 signal (SIGQUIT, kill_delete);
713 signal (SIGTRAP, kill_delete);
714 signal (SIGTERM, kill_delete);
716 signal (SIGPWR, kill_delete);
719 signal (SIGXCPU, kill_delete); /* cpu time limit exceeded */
720 signal (SIGDANGER, kill_delete); /* imminent paging space
725 while (load_dbatab ()) {
727 * Stop now if we have exceeded user specified time limit
728 * or if user sent termination or interrupt signal.
732 elapsed = time (NULL) - start_time;
733 if (maxtime > 0L && elapsed >= maxtime)
736 /* echo status for humans who might be watching */
737 hours = elapsed / 3600L;
738 seconds = elapsed - (3600L * hours); /* remaining after hours */
739 minutes = seconds / 60L;
740 seconds = seconds - (60L * minutes);
741 printf ( catgets(dtsearch_catd, MS_tomita, 26,
742 "%s %ld read, %ld deleted, %ldh %2ldm %2lds elapsed.\n"
743 " Database '%s': Current record count = %ld, Batch size = %d.\n") ,
744 aa_argv0, records_read, records_deleted,
745 hours, minutes, seconds,
746 usrblk.dblk->name, usrblk.dblk->dbrec.or_reccount, usrblk.dbacount);
747 /*****fflush (stdout);*****/
749 /* call OE to delete batch of records */
750 usrblk.request = OE_DELETE_BATCH;
752 if (DtSearchHasMessages ()) {
756 if (usrblk.retncode != OE_OK)
757 retncode_abort (572);
758 records_deleted += usrblk.dbacount;
760 } /* end main loop */
763 /* Print final status messages */
764 elapsed = time (NULL) - start_time; /* total elapsed time */
766 hours = elapsed / 3600L;
767 seconds = elapsed - (3600L * hours); /* remaining after hours */
768 minutes = seconds / 60L;
769 seconds = seconds - (60L * minutes); /* remaining after hours
771 printf ( catgets(dtsearch_catd, MS_tomita, 27,
772 "%s %ld records read from input file. %ld were deleted and\n"
773 " %ld were not found in %ld hours, %ld minutes, %ld seconds,\n") ,
774 aa_argv0, records_read, records_deleted,
775 records_read - records_deleted,
776 hours, minutes, seconds);
778 /* Figure average time for a deletion */
779 elapsed = (records_deleted) ? elapsed / records_deleted : 0L;
780 minutes = elapsed / 60L;
781 seconds = elapsed - (60L * minutes);
782 printf ( catgets(dtsearch_catd, MS_tomita, 28,
783 " or an average of %ld minutes, %ld seconds per record deleted.\n"),
789 /****************************************/
793 /****************************************/
794 int main (int argc, char *argv[])
801 setlocale (LC_ALL, "");
802 dtsearch_catd = catopen (FNAME_DTSRCAT, 0);
804 strftime (timebuf, sizeof (timebuf),
805 catgets(dtsearch_catd, MS_misc, 22, "%A, %b %d %Y, %I:%M %p"),
806 localtime (&mytime));
807 printf (catgets(dtsearch_catd, MS_tomita, 29,
810 austext_exit_last = print_exit_code;
812 signal (SIGINT, DtSearchExit);
813 signal (SIGTERM, DtSearchExit);
814 /****memset (&usrblk, 0, sizeof(USRBLK));****/
816 /* Validate program number argument */
819 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 30,
820 "\nUSAGE: %s [options]\n"
821 " -i Input file name. If not specified, defaults to %s.\n"
822 " -d[v] Print debug statements.\n"
823 " -dv turns on verbose (record-by-record) debugging.\n"
824 " -t<N> Max desired number of seconds of run time.\n"
825 " Ctrl-C/Break will also stop deletion at next record.\n"
826 " -n<N> Change number of records in a batch from %d to <N>.\n"
827 " -y Automatically answers 'yes' to Delete mode confirm prompt.\n"
828 " -d trace deletion operations.\n") ,
829 aa_argv0, FNAME_DISCARD_DATA,
830 FNAME_CONFIRM_LIST, FNAME_CONFIRM_LIST, DBACOUNT);
833 prog = toupper (argv[1][0]);
834 if (prog != 'B' && prog != 'D')
837 /* Initialize defaults depending on program mode */
839 infname = FNAME_DISCARD_DATA;
840 outfname = FNAME_CONFIRM_LIST;
843 infname = FNAME_CONFIRM_LIST;
844 outfname = PROGNAME "654";
848 /* Save rest of command line arguments */
849 for (argc -= 2, argv += 2; argc > 0; argc--, argv++) {
851 switch (tolower (arg[1])) {
862 usrblk.debug |= USRDBG_DELETE;
864 usrblk.debug |= USRDBG_VERBOSE;
872 maxtime = atol (arg + 2);
876 max_dbacount = atol (arg + 2);
880 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 31,
881 "\n%s Unknown argument '%s'.\n") ,
886 } /* end arg parsing */
888 /* Open input file to test for its existence.
889 * For the Browse program, file ptr 'inf' == NULL
890 * means the file is not open.
892 if ((inf = fopen (infname, "r")) == NULL) {
894 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 32,
895 "%s Unable to open input file '%s'.\n") ,
896 PROGNAME"710", infname);
901 /* If browsing, get output file name and
902 * open it to test for write permission.
905 if ((outf = fopen (outfname, "a ")) == NULL)
906 /* the blank in "a " works around old aix bug */
908 fprintf (aa_stderr, catgets(dtsearch_catd, MS_tomita, 33,
909 "\n%s Unable to open output file '%s'.\n") ,
910 PROGNAME"721", outfname);
915 /* Initialize the opera engine, i.e. open the database */
916 printf ( catgets(dtsearch_catd, MS_tomita, 34,
917 "Initializing %s engine...\n"),
919 strcpy (usrblk.userid, "ToMiTa");
920 usrblk.request = OE_INITIALIZE;
921 usrblk.query = AUSAPI_VERSION;
923 if (usrblk.retncode != OE_OK)
924 retncode_abort (733);
932 usrblk.request = OE_SHUTDOWN;
934 printf ( "%s", catgets(dtsearch_catd, MS_tomita, 36,
935 "Normal engine shutdown.\n") );
939 /******************* TOMITA.C *******************/