rsalz@uunet.uu.net (Rich Salz) (03/29/89)
Submitted-by: Jonathan I. Kamens <jik@PIT-MANAGER.MIT.EDU> Posting-number: Volume 18, Issue 76 Archive-name: undel/part04 #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of archive 4 (of 6)." # Contents: expunge.c pattern.c # Wrapped by jik@pit-manager on Mon Mar 27 12:16:51 1989 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'expunge.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'expunge.c'\" else echo shar: Extracting \"'expunge.c'\" \(10893 characters\) sed "s/^X//" >'expunge.c' <<'END_OF_FILE' X/* X * $Source: /mit/jik/src/delete/RCS/expunge.c,v $ X * $Author: jik $ X * X * This program is part of a package including delete, undelete, X * lsdel, expunge and purge. The software suite is meant as a X * replacement for rm which allows for file recovery. X * X * Copyright (c) 1989 by the Massachusetts Institute of Technology. X * For copying and distribution information, see the file "mit-copyright.h." X */ X X#if (!defined(lint) && !defined(SABER)) X static char rcsid_expunge_c[] = "$Header: expunge.c,v 1.7 89/03/27 12:06:47 jik Exp $"; X#endif X X/* X * Things that need to be fixed later: X * X * 1. The program should somehow store the sizes of deleted files and X * report the total amount of space regained after an expunge or purge. X */ X X#include <stdio.h> X#include <sys/types.h> X#include <sys/time.h> X#include <sys/dir.h> X#include <sys/param.h> X#include <strings.h> X#include <sys/stat.h> X#include "col.h" X#include "directories.h" X#include "util.h" X#include "pattern.h" X#include "expunge.h" X#include "mit-copyright.h" X Xextern char *malloc(), *realloc(); Xextern int current_time; X Xchar *whoami, *error_buf; X Xint timev, /* minimum mod time before undeletion */ X interactive, /* query before each expunge */ X recursive, /* expunge undeleted directories recursively */ X noop, /* print what would be done instead of doing it */ X verbose, /* print a line as each file is deleted */ X force, /* do not ask for any confirmation */ X listfiles, /* list files at toplevel */ X yield; /* print yield of expunge at end */ X Xint blocks_removed = 0; X X X X Xmain(argc, argv) Xint argc; Xchar *argv[]; X{ X extern char *optarg; X extern int optind; X int arg; X int status = 0; X X whoami = lastpart(argv[0]); X error_buf = malloc(strlen(whoami) + MAXPATHLEN + 3); X if (! error_buf) { X perror(whoami); X exit(1); X } X if (*whoami == 'p') { /* we're doing a purge */ X exit (purge()); X } X timev = 0; X yield = interactive = recursive = noop = verbose = listfiles = force = 0; X while ((arg = getopt(argc, argv, "t:irfnvly")) != -1) { X switch (arg) { X case 't': X timev = atoi(optarg); X break; X case 'i': X interactive++; X break; X case 'r': X recursive++; X break; X case 'f': X force++; X break; X case 'n': X noop++; X break; X case 'v': X verbose++; X break; X case 'l': X listfiles++; X break; X case 'y': X yield++; X break; X default: X usage(); X exit(1); X } X } X if (optind == argc) { X char *dir; X dir = "."; X status = status | expunge(&dir, 1); /* current working directory */ X } X else X status = status | expunge(&argv[optind], argc - optind); X exit(status & ERROR_MASK); X} X X X X X Xpurge() X{ X char *home[1]; X X home[0] = malloc(MAXPATHLEN); X if (! home[0]) { X perror(sprintf(error_buf, "%s: purge", whoami)); X exit(1); X } X timev = interactive = noop = verbose = force = 0; X yield = listfiles = recursive = 1; X get_home(home[0]); X if (! *home[0]) { X fprintf(stderr, "%s: purge: can't get home directory\n", whoami); X exit(1); X } X X printf("Please be patient.... this may take a while.\n\n"); X X return(expunge(home, 1)); X} X X X X Xusage() X{ X printf("Usage: %s [ options ] [ filename [ ... ]]\n", whoami); X printf("Options are:\n"); X printf(" -r recursive\n"); X printf(" -i interactive\n"); X printf(" -f force\n"); X printf(" -t n n-day-or-older expunge\n"); X printf(" -n noop\n"); X printf(" -v verbose\n"); X printf(" -l list files before expunging\n"); X printf(" -y print yield of expunge\n"); X printf(" -- end options and start filenames\n"); X} X X X X X Xexpunge(files, num) Xchar **files; Xint num; X{ X char *file_re; X char **found_files; X int num_found; X char *start_dir; X int status = 0; X int total = 0; X filerec *current; X X if (initialize_tree()) X exit(1); X X for ( ; num ; num--) { X if (*files[num - 1] == '/') { X start_dir = "/"; X file_re = parse_pattern(files[num - 1] + 1); X } X else { X start_dir = ""; X file_re = parse_pattern(files[num - 1]); X } X if (! file_re) X return(ERROR_MASK); X X found_files = get_the_files(start_dir, file_re, &num_found); X if (num_found) X num_found = process_files(found_files, num_found); X total += num_found; X if (! num_found) if (! force) { X /* X * There are three different situations here. Eiter we X * are dealing with an existing directory with no X * deleted files in it, or we are deleting with a X * non-existing deleted file with wildcards, or we are X * dealing with a non-existing deleted file without X * wildcards. In the former case we print nothing, and X * in the latter cases we print either "no match" or X * "not found" respectively X */ X if (no_wildcards(file_re)) { X if (! directory_exists(files[num - 1])) { X fprintf(stderr, "%s: %s: not found\n", X whoami, files[num - 1]); X } X } X else { X fprintf(stderr, "%s: %s: no match\n", whoami, X files[num - 1]); X } X } X free(file_re); X } X if (total && listfiles) { X list_files(); X if (! force) if (! top_level()) X return(NO_DELETE_MASK); X } X current = get_root_tree(); X if (current) X status = status | expunge_specified(current); X current = get_cwd_tree(); X if (current) X status = status | expunge_specified(current); X X if (yield) { X if (noop) X printf("Total that would be expunged: %dk\n", X blk_to_k(blocks_removed)); X else X printf("Total expunged: %dk\n", blk_to_k(blocks_removed)); X } X return(status); X} X X X Xexpunge_specified(leaf) Xfilerec *leaf; X{ X int status = 0; X X if ((leaf->specified) && ((leaf->specs.st_mode & S_IFMT) == S_IFDIR)) X status = do_directory_expunge(leaf); X /* the "do_directory_expunge" really only asks the user if he */ X /* wants to expunge the directory, it doesn't do any deleting. */ X if (! status) { X if (leaf->dirs) X status |= expunge_specified(leaf->dirs); X if (leaf->files) X status |= expunge_specified(leaf->files); X } X if (leaf->specified) X status |= really_do_expunge(leaf); X if (leaf->next) X status |= expunge_specified(leaf->next); X free_leaf(leaf); X return(status); X} X X Xprocess_files(files, num) Xchar **files; Xint num; X{ X int i; X filerec *leaf; X X for (i = 0; i < num; i++) { X if (! (leaf = add_path_to_tree(files[i]))) { X fprintf(stderr, "%s: error adding path to filename tree\n", X whoami); X exit(1); X } X X free(files[i]); X if (! timed_out(leaf, current_time, timev)) { X free_leaf(leaf); X num--; X } X } X free(files); X return(num); X} X X X X X X X X X Xdo_directory_expunge(file_ent) Xfilerec *file_ent; X{ X char buf[MAXPATHLEN]; X X get_leaf_path(file_ent, buf); X convert_to_user_name(buf, buf); X X if (interactive) { X printf("%s: Expunge directory %s? ", whoami, buf); X if (! yes()) X return(NO_DELETE_MASK); X } X return(0); X} X X X X X X X X Xreally_do_expunge(file_ent) Xfilerec *file_ent; X{ X char real[MAXPATHLEN], user[MAXPATHLEN]; X int status; X X get_leaf_path(file_ent, real); X convert_to_user_name(real, user); X X if (interactive) { X printf ("%s: Expunge %s (%dk)? ", whoami, user, X blk_to_k(file_ent->specs.st_blocks)); X if (! yes()) X return(NO_DELETE_MASK); X } X X if (noop) { X blocks_removed += file_ent->specs.st_blocks; X printf("%s: %s (%dk) would be expunged (%dk total)\n", whoami, user, X blk_to_k(file_ent->specs.st_blocks), X blk_to_k(blocks_removed)); X return(0); X } X X if ((file_ent->specs.st_mode & S_IFMT) == S_IFDIR) X status = rmdir(real); X else X status = unlink(real); X if (! status) { X blocks_removed += file_ent->specs.st_blocks; X if (verbose) X printf("%s: %s (%dk) expunged (%dk total)\n", whoami, user, X blk_to_k(file_ent->specs.st_blocks), X blk_to_k(blocks_removed)); X return(0); X } X else { X if (! force) X fprintf(stderr, "%s: %s not expunged\n", whoami, user); X return(ERROR_MASK); X } X} X X X X X X X X X Xtop_level() X{ X if (interactive) { Xprintf("The above files, which have been marked for deletion, are about to be\n"); Xprintf("expunged forever! You will be asked for confirmation before each file is\n"); Xprintf("deleted. Do you wish to continue [return = no]? "); X } X else { Xprintf("The above files, which have been marked for deletion, are about to be\n"); Xprintf("expunged forever! Make sure you don't need any of them before continuing.\n"); Xprintf("Do you wish to continue [return = no]? "); X } X return (yes()); X} X X X X X Xlist_files() X{ X filerec *current; X char **strings; X int num; X X strings = (char **) malloc(sizeof(char *)); X num = 0; X if (! strings) { X if (! force) X perror(sprintf(error_buf, "%s: list_files", whoami)); X exit(1); X } X printf("The following deleted files are going to be expunged: \n\n"); X X current = get_root_tree(); X strings = accumulate_names(current, strings, &num); X current = get_cwd_tree(); X strings = accumulate_names(current, strings, &num); X column_array(strings, num, DEF_SCR_WIDTH, 0, 0, 2, 1, 0, 1, stdout); X printf("\n"); X return(0); X} X X X X X Xchar **get_the_files(base, reg_exp, num_found) Xchar *base, *reg_exp; Xint *num_found; X{ X char **matches; X int num_matches; X char **found; X int num; X int i; X X found = (char **) malloc(0); X num = 0; X X matches = find_matches(base, reg_exp, &num_matches); X if (recursive) { X char **recurs_found; X int recurs_num; X X for (i = 0; i < num_matches; free(matches[i]), i++) { X if (is_deleted(lastpart(matches[i]))) { X found = add_str(found, num, matches[i]); X num++; X } X recurs_found = find_deleted_recurses(matches[i], &recurs_num); X add_arrays(&found, &num, &recurs_found, &recurs_num); X } X } X else { X struct stat stat_buf; X char **contents_found; X int num_contents; X X for (i = 0; i < num_matches; free(matches[i]), i++) { X if (is_deleted(lastpart(matches[i]))) { X found = add_str(found, num, matches[i]); X num++; X } X if (lstat(matches[i], &stat_buf)) X continue; X if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) { X contents_found = find_deleted_contents_recurs(matches[i], X &num_contents); X add_arrays(&found, &num, &contents_found, X &num_contents); X } X } X } X free(matches); X *num_found = num; X return(found); X} END_OF_FILE if test 10893 -ne `wc -c <'expunge.c'`; then echo shar: \"'expunge.c'\" unpacked with wrong size! fi # end of 'expunge.c' fi if test -f 'pattern.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'pattern.c'\" else echo shar: Extracting \"'pattern.c'\" \(12106 characters\) sed "s/^X//" >'pattern.c' <<'END_OF_FILE' X/* X * $Source: /mit/jik/src/delete/RCS/pattern.c,v $ X * $Author: jik $ X * X * This program is part of a package including delete, undelete, X * lsdel, expunge and purge. The software suite is meant as a X * replacement for rm which allows for file recovery. X * X * Copyright (c) 1989 by the Massachusetts Institute of Technology. X * For copying and distribution information, see the file "mit-copyright.h." X */ X X#if (!defined(lint) && !defined(SABER)) X static char rcsid_pattern_c[] = "$Header: pattern.c,v 1.8 89/03/27 12:07:48 jik Exp $"; X#endif X X#include <stdio.h> X#include <sys/types.h> X#include <sys/dir.h> X#include <sys/param.h> X#include <strings.h> X#include <sys/stat.h> X#include "directories.h" X#include "pattern.h" X#include "util.h" X#include "undelete.h" X#include "mit-copyright.h" X Xstatic char *add_char(); X Xextern char *malloc(), *realloc(); X Xextern char *whoami, *error_buf; X X/* X * parse_pattern returns an area of memory allocated by malloc when it X * is successful. Therefore, other procedures calling parse_pattern X * should use free() to free the region of memory when they are done X * with it. X */ Xchar *parse_pattern(file_pattern) Xchar *file_pattern; X{ X char *re_pattern, *cur_ptr, *re_ptr; X int guess_length; X X guess_length = strlen(file_pattern) + 5; X re_ptr = re_pattern = malloc(guess_length); X if (! re_ptr) { X perror(sprintf(error_buf, "%s: parse_pattern", whoami)); X exit(1); X } X X for (cur_ptr = file_pattern, re_ptr = re_pattern; *cur_ptr != NULL; X cur_ptr++) { X if (*cur_ptr == '\\') { X if (! cur_ptr[1]) { X fprintf(stderr, X "%s: parse_pattern: incomplete expression\n", X whoami); X return((char *) NULL); X } X if (! add_char(&re_pattern, &re_ptr, &guess_length, '\\')) X return ((char *) NULL); X cur_ptr++; X if (! add_char(&re_pattern, &re_ptr, &guess_length, *cur_ptr)) X return ((char *) NULL); X continue; X } X else if (*cur_ptr == '*') { X if (! add_char(&re_pattern, &re_ptr, &guess_length, '.')) X return ((char *) NULL); X if (! add_char(&re_pattern, &re_ptr, &guess_length, '*')) X return ((char *) NULL); X continue; X } X else if (*cur_ptr == '?') { X if (! add_char(&re_pattern, &re_ptr, &guess_length, '.')) X return ((char *) NULL); X continue; X } X else if (*cur_ptr == '.') { X if (! add_char(&re_pattern, &re_ptr, &guess_length, '\\')) X return ((char *) NULL); X if (! add_char(&re_pattern, &re_ptr, &guess_length, '.')) X return ((char *) NULL); X } X else { X if (! add_char(&re_pattern, &re_ptr, &guess_length, *cur_ptr)) X return ((char *) NULL); X } X } X if (! add_char(&re_pattern, &re_ptr, &guess_length, '\0')) X return ((char *) NULL); X return (re_pattern); X} X X X X X X X/* X * add_char() takes two char **, a length which is the current amount X * of space implemented for the string pointed to by the first *(char **), X * and a character to add to the string. It reallocs extra space if X * necessary, adds the character, and messes with the pointers if necessary. X */ Xstatic char *add_char(start, finish, length, chr) Xchar **start, **finish; Xint *length; Xchar chr; X{ X if (*finish - *start == *length) { X *start = realloc(*start, *length + 5); X if (! *start) { X perror(sprintf(error_buf, "%s: add_char", whoami)); X exit(1); X } X *finish = *start + *length - 1; X *length += 5; X } X **finish = chr; X (*finish)++; X return(*start); X} X X X X X X X/* X * add_arrays() takes pointers to two arrays of char **'s and their X * lengths, merges the two into the first by realloc'ing the first and X * then free's the second's memory usage. X */ Xadd_arrays(array1, num1, array2, num2) Xchar ***array1, ***array2; Xint *num1, *num2; X{ X int counter; X X *array1 = (char **) realloc(*array1, sizeof(char *) * (*num1 + *num2)); X if (! *array1) { X perror(sprintf(error_buf, "%s: add_arrays", whoami)); X exit(1); X } X for (counter = *num1; counter < *num1 + *num2; counter++) X *(*array1 + counter) = *(*array2 + counter - *num1); X free (*array2); X *num1 += *num2; X return(0); X} X X X X X X X Xchar **add_str(strs, num, str) Xchar **strs; Xint num; Xchar *str; X{ X strs = (char **) realloc(strs, sizeof(char *) * (num + 1)); X if (! strs) { X perror(sprintf(error_buf, "%s: add_str", whoami)); X exit(1); X } X strs[num] = malloc(strlen(str) + 1); X if (! strs[num]) { X perror(sprintf(error_buf, "%s: add_str", whoami)); X exit(1); X } X strcpy(strs[num], str); X return(strs); X} X X X X X X X Xchar **find_deleted_matches(base, expression, num_found) Xchar *base, *expression; Xint *num_found; X{ X struct direct *dp; X DIR *dirp; X char **found; X int num; X char **next; X int num_next; X char first[MAXNAMLEN], rest[MAXPATHLEN]; X char new[MAXPATHLEN]; X X#ifdef DEBUG X printf("Looking for %s in %s\n", expression, base); X#endif X found = (char **) malloc(0); X *num_found = num = 0; X X dirp = opendir(base); X if (! dirp) X return(found); X X readdir(dirp); readdir(dirp); /* get rid of . and .. */ X X strcpy(first, reg_firstpart(expression, rest)); X re_comp(first); X X for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { X if (re_exec(dp->d_name) && *rest) { X strcpy(new, append(base, dp->d_name)); X next = find_deleted_matches(new, rest, &num_next); X add_arrays(&found, &num, &next, &num_next); X } X else if (is_deleted(dp->d_name)) if (re_exec(&dp->d_name[2])) { X if (*rest) { X strcpy(new, append(base, dp->d_name)); X next = find_deleted_matches(new, rest, &num_next); X add_arrays(&found, &num, &next, &num_next); X } X else { X found = add_str(found, num, append(base, dp->d_name)); X num++; X } X } X } X closedir(dirp); X *num_found = num; X return(found); X} X X X X X Xchar **find_matches(base, expression, num_found) Xchar *base, *expression; Xint *num_found; X{ X struct direct *dp; X DIR *dirp; X char **found; X int num; X char **next; X int num_next; X char first[MAXNAMLEN], rest[MAXPATHLEN]; X char new[MAXPATHLEN]; X X#ifdef DEBUG X printf("Looking for %s in %s\n", expression, base); X#endif X found = (char **) malloc(0); X *num_found = num = 0; X X dirp = opendir(base); X if (! dirp) X return(found); X X strcpy(first, reg_firstpart(expression, rest)); X X for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { X re_comp(first); X if (re_exec(dp->d_name)) { X if (*rest) { X strcpy(new, append(base, dp->d_name)); X next = find_matches(new, rest, &num_next); X add_arrays(&found, &num, &next, &num_next); X } X else { X found = add_str(found, num, append(base, dp->d_name)); X num++; X } X } X else if (is_deleted(dp->d_name)) if (re_exec(&dp->d_name[2])) { X if (*rest) { X strcpy(new, append(base, dp->d_name)); X next = find_matches(new, rest, &num_next); X add_arrays(&found, &num, &next, &num_next); X } X else { X found = add_str(found, num, append(base, dp->d_name)); X num++; X } X } X } X closedir(dirp); X *num_found = num; X return(found); X} X X X X X X X Xchar **find_recurses(base, num_found) Xchar *base; Xint *num_found; X{ X DIR *dirp; X struct direct *dp; X char newname[MAXPATHLEN]; X char **found, **new_found; X int found_num, new_found_num; X struct stat stat_buf; X X#ifdef DEBUG X printf("Looking for subs of %s\n", base); X#endif X found = (char **) malloc(0); X *num_found = found_num = 0; X X dirp = opendir(base); X if (! dirp) X return(found); X X readdir(dirp); readdir(dirp); /* get rid of . and .. */ X X for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { X strcpy(newname, append(base, dp->d_name)); X found = add_str(found, found_num, newname); X found_num++; X if (lstat(newname, &stat_buf)) X continue; X if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) { X new_found = find_recurses(newname, &new_found_num); X add_arrays(&found, &found_num, &new_found, &new_found_num); X } X } X closedir(dirp); X *num_found = found_num; X return(found); X} X X X X X X Xchar **find_deleted_recurses(base, num_found) Xchar *base; Xint *num_found; X{ X DIR *dirp; X struct direct *dp; X char newname[MAXPATHLEN]; X char **found, **new_found; X int found_num, new_found_num; X struct stat stat_buf; X X found = (char **) malloc(0); X *num_found = found_num = 0; X X dirp = opendir(base); X if (! dirp) X return(found); X X readdir(dirp); readdir(dirp); /* get rid of . and .. */ X X for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { X strcpy(newname, append(base, dp->d_name)); X X if (is_deleted(dp->d_name)) { X found = add_str(found, found_num, newname); X found_num++; X } X if (lstat(newname, &stat_buf)) { X continue; X } X if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) { X new_found = find_deleted_recurses(newname, &new_found_num); X add_arrays(&found, &found_num, &new_found, &new_found_num); X } X } X closedir(dirp); X *num_found = found_num; X return(found); X} X X X X X X Xchar **find_contents(base, num_found) Xchar *base; Xint *num_found; X{ X DIR *dirp; X struct direct *dp; X char **found; X int num; X X#ifdef DEBUG X printf("Looking for contents of %s\n", base); X#endif X found = (char **) malloc(0); X *num_found = num = 0; X X dirp = opendir(base); X if (! dirp) X return(found); X X readdir(dirp); readdir(dirp); /* get rid of . and .. */ X X for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { X found = add_str(found, num, append(base, dp->d_name)); X num += 1; X } X closedir(dirp); X *num_found = num; X return(found); X} X X X Xchar **find_deleted_contents(base, num_found) Xchar *base; Xint *num_found; X{ X DIR *dirp; X struct direct *dp; X char **found; X int num; X X#ifdef DEBUG X printf("Looking for deleted contents of %s\n", base); X#endif X found = (char **) malloc(0); X *num_found = num = 0; X X dirp = opendir(base); X if (! dirp) X return(found); X X readdir(dirp); readdir(dirp); /* get rid of . and .. */ X X for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { X if (is_deleted(dp->d_name)) { X found = add_str(found, num, append(base, dp->d_name)); X num += 1; X } X } X closedir(dirp); X *num_found = num; X return(found); X} X X X X Xchar **find_deleted_contents_recurs(base, num_found) Xchar *base; Xint *num_found; X{ X DIR *dirp; X struct direct *dp; X char **found; X int num; X struct stat stat_buf; X char newname[MAXPATHLEN]; X char **new_found; X int new_found_num; X X#ifdef DEBUG X printf("Looking for recursive deleted contents of %s\n", base); X#endif X found = (char **) malloc(0); X *num_found = num = 0; X X dirp = opendir(base); X if (! dirp) X return(found); X X readdir(dirp); readdir(dirp); /* get rid of . and .. */ X X for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { X if (is_deleted(dp->d_name)) { X strcpy(newname, append(base, dp->d_name)); X found = add_str(found, num, newname); X num += 1; X if (lstat(newname, &stat_buf)) X continue; X if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) { X new_found = find_recurses(newname, &new_found_num); X add_arrays(&found, &num, &new_found, &new_found_num); X } X } X } X closedir(dirp); X *num_found = num; X return(found); X} X X X X/* X * returns true if the filename has no regular expression wildcards in X * it. That means no non-quoted dots or asterisks. Assumes a X * null-terminated string, and a valid regular expression. X */ Xint no_wildcards(name) Xchar *name; X{ X do { X switch (*name) { X case '\\': X name++; X break; X case '.': X return(0); X case '*': X return(0); X } X } while (*++name); X return(1); X} END_OF_FILE if test 12106 -ne `wc -c <'pattern.c'`; then echo shar: \"'pattern.c'\" unpacked with wrong size! fi # end of 'pattern.c' fi echo shar: End of archive 4 \(of 6\). cp /dev/null ark4isdone MISSING="" for I in 1 2 3 4 5 6 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 6 archives. rm -f ark[1-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0 -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.