#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define TAR_TYPE_FILE '0'
#define TAR_TYPE_DIRECTORY '5'
enum TarError {
TAR_OK = 0,
TAR_PATH_TOO_LONG,
TAR_FILE_TOO_LARGE,
TAR_CONTENT_TOO_LARGE,
TAR_UNFINISHED_FILE,
TAR_CONTENT_BROKEN,
TAR_ERROR
};
typedef enum TarError TarError;
typedef struct TarHeader {
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char typeflag;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char padding[12];
} TarHeader;
typedef struct TarOutputStream {
FILE *file;
uint64_t cur_filesize;
uint64_t cur_written;
TarError error;
} TarOutputStream;
typedef struct TarEntry {
char *path;
uint64_t size;
int type;
} TarEntry;
typedef struct TarInputStream {
FILE *file;
TarEntry cur_entry;
uint64_t cur_read;
TarError error;
} TarInputStream;
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 *prefix = path;
size_t prefixlen = 0;
char *name = path;
size_t namelen = 0;
int pathsep = 0;
char *p = path;
char c;
size_t i = 0;
while((c = *p) != '\0') {
if(c == '/') {
pathsep = 1;
} else {
if(pathsep) {
name = p;
prefixlen = i;
}
pathsep = 0;
}
i++;
p++;
}
namelen = i - prefixlen;
if(prefixlen > 154) {
tar->error = TAR_PATH_TOO_LONG;
return -1;
}
if(namelen > 99) {
tar->error = TAR_PATH_TOO_LONG;
return -1;
}
if(size >= 077777777777 ) {
tar->error = TAR_FILE_TOO_LARGE;
return -1;
}
TarHeader h;
memset(&h, 0, sizeof(TarHeader));
memcpy(h.name, name, namelen);
snprintf(h.mode, 8, "%07o", mode);
h.mode[7] = ' ';
memset(h.uid, '0', 16);
h.uid[7] = ' ';
h.gid[7] = ' ';
snprintf(h.size, 12, "%011lo", 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, prefixlen);
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);
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[1024];
memset(buf, 0, 1024);
fwrite(buf, 1, 1024, tar->file);
return 0;
}
#define IO_BUF_SIZE 0x4000
int main(int argc, char **argv) {
if(argc <= 1) {
fprintf(stderr, "Usage: %s <file> ...\n", argv[0]);
}
TarOutputStream *tar = tar_open(stdout);
for(int i=1;i<argc;i++) {
char *file = argv[i];
struct stat s;
if(stat(file, &s)) {
perror("stat");
continue;
}
uint32_t tmode = s.st_mode & 07777;
if(S_ISDIR(s.st_mode)) {
fprintf(stderr, "add: %s\n", file);
if(tar_add_dir(tar, file, tmode, s.st_mtime)) {
fprintf(stderr, "tar_add_dir failed: %d\n", tar->error);
}
} else if(S_ISREG(s.st_mode)) {
fprintf(stderr, "add: %s\n", file);
FILE *f = fopen(file, "r");
if(!f) {
perror("open");
continue;
}
if(tar_begin_file(tar, file, tmode, s.st_size, s.st_mtime)) {
fprintf(stderr, "tar_begin_file failed: %d\n", tar->error);
} else {
char buf[IO_BUF_SIZE];
size_t r;
while((r = fread(buf, 1, IO_BUF_SIZE, f)) > 0) {
if(tar_fwrite(buf, 1, r, tar) == 0) {
fprintf(stderr, "tar_fwrite failed\n");
break;
}
}
tar_end_file(tar);
}
fclose(f);
}
}
tar_close(tar);
return 0;
}