1/*
2 * Copyright (c) 2005 Rob Braun
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Rob Braun nor the names of his contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29/*
30 * 03-Apr-2005
31 * DRI: Rob Braun <bbraun@synack.net>
32 */
33
34#include <stdlib.h>
35#include <stdio.h>
36#include <string.h>
37#include <sys/types.h>
38#include <sys/stat.h>
39#include <fts.h>
40#include <unistd.h>
41#include <fcntl.h>
42#include <inttypes.h>
43#include <netinet/in.h>
44#include <libxml/xmlreader.h>
45#include <libxml/xmlwriter.h>
46#include <libxml/xmlstring.h>
47#include <limits.h>
48#include <getopt.h>
49#include <regex.h>
50#include <errno.h>
51#include <time.h>
52#include "xar.h"
53#include "config.h"
54
55#define SYMBOLIC 1
56#define NUMERIC  2
57static int Perms = 0;
58static int Local = 0;
59static char *Subdoc = NULL;
60static char *SubdocName = NULL;
61static char *Toccksum = NULL;
62static char *Filecksum = NULL;
63static char *Compression = NULL;
64static char *Rsize = NULL;
65static char *CompressionArg = NULL;
66static char *Chdir = NULL;
67
68static int Err = 0;
69static int List = 0;
70static int Verbose = 0;
71static int Coalesce = 0;
72static int LinkSame = 0;
73static int NoOverwrite = 0;
74static int SaveSuid = 0;
75
76struct lnode {
77	char *str;
78	regex_t reg;
79	struct lnode *next;
80};
81
82struct lnode *Exclude = NULL;
83struct lnode *Exclude_Tail = NULL;
84struct lnode *NoCompress = NULL;
85struct lnode *NoCompress_Tail = NULL;
86struct lnode *PropInclude = NULL;
87struct lnode *PropInclude_Tail = NULL;
88struct lnode *PropExclude = NULL;
89struct lnode *PropExclude_Tail = NULL;
90
91static int32_t err_callback(int32_t sev, int32_t err, xar_errctx_t ctx, void *usrctx);
92
93static void print_file(xar_t x, xar_file_t f) {
94	if( List && Verbose ) {
95		char *size = xar_get_size(x, f);
96		char *path = xar_get_path(f);
97		char *type = xar_get_type(x, f);
98		char *mode = xar_get_mode(x, f);
99		char *user = xar_get_owner(x, f);
100		char *group = xar_get_group(x, f);
101		char *mtime = xar_get_mtime(x, f);
102		printf("%s %8s/%-8s %10s %s %s\n", mode, user, group, size, mtime, path);
103		free(size);
104		free(type);
105		free(path);
106		free(mode);
107		free(user);
108		free(group);
109		free(mtime);
110	} else if( List || Verbose ) {
111		char *path = xar_get_path(f);
112
113		if (xar_path_issane(path) == 0)
114			printf("Warning, archive contains invalid path: %s\n", path);
115		else
116			printf("%s\n", path);
117
118		free(path);
119	}
120}
121
122static void add_subdoc(xar_t x) {
123	xar_subdoc_t s;
124	int fd;
125	unsigned char *buf;
126	unsigned int len;
127	struct stat sb;
128
129	if( SubdocName == NULL ) SubdocName = "subdoc";
130
131	fd = open(Subdoc, O_RDONLY);
132	if( fd < 0 ) {
133		fprintf(stderr, "ERROR: subdoc file %s doesn't exist.  Ignoring.\n", Subdoc);
134		return;
135	}
136	s = xar_subdoc_new(x, (const char *)SubdocName);
137	fstat(fd, &sb);
138	len = sb.st_size;
139	buf = malloc(len+1);
140	if( buf == NULL ) {
141		close(fd);
142		return;
143	}
144	memset(buf, 0, len+1);
145	read(fd, buf, len);
146	close(fd);
147
148	xar_subdoc_copyin(s, buf, len);
149
150
151	return;
152}
153
154static void extract_subdoc(xar_t x, const char *name) {
155	xar_subdoc_t i;
156
157	for( i = xar_subdoc_first(x); i; i = xar_subdoc_next(i) ) {
158		const char *sname = xar_subdoc_name(i);
159		unsigned char *sdoc;
160		int fd, size;
161		if( name && strcmp(name, sname) != 0 )
162			continue;
163		xar_subdoc_copyout(i, &sdoc, (unsigned int *)&size);
164		fd = open(Subdoc, O_WRONLY|O_CREAT|O_TRUNC, 0644);
165		if( fd < 0 ) return;
166		write(fd, sdoc, size);
167		close(fd);
168		free(sdoc);
169	}
170
171	return;
172}
173
174static int archive(const char *filename, int arglen, char *args[]) {
175	xar_t x;
176	FTS *fts;
177	FTSENT *ent;
178	int flags;
179	struct lnode *i;
180	const char *default_compression;
181
182	x = xar_open(filename, WRITE);
183	if( !x ) {
184		fprintf(stderr, "Error creating archive %s\n", filename);
185		exit(1);
186	}
187
188	if( Toccksum )
189		xar_opt_set(x, XAR_OPT_TOCCKSUM, Toccksum);
190
191	if( Filecksum )
192		xar_opt_set(x, XAR_OPT_FILECKSUM, Filecksum);
193
194	if( CompressionArg )
195		xar_opt_set(x, XAR_OPT_COMPRESSIONARG, CompressionArg);
196
197	if( Compression )
198		xar_opt_set(x, XAR_OPT_COMPRESSION, Compression);
199
200	if( Coalesce )
201		xar_opt_set(x, XAR_OPT_COALESCE, "true");
202
203	if( LinkSame )
204		xar_opt_set(x, XAR_OPT_LINKSAME, "true");
205
206	if ( Rsize != NULL )
207		xar_opt_set(x, XAR_OPT_RSIZE, Rsize);
208
209	xar_register_errhandler(x, err_callback, NULL);
210
211	for( i = PropInclude; i; i=i->next ) {
212		xar_opt_set(x, XAR_OPT_PROPINCLUDE, i->str);
213	}
214	for( i = PropExclude; i; i=i->next ) {
215		xar_opt_set(x, XAR_OPT_PROPEXCLUDE, i->str);
216	}
217
218	if( Subdoc )
219		add_subdoc(x);
220
221	if( Perms == SYMBOLIC ) {
222		xar_opt_set(x, XAR_OPT_OWNERSHIP, XAR_OPT_VAL_SYMBOLIC);
223	}
224	if( Perms == NUMERIC ) {
225		xar_opt_set(x, XAR_OPT_OWNERSHIP, XAR_OPT_VAL_NUMERIC);
226	}
227
228	default_compression = strdup(xar_opt_get(x, XAR_OPT_COMPRESSION));
229	if( !default_compression )
230		default_compression = strdup(XAR_OPT_VAL_GZIP);
231
232	flags = FTS_PHYSICAL|FTS_NOSTAT|FTS_NOCHDIR;
233	if( Local )
234		flags |= FTS_XDEV;
235	fts = fts_open(args, flags, NULL);
236	if( !fts ) {
237		fprintf(stderr, "Error traversing file tree\n");
238		exit(1);
239	}
240
241	while( (ent = fts_read(fts)) ) {
242		xar_file_t f;
243		int exclude_match = 1;
244		int nocompress_match = 1;
245		if( ent->fts_info == FTS_DP )
246			continue;
247
248		if( strcmp(ent->fts_path, "/") == 0 )
249			continue;
250		if( strcmp(ent->fts_path, ".") == 0 )
251			continue;
252
253		for( i = Exclude; i; i=i->next ) {
254			exclude_match = regexec(&i->reg, ent->fts_path, 0, NULL, 0);
255			if( !exclude_match )
256				break;
257		}
258		if( !exclude_match ) {
259			if( Verbose )
260				printf("Excluding %s\n", ent->fts_path);
261			continue;
262		}
263
264		for( i = NoCompress; i; i=i->next ) {
265			nocompress_match = regexec(&i->reg, ent->fts_path, 0, NULL, 0);
266			if( !nocompress_match ) {
267				xar_opt_set(x, XAR_OPT_COMPRESSION, XAR_OPT_VAL_NONE);
268				break;
269			}
270		}
271		f = xar_add(x, ent->fts_path);
272		if( !f ) {
273			fprintf(stderr, "Error adding file %s\n", ent->fts_path);
274		} else {
275			print_file(x, f);
276		}
277		if( !nocompress_match )
278			xar_opt_set(x, XAR_OPT_COMPRESSION, default_compression);
279	}
280	fts_close(fts);
281	if( xar_close(x) != 0 ) {
282		fprintf(stderr, "Error creating the archive\n");
283		if( !Err )
284			Err = 42;
285	}
286
287	free((char *)default_compression);
288	for( i = Exclude; i; ) {
289		struct lnode *tmp;
290		regfree(&i->reg);
291		tmp = i;
292		i = i->next;
293		free(tmp);
294	}
295	for( i = NoCompress; i; ) {
296		struct lnode *tmp;
297		regfree(&i->reg);
298		tmp = i;
299		i = i->next;
300		free(tmp);
301	}
302
303	return Err;
304}
305
306static int extract(const char *filename, int arglen, char *args[]) {
307	xar_t x;
308	xar_iter_t i;
309	xar_file_t f;
310	int files_extracted = 0;
311	int argi;
312	struct lnode *extract_files = NULL;
313	struct lnode *extract_tail = NULL;
314	struct lnode *lnodei = NULL;
315	struct lnode *dirs = NULL;
316
317	for(argi = 0; args[argi]; argi++) {
318		struct lnode *tmp;
319		int err;
320		tmp = malloc(sizeof(struct lnode));
321		tmp->str = strdup(args[argi]);
322		tmp->next = NULL;
323		err = regcomp(&tmp->reg, tmp->str, REG_NOSUB);
324		if( err ) {
325			char errstr[1024];
326			regerror(err, &tmp->reg, errstr, sizeof(errstr));
327			printf("Error with regular expression %s: %s\n", tmp->str, errstr);
328			exit(1);
329		}
330		if( extract_files == NULL ) {
331			extract_files = tmp;
332			extract_tail = tmp;
333		} else {
334			extract_tail->next = tmp;
335			extract_tail = tmp;
336		}
337
338		/* Add a clause for recursive extraction */
339		tmp = malloc(sizeof(struct lnode));
340		asprintf(&tmp->str, "%s/.*", args[argi]);
341		tmp->next = NULL;
342		err = regcomp(&tmp->reg, tmp->str, REG_NOSUB);
343		if( err ) {
344			char errstr[1024];
345			regerror(err, &tmp->reg, errstr, sizeof(errstr));
346			printf("Error with regular expression %s: %s\n", tmp->str, errstr);
347			exit(1);
348		}
349		if( extract_files == NULL ) {
350			extract_files = tmp;
351			extract_tail = tmp;
352		} else {
353			extract_tail->next = tmp;
354			extract_tail = tmp;
355		}
356	}
357
358	x = xar_open(filename, READ);
359	if( !x ) {
360		fprintf(stderr, "Error opening xar archive: %s\n", filename);
361		exit(1);
362	}
363
364	if(Chdir) {
365		if( chdir(Chdir) != 0 ) {
366			fprintf(stderr, "Unable to chdir to %s\n", Chdir);
367			exit(1);
368		}
369	}
370
371	xar_register_errhandler(x, err_callback, NULL);
372
373	if( Perms == SYMBOLIC ) {
374		xar_opt_set(x, XAR_OPT_OWNERSHIP, XAR_OPT_VAL_SYMBOLIC);
375	}
376	if( Perms == NUMERIC ) {
377		xar_opt_set(x, XAR_OPT_OWNERSHIP, XAR_OPT_VAL_NUMERIC);
378	}
379	if ( Rsize != NULL ) {
380		xar_opt_set(x, XAR_OPT_RSIZE, Rsize);
381	}
382	if( SaveSuid ) {
383		xar_opt_set(x, XAR_OPT_SAVESUID, XAR_OPT_VAL_TRUE);
384	}
385
386	i = xar_iter_new();
387	if( !i ) {
388		fprintf(stderr, "Error creating xar iterator\n");
389		exit(1);
390	}
391
392	for(f = xar_file_first(x, i); f; f = xar_file_next(i)) {
393		int matched = 0;
394		int exclude_match = 1;
395		struct lnode *i;
396
397		char *path = xar_get_path(f);
398
399		if( args[0] ) {
400			for(i = extract_files; i != NULL; i = i->next) {
401				int extract_match = 1;
402
403				extract_match = regexec(&i->reg, path, 0, NULL, 0);
404				if( !extract_match ) {
405					matched = 1;
406					break;
407				}
408			}
409		} else {
410			matched = 1;
411		}
412
413		for( i = Exclude; i; i=i->next ) {
414			exclude_match = regexec(&i->reg, path, 0, NULL, 0);
415			if( !exclude_match )
416				break;
417		}
418		if( !exclude_match ) {
419			if( Verbose )
420				printf("Excluding %s\n", path);
421			free(path);
422			continue;
423		}
424
425		if (!xar_path_issane(path)) {
426			if (Verbose)
427				printf("Warning, not extracting file \"%s\" because it's path is invalid.\n", path);
428			free(path);
429			continue;
430		}
431
432		if( matched ) {
433			struct stat sb;
434			if( NoOverwrite && (lstat(path, &sb) == 0) ) {
435				printf("%s already exists, not overwriting\n", path);
436			} else {
437				const char *prop = NULL;
438				int deferred = 0;
439				if( xar_prop_get(f, "type", &prop) == 0 ) {
440					if( strcmp(prop, "directory") == 0 ) {
441						struct lnode *tmpl = calloc(sizeof(struct lnode),1);
442						tmpl->str = (char *)f;
443						tmpl->next = dirs;
444						dirs = tmpl;
445						deferred = 1;
446					}
447				}
448				if( ! deferred ) {
449					files_extracted++;
450					print_file(x, f);
451					xar_extract(x, f);
452				}
453			}
454		}
455		free(path);
456	}
457	for(lnodei = dirs; lnodei; lnodei = lnodei->next) {
458		files_extracted++;
459		print_file(x,(xar_file_t)lnodei->str);
460		xar_extract(x, (xar_file_t)lnodei->str);
461	}
462	if( args[0] && (files_extracted == 0) ) {
463		fprintf(stderr, "No files matched extraction criteria.\n");
464		Err = 3;
465	}
466
467	if( Subdoc )
468		extract_subdoc(x, NULL);
469
470	xar_iter_free(i);
471	if( xar_close(x) != 0 ) {
472		fprintf(stderr, "Error extracting the archive\n");
473		if( !Err )
474			Err = 42;
475	}
476
477	for(lnodei = extract_files; lnodei != NULL; ) {
478		struct lnode *tmp;
479		free(lnodei->str);
480		regfree(&lnodei->reg);
481		tmp = lnodei;
482		lnodei = lnodei->next;
483		free(tmp);
484	}
485	return Err;
486}
487
488static int list_subdocs(const char *filename) {
489	xar_t x;
490	xar_subdoc_t s;
491
492	x = xar_open(filename, READ);
493	if( !x ) {
494		fprintf(stderr, "Error opening xar archive: %s\n", filename);
495		exit(1);
496	}
497
498	for(s = xar_subdoc_first(x); s; s = xar_subdoc_next(s)) {
499		printf("%s\n", xar_subdoc_name(s));
500	}
501	xar_close(x);
502
503	return Err;
504}
505
506static int list(const char *filename, int arglen, char *args[]) {
507	xar_t x;
508	xar_iter_t i;
509	xar_file_t f;
510	int argi = 0;
511	struct lnode *list_files = NULL;
512	struct lnode *list_tail = NULL;
513	struct lnode *lnodei = NULL;
514
515	for(argi = 0; args[argi]; argi++) {
516		struct lnode *tmp;
517		int err;
518		tmp = malloc(sizeof(struct lnode));
519		tmp->str = strdup(args[argi]);
520		tmp->next = NULL;
521		err = regcomp(&tmp->reg, tmp->str, REG_NOSUB);
522		if( err ) {
523			char errstr[1024];
524			regerror(err, &tmp->reg, errstr, sizeof(errstr));
525			printf("Error with regular expression %s: %s\n", tmp->str, errstr);
526			exit(1);
527		}
528		if( list_files == NULL ) {
529			list_files = tmp;
530			list_tail = tmp;
531		} else {
532			list_tail->next = tmp;
533			list_tail = tmp;
534		}
535	}
536
537	x = xar_open(filename, READ);
538	if( !x ) {
539		fprintf(stderr, "Error opening xar archive: %s\n", filename);
540		exit(1);
541	}
542
543	i = xar_iter_new();
544	if( !i ) {
545		fprintf(stderr, "Error creating xar iterator\n");
546		exit(1);
547	}
548
549	for(f = xar_file_first(x, i); f; f = xar_file_next(i)) {
550		int matched = 0;
551
552		if( args[0] ) {
553			char *path = xar_get_path(f);
554
555			if (xar_path_issane(path) == 0) {
556				fprintf(stderr, "Warning, archive contains invalid path: %s\n", path);
557				free(path);
558				continue;
559			}
560
561			for(lnodei = list_files; lnodei != NULL; lnodei = lnodei->next) {
562				int list_match = 1;
563
564				list_match = regexec(&lnodei->reg, path, 0, NULL, 0);
565				if( !list_match ) {
566					matched = 1;
567					break;
568				}
569			}
570			free(path);
571		} else {
572			matched = 1;
573		}
574
575		if( matched )
576			print_file(x, f);
577	}
578
579	xar_iter_free(i);
580	xar_close(x);
581
582	for(lnodei = list_files; lnodei != NULL; ) {
583		struct lnode *tmp;
584		free(lnodei->str);
585		regfree(&lnodei->reg);
586		tmp = lnodei;
587		lnodei = lnodei->next;
588		free(tmp);
589	}
590
591	return Err;
592}
593
594static int dumptoc(const char *filename, const char* tocfile) {
595	xar_t x;
596	x = xar_open(filename, READ);
597	if( !x ) {
598		fprintf(stderr, "Error opening xar archive: %s\n", filename);
599		exit(1);
600	}
601
602	xar_serialize(x, tocfile);
603	xar_close(x);
604	return Err;
605}
606
607static int dump_header(const char *filename) {
608	int fd;
609	xar_header_t xh;
610
611	if(filename == NULL)
612		fd = 0;
613	else {
614		fd = open(filename, O_RDONLY);
615		if( fd < 0 ) {
616			perror("open");
617			exit(1);
618		}
619	}
620
621	if( read(fd, &xh, sizeof(xh)) < sizeof(xh) ) {
622		fprintf(stderr, "error reading header\n");
623		exit(1);
624	}
625
626	printf("magic:                  0x%x ", ntohl(xh.magic));
627	if( ntohl(xh.magic) != XAR_HEADER_MAGIC )
628		printf("(BAD)\n");
629	else
630		printf("(OK)\n");
631	printf("size:                   %d\n", ntohs(xh.size));
632	printf("version:                %d\n", ntohs(xh.version));
633	printf("Compressed TOC length:  %" PRId64 "\n", xar_ntoh64(xh.toc_length_compressed));
634	printf("Uncompressed TOC length: %" PRId64 "\n", xar_ntoh64(xh.toc_length_uncompressed));
635	printf("Checksum algorithm:     %d ", ntohl(xh.cksum_alg));
636	switch( ntohl(xh.cksum_alg) ) {
637	case XAR_CKSUM_NONE: printf("(none)\n");
638	                     break;
639	case XAR_CKSUM_SHA1: printf("(SHA1)\n");
640	                     break;
641	case XAR_CKSUM_SHA256: printf("(SHA256)\n");
642		break;
643	case XAR_CKSUM_SHA512: printf("(SHA512)\n");
644		break;
645	case XAR_CKSUM_MD5: printf("(MD5)\n");
646	                    break;
647	default: printf("(unknown)\n");
648	         break;
649	};
650
651	return 0;
652}
653
654static int32_t err_callback(int32_t sev, int32_t err, xar_errctx_t ctx, void *usrctx) {
655	xar_file_t f;
656	xar_t x;
657	const char *str;
658	int e;
659
660	x = xar_err_get_archive(ctx);
661	f = xar_err_get_file(ctx);
662	str = xar_err_get_string(ctx);
663	e = xar_err_get_errno(ctx);
664
665	switch(sev) {
666	case XAR_SEVERITY_DEBUG:
667	case XAR_SEVERITY_INFO:
668		break;
669	case XAR_SEVERITY_WARNING:
670		printf("%s\n", str);
671		break;
672	case XAR_SEVERITY_NORMAL:
673		if( (err = XAR_ERR_ARCHIVE_CREATION) && f )
674			print_file(x, f);
675		break;
676	case XAR_SEVERITY_NONFATAL:
677	case XAR_SEVERITY_FATAL:
678		Err = 2;
679		printf("Error while ");
680		if( err == XAR_ERR_ARCHIVE_CREATION ) printf("creating");
681		if( err == XAR_ERR_ARCHIVE_EXTRACTION ) printf("extracting");
682		printf(" archive");
683		if( f ) {
684			const char *file = xar_get_path(f);
685			if( file ) printf(":(%s)", file);
686			free((char *)file);
687		}
688		if( str ) printf(": %s", str);
689		if( err && e ) printf(" (%s)", strerror(e));
690		if( sev == XAR_SEVERITY_NONFATAL ) {
691			printf(" - ignored");
692			printf("\n");
693		} else {
694			printf("\n");
695			exit(1);
696		}
697		break;
698	}
699	return 0;
700}
701
702static void usage(const char *prog) {
703	fprintf(stderr, "Usage: %s -[ctx][v] -f <archive> ...\n", prog);
704	fprintf(stderr, "\t-c               Creates an archive\n");
705	fprintf(stderr, "\t-x               Extracts an archive\n");
706	fprintf(stderr, "\t-t               Lists an archive\n");
707	fprintf(stderr, "\t-f <filename>    Specifies an archive to operate on [REQUIRED!]\n");
708	fprintf(stderr, "\t-v               Print filenames as they are archived\n");
709	fprintf(stderr, "\t-C <path>        On extract, chdir to this location\n");
710	fprintf(stderr, "\t-n name          Provides a name for a subdocument\n");
711	fprintf(stderr, "\t-s <filename>    On extract, specifies the file to extract\n");
712	fprintf(stderr, "\t                      subdocuments to.\n");
713	fprintf(stderr, "\t                 On archival, specifies an xml file to add\n");
714	fprintf(stderr, "\t                      as a subdocument.\n");
715	fprintf(stderr, "\t-l               On archival, stay on the local device.\n");
716	fprintf(stderr, "\t-p               On extract, set ownership based on symbolic\n");
717	fprintf(stderr, "\t                      names, if possible.\n");
718	fprintf(stderr, "\t-P               On extract, set ownership based on uid/gid.\n");
719	fprintf(stderr, "\t--toc-cksum      Specifies the hashing algorithm to use for\n");
720	fprintf(stderr, "\t                      xml header verification.\n");
721	fprintf(stderr, "\t                      Valid values: none, md5, sha1, sha256, and sha512\n");
722	fprintf(stderr, "\t                      Default: sha1\n");
723	fprintf(stderr, "\t--file-cksum     Specifies the hashing algorithm to use for\n");
724	fprintf(stderr, "\t                      file content verification.\n");
725	fprintf(stderr, "\t                      Valid values: none, md5, sha1, sha256, and sha512\n");
726	fprintf(stderr, "\t                      Default: sha1\n");
727	fprintf(stderr, "\t--dump-toc=<filename> Has xar dump the xml header into the\n");
728	fprintf(stderr, "\t                      specified file.\n");
729	fprintf(stderr, "\t--dump-header    Prints out the xar binary header information\n");
730	fprintf(stderr, "\t--compression    Specifies the compression type to use.\n");
731	fprintf(stderr, "\t                      Valid values: none, gzip, bzip2, lzma\n");
732	fprintf(stderr, "\t                      Default: gzip\n");
733	fprintf(stderr, "\t-a               Synonym for \"--compression=lzma\"\n");
734	fprintf(stderr, "\t-j               Synonym for \"--compression=bzip2\"\n");
735	fprintf(stderr, "\t-z               Synonym for \"--compression=gzip\"\n");
736	fprintf(stderr, "\t--compression-args=arg Specifies arguments to be passed\n");
737	fprintf(stderr, "\t                       to the compression engine.\n");
738	fprintf(stderr, "\t--compress-heap  Compress entire heap instead of individual files.\n");
739	fprintf(stderr, "\t                      Currently limited to gzip compression.\n");
740	fprintf(stderr, "\t--list-subdocs   List the subdocuments in the xml header\n");
741	fprintf(stderr, "\t--extract-subdoc=name Extracts the specified subdocument\n");
742	fprintf(stderr, "\t                      to a document in cwd named <name>.xml\n");
743	fprintf(stderr, "\t--exclude        POSIX regular expression of files to \n");
744	fprintf(stderr, "\t                      ignore while archiving.\n");
745	fprintf(stderr, "\t--rsize          Specifies the size of the buffer used\n");
746	fprintf(stderr, "\t                      for read IO operations in bytes.\n");
747	fprintf(stderr, "\t--coalesce-heap  When archived files are identical, only store one copy\n");
748	fprintf(stderr, "\t                      This option creates an archive which\n");
749	fprintf(stderr, "\t                      is not streamable\n");
750	fprintf(stderr, "\t--link-same      Hardlink identical files\n");
751	fprintf(stderr, "\t--no-compress    POSIX regular expression of files\n");
752	fprintf(stderr, "\t                      to archive, but not compress.\n");
753	fprintf(stderr, "\t--prop-include   File properties to include in archive\n");
754	fprintf(stderr, "\t--prop-exclude   File properties to exclude in archive\n");
755	fprintf(stderr, "\t--distribution   Only includes a subset of file properties\n");
756	fprintf(stderr, "\t                      appropriate for archive distribution\n");
757	fprintf(stderr, "\t--keep-existing  Do not overwrite existing files while extracting\n");
758	fprintf(stderr, "\t-k               Synonym for --keep-existing\n");
759	fprintf(stderr, "\t--keep-setuid    Preserve the suid/sgid bits when extracting\n");
760	fprintf(stderr, "\t--version        Print xar's version number\n");
761
762	return;
763}
764
765static void print_version() {
766	printf("xar %s\n", XAR_VERSION);
767}
768
769int main(int argc, char *argv[]) {
770	int ret;
771	char *filename = NULL;
772	char command = 0, c;
773	char **args;
774	const char *tocfile = NULL;
775	int arglen, i, err;
776	xar_t x;
777	int loptind = 0;
778	int required_dash_f = 0;  /* This release requires us to use -f */
779	struct lnode *tmp;
780	long int longtmp;
781	struct option o[] = {
782		{"toc-cksum",        required_argument, 0, 1},
783		{"file-cksum",       required_argument, 0, 19}, // out of order to avoid regressions
784		{"dump-toc",         required_argument, 0, 'd'},
785		{"compression",      required_argument, 0, 2},
786		{"list-subdocs",     no_argument,       0, 3},
787		{"help",             no_argument,       0, 'h'},
788		{"version",          no_argument,       0, 4},
789		{"dump-header",      no_argument,       0, 5},
790		{"extract-subdoc",   required_argument, 0, 6},
791		{"exclude",          required_argument, 0, 7},
792		{"rsize",            required_argument, 0, 8},
793		{"coalesce-heap",    no_argument,       0, 9},
794		{"link-same",        no_argument,       0, 10},
795		{"no-compress",      required_argument, 0, 11},
796		{"prop-include",     required_argument, 0, 12},
797		{"prop-exclude",     required_argument, 0, 13},
798		{"distribution",     no_argument,       0, 14},
799		{"keep-existing",    no_argument,       0, 15},
800		{"keep-setuid",      no_argument,       0, 16},
801		{"compression-args", required_argument, 0, 17},
802		{ 0, 0, 0, 0}
803	};
804
805	if( argc < 2 ) {
806		usage(argv[0]);
807		exit(1);
808	}
809
810	while( (c = getopt_long(argc, argv, "axcC:vtjzf:hpPln:s:d:vk", o, &loptind)) != -1 ) {
811		switch(c) {
812		case  1 : // toc-cksum
813			if( !optarg ) {
814				usage(argv[0]);
815				exit(1);
816			}
817			if( (strcmp(optarg, XAR_OPT_VAL_NONE) != 0) &&
818				(strcmp(optarg, XAR_OPT_VAL_SHA1) != 0) &&
819				(strcmp(optarg, XAR_OPT_VAL_SHA256) != 0) &&
820				(strcmp(optarg, XAR_OPT_VAL_SHA512) != 0) &&
821				(strcmp(optarg, XAR_OPT_VAL_MD5)  != 0) ) {
822				usage(argv[0]);
823				exit(1);
824			}
825			Toccksum = optarg;
826			break;
827		case 19 : // file-cksum
828			if( !optarg ) {
829				usage(argv[0]);
830				exit(1);
831			}
832			if( (strcmp(optarg, XAR_OPT_VAL_NONE) != 0) &&
833			   (strcmp(optarg, XAR_OPT_VAL_SHA1) != 0) &&
834			   (strcmp(optarg, XAR_OPT_VAL_SHA256) != 0) &&
835			   (strcmp(optarg, XAR_OPT_VAL_SHA512) != 0) &&
836			   (strcmp(optarg, XAR_OPT_VAL_MD5)  != 0) ) {
837				usage(argv[0]);
838				exit(1);
839			}
840			Filecksum = optarg;
841			break;
842		case  2 : // compression
843			if( !optarg ) {
844				usage(argv[0]);
845				exit(1);
846			}
847			if( (strcmp(optarg, XAR_OPT_VAL_NONE) != 0) &&
848				(strcmp(optarg, XAR_OPT_VAL_GZIP) != 0) &&
849				(strcmp(optarg, XAR_OPT_VAL_BZIP) != 0) &&
850				(strcmp(optarg, XAR_OPT_VAL_LZMA) != 0) ) {
851				usage(argv[0]);
852				exit(1);
853			}
854			Compression = optarg;
855			break;
856		case  3 : // list-subdocs
857			if( command && (command != 3) ) {
858				fprintf(stderr, "Conflicting commands specified\n");
859				exit(1);
860			}
861			command = 3;
862			break;
863		case  4 : // version
864			print_version();
865			exit(0);
866		case 'd': // dump-toc
867			if( !optarg ) {
868				usage(argv[0]);
869				exit(1);
870			}
871			tocfile = optarg;
872			command = 'd';
873			break;
874		case  5 : // dump-header
875			command = 5;
876			break;
877		case  6 : // extract-subdoc
878			SubdocName = optarg;
879			asprintf(&Subdoc, "%s.xml", SubdocName);
880			if( !command )
881				command = 6;
882			break;
883		case  7 : // exclude
884			tmp = malloc(sizeof(struct lnode));
885			tmp->str = optarg;
886			tmp->next = NULL;
887			err = regcomp(&tmp->reg, tmp->str, REG_NOSUB);
888			if( err ) {
889				char errstr[1024];
890				regerror(err, &tmp->reg, errstr, sizeof(errstr));
891				printf("Error with regular expression %s: %s\n", tmp->str, errstr);
892				exit(1);
893			}
894			if( Exclude == NULL ) {
895				Exclude = tmp;
896				Exclude_Tail = tmp;
897			} else {
898				Exclude_Tail->next = tmp;
899				Exclude_Tail = tmp;
900			}
901			break;
902		case  8 : // rsize
903			if ( !optarg ) {
904				usage(argv[0]);
905				exit(1);
906			}
907			longtmp = strtol(optarg, NULL, 10);
908			if( (((longtmp == LONG_MIN) || (longtmp == LONG_MAX)) && (errno == ERANGE)) || (longtmp < 1) ) {
909				fprintf(stderr, "Invalid rsize value: %s\n", optarg);
910				exit(5);
911			}
912			Rsize = optarg;
913			break;
914		case  9 : Coalesce = 1; break; // coalesce-heap
915		case 10 : LinkSame = 1; break; // link-same
916		case 11 : // no-compress
917			tmp = malloc(sizeof(struct lnode));
918			tmp->str = optarg;
919			tmp->next = NULL;
920			err = regcomp(&tmp->reg, tmp->str, REG_NOSUB);
921			if( err ) {
922				char errstr[1024];
923				regerror(err, &tmp->reg, errstr, sizeof(errstr));
924				printf("Error with regular expression %s: %s\n", tmp->str, errstr);
925				exit(1);
926			}
927			if( NoCompress == NULL ) {
928				NoCompress = tmp;
929				NoCompress_Tail = tmp;
930			} else {
931				NoCompress_Tail->next = tmp;
932				NoCompress_Tail = tmp;
933			}
934			break;
935		case 12 : // prop-include
936			tmp = malloc(sizeof(struct lnode));
937			tmp->str = optarg;
938			tmp->next = NULL;
939			if( PropInclude == NULL ) {
940				PropInclude = tmp;
941				PropInclude_Tail = tmp;
942			} else {
943				PropInclude_Tail->next = tmp;
944				PropInclude_Tail = tmp;
945			}
946			break;
947		case 13 : // prop-exclude
948			tmp = malloc(sizeof(struct lnode));
949			tmp->str = optarg;
950			tmp->next = NULL;
951			if( PropExclude == NULL ) {
952				PropExclude = tmp;
953				PropExclude_Tail = tmp;
954			} else {
955				PropExclude_Tail->next = tmp;
956				PropExclude_Tail = tmp;
957			}
958			break;
959		case 14 : // distribution
960		{
961			char *props[] = { "type", "data", "mode", "name" };
962			int i;
963			for( i = 0; i < 4; i++ ) {
964				tmp = malloc(sizeof(struct lnode));
965				tmp->str = strdup(props[i]);
966				tmp->next = NULL;
967				if( PropInclude == NULL ) {
968					PropInclude = tmp;
969					PropInclude_Tail = tmp;
970				} else {
971					PropInclude_Tail->next = tmp;
972					PropInclude_Tail = tmp;
973				}
974			}
975		}
976			break;
977		case 'k': // keep-existing
978		case 15 :
979			NoOverwrite++;
980			break;
981		case 16 : // keep-setuid
982			SaveSuid++;
983			break;
984		case 17 : // compression-args
985			CompressionArg = optarg;
986			break;
987		case 'C': // chdir to
988			if( !optarg ) {
989				usage(argv[0]);
990				exit(1);
991			}
992			Chdir = optarg;
993			break;
994		case 'c': // create
995		case 'x': // extract
996		case 't': // list
997			if( command && (command != 's') ) {
998				usage(argv[0]);
999				fprintf(stderr, "Conflicting command flags: %c and %c specified\n", c, command);
1000				exit(1);
1001			}
1002			if( c == 't' )
1003				List = 1;
1004			command = c;
1005			break;
1006		case 'a':
1007			Compression = "lzma";
1008			break;
1009		case 'j':
1010			Compression = "bzip2";
1011			break;
1012		case 'z':
1013			Compression = "gzip";
1014			break;
1015		case 'f': // filename
1016			required_dash_f = 1;
1017			filename = optarg;
1018			break;
1019		case 'p': // set ownership based on symbolic names (if possible)
1020			Perms = SYMBOLIC;
1021			break;
1022		case 'P': // set ownership based on uid/gid
1023			Perms = NUMERIC;
1024			break;
1025		case 'l': // stay on local device
1026			Local = 1;
1027			break;
1028		case 'n': // provide subdocument name
1029			SubdocName = optarg;
1030			break;
1031		case 's': // extract subdocuments to/add subdocuments from
1032			Subdoc = optarg;
1033			if( !command )
1034				command = 's';
1035			break;
1036		case 'v': // print filenames
1037			Verbose++;
1038			break;
1039		case 'h': // help
1040		default:
1041			usage(argv[0]);
1042			exit(1);
1043		}
1044	}
1045
1046	if (! required_dash_f)	{
1047		usage(argv[0]);
1048		fprintf(stderr, "\n -f option is REQUIRED\n");
1049		exit(1);
1050	}
1051
1052	switch(command) {
1053		case  5 :
1054			return dump_header(filename);
1055		case  3 :
1056			return list_subdocs(filename);
1057		case 'c':
1058			if( optind == argc ) {
1059				usage(argv[0]);
1060				fprintf(stderr, "No files to operate on.\n");
1061				exit(1);
1062			}
1063			arglen = argc - optind;
1064			args = malloc(sizeof(char*) * (arglen+1));
1065			memset(args, 0, sizeof(char*) * (arglen+1));
1066			for( i = 0; i < arglen; i++ )
1067				args[i] = strdup(argv[optind + i]);
1068
1069			return archive(filename, arglen, args);
1070		case 'd':
1071			if( !tocfile ) {
1072				usage(argv[0]);
1073				exit(1);
1074			}
1075			return dumptoc(filename, tocfile);
1076		case 'x':
1077			arglen = argc - optind;
1078			args = malloc(sizeof(char*) * (arglen+1));
1079			for( i = 0; i < arglen; i++ )
1080				args[i] = strdup(argv[optind + i]);
1081			args[i] = NULL;
1082			return extract(filename, arglen, args);
1083		case 't':
1084			arglen = argc - optind;
1085			args = calloc(sizeof(char*) * (arglen+1),1);
1086			for( i = 0; i < arglen; i++ )
1087				args[i] = strdup(argv[optind + i]);
1088			ret = list(filename, arglen, args);
1089			for( i = 0; i < arglen; i++ )
1090				free(args[i]);
1091		case  6 :
1092		case 's':
1093			x = xar_open(filename, READ);
1094			if( !x ) {
1095				fprintf(stderr, "Error opening xar archive: %s\n", filename);
1096				exit(1);
1097			}
1098			xar_register_errhandler(x, err_callback, NULL);
1099			extract_subdoc(x, SubdocName);
1100			xar_close(x);
1101			exit(Err);
1102			break;
1103		default:
1104			usage(argv[0]);
1105			fprintf(stderr, "Unrecognized command.\n");
1106			exit(1);
1107	}
1108
1109	/* unreached */
1110	exit(0);
1111}
1112