/* GNU dump extensions to tar. Copyright (C) 1988, 1992, 1993 Free Software Foundation This file is part of GNU Tar. GNU Tar is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Tar is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Tar; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef lint static char rcsid[] = "$Id: gnu.c,v 1.1 1995/10/18 08:41:09 deraadt Exp $"; #endif /* not lint */ #include #include #include #include #ifndef STDC_HEADERS extern int errno; #endif #include time_t time (); #include "tar.h" #include "port.h" #ifndef S_ISLNK #define lstat stat #endif extern time_t new_time; extern FILE *msg_file; void addname (); int check_exclude (); extern PTR ck_malloc (); extern PTR ck_realloc (); int confirm (); extern PTR init_buffer (); extern char *get_buffer (); int is_dot_or_dotdot (); extern void add_buffer (); extern void flush_buffer (); void name_gather (); int recursively_delete (); void skip_file (); char *un_quote_string (); extern char *new_name (); static void add_dir_name (); struct dirname { struct dirname *next; char *name; char *dir_text; int dev; int ino; int allnew; }; static struct dirname *dir_list; static time_t this_time; void add_dir (name, dev, ino, text) char *name; char *text; dev_t dev; ino_t ino; { struct dirname *dp; dp = (struct dirname *) ck_malloc (sizeof (struct dirname)); if (!dp) abort (); dp->next = dir_list; dir_list = dp; dp->dev = dev; dp->ino = ino; dp->name = ck_malloc (strlen (name) + 1); strcpy (dp->name, name); dp->dir_text = text; dp->allnew = 0; } void read_dir_file () { int dev; int ino; char *strp; FILE *fp; char buf[512]; static char *path = 0; if (path == 0) path = ck_malloc (PATH_MAX); time (&this_time); if (gnu_dumpfile[0] != '/') { #if defined(__MSDOS__) || defined(HAVE_GETCWD) || defined(_POSIX_VERSION) if (!getcwd (path, PATH_MAX)) { msg ("Couldn't get current directory."); exit (EX_SYSTEM); } #else char *getwd (); if (!getwd (path)) { msg ("Couldn't get current directory: %s", path); exit (EX_SYSTEM); } #endif /* If this doesn't fit, we're in serious trouble */ strcat (path, "/"); strcat (path, gnu_dumpfile); gnu_dumpfile = path; } fp = fopen (gnu_dumpfile, "r"); if (fp == 0 && errno != ENOENT) { msg_perror ("Can't open %s", gnu_dumpfile); return; } if (!fp) return; fgets (buf, sizeof (buf), fp); if (!f_new_files) { f_new_files++; new_time = atol (buf); } while (fgets (buf, sizeof (buf), fp)) { strp = &buf[strlen (buf)]; if (strp[-1] == '\n') strp[-1] = '\0'; strp = buf; dev = atol (strp); while (isdigit (*strp)) strp++; ino = atol (strp); while (isspace (*strp)) strp++; while (isdigit (*strp)) strp++; strp++; add_dir (un_quote_string (strp), dev, ino, (char *) 0); } fclose (fp); } void write_dir_file () { FILE *fp; struct dirname *dp; char *str; extern char *quote_copy_string (); fp = fopen (gnu_dumpfile, "w"); if (fp == 0) { msg_perror ("Can't write to %s", gnu_dumpfile); return; } fprintf (fp, "%lu\n", this_time); for (dp = dir_list; dp; dp = dp->next) { if (!dp->dir_text) continue; str = quote_copy_string (dp->name); if (str) { fprintf (fp, "%u %u %s\n", dp->dev, dp->ino, str); free (str); } else fprintf (fp, "%u %u %s\n", dp->dev, dp->ino, dp->name); } fclose (fp); } struct dirname * get_dir (name) char *name; { struct dirname *dp; for (dp = dir_list; dp; dp = dp->next) { if (!strcmp (dp->name, name)) return dp; } return 0; } /* Collect all the names from argv[] (or whatever), then expand them into a directory tree, and put all the directories at the beginning. */ void collect_and_sort_names () { struct name *n, *n_next; int num_names; struct stat statbuf; int name_cmp (); char *merge_sort (); name_gather (); if (gnu_dumpfile) read_dir_file (); if (!namelist) addname ("."); for (n = namelist; n; n = n_next) { n_next = n->next; if (n->found || n->dir_contents) continue; if (n->regexp) /* FIXME just skip regexps for now */ continue; if (n->change_dir) if (chdir (n->change_dir) < 0) { msg_perror ("can't chdir to %s", n->change_dir); continue; } #ifdef AIX if (statx (n->name, &statbuf, STATSIZE, STX_HIDDEN | STX_LINK)) #else if (lstat (n->name, &statbuf) < 0) #endif /* AIX */ { msg_perror ("can't stat %s", n->name); continue; } if (S_ISDIR (statbuf.st_mode)) { n->found++; add_dir_name (n->name, statbuf.st_dev); } } num_names = 0; for (n = namelist; n; n = n->next) num_names++; namelist = (struct name *) merge_sort ((PTR) namelist, num_names, (char *) (&(namelist->next)) - (char *) namelist, name_cmp); for (n = namelist; n; n = n->next) { n->found = 0; } if (gnu_dumpfile) write_dir_file (); } int name_cmp (n1, n2) struct name *n1, *n2; { if (n1->found) { if (n2->found) return strcmp (n1->name, n2->name); else return -1; } else if (n2->found) return 1; else return strcmp (n1->name, n2->name); } int dirent_cmp (p1, p2) const PTR p1; const PTR p2; { char *frst, *scnd; frst = (*(char **) p1) + 1; scnd = (*(char **) p2) + 1; return strcmp (frst, scnd); } char * get_dir_contents (p, device) char *p; int device; { DIR *dirp; register struct dirent *d; char *new_buf; char *namebuf; int bufsiz; int len; PTR the_buffer; char *buf; size_t n_strs; /* int n_size;*/ char *p_buf; char **vec, **p_vec; extern int errno; errno = 0; dirp = opendir (p); bufsiz = strlen (p) + NAMSIZ; namebuf = ck_malloc (bufsiz + 2); if (!dirp) { if (errno) msg_perror ("can't open directory %s", p); else msg ("error opening directory %s", p); new_buf = NULL; } else { struct dirname *dp; int all_children; dp = get_dir (p); all_children = dp ? dp->allnew : 0; (void) strcpy (namebuf, p); if (p[strlen (p) - 1] != '/') (void) strcat (namebuf, "/"); len = strlen (namebuf); the_buffer = init_buffer (); while (d = readdir (dirp)) { struct stat hs; /* Skip . and .. */ if (is_dot_or_dotdot (d->d_name)) continue; if (NLENGTH (d) + len >= bufsiz) { bufsiz += NAMSIZ; namebuf = ck_realloc (namebuf, bufsiz + 2); } (void) strcpy (namebuf + len, d->d_name); #ifdef AIX if (0 != f_follow_links ? statx (namebuf, &hs, STATSIZE, STX_HIDDEN) : statx (namebuf, &hs, STATSIZE, STX_HIDDEN | STX_LINK)) #else if (0 != f_follow_links ? stat (namebuf, &hs) : lstat (namebuf, &hs)) #endif { msg_perror ("can't stat %s", namebuf); continue; } if ((f_local_filesys && device != hs.st_dev) || (f_exclude && check_exclude (namebuf))) add_buffer (the_buffer, "N", 1); #ifdef AIX else if (S_ISHIDDEN (hs.st_mode)) { add_buffer (the_buffer, "D", 1); strcat (d->d_name, "A"); d->d_namlen++; } #endif /* AIX */ else if (S_ISDIR (hs.st_mode)) { if (dp = get_dir (namebuf)) { if (dp->dev != hs.st_dev || dp->ino != hs.st_ino) { if (f_verbose) msg ("directory %s has been renamed.", namebuf); dp->allnew = 1; dp->dev = hs.st_dev; dp->ino = hs.st_ino; } dp->dir_text = ""; } else { if (f_verbose) msg ("Directory %s is new", namebuf); add_dir (namebuf, hs.st_dev, hs.st_ino, ""); dp = get_dir (namebuf); dp->allnew = 1; } if (all_children) dp->allnew = 1; add_buffer (the_buffer, "D", 1); } else if (!all_children && f_new_files && new_time > hs.st_mtime && (f_new_files > 1 || new_time > hs.st_ctime)) add_buffer (the_buffer, "N", 1); else add_buffer (the_buffer, "Y", 1); add_buffer (the_buffer, d->d_name, (int) (NLENGTH (d) + 1)); } add_buffer (the_buffer, "\000\000", 2); closedir (dirp); /* Well, we've read in the contents of the dir, now sort them */ buf = get_buffer (the_buffer); if (buf[0] == '\0') { flush_buffer (the_buffer); new_buf = NULL; } else { n_strs = 0; for (p_buf = buf; *p_buf;) { int tmp; tmp = strlen (p_buf) + 1; n_strs++; p_buf += tmp; } vec = (char **) ck_malloc (sizeof (char *) * (n_strs + 1)); for (p_vec = vec, p_buf = buf; *p_buf; p_buf += strlen (p_buf) + 1) *p_vec++ = p_buf; *p_vec = 0; qsort ((PTR) vec, n_strs, sizeof (char *), dirent_cmp); new_buf = (char *) ck_malloc (p_buf - buf + 2); for (p_vec = vec, p_buf = new_buf; *p_vec; p_vec++) { char *p_tmp; for (p_tmp = *p_vec; *p_buf++ = *p_tmp++;) ; } *p_buf++ = '\0'; free (vec); flush_buffer (the_buffer); } } free (namebuf); return new_buf; } /* p is a directory. Add all the files in P to the namelist. If any of the files is a directory, recurse on the subdirectory. . . */ static void add_dir_name (p, device) char *p; int device; { char *new_buf; char *p_buf; char *namebuf; int buflen; register int len; int sublen; /* PTR the_buffer;*/ /* char *buf;*/ /* char **vec,**p_vec;*/ /* int n_strs,n_size;*/ struct name *n; int dirent_cmp (); new_buf = get_dir_contents (p, device); for (n = namelist; n; n = n->next) { if (!strcmp (n->name, p)) { n->dir_contents = new_buf ? new_buf : "\0\0\0\0"; break; } } if (new_buf) { len = strlen (p); buflen = NAMSIZ <= len ? len + NAMSIZ : NAMSIZ; namebuf = ck_malloc (buflen + 1); (void) strcpy (namebuf, p); if (namebuf[len - 1] != '/') { namebuf[len++] = '/'; namebuf[len] = '\0'; } for (p_buf = new_buf; *p_buf; p_buf += sublen + 1) { sublen = strlen (p_buf); if (*p_buf == 'D') { if (len + sublen >= buflen) { buflen += NAMSIZ; namebuf = ck_realloc (namebuf, buflen + 1); } (void) strcpy (namebuf + len, p_buf + 1); addname (namebuf); add_dir_name (namebuf, device); } } free (namebuf); } } /* Returns non-zero if p is . or .. This could be a macro for speed. */ int is_dot_or_dotdot (p) char *p; { return (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0'))); } void gnu_restore (skipcrud) int skipcrud; { char *current_dir; /* int current_dir_length; */ char *archive_dir; /* int archive_dir_length; */ PTR the_buffer; char *p; DIR *dirp; struct dirent *d; char *cur, *arc; extern struct stat hstat; /* Stat struct corresponding */ long size, copied; char *from, *to; extern union record *head; dirp = opendir (skipcrud + current_file_name); if (!dirp) { /* The directory doesn't exist now. It'll be created. In any case, we don't have to delete any files out of it */ skip_file ((long) hstat.st_size); return; } the_buffer = init_buffer (); while (d = readdir (dirp)) { if (is_dot_or_dotdot (d->d_name)) continue; add_buffer (the_buffer, d->d_name, (int) (NLENGTH (d) + 1)); } closedir (dirp); add_buffer (the_buffer, "", 1); current_dir = get_buffer (the_buffer); archive_dir = (char *) ck_malloc (hstat.st_size); if (archive_dir == 0) { msg ("Can't allocate %qd bytes for restore", hstat.st_size); skip_file ((long) hstat.st_size); return; } to = archive_dir; for (size = hstat.st_size; size > 0; size -= copied) { from = findrec ()->charptr; if (!from) { msg ("Unexpected EOF in archive\n"); break; } copied = endofrecs ()->charptr - from; if (copied > size) copied = size; bcopy ((PTR) from, (PTR) to, (int) copied); to += copied; userec ((union record *) (from + copied - 1)); } for (cur = current_dir; *cur; cur += strlen (cur) + 1) { for (arc = archive_dir; *arc; arc += strlen (arc) + 1) { arc++; if (!strcmp (arc, cur)) break; } if (*arc == '\0') { p = new_name (skipcrud + current_file_name, cur); if (f_confirm && !confirm ("delete", p)) { free (p); continue; } if (f_verbose) fprintf (msg_file, "%s: deleting %s\n", tar, p); if (recursively_delete (p)) { msg ("%s: Error while deleting %s\n", tar, p); } free (p); } } flush_buffer (the_buffer); free (archive_dir); } int recursively_delete (path) char *path; { struct stat sbuf; DIR *dirp; struct dirent *dp; char *path_buf; /* int path_len; */ if (lstat (path, &sbuf) < 0) return 1; if (S_ISDIR (sbuf.st_mode)) { /* path_len=strlen(path); */ dirp = opendir (path); if (dirp == 0) return 1; while (dp = readdir (dirp)) { if (is_dot_or_dotdot (dp->d_name)) continue; path_buf = new_name (path, dp->d_name); if (recursively_delete (path_buf)) { free (path_buf); closedir (dirp); return 1; } free (path_buf); } closedir (dirp); if (rmdir (path) < 0) return 1; return 0; } if (unlink (path) < 0) return 1; return 0; }