Files
tar/src/gnu.c
1994-11-16 02:49:10 +00:00

646 lines
13 KiB
C

/* 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 <stdio.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#ifndef STDC_HEADERS
extern int errno;
#endif
#include <time.h>
time_t time();
#include "tar.h"
#include "port.h"
#if defined(_POSIX_VERSION) || defined(DIRENT)
#include <dirent.h>
#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 <sys/dir.h>
#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 <ndir.h>
#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+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)(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+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 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;
}