Annotation of src/usr.bin/rsync/rules.c, Revision 1.1
1.1 ! claudio 1: #include <err.h>
! 2: #include <stdlib.h>
! 3: #include <stdio.h>
! 4: #include <string.h>
! 5:
! 6: #include "extern.h"
! 7:
! 8: struct rule {
! 9: char *pattern;
! 10: enum rule_type type;
! 11: #ifdef NOTYET
! 12: unsigned int modifiers;
! 13: #endif
! 14: short numseg;
! 15: unsigned char anchored;
! 16: unsigned char fileonly;
! 17: unsigned char nowild;
! 18: unsigned char onlydir;
! 19: unsigned char leadingdir;
! 20: };
! 21:
! 22: static struct rule *rules;
! 23: static size_t numrules; /* number of rules */
! 24: static size_t rulesz; /* available size */
! 25:
! 26: /* up to protocol 29 filter rules only support - + ! and no modifiers */
! 27:
! 28: const struct command {
! 29: enum rule_type type;
! 30: char sopt;
! 31: const char *lopt;
! 32: } commands[] = {
! 33: { RULE_EXCLUDE, '-', "exclude" },
! 34: { RULE_INCLUDE, '+', "include" },
! 35: { RULE_CLEAR, '!', "clear" },
! 36: #ifdef NOTYET
! 37: { RULE_MERGE, '.', "merge" },
! 38: { RULE_DIR_MERGE, ':', "dir-merge" },
! 39: { RULE_SHOW, 'S', "show" },
! 40: { RULE_HIDE, 'H', "hide" },
! 41: { RULE_PROTECT, 'P', "protect" },
! 42: { RULE_RISK, 'R', "risk" },
! 43: #endif
! 44: { 0 }
! 45: };
! 46:
! 47: #ifdef NOTYET
! 48: #define MOD_ABSOLUTE 0x0001
! 49: #define MOD_NEGATE 0x0002
! 50: #define MOD_CVSEXCLUDE 0x0004
! 51: #define MOD_SENDING 0x0008
! 52: #define MOD_RECEIVING 0x0010
! 53: #define MOD_PERISHABLE 0x0020
! 54: #define MOD_XATTR 0x0040
! 55: #define MOD_MERGE_EXCLUDE 0x0080
! 56: #define MOD_MERGE_INCLUDE 0x0100
! 57: #define MOD_MERGE_CVSCOMPAT 0x0200
! 58: #define MOD_MERGE_EXCLUDE_FILE 0x0400
! 59: #define MOD_MERGE_NO_INHERIT 0x0800
! 60: #define MOD_MERGE_WORDSPLIT 0x1000
! 61:
! 62: /* maybe support absolute and negate */
! 63: const struct modifier {
! 64: unsigned int modifier;
! 65: char sopt;
! 66: } modifiers[] = {
! 67: { MOD_ABSOLUTE, '/' },
! 68: { MOD_NEGATE, '!' },
! 69: { MOD_CVSEXCLUDE, 'C' },
! 70: { MOD_SENDING, 's' },
! 71: { MOD_RECEIVING, 'r' },
! 72: { MOD_PERISHABLE, 'p' },
! 73: { MOD_XATTR, 'x' },
! 74: /* for '.' and ':' types */
! 75: { MOD_MERGE_EXCLUDE, '-' },
! 76: { MOD_MERGE_INCLUDE, '+' },
! 77: { MOD_MERGE_CVSCOMPAT, 'C' },
! 78: { MOD_MERGE_EXCLUDE_FILE, 'e' },
! 79: { MOD_MERGE_NO_INHERIT, 'n' },
! 80: { MOD_MERGE_WORDSPLIT, 'w' },
! 81: { 0 }
! 82: }
! 83: #endif
! 84:
! 85: static struct rule *
! 86: get_next_rule(void)
! 87: {
! 88: struct rule *new;
! 89: size_t newsz;
! 90:
! 91: if (++numrules > rulesz) {
! 92: if (rulesz == 0)
! 93: newsz = 16;
! 94: else
! 95: newsz = rulesz * 2;
! 96:
! 97: new = recallocarray(rules, rulesz, newsz, sizeof(*rules));
! 98: if (new == NULL)
! 99: err(ERR_NOMEM, NULL);
! 100:
! 101: rules = new;
! 102: rulesz = newsz;
! 103: }
! 104:
! 105: return rules + numrules - 1;
! 106: }
! 107:
! 108: static enum rule_type
! 109: parse_command(const char *command, size_t len)
! 110: {
! 111: const char *mod;
! 112: size_t i;
! 113:
! 114: mod = memchr(command, ',', len);
! 115: if (mod != NULL) {
! 116: /* XXX modifiers not yet implemented */
! 117: return RULE_NONE;
! 118: }
! 119:
! 120: for (i = 0; commands[i].type != RULE_NONE; i++) {
! 121: if (strncmp(commands[i].lopt, command, len) == 0)
! 122: return commands[i].type;
! 123: if (len == 1 && commands[i].sopt == *command)
! 124: return commands[i].type;
! 125: }
! 126:
! 127: return RULE_NONE;
! 128: }
! 129:
! 130: static void
! 131: parse_pattern(struct rule *r, char *pattern)
! 132: {
! 133: size_t plen;
! 134: char *p;
! 135: short nseg = 1;
! 136:
! 137: /*
! 138: * check for / at start and end of pattern both are special and
! 139: * can bypass full path matching.
! 140: */
! 141: if (*pattern == '/') {
! 142: pattern++;
! 143: r->anchored = 1;
! 144: }
! 145: plen = strlen(pattern);
! 146: /*
! 147: * check for patterns ending in '/' and '/'+'***' and handle them
! 148: * specially. Because of this and the check above pattern will never
! 149: * start or end with a '/'.
! 150: */
! 151: if (plen > 1 && pattern[plen - 1] == '/') {
! 152: r->onlydir = 1;
! 153: pattern[plen - 1] = '\0';
! 154: }
! 155: if (plen > 4 && strcmp(pattern + plen - 4, "/***") == 0) {
! 156: r->leadingdir = 1;
! 157: pattern[plen - 4] = '\0';
! 158: }
! 159:
! 160: /* count how many segments the pattern has. */
! 161: for (p = pattern; *p != '\0'; p++)
! 162: if (*p == '/')
! 163: nseg++;
! 164: r->numseg = nseg;
! 165:
! 166: /* check if this pattern only matches against the basename */
! 167: if (nseg == 1 && !r->anchored)
! 168: r->fileonly = 1;
! 169:
! 170: if (strpbrk(pattern, "*?[") == NULL) {
! 171: /* no wildchar matching */
! 172: r->nowild = 1;
! 173: } else {
! 174: /* requires wildchar matching */
! 175: if (strstr(pattern, "**") != NULL)
! 176: r->numseg = -1;
! 177: }
! 178:
! 179: r->pattern = strdup(pattern);
! 180: if (r->pattern == NULL)
! 181: err(ERR_NOMEM, NULL);
! 182: }
! 183:
! 184: int
! 185: parse_rule(char *line, enum rule_type def)
! 186: {
! 187: enum rule_type type;
! 188: struct rule *r;
! 189: char *pattern;
! 190: size_t len;
! 191:
! 192: switch (*line) {
! 193: case '#':
! 194: case ';':
! 195: /* comment */
! 196: return 0;
! 197: case '\0':
! 198: /* ingore empty lines */
! 199: return 0;
! 200: default:
! 201: len = strcspn(line, " _");
! 202: type = parse_command(line, len);
! 203: if (type == RULE_NONE) {
! 204: if (def == RULE_NONE)
! 205: return -1;
! 206: type = def;
! 207: pattern = line;
! 208: } else
! 209: pattern = line + len + 1;
! 210:
! 211: if (*pattern == '\0' && type != RULE_CLEAR)
! 212: return -1;
! 213: if (*pattern != '\0' && type == RULE_CLEAR)
! 214: return -1;
! 215: break;
! 216: }
! 217:
! 218: r = get_next_rule();
! 219: r->type = type;
! 220: parse_pattern(r, pattern);
! 221:
! 222: return 0;
! 223: }
! 224:
! 225: void
! 226: parse_file(const char *file, enum rule_type def, int delim)
! 227: {
! 228: FILE *fp;
! 229: char *line = NULL;
! 230: size_t linesize = 0, linenum = 0;
! 231: ssize_t linelen;
! 232:
! 233: if ((fp = fopen(file, "r")) == NULL)
! 234: err(ERR_SYNTAX, "open: %s", file);
! 235:
! 236: while ((linelen = getdelim(&line, &linesize, delim, fp)) != -1) {
! 237: linenum++;
! 238: line[linelen - 1] = '\0';
! 239: if (parse_rule(line, def) == -1)
! 240: errx(ERR_SYNTAX, "syntax error in %s at entry %zu",
! 241: file, linenum);
! 242: }
! 243:
! 244: free(line);
! 245: if (ferror(fp))
! 246: err(ERR_SYNTAX, "failed to parse file %s", file);
! 247: fclose(fp);
! 248: }
! 249:
! 250: static const char *
! 251: send_command(struct rule *r)
! 252: {
! 253: static char buf[16];
! 254: char *b = buf;
! 255: char *ep = buf + sizeof(buf);
! 256:
! 257: switch (r->type) {
! 258: case RULE_EXCLUDE:
! 259: *b++ = '-';
! 260: break;
! 261: case RULE_INCLUDE:
! 262: *b++ = '+';
! 263: break;
! 264: case RULE_CLEAR:
! 265: *b++ = '!';
! 266: break;
! 267: #ifdef NOTYET
! 268: case RULE_MERGE:
! 269: *b++ = '.';
! 270: break;
! 271: case RULE_DIR_MERGE:
! 272: *b++ = ':';
! 273: break;
! 274: case RULE_SHOW:
! 275: *b++ = 'S';
! 276: break;
! 277: case RULE_HIDE:
! 278: *b++ = 'H';
! 279: break;
! 280: case RULE_PROTECT:
! 281: *b++ = 'P';
! 282: break;
! 283: case RULE_RISK:
! 284: *b++ = 'R';
! 285: break;
! 286: #endif
! 287: default:
! 288: err(ERR_SYNTAX, "unknown rule type %d", r->type);
! 289: }
! 290:
! 291: #ifdef NOTYET
! 292: for (i = 0; modifiers[i].modifier != 0; i++) {
! 293: if (rule->modifiers & modifiers[i].modifier)
! 294: *b++ = modifiers[i].sopt;
! 295: if (b >= ep - 3)
! 296: err(ERR_SYNTAX, "rule modifiers overflow");
! 297: }
! 298: #endif
! 299: if (b >= ep - 3)
! 300: err(ERR_SYNTAX, "rule prefix overflow");
! 301: *b++ = ' ';
! 302:
! 303: /* include the stripped root '/' for anchored patterns */
! 304: if (r->anchored)
! 305: *b++ = '/';
! 306: *b++ = '\0';
! 307: return buf;
! 308: }
! 309:
! 310: static const char *
! 311: postfix_command(struct rule *r)
! 312: {
! 313: static char buf[8];
! 314:
! 315: buf[0] = '\0';
! 316: if (r->onlydir)
! 317: strlcpy(buf, "/", sizeof(buf));
! 318: if (r->leadingdir)
! 319: strlcpy(buf, "/***", sizeof(buf));
! 320:
! 321: return buf;
! 322: }
! 323:
! 324: void
! 325: send_rules(struct sess *sess, int fd)
! 326: {
! 327: const char *cmd;
! 328: const char *postfix;
! 329: struct rule *r;
! 330: size_t cmdlen, len, postlen, i;
! 331:
! 332: for (i = 0; i < numrules; i++) {
! 333: r = &rules[i];
! 334: cmd = send_command(r);
! 335: if (cmd == NULL)
! 336: err(ERR_PROTOCOL,
! 337: "rules are incompatible with remote rsync");
! 338: postfix = postfix_command(r);
! 339: cmdlen = strlen(cmd);
! 340: len = strlen(r->pattern);
! 341: postlen = strlen(postfix);
! 342:
! 343: if (!io_write_int(sess, fd, cmdlen + len + postlen))
! 344: err(ERR_SOCK_IO, "send rules");
! 345: if (!io_write_buf(sess, fd, cmd, cmdlen))
! 346: err(ERR_SOCK_IO, "send rules");
! 347: if (!io_write_buf(sess, fd, r->pattern, len))
! 348: err(ERR_SOCK_IO, "send rules");
! 349: /* include the '/' stripped by onlydir */
! 350: if (postlen > 0)
! 351: if (!io_write_buf(sess, fd, postfix, postlen))
! 352: err(ERR_SOCK_IO, "send rules");
! 353: }
! 354:
! 355: if (!io_write_int(sess, fd, 0))
! 356: err(ERR_SOCK_IO, "send rules");
! 357: }
! 358:
! 359: void
! 360: recv_rules(struct sess *sess, int fd)
! 361: {
! 362: char line[8192];
! 363: size_t len;
! 364:
! 365: do {
! 366: if (!io_read_size(sess, fd, &len))
! 367: err(ERR_SOCK_IO, "receive rules");
! 368:
! 369: if (len == 0)
! 370: return;
! 371: if (len >= sizeof(line) - 1)
! 372: errx(ERR_SOCK_IO, "received rule too long");
! 373: if (!io_read_buf(sess, fd, line, len))
! 374: err(ERR_SOCK_IO, "receive rules");
! 375: line[len] = '\0';
! 376: if (parse_rule(line, RULE_NONE) == -1)
! 377: errx(ERR_PROTOCOL, "syntax error in received rules");
! 378: } while (1);
! 379: }
! 380:
! 381: static inline int
! 382: rule_matched(struct rule *r)
! 383: {
! 384: /* TODO apply negation once modifiers are added */
! 385:
! 386: if (r->type == RULE_EXCLUDE)
! 387: return -1;
! 388: else
! 389: return 1;
! 390: }
! 391:
! 392: int
! 393: rules_match(const char *path, int isdir)
! 394: {
! 395: const char *basename, *p = NULL;
! 396: struct rule *r;
! 397: size_t i;
! 398:
! 399: basename = strrchr(path, '/');
! 400: if (basename != NULL)
! 401: basename += 1;
! 402: else
! 403: basename = path;
! 404:
! 405: for (i = 0; i < numrules; i++) {
! 406: r = &rules[i];
! 407:
! 408: if (r->onlydir && !isdir)
! 409: continue;
! 410:
! 411: if (r->nowild) {
! 412: /* fileonly and anchored are mutually exclusive */
! 413: if (r->fileonly) {
! 414: if (strcmp(basename, r->pattern) == 0)
! 415: return rule_matched(r);
! 416: } else if (r->anchored) {
! 417: /*
! 418: * assumes that neither path nor pattern
! 419: * start with a '/'.
! 420: */
! 421: if (strcmp(path, r->pattern) == 0)
! 422: return rule_matched(r);
! 423: } else if (r->leadingdir) {
! 424: size_t plen = strlen(r->pattern);
! 425:
! 426: p = strstr(path, r->pattern);
! 427: /*
! 428: * match from start or dir boundary also
! 429: * match to end or to dir boundary
! 430: */
! 431: if (p != NULL && (p == path || p[-1] == '/') &&
! 432: (p[plen] == '\0' || p[plen] == '/'))
! 433: return rule_matched(r);
! 434: } else {
! 435: size_t len = strlen(path);
! 436: size_t plen = strlen(r->pattern);
! 437:
! 438: if (len >= plen && strcmp(path + len - plen,
! 439: r->pattern) == 0) {
! 440: /* match all or start on dir boundary */
! 441: if (len == plen ||
! 442: path[len - plen - 1] == '/')
! 443: return rule_matched(r);
! 444: }
! 445: }
! 446: } else {
! 447: if (r->fileonly) {
! 448: p = basename;
! 449: } else if (r->anchored || r->numseg == -1) {
! 450: /* full path matching */
! 451: p = path;
! 452: } else {
! 453: short nseg = 1;
! 454:
! 455: /* match against the last numseg elements */
! 456: for (p = path; *p != '\0'; p++)
! 457: if (*p == '/')
! 458: nseg++;
! 459: if (nseg < r->numseg) {
! 460: p = NULL;
! 461: } else {
! 462: nseg -= r->numseg;
! 463: for (p = path; *p != '\0' && nseg > 0;
! 464: p++) {
! 465: if (*p == '/')
! 466: nseg--;
! 467: }
! 468: }
! 469: }
! 470:
! 471: if (p != NULL) {
! 472: if (rmatch(r->pattern, p, r->leadingdir) == 0)
! 473: return rule_matched(r);
! 474: }
! 475: }
! 476: }
! 477:
! 478: return 0;
! 479: }