1%{
2/*-
3 * SPDX-License-Identifier: BSD-2-Clause
4 *
5 * Copyright (c) 2008 Kai Wang
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * 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 AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31#include <sys/mman.h>
32#include <sys/param.h>
33#include <sys/queue.h>
34#include <sys/stat.h>
35#include <archive.h>
36#include <archive_entry.h>
37#include <dirent.h>
38#include <errno.h>
39#include <fcntl.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <unistd.h>
44
45#include "ar.h"
46
47#define TEMPLATE "arscp.XXXXXXXX"
48
49struct list {
50	char		*str;
51	struct list	*next;
52};
53
54
55extern int	yylex(void);
56
57static void	yyerror(const char *);
58static void	arscp_addlib(char *archive, struct list *list);
59static void	arscp_addmod(struct list *list);
60static void	arscp_clear(void);
61static int	arscp_copy(int ifd, int ofd);
62static void	arscp_create(char *in, char *out);
63static void	arscp_delete(struct list *list);
64static void	arscp_dir(char *archive, struct list *list, char *rlt);
65static void	arscp_end(int eval);
66static void	arscp_extract(struct list *list);
67static void	arscp_free_argv(void);
68static void	arscp_free_mlist(struct list *list);
69static void	arscp_list(void);
70static struct list *arscp_mlist(struct list *list, char *str);
71static void	arscp_mlist2argv(struct list *list);
72static int	arscp_mlist_len(struct list *list);
73static void	arscp_open(char *fname);
74static void	arscp_prompt(void);
75static void	arscp_replace(struct list *list);
76static void	arscp_save(void);
77static int	arscp_target_exist(void);
78
79extern int		 lineno;
80
81static struct bsdar	*bsdar;
82static char		*target;
83static char		*tmpac;
84static int		 interactive;
85static int		 verbose;
86
87%}
88
89%token ADDLIB
90%token ADDMOD
91%token CLEAR
92%token CREATE
93%token DELETE
94%token DIRECTORY
95%token END
96%token EXTRACT
97%token LIST
98%token OPEN
99%token REPLACE
100%token VERBOSE
101%token SAVE
102%token LP
103%token RP
104%token COMMA
105%token EOL
106%token <str> FNAME
107%type <list> mod_list
108
109%union {
110	char		*str;
111	struct list	*list;
112}
113
114%%
115
116begin
117	: { arscp_prompt(); } ar_script
118	;
119
120ar_script
121	: cmd_list
122	|
123	;
124
125mod_list
126	: FNAME { $$ = arscp_mlist(NULL, $1); }
127	| mod_list separator FNAME { $$ = arscp_mlist($1, $3); }
128	;
129
130separator
131	: COMMA
132	|
133	;
134
135cmd_list
136	: rawcmd
137	| cmd_list rawcmd
138	;
139
140rawcmd
141	: cmd EOL { arscp_prompt(); }
142	;
143
144cmd
145	: addlib_cmd
146	| addmod_cmd
147	| clear_cmd
148	| create_cmd
149	| delete_cmd
150	| directory_cmd
151	| end_cmd
152	| extract_cmd
153	| list_cmd
154	| open_cmd
155	| replace_cmd
156	| verbose_cmd
157	| save_cmd
158	| invalid_cmd
159	| empty_cmd
160	| error
161	;
162
163addlib_cmd
164	: ADDLIB FNAME LP mod_list RP { arscp_addlib($2, $4); }
165	| ADDLIB FNAME { arscp_addlib($2, NULL); }
166	;
167
168addmod_cmd
169	: ADDMOD mod_list { arscp_addmod($2); }
170	;
171
172clear_cmd
173	: CLEAR { arscp_clear(); }
174	;
175
176create_cmd
177	: CREATE FNAME { arscp_create(NULL, $2); }
178	;
179
180delete_cmd
181	: DELETE mod_list { arscp_delete($2); }
182	;
183
184directory_cmd
185	: DIRECTORY FNAME { arscp_dir($2, NULL, NULL); }
186	| DIRECTORY FNAME LP mod_list RP { arscp_dir($2, $4, NULL); }
187	| DIRECTORY FNAME LP mod_list RP FNAME { arscp_dir($2, $4, $6); }
188	;
189
190end_cmd
191	: END { arscp_end(EXIT_SUCCESS); }
192	;
193
194extract_cmd
195	: EXTRACT mod_list { arscp_extract($2); }
196	;
197
198list_cmd
199	: LIST { arscp_list(); }
200	;
201
202open_cmd
203	: OPEN FNAME { arscp_open($2); }
204	;
205
206replace_cmd
207	: REPLACE mod_list { arscp_replace($2); }
208	;
209
210save_cmd
211	: SAVE { arscp_save(); }
212	;
213
214verbose_cmd
215	: VERBOSE { verbose = !verbose; }
216	;
217
218empty_cmd
219	:
220	;
221
222invalid_cmd
223	: FNAME { yyerror(NULL); }
224	;
225
226%%
227
228/* ARGSUSED */
229static void
230yyerror(const char *s)
231{
232
233	(void) s;
234	printf("Syntax error in archive script, line %d\n", lineno);
235}
236
237/*
238 * arscp_open first open an archive and check its validity. If the archive
239 * format is valid, it calls arscp_create to create a temporary copy of
240 * the archive.
241 */
242static void
243arscp_open(char *fname)
244{
245	struct archive		*a;
246	struct archive_entry	*entry;
247	int			 r;
248
249	if ((a = archive_read_new()) == NULL)
250		bsdar_errc(bsdar, 0, "archive_read_new failed");
251	archive_read_support_format_ar(a);
252	AC(archive_read_open_filename(a, fname, DEF_BLKSZ));
253	if ((r = archive_read_next_header(a, &entry)))
254		bsdar_warnc(bsdar, archive_errno(a), "%s",
255		    archive_error_string(a));
256	AC(archive_read_close(a));
257	AC(archive_read_free(a));
258	if (r != ARCHIVE_OK)
259		return;
260	arscp_create(fname, fname);
261}
262
263/*
264 * Create archive. in != NULL indicate it's a OPEN cmd, and resulting
265 * archive is based on modification of an existing one. If in == NULL,
266 * we are in CREATE cmd and a new empty archive will be created.
267 */
268static void
269arscp_create(char *in, char *out)
270{
271	struct archive		*a;
272	int			 ifd, ofd;
273
274	/* Delete previously created temporary archive, if any. */
275	if (tmpac) {
276		if (unlink(tmpac) < 0)
277			bsdar_errc(bsdar, errno, "unlink failed");
278		free(tmpac);
279	}
280
281	tmpac = strdup(TEMPLATE);
282	if (tmpac == NULL)
283		bsdar_errc(bsdar, errno, "strdup failed");
284	if ((ofd = mkstemp(tmpac)) < 0)
285		bsdar_errc(bsdar, errno, "mkstemp failed");
286
287	if (in) {
288		/*
289		 * Command OPEN creates a temporary copy of the
290		 * input archive.
291		 */
292		if ((ifd = open(in, O_RDONLY)) < 0) {
293			bsdar_warnc(bsdar, errno, "open failed");
294			return;
295		}
296		if (arscp_copy(ifd, ofd)) {
297			bsdar_warnc(bsdar, 0, "arscp_copy failed");
298			return;
299		}
300		close(ifd);
301		close(ofd);
302	} else {
303		/*
304		 * Command CREATE creates an "empty" archive.
305		 * (archive with only global header)
306		 */
307		if ((a = archive_write_new()) == NULL)
308			bsdar_errc(bsdar, 0, "archive_write_new failed");
309		archive_write_set_format_ar_svr4(a);
310		AC(archive_write_open_fd(a, ofd));
311		AC(archive_write_close(a));
312		AC(archive_write_free(a));
313	}
314
315	/* Override previous target, if any. */
316	if (target)
317		free(target);
318
319	target = out;
320	bsdar->filename = tmpac;
321}
322
323/* A file copying implementation using mmap. */
324static int
325arscp_copy(int ifd, int ofd)
326{
327	struct stat		 sb;
328	char			*buf, *p;
329	ssize_t			 w;
330	size_t			 bytes;
331
332	if (fstat(ifd, &sb) < 0) {
333		bsdar_warnc(bsdar, errno, "fstate failed");
334		return (1);
335	}
336	if ((p = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, ifd,
337	    (off_t)0)) == MAP_FAILED) {
338		bsdar_warnc(bsdar, errno, "mmap failed");
339		return (1);
340	}
341	for (buf = p, bytes = sb.st_size; bytes > 0; bytes -= w) {
342		w = write(ofd, buf, bytes);
343		if (w <= 0) {
344			bsdar_warnc(bsdar, errno, "write failed");
345			break;
346		}
347	}
348	if (munmap(p, sb.st_size) < 0)
349		bsdar_errc(bsdar, errno, "munmap failed");
350	if (bytes > 0)
351		return (1);
352
353	return (0);
354}
355
356/*
357 * Add all modules of archive to current archive, if list != NULL,
358 * only those modules specified in 'list' will be added.
359 */
360static void
361arscp_addlib(char *archive, struct list *list)
362{
363
364	if (!arscp_target_exist())
365		return;
366	arscp_mlist2argv(list);
367	bsdar->addlib = archive;
368	ar_write_archive(bsdar, 'A');
369	arscp_free_argv();
370	arscp_free_mlist(list);
371}
372
373/* Add modules into current archive. */
374static void
375arscp_addmod(struct list *list)
376{
377
378	if (!arscp_target_exist())
379		return;
380	arscp_mlist2argv(list);
381	ar_write_archive(bsdar, 'q');
382	arscp_free_argv();
383	arscp_free_mlist(list);
384}
385
386/* Delete modules from current archive. */
387static void
388arscp_delete(struct list *list)
389{
390
391	if (!arscp_target_exist())
392		return;
393	arscp_mlist2argv(list);
394	ar_write_archive(bsdar, 'd');
395	arscp_free_argv();
396	arscp_free_mlist(list);
397}
398
399/* Extract modules from current archive. */
400static void
401arscp_extract(struct list *list)
402{
403
404	if (!arscp_target_exist())
405		return;
406	arscp_mlist2argv(list);
407	ar_read_archive(bsdar, 'x', stdout);
408	arscp_free_argv();
409	arscp_free_mlist(list);
410}
411
412/* List modules of archive. (Simple Mode) */
413static void
414arscp_list(void)
415{
416
417	if (!arscp_target_exist())
418		return;
419	bsdar->argc = 0;
420	bsdar->argv = NULL;
421	/* Always verbose. */
422	bsdar->options |= AR_V;
423	ar_read_archive(bsdar, 't', stdout);
424	bsdar->options &= ~AR_V;
425}
426
427/* List modules of archive. (Advance Mode) */
428static void
429arscp_dir(char *archive, struct list *list, char *rlt)
430{
431	FILE	*out;
432
433	/* If rlt != NULL, redirect output to it */
434	out = stdout;
435	if (rlt) {
436		if ((out = fopen(rlt, "w")) == NULL)
437			bsdar_errc(bsdar, errno, "fopen %s failed", rlt);
438	}
439
440	bsdar->filename = archive;
441	if (list)
442		arscp_mlist2argv(list);
443	else {
444		bsdar->argc = 0;
445		bsdar->argv = NULL;
446	}
447	if (verbose)
448		bsdar->options |= AR_V;
449	ar_read_archive(bsdar, 't', out);
450	bsdar->options &= ~AR_V;
451
452	if (rlt) {
453		if (fclose(out) == EOF)
454			bsdar_errc(bsdar, errno, "fclose %s failed", rlt);
455		free(rlt);
456	}
457	free(archive);
458	bsdar->filename = tmpac;
459	arscp_free_argv();
460	arscp_free_mlist(list);
461}
462
463
464/* Replace modules of current archive. */
465static void
466arscp_replace(struct list *list)
467{
468
469	if (!arscp_target_exist())
470		return;
471	arscp_mlist2argv(list);
472	ar_write_archive(bsdar, 'r');
473	arscp_free_argv();
474	arscp_free_mlist(list);
475}
476
477/* Rename the temporary archive to the target archive. */
478static void
479arscp_save(void)
480{
481	mode_t mask;
482
483	if (target) {
484		if (rename(tmpac, target) < 0)
485			bsdar_errc(bsdar, errno, "rename failed");
486		/*
487		 * mkstemp creates temp files with mode 0600, here we
488		 * set target archive mode per process umask.
489		 */
490		mask = umask(0);
491		umask(mask);
492		if (chmod(target, 0666 & ~mask) < 0)
493			bsdar_errc(bsdar, errno, "chmod failed");
494		free(tmpac);
495		free(target);
496		tmpac = NULL;
497		target= NULL;
498		bsdar->filename = NULL;
499	} else
500		bsdar_warnc(bsdar, 0, "no open output archive");
501}
502
503/*
504 * Discard all the contents of current archive. This is achieved by
505 * invoking CREATE cmd on current archive.
506 */
507static void
508arscp_clear(void)
509{
510	char		*new_target;
511
512	if (target) {
513		new_target = strdup(target);
514		if (new_target == NULL)
515			bsdar_errc(bsdar, errno, "strdup failed");
516		arscp_create(NULL, new_target);
517	}
518}
519
520/*
521 * Quit ar(1). Note that END cmd will not SAVE current archive
522 * before exit.
523 */
524static void
525arscp_end(int eval)
526{
527
528	if (target)
529		free(target);
530	if (tmpac) {
531		if (unlink(tmpac) == -1)
532			bsdar_errc(bsdar, errno, "unlink %s failed",
533			    tmpac);
534		free(tmpac);
535	}
536
537	exit(eval);
538}
539
540/*
541 * Check if target specified, i.e, whether OPEN or CREATE has been
542 * issued by user.
543 */
544static int
545arscp_target_exist(void)
546{
547
548	if (target)
549		return (1);
550
551	bsdar_warnc(bsdar, 0, "no open output archive");
552	return (0);
553}
554
555/* Construct module list. */
556static struct list *
557arscp_mlist(struct list *list, char *str)
558{
559	struct list *l;
560
561	l = malloc(sizeof(*l));
562	if (l == NULL)
563		bsdar_errc(bsdar, errno, "malloc failed");
564	l->str = str;
565	l->next = list;
566
567	return (l);
568}
569
570/* Calculate the length of a mlist. */
571static int
572arscp_mlist_len(struct list *list)
573{
574	int len;
575
576	for(len = 0; list; list = list->next)
577		len++;
578
579	return (len);
580}
581
582/* Free the space allocated for mod_list. */
583static void
584arscp_free_mlist(struct list *list)
585{
586	struct list *l;
587
588	/* Note that list->str was freed in arscp_free_argv. */
589	for(; list; list = l) {
590		l = list->next;
591		free(list);
592	}
593}
594
595/* Convert mlist to argv array. */
596static void
597arscp_mlist2argv(struct list *list)
598{
599	char	**argv;
600	int	  i, n;
601
602	n = arscp_mlist_len(list);
603	argv = malloc(n * sizeof(*argv));
604	if (argv == NULL)
605		bsdar_errc(bsdar, errno, "malloc failed");
606
607	/* Note that module names are stored in reverse order in mlist. */
608	for(i = n - 1; i >= 0; i--, list = list->next) {
609		if (list == NULL)
610			bsdar_errc(bsdar, errno, "invalid mlist");
611		argv[i] = list->str;
612	}
613
614	bsdar->argc = n;
615	bsdar->argv = argv;
616}
617
618/* Free space allocated for argv array and its elements. */
619static void
620arscp_free_argv(void)
621{
622	int i;
623
624	for(i = 0; i < bsdar->argc; i++)
625		free(bsdar->argv[i]);
626
627	free(bsdar->argv);
628}
629
630/* Show a prompt if we are in interactive mode */
631static void
632arscp_prompt(void)
633{
634
635	if (interactive) {
636		printf("AR >");
637		fflush(stdout);
638	}
639}
640
641/* Main function for ar script mode. */
642void
643ar_mode_script(struct bsdar *ar)
644{
645
646	bsdar = ar;
647	interactive = isatty(fileno(stdin));
648	while(yyparse()) {
649		if (!interactive)
650			arscp_end(EXIT_FAILURE);
651	}
652
653	/* Script ends without END */
654	arscp_end(EXIT_SUCCESS);
655}
656