devel
/* $Id$ */
/* Copyright (c) 2005-2016 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Unix devel */
/* 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/>. */
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <libgen.h>
#include <errno.h>
#include <ar.h>
#ifndef PROGNAME
# define PROGNAME "ar"
#endif
#define min(a, b) ((a) < (b) ? (a) : (b))
/* ar */
/* private */
/* types */
typedef int Prefs;
#define PREFS_d 0x01
#define PREFS_p 0x02
#define PREFS_r 0x04
#define PREFS_t 0x08
#define PREFS_x 0x10
#define PREFS_c 0x20
#define PREFS_u 0x40
#define PREFS_v 0x80
/* prototypes */
static int _ar(Prefs * prefs, char const * archive, int filec, char * filev[]);
static int _ar_error(char const * message, int ret);
/* accessors */
static struct tm * _ar_get_date(struct ar_hdr * ar);
static int _ar_get_uid(struct ar_hdr * ar, uid_t * uid);
static int _ar_get_gid(struct ar_hdr * ar, gid_t * gid);
static int _ar_get_mode(struct ar_hdr * ar, mode_t * mode);
static int _ar_get_size(struct ar_hdr * ar, size_t * size);
/* functions */
/* ar */
static int _do_sig_check(char const * archive, FILE * fp);
static int _do_hdr_check(char const * archive, struct ar_hdr * hdr);
static int _do_seek_next(char const * archive, FILE * fp, struct ar_hdr * hdr);
static int _ar_do_r(Prefs * prefs, char const * archive, int filec,
char * filev[]);
static int _ar_do_tx(Prefs * prefs, char const * archive, FILE * fp, int filec,
char * filev[]);
static int _ar(Prefs * prefs, char const * archive, int filec, char * filev[])
{
int ret = 0;
FILE * fp;
if(*prefs & PREFS_r)
return _ar_do_r(prefs, archive, filec, filev);
if((fp = fopen(archive, "r")) == NULL)
return _ar_error(archive, 1);
if(_do_sig_check(archive, fp) != 0)
{
fclose(fp);
return 1;
}
if((ret = _ar_do_tx(prefs, archive, fp, filec, filev)) == 0
&& !feof(fp))
_ar_error(archive, 0);
if(fclose(fp) != 0)
return _ar_error(archive, 1);
return ret;
}
static int _do_create(Prefs * prefs, char const * archive, int filec,
char * filev[]);
static int _do_replace(Prefs * prefs, char const * archive, FILE * fp,
int filec, char * filev[]);
static int _ar_do_r(Prefs * prefs, char const * archive, int filec,
char * filev[])
{
int ret;
FILE * fp;
if((fp = fopen(archive, "r+")) == NULL)
{
if(errno != ENOENT)
return _ar_error(archive, 1);
if(!(*prefs & PREFS_c))
fprintf(stderr, "%s%s%s", PROGNAME ": ", archive,
": Creating archive\n");
return _do_create(prefs, archive, filec, filev);
}
ret = _do_replace(prefs, archive, fp, filec, filev);
if(fclose(fp) != 0)
return _ar_error(archive, 1);
return ret;
}
static int _create_append(Prefs * prefs, char const * archive, FILE * fp,
char const * filename);
static int _do_create(Prefs * prefs, char const * archive, int filec,
char * filev[])
{
FILE * fp;
int i;
if((fp = fopen(archive, "w")) == NULL)
return _ar_error(archive, 1);
if(fwrite(ARMAG, SARMAG, 1, fp) != 1)
{
fclose(fp);
return _ar_error(archive, 1);
}
for(i = 0; i < filec; i++)
if(_create_append(prefs, archive, fp, filev[i]) != 0)
break;
if(fclose(fp) != 0)
return _ar_error(archive, 1);
return i != filec;
}
static int _append_header(char const * archive, FILE * fp,
char const * filename, FILE * fp2);
static int _create_append(Prefs * prefs, char const * archive, FILE * fp,
char const * filename)
{
FILE * fp2;
char buf[BUFSIZ];
size_t i;
size_t size;
const char newline = '\n';
if(*prefs & PREFS_v)
printf("a - %s\n", filename);
if((fp2 = fopen(filename, "r")) == NULL)
return _ar_error(filename, 1);
if(_append_header(archive, fp, filename, fp2) != 0)
{
fclose(fp2);
return 1;
}
for(size = 0; (i = fread(buf, sizeof(char), sizeof(buf), fp2)) > 0
&& fwrite(buf, sizeof(char), i, fp) == i; size += i);
if(!feof(fp2) || i > 0)
{
_ar_error(i > 0 ? archive : filename, 0);
fclose(fp2);
return 1;
}
if(fclose(fp2) != 0)
return _ar_error(filename, 1);
if(size & 0x1 && fwrite(&newline, sizeof(newline), 1, fp) != 1)
return _ar_error(filename, 1);
return 0;
}
static int _append_header(char const * archive, FILE * fp,
char const * filename, FILE * fp2)
{
struct ar_hdr hdr;
struct stat st;
char * p;
if(fstat(fileno(fp2), &st) != 0)
return _ar_error(filename, 1);
if((p = strdup(filename)) == NULL)
return _ar_error(filename, 1);
memset(&hdr, 0, sizeof(hdr));
strncpy(hdr.ar_name, basename(p), sizeof(hdr.ar_name) - 1);
free(p);
snprintf(hdr.ar_date, sizeof(hdr.ar_date), "%u", (unsigned)st.st_mtime);
snprintf(hdr.ar_uid, sizeof(hdr.ar_uid), "%u", (unsigned)st.st_uid);
snprintf(hdr.ar_gid, sizeof(hdr.ar_gid), "%u", (unsigned)st.st_gid);
snprintf(hdr.ar_mode, sizeof(hdr.ar_mode), "%o", (unsigned)st.st_mode);
snprintf(hdr.ar_size, sizeof(hdr.ar_size), "%u", (unsigned)st.st_size);
strncpy(hdr.ar_fmag, ARFMAG, sizeof(hdr.ar_fmag));
if(fwrite(&hdr, sizeof(hdr), 1, fp) != 1)
return _ar_error(archive, 1);
return 0;
}
static int _do_replace(Prefs * prefs, char const * archive, FILE * fp,
int filec, char * filev[])
{
struct ar_hdr hdr;
int i;
size_t h;
if(_do_sig_check(archive, fp) != 0)
return 1;
while(fread(&hdr, sizeof(hdr), 1, fp) == 1)
{
if(_do_hdr_check(archive, &hdr) != 0)
return 1;
for(h = 0; h < sizeof(hdr.ar_name); h++)
if(hdr.ar_name[h] == '/')
{
hdr.ar_name[h] = '\0';
break;
}
for(i = 0; i < filec; i++)
{
if(strlen(filev[i]) > sizeof(hdr.ar_name))
continue;
/* XXX test against basename(filev[i]) instead? */
if(memcmp(hdr.ar_name, filev[i], sizeof(hdr.ar_name))
!= 0)
continue;
/* FIXME implement */
fprintf(stderr, "%s%s%s", PROGNAME ": ", filev[i],
": replacing not implemented yet\n");
filev[i] = ""; /* XXX ugly hack */
}
if(_do_seek_next(archive, fp, &hdr) != 0)
return 1;
}
for(i = 0; i < filec; i++)
{
if(strlen(filev[i]) == 0) /* XXX ugly hack */
continue;
if(_create_append(prefs, archive, fp, filev[i]) != 0)
return 1;
}
return 0;
}
static int _do_hdr_check(char const * archive, struct ar_hdr * hdr)
{
if(memcmp(ARFMAG, hdr->ar_fmag, sizeof(hdr->ar_fmag)) != 0)
{
fprintf(stderr, "%s%s%s", PROGNAME ": ", archive,
": Invalid archive\n");
return 1;
}
return 0;
}
static int _do_seek_next(char const * archive, FILE * fp, struct ar_hdr * hdr)
{
size_t size;
if(_ar_get_size(hdr, &size) != 0)
{
fprintf(stderr, "%s%s%s", PROGNAME ": ", archive,
": Invalid archive\n");
return 1;
}
if(size & 0x1)
size++;
if(fseek(fp, size, SEEK_CUR) != 0)
return _ar_error(archive, 1);
return 0;
}
static int _do_t(Prefs * prefs, char const * archive, FILE * fp,
struct ar_hdr * hdr, char const * name);
static int _do_x(Prefs * prefs, char const * archive, FILE * fp,
struct ar_hdr * hdr, char const * name);
static int _ar_do_tx(Prefs * prefs, char const * archive, FILE * fp, int filec,
char * filev[])
{
struct ar_hdr hdr;
char name[sizeof(hdr.ar_name) + 1];
unsigned int h;
int i;
char * p;
while(fread(&hdr, sizeof(hdr), 1, fp) == 1)
{
if(_do_hdr_check(archive, &hdr) != 0)
return 1;
memcpy(name, hdr.ar_name, sizeof(hdr.ar_name));
name[sizeof(name) - 1] = '\0';
for(h = 0, p = name; h < sizeof(name); h++)
if(p[h] == '/')
{
p[h] = '\0';
break;
}
if(h == 0)
{
if(_do_seek_next(archive, fp, &hdr) != 0)
return 1; /* FIXME error case? */
continue;
}
for(i = 0; i < filec; i++)
if(strcmp(name, filev[i]) == 0)
break;
if(i > 0 && i == filec)
{
if(_do_seek_next(archive, fp, &hdr) != 0)
return 1;
continue;
}
if(*prefs & PREFS_t)
{
if(_do_t(prefs, archive, fp, &hdr, name) != 0)
return 1;
continue;
}
else if(*prefs & PREFS_x)
{
if(_do_x(prefs, archive, fp, &hdr, name) != 0)
return 1;
continue;
}
/* FIXME clean up this so it won't have to appear */
fputs(PROGNAME ": Not implemented yet\n", stderr);
return 1;
}
return 0;
}
static int _do_sig_check(char const * archive, FILE * fp)
{
char sig[SARMAG];
if(fread(sig, sizeof(sig), 1, fp) != 1
&& !feof(fp))
return _ar_error(archive, 1);
if(memcmp(ARMAG, sig, sizeof(sig)) == 0)
return 0;
fprintf(stderr, "%s%s%s", PROGNAME ": ", archive,
": Invalid archive\n");
return 1;
}
static int _t_print_long(char const * archive, struct ar_hdr * hdr,
char const * name);
static int _do_t(Prefs * prefs, char const * archive, FILE * fp,
struct ar_hdr * hdr, char const * name)
{
if(*prefs & PREFS_v)
{
if(_t_print_long(archive, hdr, name) != 0)
return 1;
}
else
printf("%s\n", name);
return _do_seek_next(archive, fp, hdr);
}
static char const * _long_mode(mode_t mode);
static int _t_print_long(char const * archive, struct ar_hdr * hdr,
char const * name)
{
mode_t mode;
uid_t uid;
gid_t gid;
size_t size;
struct tm * tm;
char buf[24];
if(_ar_get_mode(hdr, &mode) != 0
|| _ar_get_uid(hdr, &uid) != 0
|| _ar_get_gid(hdr, &gid) != 0
|| _ar_get_size(hdr, &size) != 0
|| (tm = _ar_get_date(hdr)) == NULL)
{
fprintf(stderr, "%s%s%s", PROGNAME ": ", archive,
": Invalid archive\n");
return 1;
}
if(strftime(buf, sizeof(buf), "%b %d %H:%M %Y", tm) == 0)
return _ar_error("strftime", 1);
printf("%s %u/%u %10zu %s %s\n", _long_mode(mode), uid, gid, size, buf,
name);
return 0;
}
static char const * _long_mode(mode_t mode)
{
static char str[11];
int i;
for(i = 0; i < 10; i++)
str[i] = '-';
if(mode & S_IRUSR)
str[1] = 'r';
if(mode & S_IWUSR)
str[2] = 'w';
if(mode & S_IXUSR)
str[3] = 'x';
if(mode & S_IRGRP)
str[4] = 'r';
if(mode & S_IWGRP)
str[5] = 'w';
if(mode & S_IXGRP)
str[6] = 'x';
if(mode & S_IROTH)
str[7] = 'r';
if(mode & S_IWOTH)
str[8] = 'w';
if(mode & S_IXOTH)
str[9] = 'x';
str[10] = '\0';
return str;
}
static int _do_x(Prefs * prefs, char const * archive, FILE * fp,
struct ar_hdr * hdr, char const * name)
{
FILE * fp2;
int fd2;
mode_t mode;
uid_t uid;
gid_t gid;
size_t size;
char buf[BUFSIZ];
size_t i;
if(_ar_get_mode(hdr, &mode) != 0
|| _ar_get_uid(hdr, &uid) != 0
|| _ar_get_gid(hdr, &gid) != 0
|| _ar_get_size(hdr, &size) != 0
|| _ar_get_date(hdr) == NULL)
{
fprintf(stderr, "%s%s%s", PROGNAME ": ", archive,
": Invalid archive\n");
return 1;
}
if(*prefs & PREFS_v)
printf("%s%s\n", "x - ", name);
if((fp2 = fopen(name, "w")) == NULL)
return _ar_error(name, 1);
fd2 = fileno(fp2);
if(fchmod(fd2, mode) != 0)
_ar_error(name, 0);
if(fchown(fd2, uid, gid) != 0)
_ar_error(name, 0);
/* FIXME */
for(; size > 0; size -= i)
{
if((i = fread(buf, sizeof(char), min(sizeof(buf), size), fp))
== 0)
{
fclose(fp2);
return _ar_error(archive, 1);
}
if(fwrite(buf, sizeof(char), i, fp2) != i)
{
fclose(fp2);
return _ar_error(name, 1);
}
}
if(fclose(fp2) != 0)
return _ar_error(name, 1);
return 0;
}
/* accessors */
/* ar_get_date */
static struct tm * _ar_get_date(struct ar_hdr * ar)
{
struct tm * ret;
char buf[sizeof(ar->ar_date) + 1];
time_t date;
if(ar->ar_date[0] == '\0')
return NULL;
memcpy(buf, ar->ar_date, sizeof(ar->ar_date));
buf[sizeof(buf) - 1] = '\0';
date = strtol(buf, NULL, 10);
if((ret = gmtime(&date)) == NULL)
return NULL;
return ret;
}
/* ar_get_uid */
static int _ar_get_uid(struct ar_hdr * ar, uid_t * uid)
{
char buf[sizeof(ar->ar_uid) + 1];
if(ar->ar_uid[0] == '\0')
return 1;
memcpy(buf, ar->ar_uid, sizeof(ar->ar_uid));
buf[sizeof(buf) - 1] = '\0';
*uid = strtoul(buf, NULL, 10);
return 0;
}
/* ar_get_gid */
static int _ar_get_gid(struct ar_hdr * ar, gid_t * gid)
{
char buf[sizeof(ar->ar_gid) + 1];
if(ar->ar_gid[0] == '\0')
return 1;
memcpy(buf, ar->ar_gid, sizeof(ar->ar_gid));
buf[sizeof(buf) - 1] = '\0';
*gid = strtoul(buf, NULL, 10);
return 0;
}
/* ar_get_mode */
static int _ar_get_mode(struct ar_hdr * ar, mode_t * mode)
{
char buf[sizeof(ar->ar_mode) + 1];
if(ar->ar_mode[0] == '\0')
return 1;
memcpy(buf, ar->ar_mode, sizeof(ar->ar_mode));
buf[sizeof(buf) - 1] = '\0';
*mode = strtoul(buf, NULL, 8);
return 0;
}
/* ar_get_size */
static int _ar_get_size(struct ar_hdr * ar, size_t * size)
{
char buf[sizeof(ar->ar_size) + 1];
if(ar->ar_size[0] == '\0')
return 1;
memcpy(buf, ar->ar_size, sizeof(ar->ar_size));
buf[sizeof(buf) - 1] = '\0';
*size = strtoul(buf, NULL, 10);
return 0;
}
/* ar_error */
static int _ar_error(char const * message, int ret)
{
fputs(PROGNAME ": ", stderr);
perror(message);
return ret;
}
/* ar_usage */
static int _ar_usage(void)
{
fputs("Usage: " PROGNAME " -d[-v] archive file...\n\
" PROGNAME " -p[-v] archive file...\n\
" PROGNAME " -r[-cuv] archive file...\n\
" PROGNAME " -t[-v] archive [file...]\n\
" PROGNAME " -x[-v] archive [file...]\n\
-d Delete one or more files from the archive\n\
-r Replace or add files to archive\n\
-t Write a table of contents of archive\n\
-u Update older files in the archive\n\
-v Give verbose output\n\
-x Extract all or given files from the archive\n", stderr);
return 1;
}
/* main */
int main(int argc, char * argv[])
{
int o;
Prefs p = 0;
while((o = getopt(argc, argv, "dprtcuvx")) != -1)
switch(o)
{
case 'c':
p |= PREFS_c;
break;
case 'd':
p -= p & (PREFS_p|PREFS_r|PREFS_t|PREFS_x);
p |= PREFS_d;
break;
case 'p':
p -= p & (PREFS_d|PREFS_r|PREFS_t|PREFS_x);
p |= PREFS_u;
break;
case 'r':
p -= p & (PREFS_d|PREFS_p|PREFS_t|PREFS_x);
p |= PREFS_r;
break;
case 't':
p -= p & (PREFS_d|PREFS_p|PREFS_r|PREFS_x);
p |= PREFS_t;
break;
case 'x':
p -= p & (PREFS_d|PREFS_p|PREFS_r|PREFS_t);
p |= PREFS_x;
break;
case 'v':
p |= PREFS_v;
break;
default:
return _ar_usage();
}
if(!(p & (PREFS_d | PREFS_r | PREFS_t | PREFS_x))
|| optind == argc
|| (optind+1 >= argc && !(p & (PREFS_t | PREFS_x))))
return _ar_usage();
return (_ar(&p, argv[optind], argc - optind - 1, &argv[optind + 1])
== 0) ? 0 : 2;
}