/** * 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. */ #define _POSIX_C_SOURCE 200809L #include #include #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 } /** * Test whether a version of a library is compatible. * * @param version The found version. * @param required Compatible version range. * @return 1: Version is accepted. * 0: Version is incompatible. */ static int test_library_version(char *version, struct library *required) { int upper = required->upper ? version_cmp(version, required->upper) : -1; int lower = required->lower ? version_cmp(version, required->lower) : +1; upper = required->lower_closed ? (upper <= 0) : (upper < 0); lower = required->lower_closed ? (lower >= 0) : (lower > 0); return upper && lower; } /** * Locate a librarian file in a directory. * * @param lib Library specification. * @param path The pathname of the directory. * @param oldest Are older versions prefered? * @return The pathname of the library's librarian file. * `NULL` on error or if not found, if not found, * `errno` is set to 0. */ static char *locate_in_dir(struct library *lib, char *path, int oldest) { DIR *d = NULL; struct dirent *f; char *p; char *old; char *best = NULL; char *best_ver; int r, saved_errno; d = opendir(path); t (d == NULL); while ((f = (errno = 0, readdir(d)))) { p = strrchr(f->d_name, '='); if (p == NULL) continue; *p = '\0'; if (strcmp(f->d_name, lib->name)) continue; *p++ = '='; if (!test_library_version(p, lib)) continue; if (best == NULL) { old = best, best = strdup(f->d_name); } else { best_ver = strrchr(best, '='); assert(best_ver && !strchr(best_ver, '/')); r = version_cmp(p, best_ver + 1); if (oldest ? (r < 0) : (r > 0)) old = best, best = strdup(f->d_name); } if (best == NULL) { best = old; goto fail; } free(old); } t (errno); closedir(d), d = NULL; if (best == NULL) return errno = 0, NULL; p = malloc(strlen(path) + strlen(best) + 2); t (p == NULL); stpcpy(stpcpy(stpcpy(p, path), "/"), best); return p; fail: saved_errno = errno; free(best); if (d != NULL) closedir(d); errno = saved_errno; return NULL; } /** * Locate a librarian file on the system. * * @param lib Library specification. * @param path LIBRARIAN_PATH. * @param oldest Are older versions prefered? * @return The pathname of the library's librarian file. * `NULL` on error or if not found, if not found, * `errno` is set to 0. */ static char *locate(struct library *lib, char *path, int oldest) { char *p; char *end = path; char *e; char *best = NULL; char *found; char *old; char *best_ver; char *found_ver; int r, saved_errno; for (p = path; end; *e = (end ? ':' : '\0'), p = end + 1) { end = strchr(p, ':'); e = end ? end : strchr(p, '\0'); *e = '\0'; if (!*p) continue; found = locate_in_dir(lib, p, oldest); if (found == NULL) { t (errno); continue; } old = found; if (best == NULL) { old = best, best = found; } else { best_ver = strrchr(best, '='); found_ver = strrchr(found, '='); assert(best_ver && !strchr(best_ver, '/')); assert(found_ver && !strchr(found_ver, '/')); r = version_cmp(found_ver + 1, best_ver + 1); if (oldest ? (r < 0) : (r > 0)) old = best, best = found; } free(old); } return errno = 0, best; fail: saved_errno = errno; free(best); errno = saved_errno; return NULL; } /** * @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), \ free(path) 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_; char *path = NULL; /* 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--; } } if (f_deps && f_locate) goto usage; /* 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; path = strdup(path_); t (path == NULL); 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 }