Skip to content

Instantly share code, notes, and snippets.

@mortie
Created July 20, 2022 16:32
Show Gist options
  • Save mortie/ad846cd63dafcbdb02574ac13ef704db to your computer and use it in GitHub Desktop.
Save mortie/ad846cd63dafcbdb02574ac13ef704db to your computer and use it in GitHub Desktop.
Fast tar archive extractor
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
char zeroblock[512];
struct __attribute__((packed)) block_header {
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char type;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
};
void dump_header(struct block_header *h) {
fprintf(stderr, "Name: %.100s\n", h->name);
fprintf(stderr, "Mode: %.8s\n", h->mode);
fprintf(stderr, "UID: %.8s\n", h->uid);
fprintf(stderr, "GID: %.8s\n", h->gid);
fprintf(stderr, "Size: %.12s\n", h->size);
fprintf(stderr, "MTime: %.12s\n", h->mtime);
fprintf(stderr, "Checksum: %.8s\n", h->chksum);
fprintf(stderr, "Type: %c\n", h->type);
fprintf(stderr, "Linkname: %.100s\n", h->linkname);
fprintf(stderr, "Magic: %.6s\n", h->magic);
fprintf(stderr, "Version: %.2s\n", h->version);
fprintf(stderr, "Uname: %.32s\n", h->uname);
fprintf(stderr, "Gname: %.32s\n", h->gname);
fprintf(stderr, "Devmajor: %.8s\n", h->devmajor);
fprintf(stderr, "Devminor: %.8s\n", h->devminor);
fprintf(stderr, "Prefix: %.155s\n", h->prefix);
}
size_t parse_octal(char *oct, size_t len) {
size_t num = 0;
for (size_t i = 0; i < len; ++i) {
if (oct[i] == '\0') break;
num *= 8;
num += oct[i] - '0';
}
return num;
}
void xfread(void *ptr, size_t size, FILE *stream) {
if (fread(ptr, 1, size, stream) < size) {
fprintf(stderr, "Short read\n");
abort();
}
}
int dump = 0;
int read_block(FILE *f) {
char name[4096];
int hasname = 0;
char linkname[4096];
int haslinkname = 0;
unsigned char hblock[512];
xfread(hblock, sizeof(hblock), f);
struct block_header *header = (struct block_header *)hblock;
if (memcmp(hblock, zeroblock, sizeof(hblock)) == 0) {
fprintf(stderr, "Done.\n");
return 0;
}
while (header->type == 'L' || header->type == 'K') {
size_t namesize = parse_octal(header->size, sizeof(header->size));
if (namesize >= 4096) {
fprintf(stderr, "File name %zi too long. Max: 4096\n", namesize);
abort();
}
if (dump) {
dump_header(header);
}
size_t nameread = 0;
size_t n = namesize;
while (n > 512) {
nameread += 512;
n -= 512;
}
if (n) nameread += 512;
char *buf;
if (header->type == 'L') {
buf = name;
hasname = 1;
} else {
buf = linkname;
haslinkname = 1;
}
xfread(buf, nameread, f);
buf[namesize] = '\0';
xfread(hblock, sizeof(hblock), f);
}
if (!hasname) {
memcpy(name, header->name, 100);
name[100] = '\0';
}
if (!haslinkname) {
memcpy(linkname, header->linkname, 100);
linkname[100] = '\0';
}
mode_t mode = (mode_t)parse_octal(header->mode, 8);
uid_t uid = (uid_t)parse_octal(header->uid, 8);
gid_t gid = (gid_t)parse_octal(header->gid, 8);
int outfd = -1;
if (header->type == '0' || header->type == '\0') {
// Regular file
//fprintf(stderr, "FILE %s\n", name);
outfd = open(name, O_CREAT | O_WRONLY, mode);
if (outfd < 0) {
fprintf(stderr, "open %s: %s\n", name, strerror(errno));
abort();
}
if (fchown(outfd, uid, gid) < 0) {
perror("chown");
abort();
}
} else if (header->type == '1') {
// Hard link
fprintf(stderr, "Hard links aren't supported!\n");
abort();
} else if (header->type == '2') {
// Symlink
//fprintf(stderr, "SYM %s -> %s\n", name, linkname);
if (symlink(linkname, name) < 0) {
fprintf(stderr, "symlink %s -> %s: %s\n", name, linkname, strerror(errno));
abort();
}
if (lchown(name, uid, gid) < 0) {
fprintf(stderr, "chown %s: %s\n", name, strerror(errno));
perror("chown");
abort();
}
} else if (header->type == '5') {
// Directory
//fprintf(stderr, "DIR %s\n", name);
if (mkdir(name, mode) < 0) {
if (errno != EEXIST || strcmp(name, "./") != 0) {
fprintf(stderr, "mkdir %s: %s\n", name, strerror(errno));
abort();
}
}
if (chown(name, uid, gid) < 0) {
fprintf(stderr, "chown %s: %s\n", name, strerror(errno));
abort();
}
} else {
fprintf(stderr, "Unknown type: %c\n", header->type);
dump_header(header);
}
size_t fsize = parse_octal(header->size, sizeof(header->size));
char block[4096 * 1024];
while (fsize >= sizeof(block)) {
xfread(block, sizeof(block), f);
if (outfd >= 0) {
if (write(outfd, block, sizeof(block)) < sizeof(block)) {
fprintf(stderr, "Short write.\n");
abort();
}
}
fsize -= sizeof(block);
}
size_t blockread = 0;
size_t n = fsize;
while (n > 512) {
blockread += 512;
n -= 512;
}
if (n > 0) blockread += 512;
if (blockread > 0) {
xfread(block, blockread, f);
if (outfd >= 0) {
if (write(outfd, block, fsize) < fsize) {
fprintf(stderr, "Short write.\n");
abort();
}
}
}
if (outfd >= 0) {
close(outfd);
}
return 1;
}
int main(int argc, char **argv) {
while (read_block(stdin));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment