/* This file is part of "reprepro" * Copyright (C) 2005 Bernhard R. Link * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA */ #include #include #include #include #include #include #include #include #include "error.h" #include "mprintf.h" #include "strlist.h" #include "names.h" #include "configparser.h" #include "filterlist.h" static struct filterlistfile { size_t reference_count; char *filename; size_t filename_len; /*@owned@*//*@null@*/ struct filterlistitem *root; /*@dependent@*//*@null@*/ const struct filterlistitem *last; /*@owned@*//*@null@*/ struct filterlistfile *next; } *listfiles = NULL; struct filterlistitem { /*@owned@*//*@null@*/ struct filterlistitem *next; char *packagename; char *version; enum filterlisttype what; }; static void filterlistitems_free(/*@null@*//*@only@*/struct filterlistitem *list) { while (list != NULL) { struct filterlistitem *next = list->next; free(list->version); free(list->packagename); free(list); list = next; } } static void filterlistfile_unlock(struct filterlistfile *list) { assert (list != NULL); if (list->reference_count <= 1) { struct filterlistfile **p = &listfiles; assert (list->reference_count == 1); if (list->reference_count == 0) return; while (*p != NULL && *p != list) p = &(*p)->next; assert (p != NULL); if (*p == list) { *p = list->next; filterlistitems_free(list->root); free(list->filename); free(list); } } else list->reference_count--; } static inline retvalue filterlistfile_parse(struct filterlistfile *n, const char *filename, FILE *f) { char *lineend, *namestart, *nameend, *what, *version; int cmp; enum filterlisttype type; struct filterlistitem *h; char line[1001]; int lineno = 0; struct filterlistitem **last = &n->root; while (fgets(line, 1000, f) != NULL) { lineno++; lineend = strchr(line, '\n'); if (lineend == NULL) { fprintf(stderr, "Overlong or unterminated line in '%s'!\n", filename); return RET_ERROR; } while (lineend >= line && xisspace(*lineend)) *(lineend--) = '\0'; /* Ignore line only containing whitespace */ if (line[0] == '\0') continue; /* Ignore lines starting with a comment sign */ if (line[0] == '#') continue; namestart = line; while (*namestart != '\0' && xisspace(*namestart)) namestart++; nameend=namestart; while (*nameend != '\0' && !xisspace(*nameend)) nameend++; what = nameend; while (*what != '\0' && xisspace(*what)) *(what++)='\0'; if (*what == '\0') { fprintf(stderr, "Malformed line in '%s': %d!\n", filename, lineno); return RET_ERROR; } version = NULL; if (strcmp(what, "install") == 0) { type = flt_install; } else if (strcmp(what, "deinstall") == 0) { type = flt_deinstall; } else if (strcmp(what, "purge") == 0) { type = flt_purge; } else if (strcmp(what, "hold") == 0) { type = flt_hold; } else if (strcmp(what, "supersede") == 0) { type = flt_supersede; } else if (strcmp(what, "upgradeonly") == 0) { type = flt_upgradeonly; } else if (strcmp(what, "warning") == 0) { type = flt_warning; } else if (strcmp(what, "error") == 0) { type = flt_error; } else if (what[0] == '=') { what++; while (*what != '\0' && xisspace(*what)) what++; version = what; if (*version == '\0') { fprintf(stderr, "Malformed line %d in '%s': missing version after '='!\n", lineno, filename); return RET_ERROR; } while (*what != '\0' && !xisspace(*what)) what++; while (*what != '\0' && xisspace(*what)) *(what++) = '\0'; if (*what != '\0') { fprintf(stderr, "Malformed line %d in '%s': space in version!\n", lineno, filename); return RET_ERROR; } type = flt_install; } else { fprintf(stderr, "Unknown status in '%s':%d: '%s'!\n", filename, lineno, what); return RET_ERROR; } if (*last == NULL || strcmp(namestart, (*last)->packagename) < 0) last = &n->root; cmp = -1; while (*last != NULL && (cmp=strcmp(namestart, (*last)->packagename)) > 0) last = &((*last)->next); if (cmp == 0) { fprintf(stderr, "Two lines describing '%s' in '%s'!\n", namestart, filename); return RET_ERROR; } h = zNEW(struct filterlistitem); if (FAILEDTOALLOC(h)) { return RET_ERROR_OOM; } h->next = *last; *last = h; h->what = type; h->packagename = strdup(namestart); if (FAILEDTOALLOC(h->packagename)) { return RET_ERROR_OOM; } if (version == NULL) h->version = NULL; else { h->version = strdup(version); if (FAILEDTOALLOC(h->version)) return RET_ERROR_OOM; } } n->last = *last; return RET_OK; } static inline retvalue filterlistfile_read(struct filterlistfile *n, const char *filename) { FILE *f; retvalue r; f = fopen(filename, "r"); if (f == NULL) { fprintf(stderr, "Cannot open %s for reading: %s!\n", filename, strerror(errno)); return RET_ERROR; } r = filterlistfile_parse(n, filename, f); // Can this return an yet unseen error? was read-only.. (void)fclose(f); return r; } static inline retvalue filterlistfile_getl(const char *filename, size_t len, struct filterlistfile **list) { struct filterlistfile *p; retvalue r; for (p = listfiles ; p != NULL ; p = p->next) { if (p->filename_len == len && strncmp(p->filename, filename, len) == 0) { p->reference_count++; *list = p; return RET_OK; } } p = zNEW(struct filterlistfile); if (FAILEDTOALLOC(p)) return RET_ERROR_OOM; p->reference_count = 1; p->filename = strndup(filename, len); p->filename_len = len; if (FAILEDTOALLOC(p->filename)) { free(p); return RET_ERROR_OOM; } char *fullfilename = configfile_expandname(p->filename, NULL); if (FAILEDTOALLOC(fullfilename)) r = RET_ERROR_OOM; else { r = filterlistfile_read(p, fullfilename); free(fullfilename); } if (RET_IS_OK(r)) { p->next = listfiles; listfiles = p; *list = p; } else { filterlistitems_free(p->root); free(p->filename); free(p); } return r; } static inline retvalue filterlistfile_get(/*@only@*/char *filename, /*@out@*/struct filterlistfile **list) { struct filterlistfile *p; retvalue r; size_t len = strlen(filename); for (p = listfiles ; p != NULL ; p = p->next) { if (p->filename_len == len && strncmp(p->filename, filename, len) == 0) { p->reference_count++; *list = p; free(filename); return RET_OK; } } p = zNEW(struct filterlistfile); if (FAILEDTOALLOC(p)) { free(filename); return RET_ERROR_OOM; } p->reference_count = 1; p->filename = filename; p->filename_len = len; if (FAILEDTOALLOC(p->filename)) { free(p); return RET_ERROR_OOM; } char *fullfilename = configfile_expandname(p->filename, NULL); if (FAILEDTOALLOC(fullfilename)) r = RET_ERROR_OOM; else { r = filterlistfile_read(p, fullfilename); free(fullfilename); } if (RET_IS_OK(r)) { p->next = listfiles; listfiles = p; *list = p; } else { filterlistitems_free(p->root); free(p->filename); free(p); } return r; } void filterlist_release(struct filterlist *list) { size_t i; assert(list != NULL); if (list->files != NULL) { for (i = 0 ; i < list->count ; i++) filterlistfile_unlock(list->files[i]); free(list->files); list->files = NULL; } else { assert (list->count == 0); } } static const struct constant filterlisttype_listtypes[] = { {"install", (int)flt_install}, {"hold", (int)flt_hold}, {"supersede", (int)flt_supersede}, {"deinstall", (int)flt_deinstall}, {"purge", (int)flt_purge}, {"upgradeonly", (int)flt_upgradeonly}, {"warning", (int)flt_warning}, {"error", (int)flt_error}, {NULL, 0} }; retvalue filterlist_load(struct filterlist *list, struct configiterator *iter) { enum filterlisttype defaulttype; size_t count; struct filterlistfile **files; retvalue r; char *filename; r = config_getenum(iter, filterlisttype, listtypes, &defaulttype); if (r == RET_NOTHING || r == RET_ERROR_UNKNOWNFIELD) { fprintf(stderr, "Error parsing %s, line %u, column %u: Expected default action as first argument to FilterList: (one of install, purge, hold, ...)\n", config_filename(iter), config_markerline(iter), config_markercolumn(iter)); return RET_ERROR; } if (RET_WAS_ERROR(r)) return r; count = 0; files = NULL; while ((r = config_getword(iter, &filename)) != RET_NOTHING) { struct filterlistfile **n; n = realloc(files, (count+1)* sizeof(struct filterlistfile *)); if (FAILEDTOALLOC(n)) { free(filename); r = RET_ERROR_OOM; } else { n[count] = NULL; files = n; // TODO: make filename only r = filterlistfile_get(filename, &files[count]); if (RET_IS_OK(r)) count++; } if (RET_WAS_ERROR(r)) { while (count > 0) { count--; filterlistfile_unlock(files[count]); } free(files); return r; } } list->count = count; list->files = files; list->defaulttype = defaulttype; list->set = true; return RET_OK; } static inline bool find(const char *name, /*@null@*/struct filterlistfile *list) { int cmp; /*@dependent@*/const struct filterlistitem *last = list->last; assert (last != NULL); if (last->next != NULL) { cmp = strcmp(name, last->next->packagename); if (cmp == 0) { list->last = last->next; return true; } } if (last->next == NULL || cmp < 0) { cmp = strcmp(name, last->packagename); if (cmp == 0) { return true; } else if (cmp > 0) return false; last = list->root; cmp = strcmp(name, last->packagename); if (cmp == 0) { list->last = list->root; return true; } else if (cmp < 0) return false; } /* now we are after last */ while (last->next != NULL) { cmp = strcmp(name, last->next->packagename); if (cmp == 0) { list->last = last->next; return true; } if (cmp < 0) { list->last = last; return false; } last = last->next; } list->last = last; return false; } enum filterlisttype filterlist_find(const char *name, const char *version, const struct filterlist *list) { size_t i; for (i = 0 ; i < list->count ; i++) { if (list->files[i]->root == NULL) continue; if (!find(name, list->files[i])) continue; if (list->files[i]->last->version == NULL) return list->files[i]->last->what; if (strcmp(list->files[i]->last->version, version) == 0) return list->files[i]->last->what; } return list->defaulttype; } struct filterlist cmdline_bin_filter = { .count = 0, .files = NULL, /* as long as nothing added, this does not change anything. * Once something is added, that will be auto_hold */ .defaulttype = flt_unchanged, .set = false, }; struct filterlist cmdline_src_filter = { .count = 0, .files = NULL, /* as long as nothing added, this does not change anything. * Once something is added, that will be auto_hold */ .defaulttype = flt_unchanged, .set = false, }; static retvalue filterlist_cmdline_init(struct filterlist *l) { if (l->count == 0) { l->files = nzNEW(2, struct filterlistfile *); if (FAILEDTOALLOC(l->files)) return RET_ERROR_OOM; l->files[0] = zNEW(struct filterlistfile); if (FAILEDTOALLOC(l->files[0])) return RET_ERROR_OOM; l->files[0]->reference_count = 1; l->count = 1; } return RET_OK; } retvalue filterlist_cmdline_add_file(bool src, const char *filename) { retvalue r; struct filterlist *l = src ? &cmdline_src_filter : &cmdline_bin_filter; char *name; r = filterlist_cmdline_init(l); if (RET_WAS_ERROR(r)) return r; l->set = true; l->defaulttype = flt_auto_hold; if (strcmp(filename, "-") == 0) filename = "/dev/stdin"; name = strdup(filename); if (FAILEDTOALLOC(name)) return RET_ERROR_OOM; if (l->count > 1) { struct filterlistfile **n; n = realloc(l->files, (l->count + 1) * sizeof(struct filterlistfile *)); if (FAILEDTOALLOC(n)) { free(name); return RET_ERROR_OOM; } n[l->count++] = NULL; l->files = n; } else { /* already allocated in _init */ assert (l->count == 1); l->count++; } return filterlistfile_get(name, &l->files[l->count - 1]); } retvalue filterlist_cmdline_add_pkg(bool src, const char *package) { retvalue r; enum filterlisttype what; struct filterlist *l = src ? &cmdline_src_filter : &cmdline_bin_filter; struct filterlistfile *f; struct filterlistitem **p, *h; char *name, *version; const char *c; int cmp; r = filterlist_cmdline_init(l); if (RET_WAS_ERROR(r)) return r; l->set = true; l->defaulttype = flt_auto_hold; c = strchr(package, '='); if (c != NULL) { what = flt_install; name = strndup(package, c - package); if (FAILEDTOALLOC(name)) return RET_ERROR_OOM; version = strdup(c + 1); if (FAILEDTOALLOC(version)) { free(name); return RET_ERROR_OOM; } } else { version = NULL; c = strchr(package, ':'); if (c == NULL) { what = flt_install; name = strndup(package, c - package); if (FAILEDTOALLOC(name)) return RET_ERROR_OOM; } else { const struct constant *t = filterlisttype_listtypes; while (t->name != NULL) { if (strcmp(c + 1, t->name) == 0) { what = t->value; break; } t++; } if (t->name == NULL) { fprintf(stderr, "Error: unknown filter-outcome '%s' (expected 'install' or ...)\n", c + 1); return RET_ERROR; } } name = strndup(package, c - package); if (FAILEDTOALLOC(name)) return RET_ERROR_OOM; } f = l->files[0]; assert (f != NULL); p = &f->root; cmp = -1; while (*p != NULL && (cmp = strcmp(name, (*p)->packagename)) > 0) p = &((*p)->next); if (cmp == 0) { fprintf(stderr, "Package in command line filter two times: '%s'\n", name); free(name); free(version); return RET_ERROR; } h = zNEW(struct filterlistitem); if (FAILEDTOALLOC(h)) { free(name); free(version); return RET_ERROR_OOM; } h->next = *p; *p = h; h->what = what; h->packagename = name; h->version = version; f->last = h; return RET_OK; }