/* * Compare project/version strings of the form: * * PROJECT v....[ ] * * Pattern is of the form: * * PROJECT[ v[....][-[....]] ][+-]flags... * * Chunks must be separated by exactly one space. Flags are A-Za-z0-9. * Any control character is treated as end of string. */ #include "matchver.h" static int flag_val(char c) { if (c < '0') return -1; else if (c <= '9') return c-'0'; else if (c < 'A') return -1; else if (c <= 'Z') return c-'A'+10; else if (c < 'a') return -1; else if (c <= 'z') return c-'a'+36; else return -1; } static uint64_t flag_mask(char c) { int v = flag_val(c); return (v < 0) ? 0 : UINT64_C(1) << v; } static inline bool is_digit(char c) { return (unsigned int)(c - '0') < 10; } /* * Compare numeric strings with components separated by dots, return the end * of each string. */ static int compare_numbers(const char **ap, const char **bp, unsigned long bmissing) { bool adig, bdig; unsigned long an, bn; int result = 0; const char *a = *ap, *b = *bp; for (;;) { adig = is_digit(*a); bdig = is_digit(*b); if (!adig && !bdig) break; if (adig) { an = strtoul(a, (char **)&a, 10); if (*a == '.') a++; } else { an = 0; } if (bdig) { bn = strtoul(b, (char **)&b, 10); if (*b == '.') b++; } else { bn = bmissing; } /* If result is set, the answer is already known, just find the end */ if (!result) { result = (an < bn) ? -1 : (an > bn) ? 1 : 0; } } *ap = a; *bp = b; return result; } static const char *parse_flags(const char *str, uint64_t *flags, uint64_t *mask) { uint64_t polarity = -1; uint64_t f = 0, m = 0; while (1) { char c = *str++; uint64_t bit; if (c == '+') { polarity = -1; } else if (c == '-') { polarity = 0; } else { bit = flag_mask(c); if (!bit) break; m |= bit; f = (f & ~bit) | (polarity & bit); } } *flags = f; *mask = m; return str; } bool match_version(const char *version, const char *pattern) { char v, p; const char *vstart, *pstart; uint64_t vflags, pflags, pmask; while (1) { v = *version++; p = *pattern++; if (v <= ' ' && p <= ' ') break; if (v != p) return false; } if (p < ' ') return true; /* Project-only pattern */ if (v != ' ' || *version++ != 'v') return false; /* Invalid/unparsable version string */ if (*pattern++ != 'v') return false; /* Invalid pattern */ vstart = version; pstart = pattern; if (compare_numbers(&version, &pattern, 0UL) < 0) return false; if (*pattern == '-') pattern++; else pattern = pstart; if (compare_numbers(&vstart, &pattern, -1UL) > 0) return false; v = *version++; vflags = 0; if (v == ' ') { uint64_t dummy; parse_flags(version, &vflags, &dummy); } else if (v > ' ') { return false; } p = *pattern++; pflags = pmask = 0; if (p == ' ') { parse_flags(pattern, &pflags, &pmask); } else if (p > ' ') { return false; } return (vflags & pmask) == pflags; } #ifdef COMMAND #include int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s version pattern\n", argv[0]); return 127; } return !match_version(argv[1], argv[2]); } #endif