ckiss

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit acfc85b9462f978e82e74e8e5e86e6dc0ba9506d
parent 99e92c6fe137a7bbb2b69efb811b586ade8605d0
Author: aabacchus <ben@bvnf.space>
Date:   Sun, 23 Apr 2023 05:17:40 +0100

add checksum

Diffstat:
MREADME | 6+++++-
Msrc/Makefile | 7+++++--
Asrc/checksum.c | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ckiss.h | 23++++++++++++++++++++---
Msrc/main.c | 6+++++-
Asrc/source.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/source.h | 28++++++++++++++++++++++++++++
Msrc/utils.c | 34++++++++++++++++++++++++++++++++++
8 files changed, 357 insertions(+), 7 deletions(-)

diff --git a/README b/README @@ -9,7 +9,7 @@ An implementation of the kiss package manager in C. [ ] alternatives [ ] build [ ] hooks -[ ] checksum +[.] checksum [ ] download [ ] install [x] list @@ -19,3 +19,7 @@ An implementation of the kiss package manager in C. [ ] upgrade [x] version [ ] external tools + +## Dependencies + +BLAKE3 library (https://git.sr.ht/~mcf/b3sum) diff --git a/src/Makefile b/src/Makefile @@ -2,12 +2,13 @@ XCFLAGS = $(CFLAGS) -Wall -Wextra -Wshadow -pedantic -D_XOPEN_SOURCE=700 -Og -g -OBJS = utils.o list.o search.o array.o +OBJS = utils.o list.o search.o array.o checksum.o source.o +LIBS = -lblake3 all: ckiss ckiss: main.o libckiss.a - $(CC) $(LDFLAGS) main.o libckiss.a -o $@ + $(CC) -o $@ $(LDFLAGS) main.o libckiss.a $(LIBS) libckiss.a: $(OBJS) $(AR) -rcs $@ $(OBJS) @@ -31,4 +32,6 @@ test.o: ckiss.h list.o: ckiss.h search.o: ckiss.h array.h array.o: ckiss.h array.h +checksum.o: ckiss.h source.h +source.o: ckiss.h source.h main.o: ckiss.h diff --git a/src/checksum.c b/src/checksum.c @@ -0,0 +1,152 @@ +#include <blake3.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ckiss.h" +#include "source.h" + +/* reads f and returns the checksum */ +static char * +file_checksum(FILE *f) { + /* This function is essentially copied from b3sum: + * https://git.sr.ht/~mcf/b3sum/tree/cb4111ccc8061039b014fbb657c72f78984f1069/item/b3sum.c#L19 + * which is used under CC0. */ + + size_t outlen = 33, len; + unsigned char *out = malloc(outlen); + if (out == NULL) + die_perror("malloc"); + + size_t buflen = 0x4000; + char *buf = malloc(buflen); + if (buf == NULL) + die_perror("malloc"); + + blake3_hasher ctx; + blake3_hasher_init(&ctx); + + do { + len = fread(buf, 1, buflen, f); + if (len > 0) + blake3_hasher_update(&ctx, buf, len); + } while (len == buflen); + if (ferror(f)) + die_perror("fread"); + + blake3_hasher_finalize(&ctx, out, outlen); + free(buf); + + size_t hexlen = 2 * outlen; + char *hex = malloc(hexlen + 1); + if (hex == NULL) + die_perror("malloc"); + for (size_t i = 0; i < outlen; i++) + sprintf(hex + 2*i, "%02x", out[i]); + hex[hexlen] = '\0'; + + free(out); + return hex; +} + +/* returns the checksum of the file specified by s, if needed and if the cache + * is present (must download first) */ +char * +source_generate_checksum(struct source *s) { + if (s == NULL || s->type == SRC_INVAL) + die("source struct not initialised"); + if (s->type != SRC_HTTP && s->type != SRC_FILE) + return NULL; /* checksum not needed */ + + FILE *f = fopen(s->cachefile, "rb"); + if (f == NULL) + die_perror(s->cachefile); + + char *c = file_checksum(f); + fclose(f); + return c; +} + +/* returns 1 if all good, 0 if there is a checksum mismatch. */ +int +verify_checksums(char *pkg, char *pkg_path, struct source **s) { + if (s == NULL) { + mylog2(pkg, "No sources"); + return 1; + } + + FILE *f = pkg_open_file(pkg_path, "checksums", "r"); + if (f == NULL) { + /* TODO check if any sources need sums first */ + die_perror("no checksums"); + } + + char *buf = NULL; + size_t bufn = 0; + ssize_t n; + for (int i = 0; s[i] != NULL; i++) { + if (s[i]->type != SRC_HTTP && s[i]->type != SRC_FILE) + continue; + + if ((n = getline(&buf, &bufn, f)) == -1) { + free(buf); + fclose(f); + perror(NULL); + die2(s[i]->remote, "checksums missing"); + } + if (buf[n - 1] == '\n') + buf[--n] = '\0'; + + char *sum = source_generate_checksum(s[i]); + if (strcmp(buf, sum) != 0) { + free(sum); + free(buf); + fclose(f); + mylog2(s[i]->cachefile, "checksum mismatch"); + return 0; + } + free(sum); + } + free(buf); + fclose(f); + return 1; +} + +int +checksum(int argc, char **argv, struct env *e) { + int ret = 0; + if (argc < 2) + die2("checksum", "need a package name(s)"); /* TODO: crux-like */ + + for (int i = 1; i < argc; i++) { + bool needed = false; + char *pkg_path = find_pkg(argv[i], e); + if (pkg_path == NULL) + die2(argv[i], "not found"); + + FILE *f = pkg_open_file(pkg_path, "checksums", "w"); + if (f == NULL) + die_perror("couldn't open checksums file for writing"); + + struct source **s = parse_sources(argv[i], pkg_path, e); + + for (int j = 0; s[j] != NULL; j++) { + char *sum = source_generate_checksum(s[j]); + if (sum) { + fprintf(f, "%s\n", sum); + printf("%s\n", sum); + needed = true; + } + free(sum); + + free(s[j]->remote); + free(s[j]->extract_dir); + free(s[j]->cachefile); + free(s[j]); + } + free(s); + fclose(f); + mylog2(argv[i], needed ? "Generated checksums" : "No sources needing checksums"); + } + return ret; +} diff --git a/src/ckiss.h b/src/ckiss.h @@ -3,11 +3,10 @@ #include <limits.h> #include <stdbool.h> -#include <unistd.h> +#include <stdio.h> #include <stdnoreturn.h> #include <sys/stat.h> - -#include "array.h" +#include <unistd.h> #ifndef noreturn #define noreturn @@ -25,6 +24,7 @@ struct env { char **kiss_path; char **path; char *b3[5]; + char *cac_dir; char *compress; char *elf; char *get[7]; @@ -37,6 +37,10 @@ struct env { char date[17]; /* YYYY-MM-DD-HH:MM + '\0' */ }; +/* include these now in case they need struct env */ +#include "array.h" +#include "source.h" + /* called "mylog" to avoid collision with math.h log function. */ void mylog(const char *s); void mylog2(const char *name, const char *s); @@ -63,6 +67,12 @@ array_t find_in_path(char *name, array_t path, mode_t test_flags, bool limit, bo * cmd (0, 1, 2, ...). Arg list must be terminated with a NULL */ int available_cmd(array_t path, char *cmd, ...); +/* Wrapper around find_in_path. Name is an exact name, not a glob. Return the + * first package in $KISS_PATH:$sys_db, or NULL. */ +char *find_pkg(char *name, struct env *e); + +FILE *pkg_open_file(char *pkg_path, char *file, char *mode); + /* setup internal colours used by the logging functions. */ void setup_colors(struct env *e); @@ -72,8 +82,15 @@ struct env *setup_env(void); /* correctly frees a struct env. */ void destroy_env(struct env *e); +/* returns the checksum of the file specified by s, if needed and if the cache + * is present (must download first) */ +char *source_generate_checksum(struct source *s); + +/* returns 1 if all good, 0 if there is a checksum mismatch. */ +int verify_checksums(char *pkg, char *pkg_path, struct source **s); int list(int argc, char **argv, struct env *e); int search(int argc, char **argv, struct env *e); +int checksum(int argc, char **argv, struct env *e); #endif diff --git a/src/main.c b/src/main.c @@ -4,7 +4,8 @@ noreturn void usage(int r) { - mylog("ckiss [l|s|v] [pkg]..."); + mylog("ckiss [c|l|s|v] [pkg]..."); + mylog("checksum Generate checksums"); mylog("list List installed packages"); mylog("search Search for packages"); mylog("version Package manager version"); @@ -19,6 +20,9 @@ main(int argc, char **argv) { if (argc < 2) usage(0); switch (argv[1][0]) { + case 'c': + checksum(argc - 1, argv + 1, e); + break; case 'l': list(argc - 1, argv + 1, e); break; diff --git a/src/source.c b/src/source.c @@ -0,0 +1,108 @@ +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "ckiss.h" +#include "source.h" + +char * +source_get_cache(char *pkg, char *pkg_path, struct source *s, struct env *e) { + if (s == NULL || s->type == SRC_INVAL) + die("source struct not initialised"); + + switch (s->type) { + case SRC_HTTP: + { + char *filepart = strrchr(s->remote, '/'); + if (filepart == NULL) + die2(s->remote, "filepart of URL not found"); + filepart++; + char *cache = concat(e->cac_dir, "/sources/", pkg, "/", + s->extract_dir ? s->extract_dir : "", filepart, NULL); + return cache; + } + case SRC_FILE: + { + char *f = concat(pkg_path, "/", s->remote, NULL); + return f; + } + default: + return NULL; + } +} + +enum pkg_source_types +pkg_source_type(char *remote, char *pkg_path) { + if (strncmp("git+", remote, 4) == 0) + return SRC_GIT; + if (strncmp("http", remote, 4) == 0) + return SRC_HTTP; + if (strstr(remote, "://")) + die2(remote, "protocol not supported"); + + /* XXX: only relative files are supported. */ + char *f = concat(pkg_path, "/", remote, NULL); + int ok = (access(f, F_OK) == 0); + free(f); + if (ok) + return SRC_FILE; + + die2(remote, "invalid source"); +} + +struct source ** +parse_sources(char *pkg, char *pkg_path, struct env *e) { + struct source **s = NULL; + + mylog2(pkg, "Reading sources"); + + FILE *f = pkg_open_file(pkg_path, "sources", "r"); + + if (f == NULL) { + if (errno == ENOENT) { + mylog2(pkg, "no sources file, skipping"); + goto sources_ret; + } else { + die2(pkg, "couldn't open sources file"); + } + } + + char *buf = NULL; + size_t bufn = 0; + ssize_t n; + int lineno = 0; + while ((n = getline(&buf, &bufn, f)) != -1) { + if (n == 0 || buf[0] == '#' || buf[0] == '\n') + continue; + + lineno++; + + struct source *new = calloc(1, sizeof(struct source)); + if (new == NULL) die_perror("calloc"); + + array_t t = split(buf, " \t\n"); + + new->remote = t[0]; + new->extract_dir = t[1]; + free(t); /* just free t, not the strings in it */ + + new->type = pkg_source_type(new->remote, pkg_path); + new->cachefile = source_get_cache(pkg, pkg_path, new, e); + + /* add new to the list */ + s = realloc(s, sizeof(struct source *) * (lineno + 1)); + if (s == NULL) + die_perror("realloc"); + s[lineno-1] = new; + + /* add a final NULL */ + s[lineno] = NULL; + } + fclose(f); + free(buf); + +sources_ret: + free(pkg_path); + return s; +} diff --git a/src/source.h b/src/source.h @@ -0,0 +1,28 @@ +#ifndef _CKISS_SOURCE_H +#define _CKISS_SOURCE_H + +#include "ckiss.h" + +enum pkg_source_types { + SRC_INVAL = 0, + SRC_HTTP, + SRC_GIT, + SRC_FILE, +}; + +struct source { + enum pkg_source_types type; + char *remote; /* location of file to be downloaded/copied */ + char *extract_dir; /* optional dir to extract file into */ + char *cachefile; /* cache location if file downloaded */ +}; + +/* returns the location of the cache file for the source for pkg, or NULL if not + * needed (eg git sources or local files in repo) */ +char *source_get_cache(char *pkg, char *pkg_path, struct source *s, struct env *e); + +enum pkg_source_types pkg_source_type(char *remote, char *pkg_path); + +struct source **parse_sources(char *pkg, char *pkg_path, struct env *e); + +#endif diff --git a/src/utils.c b/src/utils.c @@ -170,6 +170,31 @@ available_cmd(array_t path, char *cmd, ...) { return -1; } +char * +find_pkg(char *name, struct env *e) { + array_t full_path = arr_copy(e->kiss_path); + arr_append(&full_path, e->sys_db, -1, true); + + array_t s = find_in_path(name, full_path, S_IFDIR, true, false); + char *pkg = NULL; + if (s != NULL) + pkg = s[0]; + + free(s); + arr_free(full_path); + return pkg; +} + +FILE * +pkg_open_file(char *pkg_path, char *file, char *mode) { + char *s = concat(pkg_path, "/", file, NULL); + FILE *f = fopen(s, mode); + if (f == NULL) + return NULL; + free(s); + return f; +} + void setup_colors(struct env *e) { if (!isatty(1) || e->color == 0) { @@ -222,6 +247,14 @@ setup_env(void) { e->sys_db = concat(e->root, "/var/db/kiss/installed", NULL); + t = getenv("XDG_CACHE_HOME"); + if (t == NULL || *t == '\0') { + t = getenv("HOME"); + e->cac_dir = concat(t, "/.cache/kiss", NULL); + } else { + e->cac_dir = concat(t, "/kiss", NULL); + } + time_t dt = time(NULL); struct tm *tm = localtime(&dt); if (tm == NULL) die_perror("localtime"); @@ -352,6 +385,7 @@ destroy_env(struct env *e) { free(e->path); free(e->sys_db); + free(e->cac_dir); free(e->pid); free(e); }