#include "tar.h"
#include <string.h>
#include <cx/string.h>
#include <libidav/utils.h>
const char* tar_error2str(TarError error) {
switch(error) {
default:
break;
case TAR_OK:
return "ok";
case TAR_PATH_TOO_LONG:
return "path too long";
case TAR_FILE_TOO_LARGE:
return "file too large";
case TAR_CONTENT_TOO_LARGE:
return "tar content too large";
case TAR_UNFINISHED_FILE:
return "can''t read a tar header at this position";
case TAR_CONTENT_BROKEN:
return "tar content broken";
}
return "error";
}
TarOutputStream* tar_open(
FILE *f) {
TarOutputStream *tar = malloc(
sizeof(TarOutputStream));
tar->file= f;
tar->cur_filesize =
0;
tar->cur_written =
0;
tar->error =
0;
return tar;
}
static int add_header(TarOutputStream *tar,
char *path,
uint32_t mode,
uint64_t size,
time_t mtime,
int type) {
char *p = util_parent_path(path);
const char *n = util_resource_name(path);
if(!p || !n) {
return -
1;
}
cxstring prefix = cx_str(p);
cxstring name = cx_str(n);
if(prefix.ptr[prefix.length-
1] ==
'/') {
prefix.length--;
}
if(prefix.length >
154) {
tar->error =
TAR_PATH_TOO_LONG;
free(p);
return -
1;
}
if(name.length >
99) {
tar->error =
TAR_PATH_TOO_LONG;
free(p);
return -
1;
}
if(size >=
077777777777 ) {
tar->error =
TAR_FILE_TOO_LARGE;
free(p);
return -
1;
}
TarHeader h;
memset(&h,
0,
sizeof(TarHeader));
memcpy(h.name, name.ptr, name.length);
snprintf(h.mode,
8,
"%07o", mode);
h.mode[
7] =
' ';
memset(h.uid,
'0',
8);
h.uid[
7] =
' ';
memset(h.gid,
'0',
8);
h.gid[
7] =
' ';
snprintf(h.size,
12,
"%011lo", (
unsigned long)size);
h.size[
11] =
' ';
uint64_t t = (
uint64_t)mtime;
snprintf(h.mtime,
12,
"%011lo", mtime);
h.mtime[
11] =
' ';
memset(h.chksum,
' ',
8);
h.typeflag = type;
snprintf(h.magic,
6,
"ustar");
h.version[
0] =
'0';
h.version[
1] =
'0';
char *devbuf = (
char*)h.devmajor;
snprintf(devbuf,
16,
"%015o",
0);
h.devmajor[
7] =
' ';
h.devminor[
7] =
' ';
memcpy(h.prefix, prefix.ptr, prefix.length);
uint8_t *header = (
uint8_t*)&h;
uint32_t chksum =
0;
for(
int i=
0;i<
512;i++) {
chksum += header[i];
}
snprintf(h.chksum,
8,
"%07o", chksum);
fwrite(&h,
1,
512, tar->file);
free(p);
return 0;
}
int tar_add_dir(TarOutputStream *tar,
char *path,
uint32_t mode,
time_t mtime) {
return add_header(tar, path, mode,
0, mtime,
TAR_TYPE_DIRECTORY);
}
int tar_begin_file(
TarOutputStream *tar,
char *path,
uint32_t mode,
uint64_t size,
time_t mtime)
{
if(add_header(tar, path, mode, size, mtime,
TAR_TYPE_FILE)) {
return -
1;
}
tar->cur_filesize = size;
tar->cur_written =
0;
return 0;
}
size_t tar_fwrite(
const void *ptr,
size_t s,
size_t n, TarOutputStream *stream) {
size_t w = fwrite(ptr, s, n, stream->file);
stream->cur_written += w;
if(stream->cur_written > stream->cur_filesize) {
stream->error =
TAR_CONTENT_TOO_LARGE;
return 0;
}
return w;
}
int tar_end_file(TarOutputStream *tar) {
size_t pad =
512 - tar->cur_written %
512;
char buf[
512];
memset(buf,
0,
512);
fwrite(buf,
1, pad, tar->file);
tar->cur_filesize =
0;
tar->cur_written =
0;
return 0;
}
int tar_close(TarOutputStream *tar) {
char buf[
512];
memset(buf,
0,
512);
fwrite(buf,
1,
512, tar->file);
return 0;
}
TarInputStream* tar_inputstream_open(
FILE *f) {
TarInputStream *tar = malloc(
sizeof(TarInputStream));
memset(tar,
0,
sizeof(TarInputStream));
tar->file = f;
return tar;
}
TarEntry* tar_read_entry(TarInputStream *tar) {
if(tar->cur_read < tar->cur_entry.size) {
tar->error =
TAR_UNFINISHED_FILE;
return NULL;
}
TarHeader h;
memset(&h,
0,
sizeof(TarHeader));
if(fread(&h,
1,
512, tar->file) !=
512) {
tar->error =
TAR_ERROR;
return NULL;
}
tar->error =
TAR_CONTENT_BROKEN;
char *buf = (
char*)&h;
uint64_t chksum =
0;
int chksumfield =
8 *
32;
for(
int i=
0;i<
148;i++) {
chksum += buf[i];
}
chksum += chksumfield;
for(
int i=
156;i<
512;i++) {
chksum += buf[i];
}
if(chksum != chksumfield) {
long long int cks = strtoll(h.chksum,
NULL,
8);
if(cks != chksum) {
return NULL;
}
}
if(memcmp(h.magic,
"ustar\0",
6)) {
if(!memcmp(h.magic,
"\0\0\0\0",
4)) {
tar->error =
TAR_OK;
}
return NULL;
}
if(memcmp(h.version,
"00",
2)) {
return NULL;
}
int nameterm =
0;
int prefixterm =
0;
for(
int i=
0;i<
100;i++) {
if(h.name[i] ==
0) {
nameterm =
1;
break;
}
}
for(
int i=
0;i<
155;i++) {
if(h.prefix[i] ==
0) {
prefixterm =
1;
break;
}
}
if(!nameterm || !prefixterm) {
return NULL;
}
if(!(h.size[
11] ==
' ' || h.size[
11] ==
0)) {
return NULL;
}
long long int size = strtoll(h.size,
NULL,
8);
if(size <
0) {
return NULL;
}
if(h.name[
0] ==
0) {
return NULL;
}
char *path = h.prefix[
0] !=
0 ? util_concat_path(h.prefix, h.name) : strdup(h.name);
if(tar->cur_entry.path) {
free(tar->cur_entry.path);
}
tar->cur_entry.path = path;
tar->cur_entry.size = (
uint64_t)size;
tar->cur_entry.type = h.typeflag;
tar->cur_read =
0;
tar->error =
TAR_OK;
return &tar->cur_entry;
}
size_t tar_fread(
void *ptr,
size_t s,
size_t n, TarInputStream *stream) {
size_t bufsize = s*n;
size_t available = stream->cur_entry.size - stream->cur_read;
size_t nb = available > bufsize ? bufsize : available;
if(nb ==
0) {
return 0;
}
size_t r = fread(ptr,
1, nb, stream->file);
if(r != nb) {
stream->error =
TAR_ERROR;
return 0;
}
stream->cur_read += r;
if(stream->cur_read >= stream->cur_entry.size) {
size_t pad =
512 - stream->cur_read %
512;
char buf[
512];
if(fread(buf,
1, pad, stream->file) != pad) {
stream->error =
TAR_ERROR;
return 0;
}
}
return r;
}
int tar_seek(TarInputStream *stream,
long offset,
int whence) {
return fseek(stream->file, offset, whence);
}
int tar_inputstream_close(TarInputStream *tar) {
if(tar->cur_entry.path) {
free(tar->cur_entry.path);
}
free(tar);
return 0;
}