commit acfc85b9462f978e82e74e8e5e86e6dc0ba9506d
parent 99e92c6fe137a7bbb2b69efb811b586ade8605d0
Author: aabacchus <ben@bvnf.space>
Date: Sun, 23 Apr 2023 05:17:40 +0100
add checksum
Diffstat:
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);
}