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;
							}
							