From d8f974314d09c016b12713f9366077c039186230 Mon Sep 17 00:00:00 2001 From: Anthony Mallet Date: Thu, 28 Jul 2011 22:06:24 +0200 Subject: [PATCH 3/3] [pkgtools/pkg_install] Introduce the "~" package suffix A package name can optionally be suffixed with a comma separated list of boolean tags, according to the following pattern: {pkgbase}-{version}~!?tag([+,]!?tag){0,n} Tags are alpha strings and may embed glob caracters that are interpreted according to fnmatch(3). Tags prefixed with `!' are said to be 'excluded'. Tags not prefixed are said to be 'present' or 'required'. A package name may only have 'present' tags, globbing characters have no meaning. A package name pattern may have 'required' or 'excluded' tags, and globbing characters are active. Package name matching is "loose": a package name matches some requirements if all tags from the requirements are satisfied (order is not significant). A package name without *version* specification matches all versions and all tags (e.g. "pkg" matches "pkg-1" and "pkg-1~tag", but "pkg-1" matches only "pkg-1"). All things being equal, packages with more tags sort after packages with less tags (which means that 'more tags' is considered as 'better' by e.g. pkg_admin lsbest). --- pkgtools/pkg_install/files/add/perform.c | 5 +- pkgtools/pkg_install/files/lib/opattern.c | 143 ++++++++++++++++++++++++++++++- pkgtools/pkg_install/files/lib/str.c | 34 +++++++- 3 files changed, 173 insertions(+), 9 deletions(-) diff --git pkgtools/pkg_install/files/add/perform.c pkgtools/pkg_install/files/add/perform.c index 98e9895..7a12c2d 100644 --- pkgtools/pkg_install/files/add/perform.c +++ pkgtools/pkg_install/files/add/perform.c @@ -431,14 +431,13 @@ check_other_installed(struct pkg_task *pkg) return 0; } - pkgbase = xstrdup(pkg->pkgname); + pkgbase = strduppkgbase(pkg->pkgname); - if ((iter = strrchr(pkgbase, '-')) == NULL) { + if (pkgbase == NULL) { free(pkgbase); warnx("Invalid package name %s", pkg->pkgname); return -1; } - *iter = '\0'; pkg->other_version = find_best_matching_installed_pkg(pkgbase); free(pkgbase); if (pkg->other_version == NULL) diff --git pkgtools/pkg_install/files/lib/opattern.c pkgtools/pkg_install/files/lib/opattern.c index 3861f32..7722fea 100644 --- pkgtools/pkg_install/files/lib/opattern.c +++ pkgtools/pkg_install/files/lib/opattern.c @@ -93,6 +93,97 @@ alternate_match(const char *pattern, const char *pkg) } /* + * Extract options from a "pkg[-<>=]vers~opts" pattern. + */ +static char * +mkoptions(char *buf, const char *pkg) +{ + const char *opt; + + opt = strrchr(pkg, '~'); + if (!opt) return NULL; + + (void) strncpy(buf, pkg, (size_t) MaxPathSize); + buf[opt - pkg] = '\0'; + return &buf[opt - pkg + 1]; +} + +/* + * Perform option matching on "pkg" against "reqd". + * Return 1 on match, 0 otherwise + * + * The matching is done according to the following algorithm ("equal" mean + * strcmp, "match" mean fnmatch): + * - one positive option in reqd matches no option in pkg -> fail. + * - one negative option in reqd equal one option in pkg -> fail. + * - one option in pkg equal no positive option in reqd + * if the option matches a negative option in reqd -> fail. + * if !strict -> success. + * if the option matches no positive wildcard option in reqd + * -> fail. + * - -> success. + */ +static int +options_match(char *reqd, char *pkg) +{ + char *r, *p; + int cr, cp; + int strict = 0; + int neg; + int i, j; + int m; + + /* catch-all */ + if (!reqd) return 1; + + /* count options in pattern */ + cr = 0; + for (r = strtok(reqd, "+,"); r; r = strtok(NULL, "+,")) + cr++; + + /* count options in pkg */ + cp = 0; + if (pkg) { + for (p = strtok(pkg, "+,"); p; p = strtok(NULL, "+,")) + cp++; + } + + /* for each option in pattern */ + for (r = reqd, i = 0; i < cr; r += strlen(r)+1, i++) { + if ((neg = (r[0] == '!'))) r++; + + /* test option match in pkg */ + if (!neg && !fnmatch(r, "", 0)) continue; + for (p = pkg, j = 0; j < cp; p += strlen(p)+1, j++) { + if (neg && !strcmp(r, p)) return 0; + if (!neg && !fnmatch(r, p, 0)) break; + } + if (!neg && (j >= cp)) return 0; + } + + /* for each option in pkg */ + for (p = pkg, j = 0; j < cp; p += strlen(p)+1, j++) { + /* test positive option equality in pattern */ + for (r = reqd, i = 0; i < cr; r += strlen(r)+1, i++) { + if (r[0] == '!') continue; + if (!strcmp(r, p)) break; + } + if (i < cr) continue; + + /* test option match in pattern */ + m = 0; + for (r = reqd, i = 0; i < cr; r += strlen(r)+1, i++) { + if ((neg = (r[0] == '!'))) r++; + if (neg && !fnmatch(r, p, 0)) return 0; + if (!neg && !m && !fnmatch(r, p, 0)) m = 1; + } + if (strict && !m) return 0; + } + + return 1; +} + +/* * Perform glob match on "pkg" against "pattern". * Return 1 on match, 0 otherwise */ @@ -139,6 +230,9 @@ quick_pkg_match(const char *pattern, const char *pkg) int pkg_match(const char *pattern, const char *pkg) { + char buf[2][MaxPathSize]; + char *reqopts, *pkgopts; + if (!quick_pkg_match(pattern, pkg)) return 0; @@ -146,6 +240,17 @@ pkg_match(const char *pattern, const char *pkg) /* emulate csh-type alternates */ return alternate_match(pattern, pkg); } + + /* remember options, remove from package name/pattern */ + if ((reqopts = mkoptions(buf[0], pattern)) != NULL) + pattern = buf[0]; + if ((pkgopts = mkoptions(buf[1], pkg)) != NULL) + pkg = buf[1]; + + /* match options */ + if (!options_match(reqopts, pkgopts)) { + return 0; + } if (strpbrk(pattern, "<>") != (char *) NULL) { int ret; @@ -167,7 +272,6 @@ pkg_match(const char *pattern, const char *pkg) /* globbing patterns and simple matches may be specified with or * without the version number, so check for both cases. */ - { char *pattern_ver; int retval; @@ -182,8 +286,13 @@ pkg_match(const char *pattern, const char *pkg) int pkg_order(const char *pattern, const char *first_pkg, const char *second_pkg) { + const char *first_pkg_trimmed; + const char *second_pkg_trimmed; const char *first_version; const char *second_version; + char *first_opt; + char *second_opt; + char buf[2][MaxPathSize]; if (first_pkg == NULL && second_pkg == NULL) return 0; @@ -193,8 +302,15 @@ pkg_order(const char *pattern, const char *first_pkg, const char *second_pkg) if (second_pkg == NULL) return pkg_match(pattern, first_pkg) ? 1 : 0; - first_version = strrchr(first_pkg, '-'); - second_version = strrchr(second_pkg, '-'); + first_pkg_trimmed = first_pkg; + if ((first_opt = mkoptions(buf[0], first_pkg)) != NULL) + first_pkg_trimmed = buf[0]; + second_pkg_trimmed = second_pkg; + if ((second_opt = mkoptions(buf[1], second_pkg)) != NULL) + second_pkg_trimmed = buf[1]; + + first_version = strrchr(first_pkg_trimmed, '-'); + second_version = strrchr(second_pkg_trimmed, '-'); if (first_version == NULL || !pkg_match(pattern, first_pkg)) return pkg_match(pattern, second_pkg) ? 2 : 0; @@ -202,6 +318,27 @@ pkg_order(const char *pattern, const char *first_pkg, const char *second_pkg) if (second_version == NULL || !pkg_match(pattern, second_pkg)) return pkg_match(pattern, first_pkg) ? 1 : 0; + if (dewey_cmp(first_version + 1, DEWEY_EQ, second_version + 1)) { + char *i; + int c1; + int c2; + + c1 = 0; + if (first_opt) { + for (i = strtok(first_opt, "+,"); i; + i = strtok(NULL, "+,")) c1++; + } + c2 = 0; + if (second_opt) { + for (i = strtok(second_opt, "+,"); i; + i = strtok(NULL, "+,")) c2++; + } + if (c1 > c2) + return 1; + else + return 2; + } + if (dewey_cmp(first_version + 1, DEWEY_GT, second_version + 1)) return 1; else diff --git pkgtools/pkg_install/files/lib/str.c pkgtools/pkg_install/files/lib/str.c index e2df4f1..e3fcada 100644 --- pkgtools/pkg_install/files/lib/str.c +++ pkgtools/pkg_install/files/lib/str.c @@ -92,13 +92,13 @@ dirname_of(const char *path) } /* - * Does the pkgname contain any of the special chars ("{[]?*<>")? + * Does the pkgname contain any of the special chars ("{[]?*<>!")? * If so, return 1, else 0 */ int ispkgpattern(const char *pkg) { - return strpbrk(pkg, "<>[]?*{") != NULL; + return strpbrk(pkg, "<>[]?*{!") != NULL; } /* @@ -107,5 +107,33 @@ ispkgpattern(const char *pkg) char * addpkgwildcard(const char *pkg) { - return xasprintf("%s-[0-9]*", pkg); + const char *opt; + + opt = strrchr(pkg, '~'); + if (!opt) return xasprintf("%s-[0-9]*", pkg); + + return xasprintf("%*.*s-[0-9]*%s", opt-pkg, opt-pkg, pkg, opt); +} + +/* + * Return pkgbase from pkg + */ +char * +strduppkgbase(const char *pkg) +{ + const char *opt; + char *dash; + + opt = strrchr(pkg, '~'); + if (opt) + pkg = xasprintf("%*.*s", opt-pkg, opt-pkg, pkg); + + dash = strrchr(pkg, '-'); + if (!dash) return NULL; + + if (opt) { + *dash = '\0'; + return pkg; + } else + return xasprintf("%*.*s", dash-pkg, dash-pkg, pkg); } -- 1.8.5.1