home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume33 / problem / part07 < prev    next >
Encoding:
Text File  |  1992-10-18  |  38.2 KB  |  1,294 lines

  1. Newsgroups: comp.sources.misc
  2. From: lijewski@rosserv.gsfc.nasa.gov (Mike Lijewski)
  3. Subject:  v33i009:  problem - A Problem Database Manager, Part07/07
  4. Message-ID: <1992Oct19.170002.4440@sparky.imd.sterling.com>
  5. X-Md4-Signature: 18c20c789d6cac6846a95c932a8ce770
  6. Date: Mon, 19 Oct 1992 17:00:02 GMT
  7. Approved: kent@sparky.imd.sterling.com
  8.  
  9. Submitted-by: lijewski@rosserv.gsfc.nasa.gov (Mike Lijewski)
  10. Posting-number: Volume 33, Issue 9
  11. Archive-name: problem/part07
  12. Environment: UNIX, GDBM, C++, termcap
  13.  
  14. #! /bin/sh
  15. # This is a shell archive.  Remove anything before this line, then unpack
  16. # it by saving it into a file and typing "sh file".  To overwrite existing
  17. # files, type "sh file -c".  You can also feed this as standard input via
  18. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  19. # will see the following message at the end:
  20. #        "End of archive 7 (of 7)."
  21. # Contents:  utilities.C
  22. # Wrapped by lijewski@xtesoc2 on Mon Oct 19 11:05:56 1992
  23. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  24. if test -f 'utilities.C' -a "${1}" != "-c" ; then 
  25.   echo shar: Will not clobber existing file \"'utilities.C'\"
  26. else
  27. echo shar: Extracting \"'utilities.C'\" \(36043 characters\)
  28. sed "s/^X//" >'utilities.C' <<'END_OF_FILE'
  29. X/*
  30. X** utilities.C - utility functions
  31. X**
  32. X** utilities.C utilities.C 1.51   Delta\'d: 15:10:14 10/9/92   Mike Lijewski, CNSF
  33. X**
  34. X** Copyright \(c\) 1991, 1992 Cornell University
  35. X** All rights reserved.
  36. X**
  37. X** Redistribution and use in source and binary forms are permitted
  38. X** provided that: \(1\) source distributions retain this entire copyright
  39. X** notice and comment, and \(2\) distributions including binaries display
  40. X** the following acknowledgement:  ``This product includes software
  41. X** developed by Cornell University\'\' in the documentation or other
  42. X** materials provided with the distribution and in all advertising
  43. X** materials mentioning features or use of this software. Neither the
  44. X** name of the University nor the names of its contributors may be used
  45. X** to endorse or promote products derived from this software without
  46. X** specific prior written permission.
  47. X**
  48. X** THIS SOFTWARE IS PROVIDED ``AS IS\'\' AND WITHOUT ANY EXPRESS OR
  49. X** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
  50. X** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  51. X*/
  52. X
  53. X#include <ctype.h>
  54. X
  55. X#ifndef _IBMR2
  56. X#include <libc.h>
  57. X#endif
  58. X
  59. X#include <fcntl.h>
  60. X#include <osfcn.h>
  61. X#include <pwd.h>
  62. X#include <signal.h>
  63. X#include <stdarg.h>
  64. X#include <stdlib.h>
  65. X#include <stdio.h>
  66. X
  67. X#ifndef _IBMR2
  68. X#include <unistd.h>
  69. X#endif
  70. X
  71. X// access\(2\) is prototyped in <sys/access.h> on RS/6000s
  72. X#ifdef _IBMR2
  73. X#include <sys/access.h>
  74. X#include <sys/lockf.h>
  75. X#endif
  76. X
  77. X#include <sys/stat.h>
  78. X#include <sys/types.h>
  79. X#ifdef ESIX
  80. Xtypedef int pid_t;
  81. X#endif /*ESIX*/
  82. X#include <sys/wait.h>
  83. X#include <string.h>
  84. X#include <sys/errno.h>
  85. X
  86. X// this header file is badly busted on RS/6000s
  87. X#ifndef _IBMR2
  88. X#include <unistd.h>
  89. X#endif
  90. X
  91. X#include "classes.h"
  92. X#include "display.h"
  93. X#include "lister.h"
  94. X#include "problem.h"
  95. X#include "utilities.h"
  96. X
  97. X/* remove this once GNU gets it fixed -- this stuff should be in <unistd.h> */
  98. X#ifdef __GNUG__
  99. Xextern "C" int lockf(int, int, long);
  100. Xextern "C" int flock(int, int);
  101. X#ifndef LOCK_UN
  102. X#define LOCK_UN 8
  103. X#endif
  104. X#ifndef LOCK_EX
  105. X#define LOCK_EX 2
  106. X#endif
  107. X#ifndef F_LOCK
  108. X#define F_LOCK   1
  109. X#endif
  110. X#ifndef F_ULOCK
  111. X#define F_ULOCK  0
  112. X#endif
  113. X#endif /*__GNUG__*/
  114. X
  115. Xconst int KEY_CR    = '\r';  // carriage return
  116. Xconst int KEY_BKSP  = '\b';  // backslash
  117. Xconst int KEY_CTL_L = '\f';  // repaint screen -- CTR-L
  118. X
  119. X/*
  120. X** fgetline - returns a pointer to the start of a line read from fp,
  121. X**            or the null pointer if we hit eof or get an error from
  122. X**            fgets\(\). Exits if new\(\) fails. Strips the newline from
  123. X**            the line. Caller should free memory if desired. `size\'
  124. X**            is the expected length of the line to be read.
  125. X*/
  126. X
  127. Xchar *fgetline(FILE *fp, int size)
  128. X{
  129. X    char *buffer = new char[size];
  130. X
  131. X    char *result= fgets(buffer, size, fp);
  132. X    if (result == 0)
  133. X    {
  134. X        //
  135. X        // Either error or at eof.
  136. X        //
  137. X        DELETE buffer;
  138. X        return 0;
  139. X    }
  140. X
  141. X    if (buffer[strlen(buffer) - 1] != '\n' && !feof(fp))
  142. X    {
  143. X        //
  144. X        // Longer line than buffer can hold.
  145. X        //
  146. X        char *restofline = fgetline(fp, size);
  147. X
  148. X        if (restofline == 0) return 0; // eof or error
  149. X
  150. X        char *longline = new char[strlen(buffer) + strlen(restofline) + 1];
  151. X        (void)strcat(strcpy(longline, buffer), restofline);
  152. X
  153. X        DELETE restofline;
  154. X        DELETE buffer;
  155. X
  156. X        if (longline[strlen(longline) - 1] == '\n')
  157. X            longline[strlen(longline) - 1] = '\0';
  158. X
  159. X        return longline;
  160. X    }
  161. X    else
  162. X    {
  163. X        if (buffer[strlen(buffer) - 1] == '\n')
  164. X            buffer[strlen(buffer) - 1] = '\0';
  165. X        return buffer;
  166. X    }
  167. X}
  168. X
  169. X/*
  170. X** display_string - prints a string to the given the display, guaranteeing not
  171. X**                  to print more than columns\(\) characters.  If the string
  172. X**                  exceeds the width of the window, a `!\' is placed in
  173. X**                  the final column.  `len\' is the length of the string,
  174. X**                  if known, which defaults to zero. `offset\', which
  175. X**                  defaults to zero, is non-zero in those cases where we\'ve
  176. X**                  already printed `offset\' characters to the screen line.
  177. X**                  We never call this when trying to write to the last
  178. X**                  row on the screen.  That is the dominion of message\(\).
  179. X*/
  180. X
  181. Xvoid display_string(const char *str, int length, int offset)
  182. X{
  183. X    int len = (length == 0 ? (int) strlen(str) : length) + offset;
  184. X
  185. X    if (len < columns())
  186. X    {
  187. X        (void)fputs(str, stdout);
  188. X        cursor_wrap();
  189. X    }
  190. X    else if (len > columns())
  191. X    {
  192. X        (void)printf("%*.*s%c", columns() - offset - 1, columns() - offset - 1,
  193. X                     str, '!');
  194. X        if (!AM || XN) cursor_wrap();
  195. X    }
  196. X    else
  197. X    {
  198. X        (void)fputs(str, stdout);
  199. X        if (!AM || XN) cursor_wrap();
  200. X    }
  201. X}
  202. X
  203. X/*
  204. X** error - Prints error message so it can be read.  This is the error
  205. X**         function we call once we\'ve initialized the display.
  206. X*/
  207. X
  208. Xvoid error(const char *format, ...)
  209. X{
  210. X    va_list ap;
  211. X    va_start(ap, format);
  212. X    clear_message_line();
  213. X    (void) vfprintf(stdout, format, ap);
  214. X    cursor_wrap();
  215. X    va_end(ap);
  216. X    synch_display();
  217. X    term_display();
  218. X    exit(1);
  219. X}
  220. X
  221. X/*
  222. X** eat_a_character - prints a message and then reads and
  223. X**                   discards a character from the keyboard.
  224. X**                   signals interrupt read\(2\); in fact we rely on this to
  225. X**                   correctly deal with SIGTSTP and SIGWINCH.  If we get
  226. X**                   an EINTR, we reprint the message and keep trying to
  227. X**                   read a character.  if `standout\' is true, the message
  228. X**                   is printed in standout mode; it defaults to false.
  229. X*/
  230. X
  231. Xvoid eat_a_character(const char *msg, void (*redisplay)(), int standout)
  232. X{
  233. X    char c;
  234. X    while (1)
  235. X    {
  236. X        // read a character dealing with interruption
  237. X        if (standout) enter_standout_mode();
  238. X        message(msg);
  239. X        if (standout) end_standout_mode();
  240. X        if (resumingAfterSuspension ||
  241. X#ifdef SIGWINCH
  242. X            windowSizeChanged       ||
  243. X#endif
  244. X            read(0, &c, 1) < 0      || // assume only fails when errno==EINTR
  245. X            c == KEY_CTL_L)
  246. X        {
  247. X#ifdef SIGWINCH
  248. X            if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
  249. X#endif
  250. X            resumingAfterSuspension = 0;
  251. X            redisplay();
  252. X            if (standout) enter_standout_mode();
  253. X            message(msg);
  254. X            if (standout) end_standout_mode();
  255. X        }
  256. X        else
  257. X            return;
  258. X    }
  259. X}
  260. X
  261. X/*
  262. X** execute - executes command using exec\(2\).  Returns 1 if the exec
  263. X**           went OK, otherwise it returns 0.  Assumes that the exec\'d
  264. X**           program wants our open file descriptors.
  265. X**           Forces the effective uid to the real uid in the child.
  266. X*/
  267. X
  268. Xint execute(const char *file, const char *argv[])
  269. X{
  270. X    unsetraw();
  271. X    unset_signals();
  272. X    int status;
  273. X    pid_t pid = fork();
  274. X    switch(pid)
  275. X    {
  276. X      case -1: // error
  277. X        return 0;
  278. X      case 0:  // in the child
  279. X        if (setuid(getuid()) < 0)
  280. X            error("file %s, line %d, setuid() failed" __FILE__, __LINE__);
  281. X        execvp(file, (char *const *)argv);
  282. X        // exec failed
  283. X        exit(1);
  284. X      default: // in the parent
  285. X        waitpid(pid, &status, 0);
  286. X        set_signals();
  287. X        setraw();
  288. X        return status == 0 ? 1 : 0;
  289. X    }
  290. X}
  291. X
  292. X#ifdef SIGWINCH
  293. X
  294. X/*
  295. X** winch - set flag indicating window size changed.
  296. X*/
  297. X
  298. Xint windowSizeChanged;  // should be a sig_atomic_t
  299. X
  300. Xvoid winch(int)
  301. X{
  302. X    (void)signal(SIGWINCH, SIG_IGN);
  303. X    windowSizeChanged = 1;
  304. X    (void)signal(SIGWINCH, winch);
  305. X}
  306. X
  307. X#include <sys/ioctl.h>
  308. X
  309. X/*
  310. X** adjust_window - called to adjust our window after getting a SIGWINCH
  311. X*/
  312. X
  313. Xvoid adjust_window()
  314. X{
  315. X#ifdef TIOCGWINSZ
  316. X    struct winsize w;
  317. X    if (ioctl(2, TIOCGWINSZ, (char *)&w) == 0 && w.ws_row > 0) LI = w.ws_row;
  318. X    if (ioctl(2, TIOCGWINSZ, (char *)&w) == 0 && w.ws_col > 0) CO = w.ws_col;
  319. X#endif
  320. X    if (LI < 5 || CO < 20)
  321. X        error("screen too small to be useful");
  322. X}
  323. X
  324. X#endif
  325. X
  326. X/*
  327. X** prompt - displays `msg\' prompt and then collects the response.
  328. X**          The keys of the response are echoed as they\'re collected.
  329. X**          The response should be delete\'d when no longer needed.
  330. X**          A response can contain any graphical character. Backspace
  331. X**          works as expected. Carriage return indicates the end of
  332. X**          response. Non-graphical characters are ignored.  If we
  333. X**          get suspended and resumed in prompt\(\), `redisplay\' is
  334. X**          the function to call to fixed up all but the message lines
  335. X**          of the display.  We rely on signals interrupting read\(2\).
  336. X*/
  337. X
  338. Xchar *prompt(const char *msg, void (*redisplay)())
  339. X{
  340. X    size_t written = 0;  // number of characters written to message line
  341. X    size_t len = strlen(msg);
  342. X    String nmsg(msg);
  343. X
  344. X    clear_message_line();
  345. X
  346. X    if (len < columns())
  347. X    {
  348. X        (void)fputs(nmsg, stdout);
  349. X        written = len;
  350. X    }
  351. X    else
  352. X    {
  353. X        // Leave space for columns\(\)/2 + 1 characters.
  354. X        (void)fputs((const char *)nmsg + (len-columns()/2+1), stdout);
  355. X        written = columns()/2 - 1;
  356. X    }
  357. X    synch_display();
  358. X
  359. X    //
  360. X    // We never echo into the last position in the message window.
  361. X    //
  362. X    size_t space_available = columns() - written; // available spaces in line
  363. X    char *response = new char[space_available + 1];
  364. X    size_t pos = 0;  // index of next character in `response\'
  365. X
  366. X    char key;
  367. X    for (;;)
  368. X    {
  369. X        if (resumingAfterSuspension ||
  370. X#ifdef SIGWINCH
  371. X            windowSizeChanged       ||
  372. X#endif
  373. X            read(0, &key, 1) < 0    || // assume only fails when errno == EINTR
  374. X            key == KEY_CTL_L)
  375. X        {
  376. X#ifdef SIGWINCH
  377. X            if (windowSizeChanged) { windowSizeChanged = 0; adjust_window(); }
  378. X#endif
  379. X            resumingAfterSuspension = 0;
  380. X            redisplay();
  381. X            clear_message_line();  // make sure we\'re on the message line
  382. X            response[pos] = 0;
  383. X            if (pos + len < columns())
  384. X            {
  385. X                //
  386. X                // Output message and response-to-date.
  387. X                //
  388. X                (void)fputs(nmsg, stdout);
  389. X                (void)fputs(response, stdout);
  390. X                space_available = columns() - pos - len;
  391. X            }
  392. X            else if (pos < columns())
  393. X            {
  394. X                //
  395. X                // Display the response.
  396. X                //
  397. X                (void)fputs(response, stdout);
  398. X                space_available = columns() - strlen(response);
  399. X            }
  400. X            else
  401. X            {
  402. X                //
  403. X                // Display the backend of the response.
  404. X                //
  405. X                (void)fputs(&response[pos - columns()/2 + 1], stdout);
  406. X                space_available = columns()/2 + 1;
  407. X            }
  408. X            synch_display();
  409. X        }
  410. X        else if (isprint(key))
  411. X        {
  412. X            //
  413. X            // echo character to message window and wait for another
  414. X            //
  415. X            response[pos++] = key;
  416. X            space_available--;
  417. X            if (!space_available)
  418. X            {
  419. X                //
  420. X                // Need to allocate more room for the response.
  421. X                // Note that strlen\(response\) == pos
  422. X                //
  423. X                space_available = columns()/2 + 1;
  424. X                char *nresponse = new char[pos + space_available + 1];
  425. X                response[pos] = 0;  // stringify response
  426. X                (void)strcpy(nresponse, response);
  427. X
  428. X                DELETE response;
  429. X                response = nresponse;
  430. X
  431. X                //
  432. X                // Shift prompt in message window so we
  433. X                // always have the end in view to which we\'re
  434. X                // adding characters as they\'re typed.
  435. X                //
  436. X                clear_message_line();
  437. X                (void)fputs(&response[pos - columns()/2 + 1], stdout);
  438. X                key = 0;  // nullify key
  439. X            }
  440. X            else
  441. X            {
  442. X                putchar(key);
  443. X                key = 0;  // nullify key
  444. X            }
  445. X            synch_display();
  446. X        }
  447. X        else
  448. X            switch (key)
  449. X            {
  450. X              case KEY_CR: // we have the complete response
  451. X                response[pos] = 0;
  452. X                clear_message_line();
  453. X                synch_display();
  454. X                return response;
  455. X              case KEY_BKSP: // back up one character
  456. X                if (pos == 0) { ding(); break; }
  457. X                backspace();
  458. X                DC ? delete_char_at_cursor() : clear_to_end_of_line();
  459. X                --pos;
  460. X                ++space_available;
  461. X                if (space_available == columns())
  462. X                {
  463. X                    //
  464. X                    // The only way this can happen is if we
  465. X                    // had previously shifted the response to the left.
  466. X                    // Now we must shift the response to the right.
  467. X                    //
  468. X                    clear_message_line();
  469. X                    response[pos] = 0;
  470. X                    if (pos + len < columns())
  471. X                    {
  472. X                        //
  473. X                        // Output message and response-to-date.
  474. X                        //
  475. X                        (void)fputs(nmsg, stdout);
  476. X                        (void)fputs(response, stdout);
  477. X                        space_available = columns() - pos - len;
  478. X                    }
  479. X                    else if (pos < columns())
  480. X                    {
  481. X                        //
  482. X                        // Display the response.
  483. X                        //
  484. X                        (void)fputs(response, stdout);
  485. X                        space_available = columns() - strlen(response);
  486. X                    }
  487. X                    else
  488. X                    {
  489. X                        //
  490. X                        // Display the backend of the response
  491. X                        //
  492. X                        (void)fputs(&response[pos - columns()/2 + 1], stdout);
  493. X                        space_available = columns()/2 + 1;
  494. X                    }
  495. X                }
  496. X                synch_display();
  497. X                break;
  498. X              default: ding(); break; // ignore other characters
  499. X            }
  500. X    }
  501. X}
  502. X
  503. X/*
  504. X** message - prints a message on the last line of the screen.
  505. X**           It is up to the calling process to put the cursor
  506. X**           back where it belongs.  Synchs the display.  It can
  507. X**           be called as either:
  508. X**
  509. X**                message\(msg\);
  510. X**           or
  511. X**                message\(fmt, str\);
  512. X**
  513. X**           In the later case it must be the case that the format `fmt\'
  514. X**           has exactly one `%\' into which the `str\' will be substituted
  515. X**           as in the ?printf\(\) functions.  Prints in standout mode.
  516. X*/
  517. X
  518. X// the definition -- declared in utilities.h
  519. Xint message_window_dirty = 0;
  520. X
  521. Xvoid message(const char *fmt, const char *str)
  522. X{
  523. X    char *msg;          // the complete message to be output
  524. X    int allocated = 0;  // was `msg\' allocated in new\(\) space?
  525. X
  526. X    clear_message_line();
  527. X
  528. X    if (str)
  529. X    {
  530. X        msg = new char[strlen(fmt) + strlen(str) + 1];
  531. X        const char *token = strchr(fmt, '%');
  532. X        if (token == 0)
  533. X            //
  534. X            // This shouldn\'t happen.  But if it does, let\'s
  535. X            // just print the format `fmt\'.
  536. X            //
  537. X            msg = (char *)fmt;
  538. X        else
  539. X        {
  540. X            (void)strncpy(msg, fmt, token - fmt);
  541. X            msg[token - fmt] = 0;  // strncpy doesn\'t nullify the string
  542. X            (void)strcat(msg, str);
  543. X            (void)strcat(msg, token + 1);
  544. X            allocated = 1;
  545. X        }
  546. X    }
  547. X    else
  548. X        msg = (char *)fmt;
  549. X
  550. X    if (strlen(msg) < columns())
  551. X        (void)fputs(msg, stdout);
  552. X    else
  553. X        (void)printf("%*.*s", columns() - 1, columns() - 1, msg);
  554. X
  555. X    synch_display();
  556. X    message_window_dirty = 1;
  557. X    if (allocated) DELETE msg;
  558. X}
  559. X
  560. X/*
  561. X** yes_or_no - returns true if a \'y\' or \'Y\' is typed in response to
  562. X**             the msg. We deal with being suspended and resumed.
  563. X**
  564. X**             defResponse is the assumed default response.
  565. X**
  566. X**                 defResponse == Yes ==> that return value is true unless
  567. X**                                        \'n\' or \'N\' is typed; else it
  568. X**                                        returns false.
  569. X**
  570. X**                 defResponse == No  ==> that return value is true only if
  571. X**                                        \'y\' or \'Y\' is typed; else it
  572. X**                                        return false.
  573. X**
  574. X**             If standout is true the message is displayed in standout mode.
  575. X**
  576. X**             It is assumed that the message that is printed somehow indicates
  577. X**             whether the default response is true or false.
  578. X*/
  579. X
  580. Xint yes_or_no(const char *msg,
  581. X              void (*redisplay)(),
  582. X              Response defResponse,
  583. X              int standout)
  584. X{
  585. X    if (standout) enter_standout_mode();
  586. X    message(msg);
  587. X    if (standout) end_standout_mode();
  588. X
  589. X    char key;
  590. X    while (1)
  591. X        //
  592. X        // read a character dealing with interruption
  593. X        //
  594. X        if (resumingAfterSuspension ||
  595. X#ifdef SIGWINCH
  596. X            windowSizeChanged       ||
  597. X#endif
  598. X            read(0, &key, 1) < 0    || // assume only fails when errno==EINTR 
  599. X            key == KEY_CTL_L)
  600. X        {
  601. X#ifdef SIGWINCH
  602. X            if (windowSizeChanged)
  603. X            {
  604. X                windowSizeChanged = 0;
  605. X                adjust_window();
  606. X            }
  607. X#endif
  608. X            resumingAfterSuspension = 0;
  609. X            redisplay();
  610. X            if (standout) enter_standout_mode();
  611. X            message(msg);
  612. X            if (standout) end_standout_mode();
  613. X        }
  614. X        else
  615. X            break;
  616. X    clear_message_line();
  617. X    synch_display();
  618. X
  619. X    switch (defResponse)
  620. X    {
  621. X      case Yes:
  622. X        return !(key == 'n' || key == 'N');
  623. X      case No:
  624. X        return   key == 'y' || key == 'Y';
  625. X    }
  626. X}
  627. X
  628. X/*
  629. X** lock_file - lock the file opened with file descriptor `fd\'.
  630. X**             Exits on error.
  631. X*/
  632. X
  633. Xvoid lock_file(int fd)
  634. X{
  635. X    if (lseek(fd, 0, 0) < 0) error("lseek() failed");
  636. X#ifdef FLOCK
  637. X    if (flock(fd, LOCK_EX) < 0) error("flock - LOCK_EX");
  638. X#else
  639. X    if (lockf(fd, F_LOCK, 0) < 0) error("lockf - F_LOCK");
  640. X#endif
  641. X}
  642. X
  643. X/*
  644. X** unlock_file - unlock file with file descriptor `fd\'.
  645. X**               Exits on error.
  646. X*/
  647. X
  648. Xvoid unlock_file(int fd)
  649. X{
  650. X    if (lseek(fd, 0, 0) < 0) error("lseek() failed");
  651. X#ifdef FLOCK
  652. X    if (flock(fd, LOCK_UN) < 0) error("flock - LOCK_UN");
  653. X#else
  654. X    if (lockf(fd, F_ULOCK, 0) < 0) error("lockf - F_ULOCK");
  655. X#endif
  656. X}
  657. X
  658. X/*
  659. X** cleanup - cleanup and exit after a SIGHUP, SIGTERM, SIGQUIT or SIGINT
  660. X*/
  661. X
  662. Xvoid cleanup(int) { term_display(); exit(0); }
  663. X
  664. X/*
  665. X** username - returns the username pertaining to the real uid.
  666. X**            Exits on error;
  667. X*/
  668. X
  669. Xconst char *username()
  670. X{
  671. X    static String user;
  672. X    if (user == "")
  673. X    {
  674. X        struct passwd *entry = getpwuid(getuid());
  675. X        if (!entry)
  676. X            error("file %s, line %d, getpwuid() failed", __FILE__, __LINE__);
  677. X        user =  entry->pw_name;
  678. X    }
  679. X    return user;
  680. X}
  681. X
  682. X/*
  683. X** write_to_pipe - writes the data to the pipe on the file descriptor.
  684. X**                 Exits on error. 
  685. X*/
  686. X
  687. Xvoid write_to_pipe(int fd, const char *data, int size)
  688. X{
  689. X    int nwritten = 0;
  690. X    while (size > 0)
  691. X    {
  692. X        nwritten = write(fd, data, size);
  693. X        if (nwritten <= 0)
  694. X            error("file %s, line %d, write() failed", __FILE__, __LINE__);
  695. X        size -= nwritten;
  696. X        data += nwritten;
  697. X    }
  698. X
  699. X}
  700. X
  701. X/*
  702. X** read_file - reads the file pointed to by `fp\' into the array `lines\'.
  703. X**             `lines\' must have been previously allocated in the caller,
  704. X**             to size `size\'. `len\' is the expected length of the lines
  705. X**             in the file. `pos\' is the position in `lines\'
  706. X**             to which we start adding the lines; it defaults to zero.
  707. X**             In general, this will be zero, but in one case when I\'m
  708. X**             building an argv for an execvp\(\), I want a non-zero offset.
  709. X**             Returns the number of lines in `lines\' or -1 on error.
  710. X**             `lines\' is null-terminated.
  711. X**
  712. X**             This is used only for reading files containing 
  713. X**             relatively few lines.
  714. X*/
  715. X
  716. Xint read_file(FILE *fp, char** &lines, int size, int linelen, int pos)
  717. X{
  718. X    const int chunksize = 20;  // chunksize to `grow\' by
  719. X    int nlines = 0;            // number of lines added to `lines\'
  720. X
  721. X    char *line = fgetline(fp, linelen);
  722. X    for (; line; line = fgetline(fp, linelen))
  723. X    {
  724. X        // skip comment lines
  725. X        if (*line == '#') { DELETE line; continue; }
  726. X        // strip off newline
  727. X        if (line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = 0;
  728. X        lines[pos++] = line;
  729. X        nlines++;
  730. X        if (pos == size)
  731. X        {
  732. X            // grow `Areas\'
  733. X            char **newspace = new char*[size += chunksize];
  734. X            for (int i = 0; i < pos; i++) newspace[i] = lines[i];
  735. X            DELETE lines;
  736. X            lines = newspace;
  737. X        }
  738. X    }
  739. X    if (feof(fp) && !ferror(fp))
  740. X    {
  741. X        // null terminate `lines\'
  742. X        if (pos == size)
  743. X        {
  744. X            // got to grow one more
  745. X            char **newspace = new char*[size + 1];
  746. X            for (int i = 0; i < pos; i++) newspace[i] = lines[i];
  747. X            DELETE lines;
  748. X            lines = newspace;
  749. X        }
  750. X        lines[pos] = 0;
  751. X        return nlines;
  752. X    }
  753. X    else
  754. X        return -1;
  755. X}
  756. X
  757. X/*
  758. X** tokenize - returns a null-terminated vector of the words in `line\'
  759. X**            The vector and its elements are in volatile storage
  760. X**            which we manage here.
  761. X*/
  762. X
  763. Xconst char **tokenize(const char *line, const char *separators)
  764. X{
  765. X    //
  766. X    // Since strtok modifies it\'s argument, we use a copy of `line\'.
  767. X    //
  768. X    static char *newline;     // volatile storage of vector elements
  769. X    DELETE newline;
  770. X    newline = new char[strlen(line) + 1];
  771. X    (void)strcpy(newline, line);
  772. X
  773. X    const int chunksize = 5;  // chunksize to `grow\' by
  774. X    int size   = chunksize;   // total size of vector
  775. X    int nwords = 0;           // number of words in vector
  776. X    static char **words;      // volatile storage for the word pointers
  777. X    DELETE words;
  778. X    words = new char*[chunksize];
  779. X
  780. X    if ((words[nwords++] = strtok(newline, separators)) == 0)
  781. X        return (const char **)words;
  782. X
  783. X    while (words[nwords++] = strtok(0, separators))
  784. X        if (nwords == size)
  785. X        {
  786. X            // Grow "words".
  787. X            char **newspace = new char*[size += chunksize];
  788. X            for (int i = 0; i < nwords; i++) newspace[i] = words[i];
  789. X            DELETE words;
  790. X            words = newspace;
  791. X        }
  792. X    return (const char **)words;
  793. X}
  794. X
  795. X/*
  796. X** read_and_exec_perm - returns non-zero if we have read and execute
  797. X**                      permission on the file, otherwise 0.
  798. X**                      Returns 0 on error.
  799. X*/
  800. X
  801. Xint read_and_exec_perm(const char *file)
  802. X{
  803. X    return access(file, R_OK | X_OK) == -1 ? 0 : 1;
  804. X}
  805. X
  806. X/*
  807. X** expand_tilde - expects a string of the form "~ ...".
  808. X**                Returns a new string in volatile storage
  809. X**                with the user\'s home directory in place of the `~\'.
  810. X**                The user\'s home directory is always appended
  811. X**                in the form: "/usr/staff/mjlx"; a slash is not added to
  812. X**                the end of the home directory string.  Returns the original
  813. X**                string if we cannot get the user\'s home directory.
  814. X*/
  815. X
  816. Xconst char *expand_tilde(char *str)
  817. X{
  818. X    static char *home = getenv("HOME");
  819. X    if (home == NULL)
  820. X    {
  821. X        struct passwd *user = getpwuid(getuid());
  822. X        if (user == NULL) return str;
  823. X        home = user->pw_dir;
  824. X    }
  825. X    if (*str != '~') return str;
  826. X    static String expansion;
  827. X    expansion = String(home) + (str + 1);
  828. X    return expansion;
  829. X}
  830. X
  831. X/*
  832. X** update_screen_line
  833. X**
  834. X**     `oldline\' is what is currently on the screen in row `y\'
  835. X**     `newline\' is what we want on the screen in row `y\'
  836. X**
  837. X**     We make a good attempt to optimize the output of characters to
  838. X**     the screen.  We want to display `newline\' on the screen,
  839. X**     assuming `oldline\' is what is currently displayed.  This
  840. X**     will be "good" if `oldline\' and `newline\' are quite similar.
  841. X**     That is to say, this should only be called when there is an
  842. X**     expectation that `oldline\' and `newline\' are "almost" the same.
  843. X*/
  844. X
  845. Xvoid update_screen_line(const char *oldline, const char *newline, int y)
  846. X{
  847. X    if (strcmp(oldline, newline) == 0) return;
  848. X
  849. X    size_t olen = strlen(oldline);
  850. X    size_t nlen = strlen(newline);
  851. X    size_t  len = olen < nlen ? olen : nlen;
  852. X
  853. X    //
  854. X    // Never display more than columns\(\) characters.
  855. X    //
  856. X    int chop = 0;  // do we need to chop off the tail?
  857. X    if (len > columns()) { chop = 1; len = columns(); }
  858. X
  859. X    char *equal = new char[len];
  860. X
  861. X    //
  862. X    // How similar are the two strings?
  863. X    //
  864. X    int differences = 0;
  865. X    for (int i = 0; i < len; i++) equal[i] = 1;
  866. X    for (i = 0; i < len; i++)
  867. X        if (oldline[i] != newline[i]) { differences++; equal[i] = 0; }
  868. X
  869. X    if (differences > columns()/2)
  870. X    {
  871. X        //
  872. X        // We just display the new line.
  873. X        //
  874. X        clear_to_end_of_line();
  875. X        (void)fputs(newline, stdout);
  876. X        DELETE equal;
  877. X        return;
  878. X    }
  879. X
  880. X    if (!OS)
  881. X    {
  882. X        //
  883. X        // We can just overwrite the old with the new.
  884. X        //
  885. X        int last = -2;  // position of last character written
  886. X        for (i = 0; i < len; i++)
  887. X        {
  888. X            if (equal[i]) continue;
  889. X            if (i - 1 != last) move_cursor(y, i);
  890. X            (i == len - 1 && chop) ? putchar('!') : putchar(newline[i]);
  891. X            last = i;
  892. X        }
  893. X        if (nlen > olen)
  894. X        {
  895. X            //
  896. X            // Have more characters to output.
  897. X            //
  898. X            chop = len > columns();
  899. X            move_cursor(y, i);
  900. X            for (i = (int)len; i < nlen && i < columns(); i++)
  901. X                (i == columns()-1 && chop) ? putchar('!') : putchar(newline[i]);
  902. X        }
  903. X        else if (nlen < olen)
  904. X        {
  905. X            move_cursor(y, i);
  906. X            clear_to_end_of_line();
  907. X        }
  908. X    }
  909. X    else
  910. X    {
  911. X        //
  912. X        // We can\'t overwrite.  Truncate at first difference.
  913. X        //
  914. X        int first = 0;
  915. X        for (i = 0; i < len; i++)
  916. X            if (!equal[i]) { first = i; break; }
  917. X        move_cursor(y, i);
  918. X        clear_to_end_of_line();
  919. X        for (; i < nlen && i < columns(); i++)
  920. X            (i == columns() - 1) ? putchar('!') : putchar(newline[i]);
  921. X    }
  922. X    DELETE equal;
  923. X}
  924. X
  925. X/*
  926. X** update_modeline - this routine concatenates the two strings
  927. X**                   into the modeline.  The modeline
  928. X**                   is displayed in standout mode if possible.
  929. X**                   We never put more than columns\(\) characters into
  930. X**                   the modeline.  The modeline is the penultimate
  931. X**                   line on the terminal screen.  It does not
  932. X**                   synch the display.  If head == tail == 0, we
  933. X**                   just display the old modeline.  This happens
  934. X**                   if for some reason we had to clear the screen.
  935. X*/
  936. X
  937. X// the current modeline
  938. Xchar *current_modeline;
  939. X
  940. Xvoid update_modeline(const char *head, const char *tail)
  941. X{
  942. X    move_to_modeline();
  943. X    enter_standout_mode();
  944. X
  945. X    if (head == 0)   // actually, head == tail == 0
  946. X    {
  947. X        // Redisplay old modeline.
  948. X        (void)fputs(current_modeline, stdout);
  949. X        end_standout_mode();
  950. X        return;
  951. X    }
  952. X
  953. X    int len = (int) strlen(head);
  954. X    char *new_modeline = new char[columns() + 1];
  955. X    (void)strncpy(new_modeline, head, columns());
  956. X    new_modeline[columns()] = 0;  // ensure it\'s null-terminated
  957. X    
  958. X    if (len < columns())
  959. X    {
  960. X        //
  961. X        // Write exactly columns\(\) characters to modeline.
  962. X        //
  963. X        for (int i = len; i < columns() - 1 && tail && *tail; i++, tail++)
  964. X            new_modeline[i] = *tail;
  965. X        if (i < columns() - 1)
  966. X        {
  967. X            new_modeline[i++] = ' ';
  968. X            for (; i < columns(); i++) new_modeline[i] = '-';
  969. X        }
  970. X        else if (tail && *tail)
  971. X            //
  972. X            // The tail was overly long.  Put a \'!\' in the last space
  973. X            // on the modeline to signify truncation.
  974. X            //
  975. X            new_modeline[columns() - 1] = '!';
  976. X        else
  977. X            //
  978. X            // Here len == columns\(\)-1 && there is nothing else in tail.
  979. X            //
  980. X            new_modeline[columns() - 1] = ' ';
  981. X    }
  982. X    else if (len > columns())
  983. X        new_modeline[columns() - 1] = '!';
  984. X
  985. X    if (current_modeline)
  986. X    {
  987. X        update_screen_line(current_modeline, new_modeline, rows() - 2);
  988. X        DELETE current_modeline;
  989. X    }
  990. X    else
  991. X        (void)fputs(new_modeline, stdout);
  992. X
  993. X    current_modeline = new_modeline;
  994. X    end_standout_mode();
  995. X}
  996. X
  997. X/*
  998. X** lines_displayed - returns the number of lines in the DList
  999. X**                   currently displayed on the screen.
  1000. X*/
  1001. X
  1002. Xint lines_displayed(DList *dl)
  1003. X{
  1004. X    DLink *ln = dl->firstLine();
  1005. X    for (int i = 1; ln != dl->lastLine(); i++, ln = ln->next()) ;
  1006. X    return i;
  1007. X}
  1008. X
  1009. X/*
  1010. X** get_prob_number - returns the prob# of the current line of the DList
  1011. X**                   in volatile storage.  The line is of the form:
  1012. X**
  1013. X**                       prob# ...
  1014. X**
  1015. X**                   We must know INTIMATELY how summary_lines\(\)
  1016. X**                   formats the output.
  1017. X*/
  1018. X
  1019. Xchar *get_prob_number(const DList *dl)
  1020. X{
  1021. X    static char* number = 0; // our volatile storage
  1022. X    DELETE number;           // release our volatile storage
  1023. X    const char* begin = dl->currLine()->line();
  1024. X
  1025. X    // step over any spaces preceding Prob #
  1026. X    while (*begin == ' ') begin++;
  1027. X
  1028. X    const char* end = strchr(begin, ' ');
  1029. X    int len = end - begin;
  1030. X    number  = new char[len + 1];
  1031. X    (void)strncpy(number, begin, len);
  1032. X    number[len] = 0; // stringify it
  1033. X
  1034. X    return number;
  1035. X}
  1036. X
  1037. X/*
  1038. X** leftshift_current_line - shifts the current line in DList left until
  1039. X**                          its tail is visible.
  1040. X*/
  1041. X
  1042. Xvoid leftshift_current_line(DList *dl)
  1043. X{
  1044. X    int inc = dl->currLine()->length()-columns()+1;
  1045. X    move_cursor(dl->savedYPos(), 0);
  1046. X    clear_to_end_of_line();
  1047. X    display_string(&(dl->currLine()->line())[inc],columns()-1);
  1048. X    dl->saveYXPos(dl->savedYPos(), max(goal_column(dl)-inc, 0));
  1049. X    move_cursor(dl->savedYPos(), dl->savedXPos());
  1050. X}
  1051. X
  1052. X/*
  1053. X** rightshift_current_line - rightshifts current line to "natural" position.
  1054. X*/
  1055. X
  1056. Xvoid rightshift_current_line(DList *dl)
  1057. X{
  1058. X    move_cursor(dl->savedYPos(), 0);
  1059. X    clear_to_end_of_line();
  1060. X    display_string(dl->currLine()->line(), dl->currLine()->length());
  1061. X    dl->saveYXPos(dl->savedYPos(), goal_column(dl));
  1062. X    move_cursor(dl->savedYPos(), dl->savedXPos());
  1063. X}
  1064. X
  1065. X/*
  1066. X** initial_listing - prints the initial listing screen.
  1067. X**                   Adjusts firstLine\(\), lastLine\(\) and currLine\(\).
  1068. X*/
  1069. X
  1070. Xvoid initial_listing(DList *dl)
  1071. X{
  1072. X    DLink *ln = dl->head();
  1073. X    dl->setFirst(ln);
  1074. X    dl->setCurrLine(ln);
  1075. X    cursor_home();
  1076. X    for (int i = 0; i < rows() - 2 && ln; ln = ln->next(), i++)
  1077. X    {
  1078. X        clear_to_end_of_line();
  1079. X        display_string(ln->line(), ln->length());
  1080. X    }
  1081. X
  1082. X    ln ? dl->setLast(ln->prev()) : dl->setLast(dl->tail());
  1083. X
  1084. X    //
  1085. X    // Don\'t forget to clear any remaining lines in those
  1086. X    // cases when the screen hasn\'t already been cleared and
  1087. X    // there are potentially dirty lines remaining.
  1088. X    //
  1089. X    move_cursor(i, 0);
  1090. X    for (; i < rows() - 2; i++) { clear_to_end_of_line(); cursor_down(); }
  1091. X}
  1092. X
  1093. X/*
  1094. X** seconds_in_date - returns the number of seconds in a date string
  1095. X**                   of the form:
  1096. X**
  1097. X**                       "mmm dd mm:hh:ss yyyy"
  1098. X**
  1099. X**                   This is the format returned by ctime\(3\), with
  1100. X**                   the leading "day" name chopped off.  According to ANSI C,
  1101. X**                   a long must be able to hold values up to at least
  1102. X**                   2147483647.  This will keep this function working for
  1103. X**                   many years into the future.  Eventually, CurrentYear will
  1104. X**                   have to be incremented.
  1105. X*/
  1106. X
  1107. Xlong seconds_in_date(const char *date)
  1108. X{
  1109. X    //
  1110. X    // days\[i\] is the number of days preceding month `i\',
  1111. X    // where 0 <= i <= 11
  1112. X    //
  1113. X    static const int days[] = { 0,31,59,90,120,151,181,212,243,273,304,334 };
  1114. X
  1115. X    //
  1116. X    // mhash\[*date + *\(date+1\) + *\(date+2\) - 268\] hashes to an integer
  1117. X    // in the range 0-11 signifying the month.
  1118. X    //
  1119. X    static char mhash[] = { 11, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  1120. X                            0, 0, 7, 0, 0, 2, 0, 0, 3, 0, 0, 9, 4, 8, 0,
  1121. X                            0, 6, 0, 5, 0, 0, 0, 0, 0, 0, 10 };
  1122. X
  1123. X    const long SecondsPerDay  = 86400L;
  1124. X    const long SecondsPerYear = 31536000L;
  1125. X    const int CurrentYear = 1992;
  1126. X    const char *const fmt = "%d";
  1127. X    long total = 0;
  1128. X    int tmp, mon = mhash[*date + *(date + 1) + *(date + 2) - 268];
  1129. X
  1130. X    (void)sscanf(date + 13, fmt, &tmp);   // seconds
  1131. X    total += tmp;
  1132. X    (void)sscanf(date + 10, fmt, &tmp);   // minutes
  1133. X    total += tmp * 60;
  1134. X    (void)sscanf(date + 7 , fmt, &tmp);   // hours
  1135. X    total += tmp * 3600;
  1136. X    (void)sscanf(date + 4 , fmt, &tmp);   // day of the month
  1137. X    total += (tmp - 1) * SecondsPerDay;
  1138. X    // days in months preceding this one
  1139. X    total += days[mon] * SecondsPerDay;
  1140. X    (void)sscanf(date + 16, fmt, &tmp);   // the year
  1141. X    // leap year adjustment
  1142. X    if (tmp % 4 == 0 && tmp % 100 != 0 && mon >= 2) total += SecondsPerDay;
  1143. X    total += (tmp - CurrentYear) * SecondsPerYear;
  1144. X    return total;
  1145. X}
  1146. X
  1147. X/*
  1148. X** temporary_file - returns the name of a temporary file.  The temporary
  1149. X**                  is forced to have permission 666.  Note that
  1150. X**                  we force tmpnam\(3\) to store the name for us in
  1151. X**                  volatile storage.
  1152. X*/
  1153. X
  1154. Xconst char *temporary_file()
  1155. X{
  1156. X    char *file = tmpnam(0);
  1157. X    if (file == 0)
  1158. X        error("file %s, line %d, tmpnam() failed", __FILE__, __LINE__);
  1159. X    int fd;
  1160. X    if ((fd = open(file, O_RDWR|O_CREAT, 0666)) < 0)
  1161. X        error("file %s, line %d, open(%s) failed",
  1162. X              __FILE__, __LINE__, file);
  1163. X    (void)close(fd);
  1164. X    return file;
  1165. X}
  1166. X
  1167. X/*
  1168. X** set_signals - set up our signal handlers
  1169. X*/
  1170. X
  1171. Xvoid set_signals()
  1172. X{
  1173. X    (void)signal(SIGHUP,  cleanup);
  1174. X    (void)signal(SIGINT,  cleanup);
  1175. X    (void)signal(SIGQUIT, cleanup);
  1176. X    (void)signal(SIGTERM, cleanup);
  1177. X#ifdef SIGTSTP
  1178. X    (void)signal(SIGTSTP, termstop);
  1179. X#endif
  1180. X#ifdef SIGWINCH
  1181. X    (void)signal(SIGWINCH, winch);
  1182. X#endif
  1183. X}
  1184. X
  1185. X/*
  1186. X** unset_signals - set signals back to defaults
  1187. X*/
  1188. X
  1189. Xvoid unset_signals()
  1190. X{
  1191. X    (void)signal(SIGHUP,  SIG_DFL);
  1192. X    (void)signal(SIGINT,  SIG_DFL);
  1193. X    (void)signal(SIGQUIT, SIG_DFL);
  1194. X    (void)signal(SIGTERM, SIG_DFL);
  1195. X#ifdef SIGTSTP
  1196. X    (void)signal(SIGTSTP, SIG_DFL);
  1197. X#endif
  1198. X#ifdef SIGWINCH
  1199. X    (void)signal(SIGWINCH, SIG_DFL);
  1200. X#endif
  1201. X}
  1202. X
  1203. X/*
  1204. X** block_tstp_and_winch - block SIGTSTP and SIGWINCH
  1205. X*/
  1206. X
  1207. X#ifdef BSDSIGNALS
  1208. Xstatic int oldmask;
  1209. X#elif POSIXSIGNALS
  1210. Xstatic sigset_t oldset;
  1211. X#endif
  1212. X
  1213. Xvoid block_tstp_and_winch()
  1214. X{
  1215. X#ifdef BSDSIGNALS
  1216. X    int oldmask = sigblock(sigmask(SIGTSTP)
  1217. X#ifdef SIGWINCH
  1218. X                           | sigmask(SIGWINCH)
  1219. X#endif
  1220. X                           );
  1221. X#elif POSIXSIGNALS
  1222. X    sigset_t newset;
  1223. X    sigemptyset(&newset);
  1224. X#ifdef SIGTSTP
  1225. X    sigaddset(&newset, SIGTSTP);
  1226. X#endif
  1227. X#ifdef SIGWINCH
  1228. X    sigaddset(&newset, SIGWINCH);
  1229. X#endif
  1230. X    if (sigprocmask(SIG_BLOCK, &newset, &oldset) < 0)
  1231. X        error("file %s, line %d, sigprocmask(SIG_BLOCK) failed\n",
  1232. X              __FILE__, __LINE__);
  1233. X#else
  1234. X    //
  1235. X    // We use ANSI C signals.  These can be "lost" but it\'s the
  1236. X    // best we can do.
  1237. X    //
  1238. X#ifdef SIGTSTP
  1239. X    (void)signal(SIGTSTP, SIG_IGN);
  1240. X#endif
  1241. X#ifdef SIGWINCH
  1242. X    (void)signal(SIGWINCH, SIG_IGN);
  1243. X#endif
  1244. X#endif
  1245. X}
  1246. X
  1247. X/*
  1248. X** unblock_tstp_and_winch - unblock SIGTSTP and SIGWINCH
  1249. X*/
  1250. X
  1251. Xvoid unblock_tstp_and_winch()
  1252. X{
  1253. X#ifdef BSDSIGNALS
  1254. X    (void)sigsetmask(oldmask);
  1255. X#elif POSIXSIGNALS
  1256. X    if (sigprocmask(SIG_SETMASK, &oldset, 0) < 0)
  1257. X        error("file %s, line %d, sigprocmask(SIG_SETMASK) failed\n",
  1258. X              __FILE__, __LINE__);
  1259. X#else
  1260. X#ifdef SIGTSTP
  1261. X    (void)signal(SIGTSTP, termstop);
  1262. X#endif
  1263. X#ifdef SIGWINCH
  1264. X    (void)signal(SIGWINCH, winch);
  1265. X#endif
  1266. X#endif
  1267. X}
  1268. X
  1269. END_OF_FILE
  1270. if test 36043 -ne `wc -c <'utilities.C'`; then
  1271.     echo shar: \"'utilities.C'\" unpacked with wrong size!
  1272. fi
  1273. # end of 'utilities.C'
  1274. fi
  1275. echo shar: End of archive 7 \(of 7\).
  1276. cp /dev/null ark7isdone
  1277. MISSING=""
  1278. for I in 1 2 3 4 5 6 7 ; do
  1279.     if test ! -f ark${I}isdone ; then
  1280.     MISSING="${MISSING} ${I}"
  1281.     fi
  1282. done
  1283. if test "${MISSING}" = "" ; then
  1284.     echo You have unpacked all 7 archives.
  1285.     rm -f ark[1-9]isdone
  1286. else
  1287.     echo You still need to unpack the following archives:
  1288.     echo "        " ${MISSING}
  1289. fi
  1290. ##  End of shell archive.
  1291. exit 0
  1292.  
  1293. exit 0 # Just in case...
  1294.