File: [local] / src / usr.bin / rsync / rules.c (download)
Revision 1.5, Mon Dec 26 19:16:02 2022 UTC (16 months, 3 weeks ago) by jmc
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD Changes since 1.4: +2 -2 lines
spelling fixes; from paul tagliamonte
amendments to his diff are noted on tech
|
/* $OpenBSD: rules.c,v 1.5 2022/12/26 19:16:02 jmc Exp $ */
/*
* Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "extern.h"
struct rule {
char *pattern;
enum rule_type type;
#ifdef NOTYET
unsigned int modifiers;
#endif
short numseg;
unsigned char anchored;
unsigned char fileonly;
unsigned char nowild;
unsigned char onlydir;
unsigned char leadingdir;
};
static struct rule *rules;
static size_t numrules; /* number of rules */
static size_t rulesz; /* available size */
/* up to protocol 29 filter rules only support - + ! and no modifiers */
const struct command {
enum rule_type type;
char sopt;
const char *lopt;
} commands[] = {
{ RULE_EXCLUDE, '-', "exclude" },
{ RULE_INCLUDE, '+', "include" },
{ RULE_CLEAR, '!', "clear" },
#ifdef NOTYET
{ RULE_MERGE, '.', "merge" },
{ RULE_DIR_MERGE, ':', "dir-merge" },
{ RULE_SHOW, 'S', "show" },
{ RULE_HIDE, 'H', "hide" },
{ RULE_PROTECT, 'P', "protect" },
{ RULE_RISK, 'R', "risk" },
#endif
{ 0 }
};
#ifdef NOTYET
#define MOD_ABSOLUTE 0x0001
#define MOD_NEGATE 0x0002
#define MOD_CVSEXCLUDE 0x0004
#define MOD_SENDING 0x0008
#define MOD_RECEIVING 0x0010
#define MOD_PERISHABLE 0x0020
#define MOD_XATTR 0x0040
#define MOD_MERGE_EXCLUDE 0x0080
#define MOD_MERGE_INCLUDE 0x0100
#define MOD_MERGE_CVSCOMPAT 0x0200
#define MOD_MERGE_EXCLUDE_FILE 0x0400
#define MOD_MERGE_NO_INHERIT 0x0800
#define MOD_MERGE_WORDSPLIT 0x1000
/* maybe support absolute and negate */
const struct modifier {
unsigned int modifier;
char sopt;
} modifiers[] = {
{ MOD_ABSOLUTE, '/' },
{ MOD_NEGATE, '!' },
{ MOD_CVSEXCLUDE, 'C' },
{ MOD_SENDING, 's' },
{ MOD_RECEIVING, 'r' },
{ MOD_PERISHABLE, 'p' },
{ MOD_XATTR, 'x' },
/* for '.' and ':' types */
{ MOD_MERGE_EXCLUDE, '-' },
{ MOD_MERGE_INCLUDE, '+' },
{ MOD_MERGE_CVSCOMPAT, 'C' },
{ MOD_MERGE_EXCLUDE_FILE, 'e' },
{ MOD_MERGE_NO_INHERIT, 'n' },
{ MOD_MERGE_WORDSPLIT, 'w' },
{ 0 }
}
#endif
static struct rule *
get_next_rule(void)
{
struct rule *new;
size_t newsz;
if (++numrules > rulesz) {
if (rulesz == 0)
newsz = 16;
else
newsz = rulesz * 2;
new = recallocarray(rules, rulesz, newsz, sizeof(*rules));
if (new == NULL)
err(ERR_NOMEM, NULL);
rules = new;
rulesz = newsz;
}
return rules + numrules - 1;
}
static enum rule_type
parse_command(const char *command, size_t len)
{
const char *mod;
size_t i;
mod = memchr(command, ',', len);
if (mod != NULL) {
/* XXX modifiers not yet implemented */
return RULE_NONE;
}
for (i = 0; commands[i].type != RULE_NONE; i++) {
if (strncmp(commands[i].lopt, command, len) == 0)
return commands[i].type;
if (len == 1 && commands[i].sopt == *command)
return commands[i].type;
}
return RULE_NONE;
}
static void
parse_pattern(struct rule *r, char *pattern)
{
size_t plen;
char *p;
short nseg = 1;
/*
* check for / at start and end of pattern both are special and
* can bypass full path matching.
*/
if (*pattern == '/') {
pattern++;
r->anchored = 1;
}
plen = strlen(pattern);
/*
* check for patterns ending in '/' and '/'+'***' and handle them
* specially. Because of this and the check above pattern will never
* start or end with a '/'.
*/
if (plen > 1 && pattern[plen - 1] == '/') {
r->onlydir = 1;
pattern[plen - 1] = '\0';
}
if (plen > 4 && strcmp(pattern + plen - 4, "/***") == 0) {
r->leadingdir = 1;
pattern[plen - 4] = '\0';
}
/* count how many segments the pattern has. */
for (p = pattern; *p != '\0'; p++)
if (*p == '/')
nseg++;
r->numseg = nseg;
/* check if this pattern only matches against the basename */
if (nseg == 1 && !r->anchored)
r->fileonly = 1;
if (strpbrk(pattern, "*?[") == NULL) {
/* no wildchar matching */
r->nowild = 1;
} else {
/* requires wildchar matching */
if (strstr(pattern, "**") != NULL)
r->numseg = -1;
}
r->pattern = strdup(pattern);
if (r->pattern == NULL)
err(ERR_NOMEM, NULL);
}
int
parse_rule(char *line, enum rule_type def)
{
enum rule_type type;
struct rule *r;
char *pattern;
size_t len;
switch (*line) {
case '#':
case ';':
/* comment */
return 0;
case '\0':
/* ignore empty lines */
return 0;
default:
len = strcspn(line, " _");
type = parse_command(line, len);
if (type == RULE_NONE) {
if (def == RULE_NONE)
return -1;
type = def;
pattern = line;
} else
pattern = line + len + 1;
if (*pattern == '\0' && type != RULE_CLEAR)
return -1;
if (*pattern != '\0' && type == RULE_CLEAR)
return -1;
break;
}
r = get_next_rule();
r->type = type;
parse_pattern(r, pattern);
return 0;
}
void
parse_file(const char *file, enum rule_type def)
{
FILE *fp;
char *line = NULL;
size_t linesize = 0, linenum = 0;
ssize_t linelen;
if ((fp = fopen(file, "r")) == NULL)
err(ERR_SYNTAX, "open: %s", file);
while ((linelen = getline(&line, &linesize, fp)) != -1) {
linenum++;
line[linelen - 1] = '\0';
if (parse_rule(line, def) == -1)
errx(ERR_SYNTAX, "syntax error in %s at entry %zu",
file, linenum);
}
free(line);
if (ferror(fp))
err(ERR_SYNTAX, "failed to parse file %s", file);
fclose(fp);
}
static const char *
send_command(struct rule *r)
{
static char buf[16];
char *b = buf;
char *ep = buf + sizeof(buf);
switch (r->type) {
case RULE_EXCLUDE:
*b++ = '-';
break;
case RULE_INCLUDE:
*b++ = '+';
break;
case RULE_CLEAR:
*b++ = '!';
break;
#ifdef NOTYET
case RULE_MERGE:
*b++ = '.';
break;
case RULE_DIR_MERGE:
*b++ = ':';
break;
case RULE_SHOW:
*b++ = 'S';
break;
case RULE_HIDE:
*b++ = 'H';
break;
case RULE_PROTECT:
*b++ = 'P';
break;
case RULE_RISK:
*b++ = 'R';
break;
#endif
default:
err(ERR_SYNTAX, "unknown rule type %d", r->type);
}
#ifdef NOTYET
for (i = 0; modifiers[i].modifier != 0; i++) {
if (rule->modifiers & modifiers[i].modifier)
*b++ = modifiers[i].sopt;
if (b >= ep - 3)
err(ERR_SYNTAX, "rule modifiers overflow");
}
#endif
if (b >= ep - 3)
err(ERR_SYNTAX, "rule prefix overflow");
*b++ = ' ';
/* include the stripped root '/' for anchored patterns */
if (r->anchored)
*b++ = '/';
*b++ = '\0';
return buf;
}
static const char *
postfix_command(struct rule *r)
{
static char buf[8];
buf[0] = '\0';
if (r->onlydir)
strlcpy(buf, "/", sizeof(buf));
if (r->leadingdir)
strlcpy(buf, "/***", sizeof(buf));
return buf;
}
void
send_rules(struct sess *sess, int fd)
{
const char *cmd;
const char *postfix;
struct rule *r;
size_t cmdlen, len, postlen, i;
for (i = 0; i < numrules; i++) {
r = &rules[i];
cmd = send_command(r);
if (cmd == NULL)
err(ERR_PROTOCOL,
"rules are incompatible with remote rsync");
postfix = postfix_command(r);
cmdlen = strlen(cmd);
len = strlen(r->pattern);
postlen = strlen(postfix);
if (!io_write_int(sess, fd, cmdlen + len + postlen))
err(ERR_SOCK_IO, "send rules");
if (!io_write_buf(sess, fd, cmd, cmdlen))
err(ERR_SOCK_IO, "send rules");
if (!io_write_buf(sess, fd, r->pattern, len))
err(ERR_SOCK_IO, "send rules");
/* include the '/' stripped by onlydir */
if (postlen > 0)
if (!io_write_buf(sess, fd, postfix, postlen))
err(ERR_SOCK_IO, "send rules");
}
if (!io_write_int(sess, fd, 0))
err(ERR_SOCK_IO, "send rules");
}
void
recv_rules(struct sess *sess, int fd)
{
char line[8192];
size_t len;
do {
if (!io_read_size(sess, fd, &len))
err(ERR_SOCK_IO, "receive rules");
if (len == 0)
return;
if (len >= sizeof(line) - 1)
errx(ERR_SOCK_IO, "received rule too long");
if (!io_read_buf(sess, fd, line, len))
err(ERR_SOCK_IO, "receive rules");
line[len] = '\0';
if (parse_rule(line, RULE_NONE) == -1)
errx(ERR_PROTOCOL, "syntax error in received rules");
} while (1);
}
static inline int
rule_matched(struct rule *r)
{
/* TODO apply negation once modifiers are added */
if (r->type == RULE_EXCLUDE)
return -1;
else
return 1;
}
int
rules_match(const char *path, int isdir)
{
const char *basename, *p = NULL;
struct rule *r;
size_t i;
basename = strrchr(path, '/');
if (basename != NULL)
basename += 1;
else
basename = path;
for (i = 0; i < numrules; i++) {
r = &rules[i];
if (r->onlydir && !isdir)
continue;
if (r->nowild) {
/* fileonly and anchored are mutually exclusive */
if (r->fileonly) {
if (strcmp(basename, r->pattern) == 0)
return rule_matched(r);
} else if (r->anchored) {
/*
* assumes that neither path nor pattern
* start with a '/'.
*/
if (strcmp(path, r->pattern) == 0)
return rule_matched(r);
} else if (r->leadingdir) {
size_t plen = strlen(r->pattern);
p = strstr(path, r->pattern);
/*
* match from start or dir boundary also
* match to end or to dir boundary
*/
if (p != NULL && (p == path || p[-1] == '/') &&
(p[plen] == '\0' || p[plen] == '/'))
return rule_matched(r);
} else {
size_t len = strlen(path);
size_t plen = strlen(r->pattern);
if (len >= plen && strcmp(path + len - plen,
r->pattern) == 0) {
/* match all or start on dir boundary */
if (len == plen ||
path[len - plen - 1] == '/')
return rule_matched(r);
}
}
} else {
if (r->fileonly) {
p = basename;
} else if (r->anchored || r->numseg == -1) {
/* full path matching */
p = path;
} else {
short nseg = 1;
/* match against the last numseg elements */
for (p = path; *p != '\0'; p++)
if (*p == '/')
nseg++;
if (nseg < r->numseg) {
p = NULL;
} else {
nseg -= r->numseg;
for (p = path; *p != '\0' && nseg > 0;
p++) {
if (*p == '/')
nseg--;
}
}
}
if (p != NULL) {
if (rmatch(r->pattern, p, r->leadingdir) == 0)
return rule_matched(r);
}
}
}
return 0;
}