/** * MIT/X Consortium License * * Copyright © 2015 Mattias Andrée * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #define t(...) do { if (__VA_ARGS__) goto fail; } while (0) /** * Default value for the environment variable LIBRARIAN_PATH. */ #ifndef DEFAULT_PATH # define DEFAULT_PATH "/usr/local/share/librarian:/usr/share/librarian" #endif /** * A library and version range. */ struct library { /** * The name of the library. */ const char *name; /** * The lowest acceptable version. * `NULL` if unbounded. */ char *lower; /** * The highest acceptable version. * `NULL` if unbounded. */ char *upper; /** * Is the version stored in * `lower` acceptable. */ int lower_closed; /** * Is the version stored in * `ypper` acceptable. */ int upper_closed; }; /** * The name of the process. */ static const char *argv0; /** * Determine whether a string is the * name of a non-reserved variable. * * @param s The string. * @return 1: The string is a varible name. * 0: The string is a library. */ static int is_variable(const char *s) { for (; *s; s++) { if (isupper(*s)) ; else if (isdigit(*s)) ; else if (strchr("_-", *s)) ; else return 0; } return 1; } /** * Parse a library–library-version range * argument. * * @param s The string. * @param lib Output parameter for the library spec:s. * @return 0: Successful. * 1: Syntax error. */ static int parse_library(char *s, struct library *lib) { char *p; char c; memset(lib, 0, sizeof(*lib)); if (strchr(s, '/') || strchr("<>=", *s)) return 1; lib->name = s; p = strpbrk(s, "<>="); if (p == NULL) return 0; c = *p, *p++ = '\0'; switch (c) { case '=': lib->lower_closed = lib->upper_closed = 1; lib->lower = lib->upper = p; break; case '>': p += lib->lower_closed = (*p == '='); lib->lower = p; s = strchr(p, '<'); if (s == NULL) break; *s++ = '\0'; if (!*(p = s)) return 0; /* fall through */ case '<': p += lib->upper_closed = (*p == '='); lib->upper = p; break; default: assert(0); abort(); break; } return (strpbrk(p, "<>=") || !*p); } /** * Compare two version numbers that do not * compare any does, and does not have an * epoch. * * @param a One of the version numbers. * @param b The other version number. * @return <0: `a` < `b`. * =0: `a` = `b`. * >1: `a` > `b`. */ static int version_subcmp(char *a, char *b) { char *ap; char *bp; char ac, bc; int r; while (*a || *b) { /* Compare digit part. */ /* (We must support arbitrary lenght.) */ ap = a + strspn(a, "0123456789"); bp = b + strspn(b, "0123456789"); while (*a == '0') a++; while (*b == '0') b++; ac = *ap, bc = *bp; *ap = '\0', *bp = '\0'; if (ap - a < bp - b) return -1; if (ap - a > bp - b) return +1; r = strcmp(a, b); *ap = ac, *bp = bc; a = *a ? (ap + 1) : a; b = *b ? (bp + 1) : b; if (r) return r; /* Compare letter (non-digit) part. */ ap = a + strcspn(a, "0123456789"); bp = b + strcspn(b, "0123456789"); ac = *ap, bc = *bp; *ap = '\0', *bp = '\0'; r = strcmp(a, b); *ap = ac, *bp = bc; a = *a ? (ap + 1) : a; b = *b ? (bp + 1) : b; if (r) return r; } return 0; } /** * Compare two version numbers. * * @param a One of the version numbers. * @param b The other version number. * @return <0: `a` < `b`. * =0: `a` = `b`. * >0: `a` > `b`. */ static int version_cmp(char *a, char *b) { #define COMPARE \ if (ap && bp) { \ ac = *ap, bc = *bp; \ *ap = *bp = '\0'; \ r = version_subcmp(a, b); \ *ap = ac, *bp = bc; \ a = ap + 1, b = bp + 1; \ } else if (ap) { \ ac = *ap, *ap = '\0'; \ r = version_subcmp(a, nil); \ *ap = ac, a = ap + 1; \ } else if (bp) { \ bc = *bp, *bp = '\0'; \ r = version_subcmp(nil, b); \ *bp = bc, b = bp + 1; \ } \ if (r) return r char *ap; char *bp; char ac, bc; int r = 0; static char nil[1] = { '\0' }; /* Compare epoch. */ ap = strchr(a, ':'); bp = strchr(b, ':'); COMPARE; /* Compare non-epoch */ for (;;) { ap = strchr(a, '.'); bp = strchr(b, '.'); if (!ap && !ap) return 0; COMPARE; } #undef COMPARE } /** * @return 0: Program was successful. * 1: An error occurred. * 2: A library was not found. * 3: Usage error. */ int main(int argc, char *argv[]) { #define CLEANUP \ free(libraries) int dashed = 0, f_deps = 0, f_locate = 0, f_oldest = 0; char *arg; char **args = argv; char **args_last = argv; char **variables = argv; char **variables_last = argv; struct library *libraries = NULL; struct library *libraries_last; const char *path; /* Parse arguments. */ argv0 = argv ? (argc--, *argv++) : "pp"; while (argc) { if (!dashed && !strcmp(*argv, "--")) { dashed = 1; argv++; argc--; } else if (!dashed && (**argv == '-')) { arg = *argv++; argc--; if (!*arg) goto usage; for (arg++; *arg; arg++) { if (*arg == 'd') f_deps = 1; else if (*arg == 'l') f_locate = 1; else if (*arg == 'o') f_oldest = 1; else goto usage; } } else { *args_last++ = *argv++; argc--; } } /* Parse VARIABLE and LIBRARY arguments. */ libraries = malloc((size_t)(args_last - args) * sizeof(*libraries)); libraries_last = libraries; t (libraries == NULL); for (; args != args_last; args++) { if (is_variable(*args)) *variables_last = *args; else if (parse_library(*args, libraries_last++)) goto usage; } /* Get LIBRARIAN_PATH. */ path = getenv("LIBRARIAN_PATH"); if (!path || !*path) path = DEFAULT_PATH; CLEANUP; return 0; fail: perror(argv0); CLEANUP; return 1; usage: fprintf(stderr, "%s: Invalid arguments, see `man 1 librarian'.\n", argv0); CLEANUP; return 3; #undef CLEANUP }