Code:
#define _BSD_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
void create_tarfile (const char *tarfile, char **files);
void list_tarfile (const char *tarfile);
void extract_tarfile (const char *tarfile);
void store_file (FILE *fout, const char *name);
void write_dir (FILE *fout, const char *name);
void write_file (FILE *fout, const char *name);
void extract_file (FILE *fin, const char *name, uint64_t size);
void die (const char *msg);
void usage (const char *progname);
int main(int argc, char **argv)
{
if (argc < 2)
usage(argv[0]);
else if (strcmp(argv[1], "c") == 0)
{
if (argc < 4) usage(argv[0]);
create_tarfile(argv[2], argv + 3);
}
else if (strcmp(argv[1], "t") == 0)
{
if (argc < 3) usage(argv[0]);
list_tarfile(argv[2]);
}
else if (strcmp(argv[1], "x") == 0)
{
if (argc < 3) usage(argv[0]);
extract_tarfile(argv[2]);
}
else
usage(argv[0]);
return 0;
}
void create_tarfile(const char *tarfile, char **files)
{
FILE *fout = fopen(tarfile, "wb");
if (!fout) die("create_tarfile");
for ( ; *files; ++files)
store_file(fout, *files);
fclose(fout);
}
void store_file(FILE *fout, const char *name)
{
struct stat sb;
if (stat(name, &sb) == -1) die("store_file: stat");
bool is_dir = false;
switch (sb.st_mode & S_IFMT) {
case S_IFDIR: is_dir = true; break;
case S_IFREG: break;
default: die("store_file: not dir or regular file");
}
uint32_t filename_size = strlen(name) + is_dir;
fwrite(&filename_size, sizeof filename_size, 1, fout);
fwrite(name, 1, filename_size - is_dir, fout);
uint64_t size = sb.st_size;
if (is_dir)
{
fputc('/', fout); // dir names end in /
size = 0; // dirs have a "size" of 0
}
fwrite(&size, sizeof size, 1, fout);
if (is_dir)
write_dir(fout, name);
else
write_file(fout, name);
}
void write_dir(FILE *fout, const char *name)
{
char n[FILENAME_MAX];
DIR *dir = opendir(name);
struct dirent *ent;
while ((ent = readdir(dir)) != NULL)
{
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
strcpy(n, name);
strcat(n, "/");
strcat(n, ent->d_name);
store_file(fout, n);
}
closedir(dir);
}
void write_file(FILE *fout, const char *name)
{
char buf[BUFSIZ];
FILE *fin = fopen(name, "rb");
if (!fin) die("store_file");
for (size_t n; (n = fread(buf, 1, sizeof buf, fin)) > 0; )
fwrite(buf, 1, n, fout);
fclose(fin);
}
void list_tarfile(const char *tarfile)
{
FILE *fin = fopen(tarfile, "rb");
if (!fin) die("list_tarfile");
for (int name_size; fread(&name_size, sizeof name_size, 1, fin) > 0; )
{
char name[256] = {0};
fread(name, 1, name_size, fin);
printf("%s\n", name);
uint64_t size;
fread(&size, sizeof size, 1, fin);
fseek(fin, size, SEEK_CUR);
}
fclose(fin);
}
void extract_tarfile(const char *tarfile)
{
FILE *fin = fopen(tarfile, "rb");
if (!fin) die("extract_tarfile");
char name[FILENAME_MAX];
for (int name_size; fread(&name_size, sizeof name_size, 1, fin) > 0; )
{
fread(name, 1, name_size, fin);
name[name_size] = '\0';
off_t size;
fread(&size, sizeof size, 1, fin);
if (name[strlen(name) - 1] == '/')
mkdir(name, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
else
extract_file(fin, name, size);
}
fclose(fin);
}
void extract_file(FILE *fin, const char *name, uint64_t size)
{
FILE *fout = fopen(name, "wb");
if (!fout) die("extract_tarfile");
char buf[BUFSIZ];
const uint64_t bsz = sizeof buf;
for (uint64_t n;
(n = fread(buf, 1, size > bsz ? bsz : size, fin)) > 0;
size -= n)
fwrite(buf, 1, n, fout);
fclose(fout);
}
void die(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
void usage(const char *progname)
{
fprintf(stderr, "Usage: Create: %s c TARFILE FILE...\n", progname);
fprintf(stderr, " List: %s t TARFILE\n", progname);
fprintf(stderr, " Extract: %s x TARFILE\n", progname);
exit(EXIT_FAILURE);
}