/* untar.c * * state machine for handling basic TAR files. (ustar format only) * */ #include #include #include #include #include "tt.h" #include "untar.h" #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) #define TAR_RECORD_LEN 512 #define MAGIC_USTAR "ustar" #define MAGIC_USTAR_LEN 5 #ifdef NDEBUG #define DEBUG_ONLY(x) /* nothing */ #else #define DEBUG_ONLY(x) x #endif /* expects the following variables: * size_t len; * char *p; */ /* broke the macro up into 2 parts because of how TT uses line numbers */ #define GET_STR1(ts, entry_len, entry) do { \ size_t cnt; /* careful using this accross TT_WAIT calls */\ for((ts)->data_len=0;(ts)->data_len<(entry_len);(ts)->data_len+=cnt) { #define GET_STR2(ts, entry_len, entry) \ TT_WAIT_UNTIL(&(ts)->tts, len>0); \ cnt=MIN(len, (entry_len)-(ts)->data_len); \ (ts)->is_zero=(ts)->is_zero && is_zero(p, cnt); \ memcpy((ts)->rec.entry+(ts)->data_len, p, cnt); \ (ts)->data_remaining-=cnt; (ts)->total_data+=cnt; len-=cnt; p+=cnt; \ } \ (ts)->rec.entry[(ts)->data_len]=0; /* null terminate */ \ DEBUG_ONLY(fprintf(stderr, "%s=\"%s\"\n", #entry, (ts)->rec.entry)); \ } while(0) #define GET_NUM1(ts, entry_len, entry) do { \ char *endptr; \ size_t cnt; /* careful using this accross TT_WAIT calls */\ for((ts)->data_len=0;(ts)->data_len<(entry_len);(ts)->data_len+=cnt) { #define GET_NUM2(ts, entry_len, entry) \ TT_WAIT_UNTIL(&(ts)->tts, len>0); \ cnt=MIN(len, (entry_len)-(ts)->data_len); \ (ts)->is_zero=(ts)->is_zero && is_zero(p, cnt); \ memcpy((ts)->tempnum+(ts)->data_len, p, cnt); \ (ts)->data_remaining-=cnt; (ts)->total_data+=cnt; len-=cnt; p+=cnt; \ } \ (ts)->tempnum[(ts)->data_len]=0; /* null terminate */ \ if(!(ts)->is_zero) { \ (ts)->rec.entry=strtoul((ts)->tempnum, &endptr, 8); \ if(*endptr) { /* number conversion error */ \ fprintf(stderr, "%s:tar corruption(%s)\n", (ts)->name, #entry); \ goto failed; \ } \ DEBUG_ONLY(fprintf(stderr, "%s=%llu\n", #entry, (long long)(ts)->rec.entry)); \ } else (ts)->zero_skipped=1; \ } while(0) struct untar_stream { char *name; struct tt_state tts; size_t data_len; /* used as a local variable for the current state */ void *opaque; void (*new_file)(void *, struct untar_stream *, struct tar_record *); void (*file_data)(void *, struct untar_stream *, struct tar_record *, u_long off, size_t len, u_char *data); char tempnum[TAR_ULONG_LEN+1]; /* a buffer for any of the numeric types */ struct tar_record rec; /* a cache for the tar entry header */ u_long data_remaining, data_count; off_t total_data; int is_zero; /* header was zero */ int zero_skipped; /* some portion of data processing was skipped */ int finished; /* TODO: add callback handlers */ }; static int is_zero(const u_char *data, size_t len) { while(len) { if(*data) return 0; data++; len--; } return 1; } struct untar_stream *untar_stream_create(const char *name) { struct untar_stream *ret; ret=calloc(1, sizeof *ret); if(!ret) { perror(__func__); return 0; } ret->name=strdup(name?name:"(none)"); TT_INITIALIZE(&ret->tts); return ret; } void untar_set_opaque(struct untar_stream *ts, void *opaque) { ts->opaque=opaque; } void untar_set_file_data(struct untar_stream *ts, void (*file_data)(void *, struct untar_stream *, struct tar_record *, u_long off, size_t len, u_char *data)) { ts->file_data=file_data; } void untar_set_new_file(struct untar_stream *ts, void (*new_file)(void *, struct untar_stream *, struct tar_record *)) { ts->new_file=new_file; } void untar_stream_finished(struct untar_stream *ts) { if(!ts) return; free(ts->name); } static int process(struct untar_stream *ts, size_t len, u_char *p, int *res) { assert(ts!=NULL); assert(p!=NULL); TT_BEGIN(&ts->tts); while(1) { /* fprintf(stderr, "HEADER START\n"); */ memset(&ts->rec, 0, sizeof ts->rec); /* possibly unnecessary */ ts->data_remaining=512; ts->is_zero=1; ts->zero_skipped=0; /* filename */ GET_STR1(ts, TAR_NAME_LEN, name); GET_STR2(ts, TAR_NAME_LEN, name); /* mode */ GET_NUM1(ts, TAR_UINT_LEN, mode); GET_NUM2(ts, TAR_UINT_LEN, mode); /* uid */ GET_NUM1(ts, TAR_UINT_LEN, uid); GET_NUM2(ts, TAR_UINT_LEN, uid); /* gid */ GET_NUM1(ts, TAR_UINT_LEN, gid); GET_NUM2(ts, TAR_UINT_LEN, gid); /* size */ GET_NUM1(ts, TAR_ULONG_LEN, size); GET_NUM2(ts, TAR_ULONG_LEN, size); /* mtime */ GET_NUM1(ts, TAR_ULONG_LEN, mtime); GET_NUM2(ts, TAR_ULONG_LEN, mtime); /* chksum */ GET_NUM1(ts, TAR_UINT_LEN, chksum); GET_NUM2(ts, TAR_UINT_LEN, chksum); /* linkflag */ TT_WAIT_UNTIL(&ts->tts, len>0); ts->rec.linkflag=*p; p++; len--; ts->data_remaining--; ts->total_data++; DEBUG_ONLY(fprintf(stderr, "%s=\"%c\"\n", "linkflag", ts->rec.linkflag)); /* linkname */ GET_STR1(ts, TAR_NAME_LEN, linkname); GET_STR2(ts, TAR_NAME_LEN, linkname); /* magic */ GET_STR1(ts, TAR_MAGIC_LEN, magic); GET_STR2(ts, TAR_MAGIC_LEN, magic); if(!ts->is_zero && memcmp(ts->rec.magic, MAGIC_USTAR, MAGIC_USTAR_LEN)) { fprintf(stderr, "%s:not a tar file(magic=%s)\n", ts->name, ts->rec.magic); goto failed; } /* uname */ GET_STR1(ts, TAR_USER_LEN, uname); GET_STR2(ts, TAR_USER_LEN, uname); /* gname */ GET_STR1(ts, TAR_GROUP_LEN, gname); GET_STR2(ts, TAR_GROUP_LEN, gname); /* devmajor */ GET_NUM1(ts, TAR_UINT_LEN, devmajor); GET_NUM2(ts, TAR_UINT_LEN, devmajor); /* devminor */ GET_NUM1(ts, TAR_UINT_LEN, devminor); GET_NUM2(ts, TAR_UINT_LEN, devminor); /* eat remaining of 512 byte block */ while(ts->data_remaining>0) { long cnt; TT_WAIT_UNTIL(&ts->tts, len>0); cnt=(ts->data_remaining>=len) ? len : ts->data_remaining; ts->is_zero=ts->is_zero && is_zero(p, cnt); ts->data_remaining-=cnt; len-=cnt; p+=cnt; ts->total_data+=cnt; } if(ts->is_zero) { /* end of archive */ #ifndef NDEBUG fprintf(stderr, "%s:end of archive\n", ts->name); #endif ts->finished=1; /* done */ *res=1; /* success */ TT_EXIT(&ts->tts); } else if(ts->zero_skipped) { fprintf(stderr, "%s:tar corruption in header\n", ts->name); goto failed; } if(ts->new_file) ts->new_file(ts->opaque, ts, &ts->rec); DEBUG_ONLY(fprintf(stderr, "Start Data..\n")); ts->data_remaining=(ts->rec.size+512-1)/512*512; ts->data_count=0; /* loaded header, now we process the data */ while(ts->data_remaining>0) { u_long cnt; /* count based on the rounded up block size */ u_long real_cnt; /* count based on the real size */ TT_WAIT_UNTIL(&ts->tts, len>0); cnt=(ts->data_remaining>=len) ? len : ts->data_remaining; real_cnt=(ts->rec.size-ts->data_count)>=len?len:(ts->rec.size-ts->data_count); /* fprintf(stderr, "rem=%lu total=%lu cnt=%lu len=%zu\n", ts->data_remaining, ts->total_data, cnt, len); */ /* if ts->file_data not defined, then we silently ignore data */ if(ts->file_data) ts->file_data(ts->opaque, ts, &ts->rec, ts->data_count, real_cnt, p); ts->data_count+=real_cnt; ts->data_remaining-=cnt; len-=cnt; p+=cnt; ts->total_data+=cnt; } /* fprintf(stderr, "total=%lu\n", ts->total_data); */ } failed: *res=0; TT_END(&ts->tts); } /* return zero on error. else success */ int untar_stream_push(struct untar_stream *ts, size_t len, void *data) { int res=0; if(process(ts, len, data, &res)==TT_STATUS_EXITED) { #ifndef NDEBUG if(res) fprintf(stderr, "%s:tar file is finished\n", ts->name); #endif return res; } return 1; /* success */ } /* return non-zero when tar is complete */ int untar_finished(struct untar_stream *ts) { return ts->finished; } #ifdef STAND_ALONE #include #include int main(int argc, char **argv) { struct untar_stream *ts; int i, res; gzFile *gzf; char buf[7]; int ret=EXIT_SUCCESS; for(i=1;i