others

/* $Id$ */
/* Copyright (c) 2006-2014 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Unix others */
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* TODO:
* - libutils in UNIX
* - reuse code from mkdir -p and ls -l
* - share code with ar etc (packing framework)
* - implement ustar format */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "tar.h"
#ifndef PROGNAME
# define PROGNAME "tar"
#endif
#ifndef min
# define min(a, b) ((a) < (b) ? (a) : (b))
#endif
/* tar */
/* private */
/* types */
typedef int Prefs;
#define PREFS_A 0x01
#define PREFS_c 0x02
#define PREFS_t 0x04
#define PREFS_v 0x08
#define PREFS_vv 0x18
#define PREFS_x 0x20
/* constants */
#define TAR_BLKSIZ 512
/* prototypes */
static int _tar(Prefs * prefs, char const * archive, int filec,
char * filev[]);
static int _tar_error(char const * message, int ret);
static int _tar_usage(void);
/* functions */
/* tar */
static int _tar_create(Prefs * prefs, char const * archive, int filec,
char * filev[]);
static int _tar_extract(Prefs * prefs, char const * archive, int filec,
char * filev[]);
static int _tar_list(Prefs * prefs, char const * archive, int filec,
char * filev[]);
static int _tar(Prefs * prefs, char const * archive, int filec,
char * filev[])
{
if(*prefs & PREFS_c)
return _tar_create(prefs, archive, filec, filev);
if(*prefs & PREFS_t)
return _tar_list(prefs, archive, filec, filev);
if(*prefs & PREFS_x)
return _tar_extract(prefs, archive, filec, filev);
return 1;
}
#define _from_buffer_cpy(a) tfhb->a[sizeof(tfhb->a)-1] = '\0'; \
tfh->a = strtol(tfhb->a, &p, 8); \
if(*tfhb->a == '\0' || *p != '\0') return 1;
static int _tar_from_buffer(TarFileHeaderBuffer * tfhb, TarFileHeader * tfh)
{
char * p;
size_t len;
_from_buffer_cpy(mode);
_from_buffer_cpy(uid);
_from_buffer_cpy(gid);
_from_buffer_cpy(size); /* FIXME data type too short? */
_from_buffer_cpy(mtime);
for(p = tfhb->filename; *p == '/'; p++);
/* FIXME ".." directory traversal */
memcpy(&tfh->filename, p, tfhb->filename + sizeof(tfhb->filename) - p);
tfh->filename[sizeof(tfh->filename) - 1] = '\0';
len = strlen(tfh->filename);
if(tfh->filename[len] == '/')
tfh->type = FT_DIRECTORY;
else
tfh->type = tfhb->type;
memcpy(&tfh->link, tfhb->link, sizeof(tfhb->link));
tfh->link[sizeof(tfh->link) - 1] = '\0';
return 0;
}
static int _tar_mkdir_parent(char * filename)
{
char * p;
struct stat st;
if(filename[0] == '\0')
return 0;
for(p = &filename[1]; *p != '\0'; p++)
{
if(*p != '/')
continue;
*p = '\0';
if(!(stat(filename, &st) == 0 && S_ISDIR(st.st_mode))
&& mkdir(filename, 0777) == -1)
{
*p = '/';
return _tar_error(filename, 1);
}
for(*p++ = '/'; *p == '/'; p++);
if(*p == '\0')
return 0;
}
return 0;
}
static void _tar_print(Prefs * prefs, TarFileHeader * fh)
{
if((*prefs & PREFS_vv) == PREFS_vv)
/* FIXME */
fprintf(stderr, "%s %u %u %s\n", "----------",
(unsigned)fh->uid, (unsigned)fh->gid,
fh->filename);
else if(*prefs & PREFS_v)
fprintf(stderr, "%s\n", fh->filename);
}
static int _tar_seek(FILE * fp, char const * archive, size_t count)
{
char buf[TAR_BLKSIZ];
size_t step;
if(fp == stdin)
for(; count != 0; count -= step)
{
if((step = count % TAR_BLKSIZ) == 0)
step = TAR_BLKSIZ;
if(fread(buf, sizeof(*buf), step, fp) != step)
return _tar_error(archive, 1);
}
else if(fseek(fp, count, SEEK_CUR) != 0)
return _tar_error(archive, 1);
return 0;
}
static int _tar_skip(FILE * fp, char const * archive, TarFileHeader * fh)
{
size_t count = fh->size % TAR_BLKSIZ;
if(count != 0)
count = TAR_BLKSIZ - count;
count+=fh->size;
return _tar_seek(fp, archive, count);
}
static void _tar_stat_to_buffer(char const * filename, struct stat * st,
TarFileHeaderBuffer * tfhb)
{
uint8_t * p;
size_t i;
int checksum = 0;
memset(tfhb, 0, sizeof(*tfhb));
snprintf(tfhb->filename, sizeof(tfhb->filename), "%s", filename);
snprintf(tfhb->mode, sizeof(tfhb->mode), "%07o", (unsigned)st->st_mode);
snprintf(tfhb->uid, sizeof(tfhb->uid), "%07o", (unsigned)st->st_uid);
snprintf(tfhb->gid, sizeof(tfhb->gid), "%07o", (unsigned)st->st_gid);
snprintf(tfhb->size, sizeof(tfhb->size), "%011o",
(unsigned)st->st_size);
snprintf(tfhb->mtime, sizeof(tfhb->mtime), "%011o",
(unsigned)st->st_mtime);
memset(&tfhb->checksum, ' ', sizeof(tfhb->checksum));
if(S_ISDIR(st->st_mode))
tfhb->type = FT_DIRECTORY;
else if(S_ISCHR(st->st_mode))
tfhb->type = FT_CHAR;
else if(S_ISBLK(st->st_mode))
tfhb->type = FT_BLOCK;
/* FIXME link */
p = (uint8_t *)tfhb;
for(i = 0; i < sizeof(*tfhb); i++)
checksum += p[i];
snprintf(tfhb->checksum, sizeof(tfhb->checksum), "%06o%c ", checksum,
'\0');
}
static int _create_do(Prefs * prefs, FILE * fp, char const * archive,
char const * filename);
static int _tar_create(Prefs * prefs, char const * archive, int filec,
char * filev[])
{
FILE * fp = stdout;
int i;
if(archive != NULL && (fp = fopen(archive, "w")) == NULL)
return _tar_error(archive, 1);
for(i = 0; i < filec; i++)
if(_create_do(prefs, fp, archive, filev[i]) != 0)
break;
if(i != filec)
{
fclose(fp);
return 1;
}
for(i = 0; i < TAR_BLKSIZ * 2 && fputc('\0', fp) == '\0'; i++);
if(archive != NULL)
fclose(fp);
return (i == TAR_BLKSIZ * 2) ? 0 : 1;
}
static int _doc_header(Prefs * prefs, FILE * fp, char const * archive,
FILE * fp2, char const * filename, TarFileHeaderBuffer * tfhb);
static int _doc_normal(FILE * fp, char const * archive, FILE * fp2,
char const * filename);
static int _create_do(Prefs * prefs, FILE * fp, char const * archive,
char const * filename)
{
FILE * fp2;
TarFileHeaderBuffer tfhb;
int ret;
if((fp2 = fopen(filename, "r")) == NULL)
return 1;
if(_doc_header(prefs, fp, archive, fp2, filename, &tfhb) != 0)
{
fclose(fp2);
return 1;
}
switch(tfhb.type)
{
case FT_NORMAL:
case FT_CONTIGUOUS:
ret = _doc_normal(fp, archive, fp2, filename);
break;
case FT_HARDLINK:
case FT_SYMLINK:
case FT_CHAR:
case FT_BLOCK:
case FT_DIRECTORY: /* FIXME recurse */
case FT_FIFO:
ret = 0;
break;
default:
ret = 1;
}
fclose(fp2);
return ret;
}
static int _doc_header(Prefs * prefs, FILE * fp, char const * archive,
FILE * fp2, char const * filename, TarFileHeaderBuffer * tfhb)
{
TarFileHeader tfh;
struct stat st;
int i;
if(fstat(fileno(fp2), &st) != 0)
return _tar_error(filename, 1);
_tar_stat_to_buffer(filename, &st, tfhb);
_tar_from_buffer(tfhb, &tfh);
_tar_print(prefs, &tfh);
if(fwrite(tfhb, sizeof(*tfhb), 1, fp) != 1)
return _tar_error(archive, 1);
for(i = sizeof(*tfhb); i < TAR_BLKSIZ && fputc('\0', fp) == '\0'; i++);
if(i != TAR_BLKSIZ)
return _tar_error(archive, 1);
return 0;
}
static int _doc_normal(FILE * fp, char const * archive, FILE * fp2,
char const * filename)
{
int ret = 0;
size_t read;
size_t cnt;
char buf[BUFSIZ];
for(cnt = 0; (read = fread(buf, sizeof(*buf), sizeof(buf), fp2)) != 0;
cnt += read)
if(fwrite(buf, sizeof(*buf), read, fp) != read)
{
ret = _tar_error(archive, 1);
break;
}
if(ret == 0 && read == 0 && !feof(fp2))
return _tar_error(filename, 1);
for(cnt = TAR_BLKSIZ - (cnt % TAR_BLKSIZ);
cnt > 0 && fputc('\0', fp) == '\0'; cnt--);
return (cnt == 0) ? 0 : _tar_error(archive, 1);
}
static int _extract_do(Prefs * prefs, FILE * fp, char const * archive,
TarFileHeader * fh, int filec, char * filev[]);
static int _tar_extract(Prefs * prefs, char const * archive, int filec,
char * filev[])
{
FILE * fp = stdin;
TarFileHeaderBuffer fhdrb;
TarFileHeader fhdr;
size_t size;
int ret = 0;
if(archive != NULL && (fp = fopen(archive, "r")) == NULL)
return _tar_error(archive, 1);
while((size = fread(&fhdrb, sizeof(fhdrb), 1, fp)) == 1)
{
if(_tar_seek(fp, archive, TAR_BLKSIZ - sizeof(fhdrb)) != 0)
{
ret = 1;
break;
}
if(_tar_from_buffer(&fhdrb, &fhdr) != 0
|| _extract_do(prefs, fp, archive, &fhdr,
filec, filev) != 0)
ret = 1;
}
if(ret == 0 && size == 0 && !feof(fp))
ret = _tar_error(archive, 1);
if(archive != NULL)
fclose(fp);
return ret;
}
static int _dox_normal(FILE * fp, char const * archive, TarFileHeader * fh);
static int _dox_hardlink(TarFileHeader * fh);
static int _dox_symlink(TarFileHeader * fh);
static int _dox_char(FILE * fp, char const * archive, TarFileHeader * fh);
static int _dox_block(FILE * fp, char const * archive, TarFileHeader * fh);
static int _dox_directory(TarFileHeader * fh);
static int _dox_fifo(TarFileHeader * fh);
static int _extract_do(Prefs * prefs, FILE * fp, char const * archive,
TarFileHeader * fh, int filec, char * filev[])
{
int i;
for(i = 0; i < filec; i++)
if(strcmp(fh->filename, filev[i]) == 0)
break;
if(filec != 0 && i == filec)
return _tar_skip(fp, archive, fh);
_tar_print(prefs, fh);
if(_tar_mkdir_parent(fh->filename) != 0)
return _tar_skip(fp, archive, fh);
switch((int)fh->type)
{
case FT_NORMAL:
case FT_CONTIGUOUS:
case '0': /* FIXME GNU compatibility */
return _dox_normal(fp, archive, fh);
case FT_HARDLINK:
return _dox_hardlink(fh);
case FT_SYMLINK:
return _dox_symlink(fh);
case FT_CHAR:
return _dox_char(fp, archive, fh);
case FT_BLOCK:
return _dox_block(fp, archive, fh);
case FT_DIRECTORY:
return _dox_directory(fh);
case FT_FIFO:
return _dox_fifo(fh);
default:
return _tar_skip(fp, archive, fh);
}
}
static int _dox_normal(FILE * fp, char const * archive, TarFileHeader * fh)
{
FILE * fp2;
size_t cnt;
size_t read;
char buf[BUFSIZ];
if((fp2 = fopen(fh->filename, "w")) == NULL)
return _tar_error(fh->filename, 1);
for(cnt = 0; cnt < fh->size; cnt += BUFSIZ)
{
read = fread(buf, sizeof(*buf), min(BUFSIZ, fh->size - cnt),
fp);
if(read == 0)
return _tar_error(archive, 1);
if(fwrite(buf, sizeof(*buf), read, fp2) != read)
{
fclose(fp2);
return _tar_error(fh->filename, 1);
}
}
fclose(fp2);
if((cnt = fh->size % TAR_BLKSIZ) != 0
&& _tar_seek(fp, archive, TAR_BLKSIZ - cnt) != 0)
return 1;
return 0;
}
static int _dox_hardlink(TarFileHeader * fh)
{
if(link(fh->link, fh->filename) != 0)
return _tar_error(fh->filename, 1);
return 0;
}
static int _dox_symlink(TarFileHeader * fh)
{
if(symlink(fh->link, fh->filename) != 0)
return _tar_error(fh->filename, 1);
return 0;
}
static int _dox_char(FILE * fp, char const * archive, TarFileHeader * fh)
{
/* FIXME */
return 1;
}
static int _dox_block(FILE * fp, char const * archive, TarFileHeader * fh)
{
/* FIXME */
return 1;
}
static int _dox_directory(TarFileHeader * fh)
{
if(mkdir(fh->filename, fh->mode) != 0)
return _tar_error(fh->filename, 1);
return 0;
}
static int _dox_fifo(TarFileHeader * fh)
{
if(mkfifo(fh->filename, fh->mode) != 0)
return _tar_error(fh->filename, 1);
return 0;
}
static int _list_do(Prefs * prefs, FILE * fp, char const * archive,
TarFileHeader * fh, int filec, char * filev[]);
static int _tar_list(Prefs * prefs, char const * archive, int filec,
char * filev[])
{
FILE * fp = stdin;
TarFileHeaderBuffer fhdrb;
TarFileHeader fhdr;
size_t size;
int ret = 0;
if(archive != NULL && (fp = fopen(archive, "r")) == NULL)
return _tar_error(archive, 1);
while((size = fread(&fhdrb, sizeof(fhdrb), 1, fp)) == 1)
{
if(_tar_seek(fp, archive, TAR_BLKSIZ - sizeof(fhdrb)) != 0)
{
ret = 1;
break;
}
if(_tar_from_buffer(&fhdrb, &fhdr) != 0
|| _list_do(prefs, fp, archive, &fhdr, filec,
filev) != 0)
ret = 1;
}
if(ret == 0 && size == 0 && !feof(fp))
ret = _tar_error(archive, 1);
if(archive != NULL)
fclose(fp);
return ret;
}
static int _list_do(Prefs * prefs, FILE * fp, char const * archive,
TarFileHeader * fh, int filec, char * filev[])
{
int i;
for(i = 0; i < filec; i++)
if(strcmp(fh->filename, filev[i]) == 0)
break;
if(filec == 0 || i != filec)
_tar_print(prefs, fh);
return _tar_skip(fp, archive, fh);
}
/* tar_error */
static int _tar_error(char const * message, int ret)
{
fputs(PROGNAME ": ", stderr);
perror(message);
return ret;
}
/* tar_usage */
static int _tar_usage(void)
{
fputs("Usage: " PROGNAME " -ctvx [-f archive][file...]\n\
-c Create an archive\n\
-f Specify an archive to work with (default: stdin or stdout)\n\
-t List the contents of an archive\n\
-v Verbose mode\n\
-x Extract from archive\n", stderr);
return 1;
}
/* public */
/* functions */
/* main */
int main(int argc, char * argv[])
{
int o;
Prefs prefs = 0;
char const * archive = NULL;
while((o = getopt(argc, argv, "cvtxf:")) != -1)
switch(o)
{
case 'c':
prefs -= prefs & PREFS_t;
prefs -= prefs & PREFS_x;
prefs |= PREFS_c;
break;
case 'f':
archive = optarg;
break;
case 't':
prefs -= prefs & PREFS_c;
prefs -= prefs & PREFS_x;
prefs |= PREFS_t;
break;
case 'v':
prefs |= (prefs & PREFS_v) == PREFS_v
? PREFS_vv : PREFS_v;
break;
case 'x':
prefs -= prefs & PREFS_c;
prefs -= prefs & PREFS_t;
prefs |= PREFS_x;
break;
default:
return _tar_usage();
}
if(prefs == 0)
return _tar_usage();
return (_tar(&prefs, archive, argc - optind, &argv[optind]) == 0)
? 0 : 2;
}