/* * Copyright (c) 2019, SUSE LLC * * This program is licensed under the BSD license, read LICENSE.BSD * for further information */ /* * conda.c * * evr comparison and package matching for conda */ #include #include #include #include #include #include #include #include "pool.h" #include "repo.h" #include "util.h" #include "conda.h" #ifdef _WIN32 #include "strfncs.h" #endif static const char * endseg(const char *seg, const char *end) { for (; seg < end; seg++) if (*seg == '.' || *seg == '-' || *seg == '_') break; return seg; } static const char * endpart(const char *seg, const char *end) { if (seg == end) return seg; if (*seg >= '0' && *seg <= '9') { for (seg++; seg < end; seg++) if (!(*seg >= '0' && *seg <= '9')) break; } else if (*seg == '*') { for (seg++; seg < end; seg++) if (*seg != '*') break; } else { for (seg++; seg < end; seg++) if ((*seg >= '0' && *seg <= '9') || *seg == '*') break; } return seg; } /* C implementation of the version comparison code in conda/models/version.py */ /* startswith == 1 : check if s1 starts with s2 */ static int solv_vercmp_conda(const char *s1, const char *q1, const char *s2, const char *q2, int startswith) { const char *s1p, *s2p; const char *s1e, *s2e; int r, isfirst; const char *q2end = 0; if (startswith) { for (q2end = q2; q2end > s2; q2end--) if (q2end[-1] != '.' && q2end[-1] != '-' && q2end[-1] != '_') break; } for (;;) { while (s1 < q1 && (*s1 == '.' || *s1 == '-' || *s1 == '_')) s1++; while (s2 < q2 && (*s2 == '.' || *s2 == '-' || *s2 == '_')) s2++; if (s1 == q1 && s2 == q2) return 0; if (startswith && s2 == q2) return 0; /* find end of component */ s1e = endseg(s1, q1); s2e = endseg(s2, q2); for (isfirst = 1; ; isfirst = 0) { if (s1 == s1e && s2 == s2e) break; if (s2 == q2end) return 0; s1p = endpart(s1, s1e); s2p = endpart(s2, s2e); /* prepend 0 if not numeric */ if (isfirst) { if (s1p != s1 && !(*s1 >= '0' && *s1 <= '9')) s1p = s1; if (s2p != s2 && !(*s2 >= '0' && *s2 <= '9')) s2p = s2; } /* special case "post" */ if (s1p - s1 == 4 && !strncasecmp(s1, "post", 4)) { if (s2p - s2 == 4 && !strncasecmp(s2, "post", 4)) { s1 = s1p; s2 = s2p; continue; } return 1; } if (s2p - s2 == 4 && !strncasecmp(s2, "post", 4)) return -1; if (isfirst || ((s1 == s1p || (*s1 >= '0' && *s1 <= '9')) && (s2 == s2p || (*s2 >= '0' && *s2 <= '9')))) { /* compare numbers */ while (s1 < s1p && *s1 == '0') s1++; while (s2 < s2p && *s2 == '0') s2++; if (s1p - s1 < s2p - s2) return -1; if (s1p - s1 > s2p - s2) return 1; r = s1p - s1 ? strncmp(s1, s2, s1p - s1) : 0; if (r) return r; } else if (s1 == s1p || (*s1 >= '0' && *s1 <= '9')) return 1; else if (s2 == s2p || (*s2 >= '0' && *s2 <= '9')) return -1; else { /* special case "dev" */ if (*s2 != '*' && s1p - s1 == 3 && !strncasecmp(s1, "dev", 3)) { if (s2p - s2 == 3 && !strncasecmp(s2, "dev", 3)) { s1 = s1p; s2 = s2p; continue; } return -1; } if (*s1 != '*' && s2p - s2 == 3 && !strncasecmp(s2, "dev", 3)) return 1; /* compare strings */ r = s2p - s2 > s1p - s1 ? s1p - s1 : s2p - s2; if (r) r = strncasecmp(s1, s2, r); if (r) return r; if (s1p - s1 < s2p - s2) return -1; if (s1p - s1 > s2p - s2) return 1; } s1 = s1p; s2 = s2p; } } } static int pool_evrcmp_conda_int(const char *evr1, const char *evr1e, const char *evr2, const char *evr2e, int startswith) { static char zero[2] = {'0', 0}; const char *s1, *s2; const char *r1, *r2; int r; /* split and compare epoch */ for (s1 = evr1; s1 < evr1e && *s1 >= '0' && *s1 <= '9'; s1++) ; for (s2 = evr2; s2 < evr2e && *s2 >= '0' && *s2 <= '9'; s2++) ; if (s1 == evr1 || s1 == evr1e || *s1 != '!') s1 = 0; if (s2 == evr1 || s2 == evr2e || *s2 != '!') s2 = 0; if (s1 || s2) { r = solv_vercmp_conda(s1 ? evr1 : zero, s1 ? s1 : zero + 1, s2 ? evr2 : zero, s2 ? s2 : zero + 1, 0); if (r) return r; if (s1) evr1 = s1 + 1; if (s2) evr2 = s2 + 1; } /* split into version/localversion */ for (s1 = evr1, r1 = 0; s1 < evr1e; s1++) if (*s1 == '+') r1 = s1; for (s2 = evr2, r2 = 0; s2 < evr2e; s2++) if (*s2 == '+') r2 = s2; r = solv_vercmp_conda(evr1, r1 ? r1 : s1, evr2, r2 ? r2 : s2, r2 ? 0 : startswith); if (r) return r; if (!r1 && !r2) return 0; if (!r1 && r2) return -1; if (r1 && !r2) return 1; return solv_vercmp_conda(r1 + 1, s1, r2 + 1, s2, startswith); } int pool_evrcmp_conda(const Pool *pool, const char *evr1, const char *evr2, int mode) { if (evr1 == evr2) return 0; return pool_evrcmp_conda_int(evr1, evr1 + strlen(evr1), evr2, evr2 + strlen(evr2), 0); } static int regexmatch(const char *evr, const char *version, size_t versionlen, int icase) { regex_t reg; char *buf = solv_malloc(versionlen + 1); int r; memcpy(buf, version, versionlen); buf[versionlen] = 0; if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB | (icase ? REG_ICASE : 0))) { solv_free(buf); return 0; } r = regexec(®, evr, 0, NULL, 0); regfree(®); solv_free(buf); return r == 0; } static int globmatch(const char *evr, const char *version, size_t versionlen, int icase) { regex_t reg; char *buf = solv_malloc(2 * versionlen + 3); size_t i, j; int r; buf[0] = '^'; j = 1; for (i = 0, j = 1; i < versionlen; i++) { if (version[i] == '.' || version[i] == '+' || version[i] == '*') buf[j++] = version[i] == '*' ? '.' : '\\'; buf[j++] = version[i]; } buf[j++] = '$'; buf[j] = 0; if (regcomp(®, buf, REG_EXTENDED | REG_NOSUB | (icase ? REG_ICASE : 0))) { solv_free(buf); return 0; } r = regexec(®, evr, 0, NULL, 0); regfree(®); solv_free(buf); return r == 0; } /* return true if solvable s matches the version */ /* see conda/models/version.py */ static int solvable_conda_matchversion_single(Solvable *s, const char *version, size_t versionlen) { const char *evr; size_t i; int r; if (versionlen == 0 || (versionlen == 1 && *version == '*')) return 1; /* matches every version */ evr = pool_id2str(s->repo->pool, s->evr); if (versionlen >= 2 && version[0] == '^' && version[versionlen - 1] == '$') return regexmatch(evr, version, versionlen, 0); if (version[0] == '=' || version[0] == '<' || version[0] == '>' || version[0] == '!' || version[0] == '~') { int flags = 0; int oplen; if (version[0] == '=') flags = version[1] == '=' ? REL_EQ : 8; else if (version[0] == '!' || version[0] == '~') { if (version[1] != '=') return 0; flags = version[0] == '!' ? REL_LT | REL_GT : 9; } else if (version[0] == '<' || version[0] == '>') { flags = version[0] == '<' ? REL_LT : REL_GT; if (version[1] == '=') flags |= REL_EQ; } else return 0; oplen = flags == 8 || flags == REL_LT || flags == REL_GT ? 1 : 2; if (versionlen < oplen + 1) return 0; version += oplen; versionlen -= oplen; if (version[0] == '=' || version[0] == '<' || version[0] == '>' || version[0] == '!' || version[0] == '~') return 0; /* bad chars after op */ if (versionlen >= 2 && version[versionlen - 2] == '.' && version[versionlen - 1] == '*') { if (flags == 8 || flags == (REL_GT | REL_EQ)) versionlen -= 2; else if (flags == (REL_LT | REL_GT)) { versionlen -= 2; flags = 10; } else return 0; } if (flags < 8) { /* we now have an op and a version */ r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0); if (r < 0) return (flags & REL_LT) ? 1 : 0; if (r == 0) return (flags & REL_EQ) ? 1 : 0; if (r > 0) return (flags & REL_GT) ? 1 : 0; return 0; } if (flags == 8 || flags == 10) /* startswith, not-startswith */ { r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1); return flags == 8 ? r == 0 : r != 0; } else if (flags == 9) /* compatible release op */ { r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0); if (r < 0) return 0; /* split off last component */ while (versionlen > 0 && version[versionlen - 1] != '.') versionlen--; if (versionlen < 2) return 0; versionlen--; r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1); return r == 0 ? 1 : 0; } return 0; } /* do we have a '*' in the version */ for (i = 0; i < versionlen; i++) if (version[i] == '*') { for (i++; i < versionlen; i++) if (version[i] != '*') break; if (i < versionlen) return globmatch(evr, version, versionlen, 1); } if (versionlen > 1 && version[versionlen - 1] == '*') { /* startswith */ while (versionlen > 0 && version[versionlen - 1] == '*') versionlen--; while (versionlen > 0 && version[versionlen - 1] == '.') versionlen--; r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1); return r == 0 ? 1 : 0; } /* do we have a '@' in the version? */ for (i = 0; i < versionlen; i++) if (version[i] == '@') return strncmp(evr, version, versionlen) == 0 && evr[versionlen] == 0; r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0); return r == 0 ? 1 : 0; } static int solvable_conda_matchversion_rec(Solvable *s, const char **versionp, const char *versionend) { const char *version = *versionp; int v, vor = 0, vand = -1; /* -1: doing OR, 0,1: doing AND */ if (version == versionend) return -1; for (;;) { if (*version == '(') { version++; v = solvable_conda_matchversion_rec(s, &version, versionend); if (v == -1 || version == versionend || *version != ')') return -1; version++; } else if (*version == ')' || *version == '|' || *version == ',') return -1; else { const char *vstart = version; while (version < versionend && *version != '(' && *version != ')' && *version != '|' && *version != ',') version++; if (vand >= 0 ? !vand : vor) v = 0; /* no need to call expensive matchversion if the result does not matter */ else v = solvable_conda_matchversion_single(s, vstart, version - vstart) ? 1 : 0; } if (version == versionend || *version == ')') { *versionp = version; return vor | (vand >= 0 ? (vand & v) : v); } if (*version == ',') vand = vand >= 0 ? (vand & v) : v; else if (*version == '|') { vor |= vand >= 0 ? (vand & v) : v; vand = -1; } else return -1; version++; } } static int solvable_conda_matchbuild(Solvable *s, const char *build, const char *buildend) { const char *bp; const char *flavor = solvable_lookup_str(s, SOLVABLE_BUILDFLAVOR); if (!flavor) flavor = ""; if (build == buildend) return *flavor ? 0 : 1; if (build + 1 == buildend && *build == '*') return 1; if (*build == '^' && buildend[-1] == '$') return regexmatch(flavor, build, buildend - build, 0); for (bp = build; bp < buildend; bp++) if (*bp == '*') return globmatch(flavor, build, buildend - build, 0); return strncmp(flavor, build, buildend - build) == 0 && flavor[buildend - build] == 0 ? 1 : 0; } /* return true if solvable s matches the version */ /* see conda/models/match_spec.py */ int solvable_conda_matchversion(Solvable *s, const char *version) { const char *build, *versionend; int r; /* split off build */ if ((build = strchr(version, ' ')) != 0) { versionend = build++; while (*build == ' ') build++; } else versionend = version + strlen(version); r = solvable_conda_matchversion_rec(s, &version, versionend); if (r != 1 || version != versionend) return 0; if (build && !solvable_conda_matchbuild(s, build, build + strlen(build))) return 0; return r; } static Id pool_addrelproviders_conda_slow(Pool *pool, const char *namestr, Id evr, Queue *plist, int mode) { size_t namestrlen = strlen(namestr); const char *evrstr = evr == 0 || evr == 1 ? 0 : pool_id2str(pool, evr); Id p; FOR_POOL_SOLVABLES(p) { Solvable *s = pool->solvables + p; if (!pool_installable(pool, s)) continue; if (mode == 1 && !globmatch(pool_id2str(pool, s->name), namestr, namestrlen, 1)) continue; if (mode == 2 && !regexmatch(pool_id2str(pool, s->name), namestr, namestrlen, 1)) continue; if (!evrstr || solvable_conda_matchversion(s, evrstr)) queue_push(plist, p); } return 0; } /* called from pool_addrelproviders, plist is an empty queue * it can either return an offset into the whatprovides array * or fill the plist queue and return zero */ Id pool_addrelproviders_conda(Pool *pool, Id name, Id evr, Queue *plist) { const char *namestr = pool_id2str(pool, name), *np; size_t l, nuc = 0; Id wp, p, *pp; /* is this a regex? */ if (*namestr && *namestr == '^') { l = strlen(namestr); if (namestr[l - 1] == '$') return pool_addrelproviders_conda_slow(pool, namestr, evr, plist, 2); } /* is this '*'? */ if (*namestr && *namestr == '*' && namestr[1] == 0) return pool_addrelproviders_conda_slow(pool, namestr, evr, plist, 0); /* does the string contain '*' or uppercase? */ for (np = namestr; *np; np++) { if (*np == '*') return pool_addrelproviders_conda_slow(pool, namestr, evr, plist, 1); else if (*np >= 'A' && *np <= 'Z') nuc++; } if (nuc) { char *nbuf = solv_strdup(namestr), *nbufp; for (nbufp = nbuf; *nbufp; nbufp++) *nbufp = *nbufp >= 'A' && *nbufp <= 'Z' ? *nbufp + ('a' - 'A') : *nbufp; name = pool_str2id(pool, nbuf, 0); wp = name ? pool_whatprovides(pool, name) : 0; solv_free(nbuf); } else wp = pool_whatprovides(pool, name); if (wp && evr && evr != 1) { const char *evrstr = pool_id2str(pool, evr); pp = pool->whatprovidesdata + wp; while ((p = *pp++) != 0) { if (solvable_conda_matchversion(pool->solvables + p, evrstr)) queue_push(plist, p); else wp = 0; } } return wp; } /* create a CONDA_REL relation from a matchspec */ Id pool_conda_matchspec(Pool *pool, const char *name) { const char *p2; char *name2; char *p, *pp; char *build, *buildend, *version, *versionend; Id nameid, evrid; int haveglob = 0; /* ignore channel and namespace for now */ if ((p2 = strrchr(name, ':'))) name = p2 + 1; name2 = solv_strdup(name); /* find end of name */ for (p = name2; *p && *p != ' ' && *p != '=' && *p != '<' && *p != '>' && *p != '!' && *p != '~'; p++) { if (*p >= 'A' && *p <= 'Z') *(char *)p += 'a' - 'A'; /* lower case the name */ else if (*p == '*') haveglob = 1; } if (p == name2) { solv_free(name2); return 0; /* error: empty package name */ } nameid = pool_strn2id(pool, name2, p - name2, 1); while (*p == ' ') p++; if (!*p) { if (*name2 != '^' && !haveglob) { solv_free(name2); return nameid; /* return a simple dependency if no glob/regex */ } evrid = pool_str2id(pool, "*", 1); solv_free(name2); return pool_rel2id(pool, nameid, evrid, REL_CONDA, 1); } /* have version */ version = p; versionend = p + strlen(p); while (versionend > version && versionend[-1] == ' ') versionend--; build = buildend = 0; /* split of build */ p = versionend; for (;;) { while (p > version && p[-1] != ' ' && p[-1] != '-' && p[-1] != '=' && p[-1] != ',' && p[-1] != '|' && p[-1] != '<' && p[-1] != '>' && p[-1] != '~') p--; if (p <= version + 1 || (p[-1] != ' ' && p[-1] != '=')) break; /* no build */ /* check char before delimiter */ if (p[-2] == '=' || p[-2] == '!' || p[-2] == '|' || p[-2] == ',' || p[-2] == '<' || p[-2] == '>' || p[-2] == '~') { /* illegal combination */ if (p[-1] == ' ') { p--; continue; /* special case space: it may be in the build */ } break; /* no build */ } if (p < versionend) { build = p; buildend = versionend; versionend = p - 1; } break; } /* do weird version translation */ if (versionend > version && version[0] == '=') { if (versionend - version >= 2 && version[1] == '=') { if (!build) version += 2; } else if (build) version += 1; else { for (p = version + 1; p < versionend; p++) if (*p == '=' || *p == ',' || *p == '|') break; if (p == versionend) { memmove(version, version + 1, versionend - version - 1); versionend[-1] = '*'; } } } #if 0 printf("version: >%.*s<\n", (int)(versionend - version), version); if (build) printf("build: >%.*s<\n", (int)(buildend - build), build); #endif /* strip spaces from version */ for (p = pp = version; pp < versionend; pp++) if (*pp != ' ') *p++ = *pp; if (build) { *p++ = ' '; memcpy(p, build, buildend - build); p += buildend - build; } evrid = pool_strn2id(pool, version, p - version, 1); solv_free(name2); return pool_rel2id(pool, nameid, evrid, REL_CONDA, 1); }