diff --git a/src/swupd_lib/heuristics.c b/src/swupd_lib/heuristics.c index 06807bea..0e6dfa3d 100644 --- a/src/swupd_lib/heuristics.c +++ b/src/swupd_lib/heuristics.c @@ -29,91 +29,124 @@ #include "swupd.h" #include "heuristics.h" -/* trailing slash is to indicate dir itself is expected to exist, but - * contents are ignored */ -static bool is_config(char *filename) +typedef int (*compare_fn_t)(const char *s1, const char *s2); +typedef void (*apply_fn_t)(struct file *f); + +struct rule { + char *str; + compare_fn_t cmp; + apply_fn_t apply; +}; + +static void apply_config(struct file *f) { - if (str_starts_with(filename, "/etc/") == 0) { - return true; - } - return false; + f->is_config = 1; } -static void config_file_heuristics(struct file *file) +static void apply_state(struct file *f) { - if (is_config(file->filename)) { - file->is_config = 1; - } + f->is_state = 1; } -/* trailing slash is to indicate dir itself is expected to exist, but - * contents are ignored */ -static bool is_state(char *filename) +static void apply_boot(struct file *f) +{ + f->is_boot = 1; +} + +static void apply_boot_and_bootmanager(struct file *f) +{ + f->is_boot = 1; + globals.need_update_bootmanager = true; +} + +static void apply_bootmanager(struct file UNUSED_PARAM *f) +{ + globals.need_update_bootmanager = true; +} + +static void apply_systemd(struct file UNUSED_PARAM *f) +{ + globals.need_systemd_reexec = true; +} + +static void apply_src_state(struct file *f) +{ + // /usr/src/debug, /usr/src/kernel and everything inside /usr/src/kernel/ + // directory is not a state + if (str_cmp(f->filename, "/usr/src/debug") == 0 || + str_cmp(f->filename, "/usr/src/kernel") == 0 || + str_starts_with(f->filename, "/usr/src/kernel/") == 0) { + return; + } + + f->is_state = 1; +} + +static int h_starts_with(const char *s1, const char *s2) +{ + return str_starts_with(s1, s2); +} + +static int h_strcmp(const char *s1, const char *s2) +{ + return str_cmp(s1, s2); +} + +static const struct rule heuristic_rules[] = { + // Boot Files + {"/boot/", h_starts_with, apply_boot }, + {"/usr/lib/modules/", h_starts_with, apply_boot }, + + // State files + {"/data", h_starts_with, apply_state }, + {"/dev/", h_starts_with, apply_state }, + {"/home/", h_starts_with, apply_state }, + {"/lost+found", h_starts_with, apply_state }, + {"/proc/", h_starts_with, apply_state }, + {"/root/", h_starts_with, apply_state }, + {"/run/", h_starts_with, apply_state }, + {"/sys/", h_starts_with, apply_state }, + {"/tmp/", h_starts_with, apply_state }, + {"/var/", h_starts_with, apply_state }, + + // Filtered state on /usr/src + {"/usr/src/", h_starts_with, apply_src_state }, + + // Config files + {"/etc/", h_starts_with, apply_config }, + + // Boot managers + {"/usr/bin/bootctl", h_strcmp, apply_bootmanager }, + {"/usr/bin/clr-boot-manager", h_strcmp, apply_bootmanager }, + {"/usr/bin/gummiboot", h_strcmp, apply_bootmanager }, + {"/usr/lib/gummiboot", h_strcmp, apply_bootmanager }, + {"/usr/share/syslinux/ldlinux.c32", h_strcmp, apply_bootmanager }, + + {"/usr/lib/kernel/", h_starts_with, apply_boot_and_bootmanager }, + {"/usr/lib/systemd/boot", h_starts_with, apply_boot_and_bootmanager }, + + // Systemd + {"/usr/lib/systemd/systemd", h_strcmp, apply_systemd}, + + { 0 } +}; + +static bool check_in_mounted_directory(char *filename) { if (is_directory_mounted(filename)) { return true; } - if ((str_len(filename) == 14) && (str_starts_with(filename, "/usr/src/debug") == 0)) { - return false; - } - - /* Compare the first part of the path, first all the entries inside - * kernel directory, then only the kernel directory */ - if ((str_starts_with(filename, "/usr/src/kernel/") == 0) || - ((str_len(filename) == 15) && (str_starts_with(filename, "/usr/src/kernel") == 0))) { - return false; - } - - if ((str_starts_with(filename, "/data") == 0) || - (str_starts_with(filename, "/dev/") == 0) || - (str_starts_with(filename, "/home/") == 0) || - (str_starts_with(filename, "/lost+found") == 0) || - (str_starts_with(filename, "/proc/") == 0) || - (str_starts_with(filename, "/root/") == 0) || - (str_starts_with(filename, "/run/") == 0) || - (str_starts_with(filename, "/sys/") == 0) || - (str_starts_with(filename, "/tmp/") == 0) || - (str_starts_with(filename, "/usr/src/") == 0) || - (str_starts_with(filename, "/var/") == 0)) { - return true; - } return false; } static void runtime_state_heuristics(struct file *file) { - if (is_state(file->filename)) { + if (check_in_mounted_directory(file->filename)) { file->is_state = 1; } } -static void boot_file_heuristics(struct file *file) -{ - if ((str_starts_with(file->filename, "/boot/") == 0) || - (str_starts_with(file->filename, "/usr/lib/modules/") == 0)) { - file->is_boot = 1; - } - - if (str_starts_with(file->filename, "/usr/lib/kernel/") == 0 || - str_starts_with(file->filename, "/usr/lib/systemd/boot") == 0) { - file->is_boot = 1; - globals.need_update_bootmanager = true; - } - - if (str_cmp(file->filename, "/usr/lib/systemd/systemd") == 0) { - globals.need_systemd_reexec = true; - } - - if (str_cmp(file->filename, "/usr/lib/gummiboot") == 0 || - str_cmp(file->filename, "/usr/bin/gummiboot") == 0 || - str_cmp(file->filename, "/usr/bin/bootctl") == 0 || - str_cmp(file->filename, "/usr/bin/clr-boot-manager") == 0 || - str_cmp(file->filename, "/usr/share/syslinux/ldlinux.c32") == 0) { - globals.need_update_bootmanager = true; - } -} - /* Determines whether or not FILE should be ignored for this swupd action. Note * that boot files are ignored only if they are marked as deleted; this does * not happen in current manifests produced by swupd-server, but this check is @@ -131,21 +164,50 @@ static void check_ignore_file(struct file *file) } } -static void apply_heuristics_for_file(struct file *file) +static struct rule *dup_rule(const struct rule *r) { + struct rule * rule; + + rule = malloc_or_die(sizeof(struct rule)); + + *rule = *r; + + return rule; +} + +static void apply_heuristics_for_file(struct list *rules, struct file *file) +{ + struct list *iter; + + // Apply all rules + for (iter = rules; iter; iter = iter->next) { + const struct rule *rule = iter->data; + if (rule->cmp(file->filename, rule->str) == 0) { + rule->apply(file); + } + } + runtime_state_heuristics(file); - boot_file_heuristics(file); - config_file_heuristics(file); check_ignore_file(file); } void heuristics_apply(struct list *files) { + struct list *rules = NULL; struct list *iter; struct file *file; + int i; + // Create the rules list + for (i = 0; heuristic_rules[i].str; i++) { + rules = list_prepend_data(rules, dup_rule(&heuristic_rules[i])); + } + + // Apply the rules for (iter = files; iter; iter = iter->next) { file = iter->data; - apply_heuristics_for_file(file); + apply_heuristics_for_file(rules, file); } + + list_free_list_and_data(rules, free); } diff --git a/test/unit/test_heuristics.c b/test/unit/test_heuristics.c index 32f9e9cd..06da41c7 100644 --- a/test/unit/test_heuristics.c +++ b/test/unit/test_heuristics.c @@ -103,6 +103,7 @@ void test_heuristics() int i; + // Test heuristics on files one by one for (i = 0; test_files[i].filename; i++) { struct list *files; struct file *f; @@ -123,6 +124,32 @@ void test_heuristics() list_free_list_and_data(files, free); } + + + // Test if no heuristic is ignore when running on a list of all files + struct list *files = NULL; + for (i = 0; test_files[i].filename; i++) { + struct file *f; + + f = new_file(strdup_or_die(test_files[i].filename)); + files = list_prepend_data(files, f); + } + files = list_sort(files, cmp_file_filename_is_deleted); + heuristics_apply(files); + for (i = 0; test_files[i].filename; i++) { + struct list *iter; + for (iter = files; iter; iter = iter->next) { + struct file *f = iter->data; + if (str_cmp(f->filename, test_files[i].filename) == 0) { + check(f->is_boot == test_files[i].is_boot); + check(f->is_state == test_files[i].is_state); + check(f->is_config == test_files[i].is_config); + check(f->do_not_update == test_files[i].do_not_update); + break; + } + } + } + list_free_list_and_data(files, free_file_data); } int main() {