1228753Smm/*-
2228753Smm * Copyright (c) 2008 Joerg Sonnenberger
3228753Smm * All rights reserved.
4228753Smm *
5228753Smm * Redistribution and use in source and binary forms, with or without
6228753Smm * modification, are permitted provided that the following conditions
7228753Smm * are met:
8228753Smm * 1. Redistributions of source code must retain the above copyright
9228753Smm *    notice, this list of conditions and the following disclaimer.
10228753Smm * 2. Redistributions in binary form must reproduce the above copyright
11228753Smm *    notice, this list of conditions and the following disclaimer in the
12228753Smm *    documentation and/or other materials provided with the distribution.
13228753Smm *
14228753Smm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15228753Smm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16228753Smm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17228753Smm * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18228753Smm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19228753Smm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20228753Smm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21228753Smm * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22228753Smm * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23228753Smm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24228753Smm */
25228753Smm
26228753Smm#include "bsdtar_platform.h"
27228763Smm__FBSDID("$FreeBSD$");
28228753Smm
29248616Smm#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H)
30228753Smm#include "bsdtar.h"
31228753Smm
32228753Smm#include <errno.h>
33248616Smm#ifdef HAVE_PCREPOSIX_H
34248616Smm#include <pcreposix.h>
35248616Smm#else
36228753Smm#include <regex.h>
37248616Smm#endif
38228753Smm#include <stdlib.h>
39228753Smm#include <string.h>
40228753Smm
41228753Smm#ifndef REG_BASIC
42228753Smm#define	REG_BASIC 0
43228753Smm#endif
44228753Smm
45228753Smm#include "err.h"
46228753Smm
47228753Smmstruct subst_rule {
48228753Smm	struct subst_rule *next;
49228753Smm	regex_t re;
50228753Smm	char *result;
51232153Smm	unsigned int global:1, print:1, regular:1, symlink:1, hardlink:1;
52228753Smm};
53228753Smm
54228753Smmstruct substitution {
55228753Smm	struct subst_rule *first_rule, *last_rule;
56228753Smm};
57228753Smm
58228753Smmstatic void
59228753Smminit_substitution(struct bsdtar *bsdtar)
60228753Smm{
61228753Smm	struct substitution *subst;
62228753Smm
63228753Smm	bsdtar->substitution = subst = malloc(sizeof(*subst));
64228753Smm	if (subst == NULL)
65228753Smm		lafe_errc(1, errno, "Out of memory");
66228753Smm	subst->first_rule = subst->last_rule = NULL;
67228753Smm}
68228753Smm
69228753Smmvoid
70228753Smmadd_substitution(struct bsdtar *bsdtar, const char *rule_text)
71228753Smm{
72228753Smm	struct subst_rule *rule;
73228753Smm	struct substitution *subst;
74228753Smm	const char *end_pattern, *start_subst;
75228753Smm	char *pattern;
76228753Smm	int r;
77228753Smm
78228753Smm	if ((subst = bsdtar->substitution) == NULL) {
79228753Smm		init_substitution(bsdtar);
80228753Smm		subst = bsdtar->substitution;
81228753Smm	}
82228753Smm
83228753Smm	rule = malloc(sizeof(*rule));
84228753Smm	if (rule == NULL)
85228753Smm		lafe_errc(1, errno, "Out of memory");
86228753Smm	rule->next = NULL;
87228753Smm
88228753Smm	if (subst->last_rule == NULL)
89228753Smm		subst->first_rule = rule;
90228753Smm	else
91228753Smm		subst->last_rule->next = rule;
92228753Smm	subst->last_rule = rule;
93228753Smm
94228753Smm	if (*rule_text == '\0')
95228753Smm		lafe_errc(1, 0, "Empty replacement string");
96228753Smm	end_pattern = strchr(rule_text + 1, *rule_text);
97228753Smm	if (end_pattern == NULL)
98228753Smm		lafe_errc(1, 0, "Invalid replacement string");
99228753Smm
100228753Smm	pattern = malloc(end_pattern - rule_text);
101228753Smm	if (pattern == NULL)
102228753Smm		lafe_errc(1, errno, "Out of memory");
103228753Smm	memcpy(pattern, rule_text + 1, end_pattern - rule_text - 1);
104228753Smm	pattern[end_pattern - rule_text - 1] = '\0';
105228753Smm
106228753Smm	if ((r = regcomp(&rule->re, pattern, REG_BASIC)) != 0) {
107228753Smm		char buf[80];
108228753Smm		regerror(r, &rule->re, buf, sizeof(buf));
109228753Smm		lafe_errc(1, 0, "Invalid regular expression: %s", buf);
110228753Smm	}
111228753Smm	free(pattern);
112228753Smm
113228753Smm	start_subst = end_pattern + 1;
114228753Smm	end_pattern = strchr(start_subst, *rule_text);
115228753Smm	if (end_pattern == NULL)
116228753Smm		lafe_errc(1, 0, "Invalid replacement string");
117228753Smm
118228753Smm	rule->result = malloc(end_pattern - start_subst + 1);
119228753Smm	if (rule->result == NULL)
120228753Smm		lafe_errc(1, errno, "Out of memory");
121228753Smm	memcpy(rule->result, start_subst, end_pattern - start_subst);
122228753Smm	rule->result[end_pattern - start_subst] = '\0';
123228753Smm
124232153Smm	/* Defaults */
125232153Smm	rule->global = 0; /* Don't do multiple replacements. */
126232153Smm	rule->print = 0; /* Don't print. */
127232153Smm	rule->regular = 1; /* Rewrite regular filenames. */
128232153Smm	rule->symlink = 1; /* Rewrite symlink targets. */
129232153Smm	rule->hardlink = 1; /* Rewrite hardlink targets. */
130228753Smm
131228753Smm	while (*++end_pattern) {
132228753Smm		switch (*end_pattern) {
133228753Smm		case 'g':
134228753Smm		case 'G':
135228753Smm			rule->global = 1;
136228753Smm			break;
137232153Smm		case 'h':
138232153Smm			rule->hardlink = 1;
139232153Smm			break;
140232153Smm		case 'H':
141232153Smm			rule->hardlink = 0;
142232153Smm			break;
143228753Smm		case 'p':
144228753Smm		case 'P':
145228753Smm			rule->print = 1;
146228753Smm			break;
147232153Smm		case 'r':
148232153Smm			rule->regular = 1;
149232153Smm			break;
150232153Smm		case 'R':
151232153Smm			rule->regular = 0;
152232153Smm			break;
153228753Smm		case 's':
154228753Smm			rule->symlink = 1;
155228753Smm			break;
156232153Smm		case 'S':
157232153Smm			rule->symlink = 0;
158232153Smm			break;
159228753Smm		default:
160228753Smm			lafe_errc(1, 0, "Invalid replacement flag %c", *end_pattern);
161228753Smm		}
162228753Smm	}
163228753Smm}
164228753Smm
165228753Smmstatic void
166228753Smmrealloc_strncat(char **str, const char *append, size_t len)
167228753Smm{
168228753Smm	char *new_str;
169228753Smm	size_t old_len;
170228753Smm
171228753Smm	if (*str == NULL)
172228753Smm		old_len = 0;
173228753Smm	else
174228753Smm		old_len = strlen(*str);
175228753Smm
176228753Smm	new_str = malloc(old_len + len + 1);
177228753Smm	if (new_str == NULL)
178228753Smm		lafe_errc(1, errno, "Out of memory");
179232153Smm	if (*str != NULL)
180232153Smm		memcpy(new_str, *str, old_len);
181228753Smm	memcpy(new_str + old_len, append, len);
182228753Smm	new_str[old_len + len] = '\0';
183228753Smm	free(*str);
184228753Smm	*str = new_str;
185228753Smm}
186228753Smm
187228753Smmstatic void
188228753Smmrealloc_strcat(char **str, const char *append)
189228753Smm{
190228753Smm	char *new_str;
191228753Smm	size_t old_len;
192228753Smm
193228753Smm	if (*str == NULL)
194228753Smm		old_len = 0;
195228753Smm	else
196228753Smm		old_len = strlen(*str);
197228753Smm
198228753Smm	new_str = malloc(old_len + strlen(append) + 1);
199228753Smm	if (new_str == NULL)
200228753Smm		lafe_errc(1, errno, "Out of memory");
201232153Smm	if (*str != NULL)
202232153Smm		memcpy(new_str, *str, old_len);
203228753Smm	strcpy(new_str + old_len, append);
204228753Smm	free(*str);
205228753Smm	*str = new_str;
206228753Smm}
207228753Smm
208228753Smmint
209232153Smmapply_substitution(struct bsdtar *bsdtar, const char *name, char **result,
210232153Smm    int symlink_target, int hardlink_target)
211228753Smm{
212228753Smm	const char *path = name;
213228753Smm	regmatch_t matches[10];
214228753Smm	size_t i, j;
215228753Smm	struct subst_rule *rule;
216228753Smm	struct substitution *subst;
217228753Smm	int c, got_match, print_match;
218228753Smm
219228753Smm	*result = NULL;
220228753Smm
221228753Smm	if ((subst = bsdtar->substitution) == NULL)
222228753Smm		return 0;
223228753Smm
224228753Smm	got_match = 0;
225228753Smm	print_match = 0;
226228753Smm
227228753Smm	for (rule = subst->first_rule; rule != NULL; rule = rule->next) {
228232153Smm		if (symlink_target) {
229232153Smm			if (!rule->symlink)
230232153Smm				continue;
231232153Smm		} else if (hardlink_target) {
232232153Smm			if (!rule->hardlink)
233232153Smm				continue;
234232153Smm		} else { /* Regular filename. */
235232153Smm			if (!rule->regular)
236232153Smm				continue;
237232153Smm		}
238232153Smm
239228753Smm		if (regexec(&rule->re, name, 10, matches, 0))
240228753Smm			continue;
241228753Smm
242228753Smm		got_match = 1;
243228753Smm		print_match |= rule->print;
244228753Smm		realloc_strncat(result, name, matches[0].rm_so);
245228753Smm
246228753Smm		for (i = 0, j = 0; rule->result[i] != '\0'; ++i) {
247228753Smm			if (rule->result[i] == '~') {
248228753Smm				realloc_strncat(result, rule->result + j, i - j);
249232153Smm				realloc_strncat(result,
250232153Smm				    name + matches[0].rm_so,
251232153Smm				    matches[0].rm_eo - matches[0].rm_so);
252228753Smm				j = i + 1;
253228753Smm				continue;
254228753Smm			}
255228753Smm			if (rule->result[i] != '\\')
256228753Smm				continue;
257228753Smm
258228753Smm			++i;
259228753Smm			c = rule->result[i];
260228753Smm			switch (c) {
261228753Smm			case '~':
262228753Smm			case '\\':
263228753Smm				realloc_strncat(result, rule->result + j, i - j - 1);
264228753Smm				j = i;
265228753Smm				break;
266228753Smm			case '1':
267228753Smm			case '2':
268228753Smm			case '3':
269228753Smm			case '4':
270228753Smm			case '5':
271228753Smm			case '6':
272228753Smm			case '7':
273228753Smm			case '8':
274228753Smm			case '9':
275228753Smm				realloc_strncat(result, rule->result + j, i - j - 1);
276228753Smm				if ((size_t)(c - '0') > (size_t)(rule->re.re_nsub)) {
277228753Smm					free(*result);
278228753Smm					*result = NULL;
279228753Smm					return -1;
280228753Smm				}
281228753Smm				realloc_strncat(result, name + matches[c - '0'].rm_so, matches[c - '0'].rm_eo - matches[c - '0'].rm_so);
282228753Smm				j = i + 1;
283228753Smm				break;
284228753Smm			default:
285228753Smm				/* Just continue; */
286228753Smm				break;
287228753Smm			}
288228753Smm
289228753Smm		}
290228753Smm
291228753Smm		realloc_strcat(result, rule->result + j);
292228753Smm
293228753Smm		name += matches[0].rm_eo;
294228753Smm
295228753Smm		if (!rule->global)
296228753Smm			break;
297228753Smm	}
298228753Smm
299228753Smm	if (got_match)
300228753Smm		realloc_strcat(result, name);
301228753Smm
302228753Smm	if (print_match)
303228753Smm		fprintf(stderr, "%s >> %s\n", path, *result);
304228753Smm
305228753Smm	return got_match;
306228753Smm}
307228753Smm
308228753Smmvoid
309228753Smmcleanup_substitution(struct bsdtar *bsdtar)
310228753Smm{
311228753Smm	struct subst_rule *rule;
312228753Smm	struct substitution *subst;
313228753Smm
314228753Smm	if ((subst = bsdtar->substitution) == NULL)
315228753Smm		return;
316228753Smm
317228753Smm	while ((rule = subst->first_rule) != NULL) {
318228753Smm		subst->first_rule = rule->next;
319228753Smm		free(rule->result);
320228753Smm		free(rule);
321228753Smm	}
322228753Smm	free(subst);
323228753Smm}
324248616Smm#endif /* defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) */
325