/* * Copyright (c) 2013, 2015, 2016 Jonas 'Sortie' Termansen. * * 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. * * rules.c * Determines whether a given path is included in the filesystem. */ #include #include #include #include #include #include #include #include #include #include #include "rules.h" static struct InclusionRule** rules; static size_t num_rules; static size_t num_rules_allocated; static bool default_inclusion = true; static bool default_inclusion_determined; static char** manifest; static size_t manifest_used; static size_t manifest_length; static const char* SkipCharacters(const char* str, char c) { while ( *str == c) str++; return str; } // /usr/bin/foobar match /usr = true // /usr/bin/foobar match usr = false // ///usr////bin//foobar match //usr// = true // ///usr////bin//foobar match //usr//./evince = false // TODO: Should this support . and .. too? static bool PathMatchesPattern(const char* path, const char* pattern) { bool last_was_slash = false; while ( true ) { if ( !*pattern ) return !*path || last_was_slash; if ( (last_was_slash = *pattern == '/') ) { if ( *path == '/' ) { path = SkipCharacters(path, '/'); pattern = SkipCharacters(pattern, '/'); continue; } return false; } if ( *pattern++ != *path++ ) return false; } } static int search_path(const void* a_ptr, const void* b_ptr) { const char* key = (const char*) a_ptr; char* path = *(char**) b_ptr; return strcmp(key, path); } bool IncludesPath(const char* path) { bool determined = false; bool included = false; for ( size_t i = 0; i < num_rules; i++ ) { struct InclusionRule* rule = rules[i]; if ( !PathMatchesPattern(path, rule->pattern) ) continue; switch ( rules[i]->rule ) { case RULE_INCLUDE: included = true; determined = true; break; case RULE_EXCLUDE: included = false; determined = true; break; } } if ( !determined ) included = default_inclusion; if ( !included ) return false; if ( manifest_used && !bsearch(path, manifest, manifest_used, sizeof(char*), search_path) ) return false; return true; } bool ChangeRulesAmount(size_t new_length) { size_t new_num_rules = new_length < num_rules ? new_length : num_rules; for ( size_t i = new_num_rules; i < num_rules; i++ ) { free(rules[i]->pattern); free(rules[i]); } num_rules = new_num_rules; struct InclusionRule** new_rules = (struct InclusionRule**) malloc(sizeof(struct InclusionRule*) * new_length); for ( size_t i = 0; i < new_length && i < num_rules; i++ ) new_rules[i] = rules[i]; free(rules); rules = new_rules; num_rules_allocated = new_length; return true; } bool AddRule(struct InclusionRule* rule) { if ( num_rules == num_rules_allocated ) { size_t new_length = num_rules_allocated ? 2 * num_rules_allocated : 32; if ( !ChangeRulesAmount(new_length) ) return false; } rules[num_rules++] = rule; return true; } static const char* SkipWhitespace(const char* line) { while ( *line && isspace((unsigned char) *line) ) line++; return line; } static bool IsLineComment(const char* line) { return !*line || *line == '#'; } static const char* IsLineCommand(const char* line, const char* command) { while ( *line && isspace((unsigned char) *line) ) line++; size_t cmdlen = strlen(command); if ( strncmp(line, command, cmdlen) != 0 ) return NULL; if ( line[cmdlen] && !isspace((unsigned char) line[cmdlen]) ) return NULL; while ( line[cmdlen] && isspace((unsigned char) line[cmdlen]) ) cmdlen++; return line + cmdlen; } bool AddRulesFromFile(FILE* fp, const char* fpname) { size_t rules_at_start = num_rules; size_t line_size; size_t line_num = 0; char* mem = NULL; ssize_t line_len; while ( 0 < (line_len = getline(&mem, &line_size, fp)) ) { char* line = mem; line_num++; if ( line[line_len-1] == '\n' ) line[--line_len] = '\0'; line = (char*) SkipWhitespace((char*) line); if ( IsLineComment(line) ) continue; const char* parameter; if ( (parameter = IsLineCommand(line, "default")) ) { bool value; if ( !strcmp(parameter, "true") ) value = true; else if ( !strcmp(parameter, "false") ) value = false; else { warnx("%s:%zu: not a boolean '%s'", fpname, line_num, parameter); goto error_out; } if ( !default_inclusion_determined ) default_inclusion = value, default_inclusion_determined = true; else default_inclusion = default_inclusion || value; } else if ( (parameter = IsLineCommand(line, "exclude")) || (parameter = IsLineCommand(line, "include")) ) { if ( !*parameter ) { warnx("%s:%zu: no parameter given", fpname, line_num); goto error_out; } const char* pattern = parameter; enum InclusionRuleType type = line[0] == 'e' ? RULE_EXCLUDE : RULE_INCLUDE; struct InclusionRule* rule = (struct InclusionRule*) malloc(sizeof(struct InclusionRule)); rule->pattern = strdup(pattern); rule->rule = type; if ( !AddRule(rule) ) goto error_out_errno; } else { warnx("%s:%zu: line not understood: '%s'", fpname, line_num, line); goto error_out; } } if ( ferror(fp) ) { error_out_errno: warn("%s", fpname); error_out: free(mem); ChangeRulesAmount(rules_at_start); return false; } free(mem); return true; } int compare_path(const void* a_ptr, const void* b_ptr) { const char* a = *(const char* const*) a_ptr; const char* b = *(const char* const*) b_ptr; return strcmp(a, b); } bool AddManifestPath(const char* path) { if ( manifest_used == manifest_length ) { size_t new_length = 2 * manifest_length; if ( new_length == 0 ) new_length = 64; size_t new_size = new_length * sizeof(char*); char** new_manifest = (char**) realloc(manifest, new_size); if ( !new_manifest ) { warn("malloc"); return false; } manifest = new_manifest; manifest_length = new_length; } char* copy = strdup(path); if ( !copy ) { warn("malloc"); return false; } manifest[manifest_used++] = copy; return true; } bool AddManifestFromFile(FILE* fp, const char* fpname) { char* line = NULL; size_t line_size = 0; ssize_t line_len; while ( 0 < (line_len = getline(&line, &line_size, fp)) ) { if ( line[line_len-1] == '\n' ) line[--line_len] = '\0'; if ( !AddManifestPath(line) ) return false; } free(line); if ( ferror(fp) ) { warn("%s", fpname); return false; } if ( !AddManifestPath("/") || !AddManifestPath("/tix") || !AddManifestPath("/tix/manifest") ) return false; char* fpname_copy = strdup(fpname); if ( !fpname_copy ) { warn("malloc"); return false; } const char* fpname_basename = basename(fpname_copy); char* manifest_path; if ( asprintf(&manifest_path, "/tix/manifest/%s", fpname_basename) < 0 ) { free(fpname_copy); warn("malloc"); return false; } free(fpname_copy); if ( !AddManifestPath(manifest_path) ) return free(manifest_path), false; free(manifest_path); qsort(manifest, manifest_used, sizeof(char*), compare_path); return true; }