/* * $Id: undelete.c,v 1.30 2002-11-20 19:09:24 zacheiss Exp $ * * This program is part of a package including delete, undelete, * lsdel, expunge and purge. The software suite is meant as a * replacement for rm which allows for file recovery. * * Copyright (c) 1989 by the Massachusetts Institute of Technology. * For copying and distribution information, see the file "mit-copying.h." */ #include #include #include #include #include #include #include #include #include "delete_errs.h" #include "pattern.h" #include "directories.h" #include "undelete.h" #include "shell_regexp.h" #include "mit-copying.h" #include "errors.h" #include "util.h" static void usage(void); static int interactive_mode(void), undelete(char *name); static int recurs_and_undelete(filerec *leaf), do_undelete(filerec *file_ent); static int do_file_rename(char *real_name, char *user_name); static int process_files(char **files, int num); static int unlink_completely(char *filename); static int filecmp(const void *arg1, const void *arg2); static int choose_better(char *str1, char *str2); int interactive, recursive, verbose, directoriesonly, noop, force; int main(int argc, char *argv[]) { extern char *optarg; extern int optind; int arg; int retval; #if defined(__APPLE__) && defined(__MACH__) add_error_table(&et_del_error_table); #else initialize_del_error_table(); #endif whoami = lastpart(argv[0]); interactive = recursive = verbose = directoriesonly = noop = force = 0; while ((arg = getopt(argc, argv, "firvnR")) != -1) { switch (arg) { case 'f': force++; break; case 'i': interactive++; break; case 'r': recursive++; if (directoriesonly) { fprintf(stderr, "%s: -r and -R and mutually exclusive.\n", whoami); usage(); exit(1); } break; case 'v': verbose++; break; case 'n': noop++; break; case 'R': directoriesonly++; if (recursive) { fprintf(stderr, "%s: -r and -R are mutually exclusive.\n", whoami); usage(); exit(1); } break; default: usage(); exit(1); } } report_errors = ! force; if (optind == argc) { if (interactive_mode()) error("interactive_mode"); } else while (optind < argc) { retval = undelete(argv[optind]); if (retval) error(argv[optind]); optind++; } exit(((! force) && error_occurred) ? 1 : 0); } static int interactive_mode(void) { char buf[MAXPATHLEN]; char *ptr; int status = 0; int retval; if (verbose) { printf("Enter the files to be undeleted, one file per line.\n"); printf("Hit on a line by itself to exit.\n\n"); } do { printf("%s: ", whoami); ptr = fgets(buf, MAXPATHLEN, stdin); if (! ptr) { printf("\n"); return status; } ptr = strchr(buf, '\n'); /* fgets breakage */ if (ptr) *ptr = '\0'; if (! *buf) return status; retval = undelete(buf); if (retval) { error(buf); status = retval; } } while (*buf); return status; } static void usage() { fprintf(stderr, "Usage: %s [ options ] [filename ...]\n", whoami); fprintf(stderr, "Options are:\n"); fprintf(stderr, " -r recursive\n"); fprintf(stderr, " -i interactive\n"); fprintf(stderr, " -f force\n"); fprintf(stderr, " -v verbose\n"); fprintf(stderr, " -n noop\n"); fprintf(stderr, " -R directories only (i.e. no recursion)\n"); fprintf(stderr, " -- end options and start filenames\n"); fprintf(stderr, "-r and -D are mutually exclusive\n"); } static int undelete(char *name) { char **found_files; int num_found; int status = 0; filerec *current; int retval; if ((retval = get_the_files(name, &num_found, &found_files))) { error(name); return retval; } if (num_found) { if ((retval = process_files(found_files, num_found))) { error(name); return retval; } if (*name == '/') current = get_root_tree(); else current = get_cwd_tree(); status = recurs_and_undelete(current); if (status) { error(name); return status; } } else { if (no_wildcards(name)) { set_error(ENOENT) } else set_error(DELETE_ENOMATCH); error(name); return error_code; } return status; } static int recurs_and_undelete(filerec *leaf) { int status = 0; int retval; if (leaf->specified) { retval = do_undelete(leaf); if (retval) { error("do_undelete"); status = retval; } } if (! status) { /* recurse only if if top-level undelete */ /* succeeded or was not requested */ if (leaf->dirs) { retval = recurs_and_undelete(leaf->dirs); if (retval) { error("recurs_and_undelete"); status = retval; } } if (leaf->files) { retval = recurs_and_undelete(leaf->files); if (retval) { error("recurs_and_undelete"); status = retval; } } } if (leaf->next) { retval = recurs_and_undelete(leaf->next); if (retval) { error("recurs_and_undelete"); status = retval; } } free_leaf(leaf); return status; } static int process_files(char **files, int num) { int i; listrec *filelist; struct filrec *not_needed; int retval; filelist = (listrec *) Malloc((unsigned) (sizeof(listrec) * num)); if ((! filelist) && num) { set_error(errno); error("process_files"); return error_code; } for (i = 0; i < num; i++) { filelist[i].real_name = Malloc((unsigned) (strlen(files[i]) + 1)); if (! filelist[i].real_name) { set_error(errno); error("process_files"); return error_code; } (void) strcpy(filelist[i].real_name, files[i]); filelist[i].user_name = Malloc((unsigned) (strlen(files[i]) + 1)); if (! filelist[i].user_name) { set_error(errno); error("process_files"); return error_code; } (void) convert_to_user_name(files[i], filelist[i].user_name); free(files[i]); } free((char *) files); if ((retval = sort_files(filelist, num))) { error("sort_files"); return retval; } if ((retval = unique(&filelist, &num))) { error("unique"); return retval; } if ((retval = initialize_tree())) { error("initialize_tree"); return retval; } for (i = 0; i < num; i++) { if ((retval = add_path_to_tree(filelist[i].real_name, ¬_needed))) { error("add_path_to_tree"); return retval; } else { free(filelist[i].real_name); free(filelist[i].user_name); } } free((char *) filelist); return 0; } static int do_undelete(filerec *file_ent) { struct stat stat_buf; char user_name[MAXPATHLEN], real_name[MAXPATHLEN]; int retval; if ((retval = get_leaf_path(file_ent, real_name))) { if (! force) fprintf(stderr, "%s: %s: %s\n", whoami, "get_leaf_path", error_message(retval)); return retval; } (void) convert_to_user_name(real_name, user_name); if (interactive) { if ((file_ent->specs.st_mode & S_IFMT) == S_IFDIR) printf("%s: Undelete directory %s? ", whoami, user_name); else printf("%s: Undelete %s? ", whoami, user_name); if (! yes()) { set_status(UNDEL_NOT_UNDELETED); return error_code; } } if (! lstat(user_name, &stat_buf)) if (! force) { printf("%s: An undeleted %s already exists.\n", whoami, user_name); printf("Do you wish to continue with the undelete and overwrite that version? "); if (! yes()) { set_status(UNDEL_NOT_UNDELETED); return error_code; } if ((! noop) && (retval = unlink_completely(user_name))) { error(user_name); return retval; } } if (noop) { printf("%s: %s would be undeleted\n", whoami, user_name); return 0; } if ((retval = do_file_rename(real_name, user_name))) { error("do_file_rename"); return retval; } else { if (verbose) printf("%s: %s undeleted\n", whoami, user_name); return 0; } } static int do_file_rename(char *real_name, char *user_name) { char *ptr; int retval; char error_buf[MAXPATHLEN+MAXPATHLEN+14]; char old_name[MAXPATHLEN], new_name[MAXPATHLEN]; char buf[MAXPATHLEN]; (void) strcpy(old_name, real_name); (void) strcpy(new_name, real_name); ptr = strrindex(new_name, ".#"); (void) convert_to_user_name(ptr, ptr); (void) strcpy(ptr, firstpart(ptr, buf)); (void) strcpy(&old_name[ptr - new_name], firstpart(&old_name[ptr - new_name], buf)); if (rename(old_name, new_name)) { set_error(errno); (void) sprintf(error_buf, "renaming %s to %s", old_name, new_name); error(error_buf); return error_code; } if (ptr > new_name) { *--ptr = '\0'; old_name[ptr - new_name] = '\0'; } if ((retval = change_path(real_name, user_name))) { error("change_path"); return retval; } return 0; } static int filecmp(const void *arg1, const void *arg2) { listrec *file1 = (listrec *) arg1, *file2 = (listrec *) arg2; return(strcmp(file1->user_name, file2->user_name)); } int sort_files(data, num_data) listrec *data; int num_data; { qsort(data, num_data, sizeof(listrec), filecmp); return 0; } int unique(the_files, number) listrec **the_files; int *number; { int i, last; int offset; listrec *files; files = *the_files; for (last = 0, i = 1; i < *number; i++) { if (! strcmp(files[last].user_name, files[i].user_name)) { int better; better = choose_better(files[last].real_name, files[i].real_name); if (better == 1) { /* the first one is better */ free (files[i].real_name); free (files[i].user_name); files[i].real_name = (char *) NULL; } else { free (files[last].real_name); free (files[last].user_name); files[last].real_name = (char *) NULL; last = i; } } else last = i; } for (offset = 0, i = 0; i + offset < *number; i++) { if (! files[i].real_name) offset++; if (i + offset < *number) files[i] = files[i + offset]; } *number -= offset; files = (listrec *) realloc((char *) files, (unsigned) (sizeof(listrec) * *number)); if ((! files) && *number) { set_error(errno); error("realloc"); return errno; } *the_files = files; return 0; } static int choose_better(char *str1, char *str2) { char *pos1, *pos2; pos1 = strindex(str1, ".#"); pos2 = strindex(str2, ".#"); while (pos1 && pos2) { if (pos1 - str1 < pos2 - str2) return(2); else if (pos2 - str2 < pos1 - str1) return(1); pos1 = strindex(pos1 + 1, ".#"); pos2 = strindex(pos2 + 1, ".#"); } if (! pos1) return(1); else return(2); } static int unlink_completely(char *filename) { char buf[MAXPATHLEN]; struct stat stat_buf; DIR *dirp; struct dirent *dp; int retval; int status = 0; if (lstat(filename, &stat_buf)) { set_error(errno); error(filename); return error_code; } if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) { dirp = Opendir(filename); if (! dirp) { set_error(errno); error(filename); return error_code; } for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { if (is_dotfile(dp->d_name)) continue; (void) strcpy(buf, append(filename, dp->d_name)); if ((retval = unlink_completely(buf))) { error(buf); status = retval; } } closedir(dirp); if ((retval = rmdir(filename))) { set_error(errno); error(filename); return error_code; } } else if ((retval = unlink(filename))) { set_error(errno); error(filename); return error_code; } return status; } int get_the_files(name, num_found, found) char *name; int *num_found; char ***found; { int retval; int options; options = FIND_DELETED; if (recursive) options |= RECURS_DELETED; if (! directoriesonly) options |= FIND_CONTENTS; retval = find_matches(name, num_found, found, options); if (retval) { error("find_matches"); return retval; } return 0; }