/* GNU dump extensions to tar. Copyright (C) 1988, 1992 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. */ #include #include #include #include #ifndef STDC_HEADERS extern int errno; #endif #include time_t time(); #include "tar.h" #include "port.h" #if defined(_POSIX_VERSION) || defined(DIRENT) #include #ifdef direct #undef direct #endif /* direct */ #define direct dirent #define DP_NAMELEN(x) strlen((x)->d_name) #endif /* _POSIX_VERSION or DIRENT */ #if !defined(_POSIX_VERSION) && !defined(DIRENT) && defined(BSD42) #include #define DP_NAMELEN(x) (x)->d_namlen #endif /* not _POSIX_VERSION and BSD42 */ #ifdef __MSDOS__ #include "msd_dir.h" #define DP_NAMELEN(x) (x)->d_namlen #define direct dirent #endif #if defined(USG) && !defined(_POSIX_VERSION) && !defined(DIRENT) #include #define DP_NAMELEN(x) strlen((x)->d_name) #endif /* USG and not _POSIX_VERSION and not DIRENT */ #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 *)malloc(sizeof(struct dirname)); if(!dp) abort(); dp->next=dir_list; dir_list=dp; dp->dev=dev; dp->ino=ino; dp->name=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(USG) || 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 direct *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(DP_NAMELEN(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)(DP_NAMELEN(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 **)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 *)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 direct *d; char *cur,*arc; extern struct stat hstat; /* Stat struct corresponding */ long size,copied; char *from,*to; extern union record *head; dirp=opendir(skipcrud+head->header.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)(DP_NAMELEN(d)+1)); } closedir(dirp); add_buffer(the_buffer,"",1); current_dir=get_buffer(the_buffer); archive_dir=(char *)malloc(hstat.st_size); if(archive_dir==0) { msg("Can't allocate %d 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+head->header.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 direct *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; }