The -s replacement scanner consumes a backslash and then reads the escaped byte. If the replacement ends in one literal backslash, the loop can advance past the terminating NUL and read one byte past the allocated replacement buffer. Stop scanning when a backslash is followed by the terminating NUL. The final literal append then preserves the trailing backslash.
342 lines
7.5 KiB
C
342 lines
7.5 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2008 Joerg Sonnenberger
|
|
* All rights reserved.
|
|
*/
|
|
|
|
#include "bsdtar_platform.h"
|
|
|
|
#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) || defined(HAVE_PCRE2POSIX_H)
|
|
#include "bsdtar.h"
|
|
|
|
#include <errno.h>
|
|
#if defined(HAVE_PCREPOSIX_H)
|
|
#include <pcreposix.h>
|
|
#elif defined(HAVE_PCRE2POSIX_H)
|
|
#include <pcre2posix.h>
|
|
#else
|
|
#include <regex.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifndef REG_BASIC
|
|
#define REG_BASIC 0
|
|
#endif
|
|
|
|
#include "lafe_err.h"
|
|
|
|
struct subst_rule {
|
|
struct subst_rule *next;
|
|
regex_t re;
|
|
char *result;
|
|
unsigned int global:1, print:1, regular:1, symlink:1, hardlink:1, from_begin:1;
|
|
};
|
|
|
|
struct substitution {
|
|
struct subst_rule *first_rule, *last_rule;
|
|
};
|
|
|
|
static void
|
|
init_substitution(struct bsdtar *bsdtar)
|
|
{
|
|
struct substitution *subst;
|
|
|
|
bsdtar->substitution = subst = malloc(sizeof(*subst));
|
|
if (subst == NULL)
|
|
lafe_errc(1, errno, "Out of memory");
|
|
subst->first_rule = subst->last_rule = NULL;
|
|
}
|
|
|
|
void
|
|
add_substitution(struct bsdtar *bsdtar, const char *rule_text)
|
|
{
|
|
struct subst_rule *rule;
|
|
struct substitution *subst;
|
|
const char *end_pattern, *start_subst;
|
|
char *pattern;
|
|
int r;
|
|
|
|
if ((subst = bsdtar->substitution) == NULL) {
|
|
init_substitution(bsdtar);
|
|
subst = bsdtar->substitution;
|
|
}
|
|
|
|
rule = malloc(sizeof(*rule));
|
|
if (rule == NULL)
|
|
lafe_errc(1, errno, "Out of memory");
|
|
rule->next = NULL;
|
|
rule->result = NULL;
|
|
|
|
if (subst->last_rule == NULL)
|
|
subst->first_rule = rule;
|
|
else
|
|
subst->last_rule->next = rule;
|
|
subst->last_rule = rule;
|
|
|
|
const char delim = *rule_text;
|
|
if (delim == '\0')
|
|
lafe_errc(1, 0, "Empty replacement string");
|
|
end_pattern = strchr(rule_text + 1, delim);
|
|
if (end_pattern == NULL)
|
|
lafe_errc(1, 0, "Invalid replacement string \"%s\": "
|
|
"missing closing delimiter '%c' after pattern",
|
|
rule_text, delim);
|
|
|
|
pattern = malloc(end_pattern - rule_text);
|
|
if (pattern == NULL)
|
|
lafe_errc(1, errno, "Out of memory");
|
|
memcpy(pattern, rule_text + 1, end_pattern - rule_text - 1);
|
|
pattern[end_pattern - rule_text - 1] = '\0';
|
|
|
|
if ((r = regcomp(&rule->re, pattern, REG_BASIC)) != 0) {
|
|
char buf[80];
|
|
regerror(r, &rule->re, buf, sizeof(buf));
|
|
lafe_errc(1, 0, "Invalid regular expression: %s", buf);
|
|
}
|
|
free(pattern);
|
|
|
|
start_subst = end_pattern + 1;
|
|
end_pattern = strchr(start_subst, delim);
|
|
if (end_pattern == NULL)
|
|
lafe_errc(1, 0, "Invalid replacement string \"%s\": "
|
|
"missing closing delimiter '%c' after replacement",
|
|
rule_text, delim);
|
|
|
|
rule->result = malloc(end_pattern - start_subst + 1);
|
|
if (rule->result == NULL)
|
|
lafe_errc(1, errno, "Out of memory");
|
|
memcpy(rule->result, start_subst, end_pattern - start_subst);
|
|
rule->result[end_pattern - start_subst] = '\0';
|
|
|
|
/* Defaults */
|
|
rule->global = 0; /* Don't do multiple replacements. */
|
|
rule->print = 0; /* Don't print. */
|
|
rule->regular = 1; /* Rewrite regular filenames. */
|
|
rule->symlink = 1; /* Rewrite symlink targets. */
|
|
rule->hardlink = 1; /* Rewrite hardlink targets. */
|
|
rule->from_begin = 0; /* Don't match from start. */
|
|
|
|
while (*++end_pattern) {
|
|
switch (*end_pattern) {
|
|
case 'b':
|
|
case 'B':
|
|
rule->from_begin = 1;
|
|
break;
|
|
case 'g':
|
|
case 'G':
|
|
rule->global = 1;
|
|
break;
|
|
case 'h':
|
|
rule->hardlink = 1;
|
|
break;
|
|
case 'H':
|
|
rule->hardlink = 0;
|
|
break;
|
|
case 'p':
|
|
case 'P':
|
|
rule->print = 1;
|
|
break;
|
|
case 'r':
|
|
rule->regular = 1;
|
|
break;
|
|
case 'R':
|
|
rule->regular = 0;
|
|
break;
|
|
case 's':
|
|
rule->symlink = 1;
|
|
break;
|
|
case 'S':
|
|
rule->symlink = 0;
|
|
break;
|
|
default:
|
|
lafe_errc(1, 0, "Invalid replacement flag %c", *end_pattern);
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
realloc_strncat(char **str, const char *append, size_t len)
|
|
{
|
|
char *new_str;
|
|
size_t old_len;
|
|
|
|
if (*str == NULL)
|
|
old_len = 0;
|
|
else
|
|
old_len = strlen(*str);
|
|
|
|
new_str = malloc(old_len + len + 1);
|
|
if (new_str == NULL)
|
|
lafe_errc(1, errno, "Out of memory");
|
|
if (*str != NULL)
|
|
memcpy(new_str, *str, old_len);
|
|
memcpy(new_str + old_len, append, len);
|
|
new_str[old_len + len] = '\0';
|
|
free(*str);
|
|
*str = new_str;
|
|
}
|
|
|
|
static void
|
|
realloc_strcat(char **str, const char *append)
|
|
{
|
|
char *new_str;
|
|
size_t old_len;
|
|
|
|
if (*str == NULL)
|
|
old_len = 0;
|
|
else
|
|
old_len = strlen(*str);
|
|
|
|
new_str = malloc(old_len + strlen(append) + 1);
|
|
if (new_str == NULL)
|
|
lafe_errc(1, errno, "Out of memory");
|
|
if (*str != NULL)
|
|
memcpy(new_str, *str, old_len);
|
|
strcpy(new_str + old_len, append);
|
|
free(*str);
|
|
*str = new_str;
|
|
}
|
|
|
|
int
|
|
apply_substitution(struct bsdtar *bsdtar, const char *name, char **result,
|
|
int symlink_target, int hardlink_target)
|
|
{
|
|
const char *path = name;
|
|
regmatch_t matches[10];
|
|
char* buffer = NULL;
|
|
size_t i, j;
|
|
struct subst_rule *rule;
|
|
struct substitution *subst;
|
|
int c, got_match, print_match;
|
|
|
|
*result = NULL;
|
|
|
|
if ((subst = bsdtar->substitution) == NULL)
|
|
return 0;
|
|
|
|
got_match = 0;
|
|
print_match = 0;
|
|
|
|
for (rule = subst->first_rule; rule != NULL; rule = rule->next) {
|
|
if (symlink_target) {
|
|
if (!rule->symlink)
|
|
continue;
|
|
} else if (hardlink_target) {
|
|
if (!rule->hardlink)
|
|
continue;
|
|
} else { /* Regular filename. */
|
|
if (!rule->regular)
|
|
continue;
|
|
}
|
|
|
|
if (rule->from_begin && *result) {
|
|
realloc_strcat(result, name);
|
|
if (buffer) buffer[0] = 0;
|
|
realloc_strcat(&buffer, *result);
|
|
name = buffer;
|
|
(*result)[0] = 0;
|
|
}
|
|
|
|
char isEnd = 0;
|
|
do {
|
|
isEnd = *name == '\0';
|
|
if (regexec(&rule->re, name, 10, matches, 0))
|
|
break;
|
|
|
|
got_match = 1;
|
|
print_match |= rule->print;
|
|
realloc_strncat(result, name, matches[0].rm_so);
|
|
|
|
for (i = 0, j = 0; rule->result[i] != '\0'; ++i) {
|
|
if (rule->result[i] == '~') {
|
|
realloc_strncat(result, rule->result + j, i - j);
|
|
realloc_strncat(result,
|
|
name + matches[0].rm_so,
|
|
matches[0].rm_eo - matches[0].rm_so);
|
|
j = i + 1;
|
|
continue;
|
|
}
|
|
if (rule->result[i] != '\\')
|
|
continue;
|
|
|
|
++i;
|
|
c = rule->result[i];
|
|
if (c == '\0')
|
|
break;
|
|
switch (c) {
|
|
case '~':
|
|
case '\\':
|
|
realloc_strncat(result, rule->result + j, i - j - 1);
|
|
j = i;
|
|
break;
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
realloc_strncat(result, rule->result + j, i - j - 1);
|
|
if ((size_t)(c - '0') > (size_t)(rule->re.re_nsub)) {
|
|
free(buffer);
|
|
free(*result);
|
|
*result = NULL;
|
|
return -1;
|
|
}
|
|
realloc_strncat(result, name + matches[c - '0'].rm_so, matches[c - '0'].rm_eo - matches[c - '0'].rm_so);
|
|
j = i + 1;
|
|
break;
|
|
default:
|
|
/* Just continue; */
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
realloc_strcat(result, rule->result + j);
|
|
if (matches[0].rm_eo > 0) {
|
|
name += matches[0].rm_eo;
|
|
} else if (!isEnd) {
|
|
// We skip a character because the match is 0-length
|
|
// so we need to add it to the output
|
|
realloc_strncat(result, name, 1);
|
|
name += 1;
|
|
}
|
|
} while (rule->global && !isEnd); // Testing one step after because sed et al. run 0-length patterns a last time on the empty string at the end
|
|
}
|
|
|
|
if (got_match)
|
|
realloc_strcat(result, name);
|
|
|
|
free(buffer);
|
|
|
|
if (print_match)
|
|
fprintf(stderr, "%s >> %s\n", path, *result);
|
|
|
|
return got_match;
|
|
}
|
|
|
|
void
|
|
cleanup_substitution(struct bsdtar *bsdtar)
|
|
{
|
|
struct subst_rule *rule;
|
|
struct substitution *subst;
|
|
|
|
if ((subst = bsdtar->substitution) == NULL)
|
|
return;
|
|
|
|
while ((rule = subst->first_rule) != NULL) {
|
|
subst->first_rule = rule->next;
|
|
free(rule->result);
|
|
regfree(&rule->re);
|
|
free(rule);
|
|
}
|
|
free(subst);
|
|
}
|
|
#endif /* defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) || defined(HAVE_PCRE2POSIX_H) */
|