/* _,.---._ .-._ .--.-. ,--.--------. * _,..---._ ,-.' , - `. /==/ \ .-._/==/ //==/, - , -\ * /==/, - \ /==/_, , - \|==|, \/ /, |==\ -\\==\.-. - ,-./ * |==| _ _\==| .=. |==|- \| | \==\- \`--`\==\- \ * |==| .=. |==|_ : ;=: - |==| , | -| `--`-' \==\_ \ * |==|,| | -|==| , '=' |==| - _ | |==|- | * |==| '=' /\==\ - ,_ /|==| /\ , | |==|, | * |==|-, _`/ '.='. - .' /==/, | |- | /==/ -/ * `-.`.____.' `--`--'' `--`./ `--` `--`--` * _ __ ,---. .-._ .=-.-. _,.----. * .-`.' ,`..--.' \ /==/ \ .-._ /==/_ /.' .' - \ * /==/, - \==\-/\ \ |==|, \/ /, /==|, |/==/ , ,-' * |==| _ .=. /==/-|_\ | |==|- \| ||==| ||==|- | . * |==| , '=',\==\, - \ |==| , | -||==|- ||==|_ `-' \ * |==|- '..'/==/ - ,| |==| - _ ||==| ,||==| _ , | * |==|, | /==/- /\ - \|==| /\ , ||==|- |\==\. / * /==/ - | \==\ _.\=\.-'/==/, | |- |/==/. / `-.`.___.-' * `--`---' `--` `--`./ `--``--`-` * * @(#)Copyright (c) 2023, Nathan D. Fisher. * * This is free software. It comes with NO WARRANTY. * Permission to use, modify and distribute this source code * is granted subject to the following conditions. * 1/ that the above copyright notice and this notice * are preserved in all copies and that due credit be given * to the author. * 2/ that any changes to this code are clearly commented * as such so that the author does not get blamed for bugs * other than his own. */ #include // PATH_MAX #include #include // uint_t #if defined(__FreeBSD__) || defined(__DragonFly__) #include #include #include #elif defined(__NetBSD__) || defined(__OpenBSD__) #include #include #include #elif defined(__linux__) #include #include #include // major, minor, dev_t #endif /* if defined (__FreeBSD__) */ #include #include // fopen, fread, fwrite, FILE #include // free, malloc #include // memcpy, strlen #include // readlink #include // stat #include "bytes.h" #include "haggis.h" #include "linklist.h" static unsigned char header[7] = {0x89, 'h', 'a', 'g', 'g', 'i', 's'}; int haggis_store_header(FILE *stream) { if (fwrite(header, 1, 7, stream) < 7) return 1; return 0; } int haggis_check_header(FILE *stream) { unsigned char *buf[7]; if (fread(buf, 1, 7, stream) < 7) return 1; if (memcmp(buf, header, 7)) return 2; return 0; } void haggis_device_init(dev_t rdev, haggis_device *dev) { dev->major.val = (uint32_t)major(rdev); dev->minor.val = (uint32_t)minor(rdev); } int haggis_store_device(FILE *stream, haggis_device *dev) { if (fwrite(dev->major.bytes, 1, 4, stream) != 4) return 1; if (fwrite(dev->minor.bytes, 1, 4, stream) != 4) return 1; return 0; } int haggis_load_device(FILE *stream, haggis_device *dev) { if (fread(dev->major.bytes, 1, 4, stream) != 4) return 1; if (fread(dev->minor.bytes, 1, 4, stream) != 4) return 1; return 0; } int haggis_store_cksum(FILE *stream, haggis_checksum *cksum) { u8 flag; switch (cksum->tag) { case md5: flag = 0; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (fwrite(cksum->sum.md5, 1, 16, stream) != 16) return 1; break; case sha1: flag = 1; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (fwrite(cksum->sum.sha1, 1, 20, stream) != 20) return 1; break; case sha256: flag = 2; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (fwrite(cksum->sum.sha256, 1, 32, stream) != 32) return 1; break; case skip: flag = 3; if (fwrite(&flag, 1, 1, stream) != 1) return 1; break; } return 0; } int haggis_load_cksum(FILE *stream, haggis_checksum *cksum) { u8 flag; if (fread(&flag, 1, 1, stream) != 1) return 1; switch (flag) { case md5: cksum->tag = 0; if (fread(&cksum->sum.md5, 1, 16, stream) != 16) return 1; break; case sha1: cksum->tag = 1; if (fread(&cksum->sum.sha1, 1, 20, stream) != 20) return 1; break; case sha256: cksum->tag = 2; if (fread(&cksum->sum.sha256, 1, 32, stream) != 32) return 1; break; case skip: cksum->tag = 3; break; } return 0; } void init_md5(haggis_file *f) { MD5_CTX ctx; f->cksum.tag = md5; MD5Init(&ctx); MD5Update(&ctx, f->data, (size_t)f->len.val); MD5Final(f->cksum.sum.md5, &ctx); } int validate_md5(haggis_file *file) { MD5_CTX ctx; u8 digest[16]; MD5Init(&ctx); MD5Update(&ctx, file->data, (size_t)file->len.val); MD5Final(digest, &ctx); if (memcmp(file->cksum.sum.md5, digest, sizeof(digest))) return 2; return 0; } #if defined(__FreeBSD__) || defined(__DragonFly__) void init_sha1(haggis_file *f) { SHA1_CTX ctx; f->cksum.tag = sha1; SHA1_Init(&ctx); SHA1_Update(&ctx, f->data, (size_t)f->len.val); SHA1_Final(f->cksum.sum.sha1, &ctx); } int validate_sha1(haggis_file *file) { SHA1_CTX ctx; u8 digest[20]; SHA1_Init(&ctx); SHA1_Update(&ctx, file->data, (size_t)file->len.val); SHA1_Final(digest, &ctx); if (memcmp(file->cksum.sum.sha1, digest, sizeof(digest))) return 2; return 0; } #elif defined(__linux__) || defined(__NetBSD__) || defined(__OpenBSD__) void init_sha1(haggis_file *f) { SHA1_CTX ctx; f->cksum.tag = sha1; SHA1_Init(&ctx); SHA1_Update(&ctx, f->data, (size_t)f->len.val); SHA1_Final(f->cksum.sum.sha1, &ctx); } int validate_sha1(haggis_file *file) { SHA1_CTX ctx; u8 digest[20]; SHA1Init(&ctx); SHA1Update(&ctx, file->data, (size_t)file->len.val); SHA1Final(digest, &ctx); if (memcmp(file->cksum.sum.sha1, digest, sizeof(digest))) return 2; return 0; } #endif /* if defined (__FreeBSD__) */ #if defined(__FreeBSD__) || defined(__DragonFly) || defined(__NetBSD__) void init_sha256(haggis_file *f) { SHA256_CTX ctx; f->cksum.tag = sha256; SHA256_Init(&ctx); SHA256_Update(&ctx, f->data, (size_t)f->len.val); SHA256_Final(f->cksum.sum.sha256, &ctx); } int validate_sha256(haggis_file *file) { SHA256_CTX ctx; u8 digest[32]; SHA256_Init(&ctx); SHA256_Update(&ctx, file->data, (size_t)file->len.val); SHA256_Final(digest, &ctx); if (memcmp(file->cksum.sum.sha256, digest, sizeof(digest))) return 2; return 0; } #elif defined(__linux__) || defined(__OpenBSD__) void init_sha256(haggis_file *f) { SHA2_CTX ctx; f->cksum.tag = sha256; SHA256_Init(&ctx); SHA256_Update(&ctx, f->data, (size_t)f->len.val); SHA256_Final(f->cksum.sum.sha256, &ctx); } int validate_sha256(haggis_file *file) { SHA2_CTX ctx; u8 digest[32]; SHA256Init(&ctx); SHA256Update(&ctx, file->data, (size_t)file->len.val); SHA256Final(digest, &ctx); if (memcmp(file->cksum.sum.sha256, digest, sizeof(digest))) return 2; return 0; } #endif /* if defined (__FreeBSD__) */ void haggis_init_cksum(haggis_file *f, haggis_algorithm a) { switch (a) { case md5: init_md5(f); break; case sha1: init_sha1(f); break; case sha256: init_sha256(f); break; case skip: break; } } int haggis_validate_cksum(haggis_file *file) { switch (file->cksum.tag) { case md5: return validate_md5(file); case sha1: return validate_sha1(file); case sha256: return validate_sha256(file); case skip: return 0; } return 0; } int haggis_file_init(char *path, haggis_file *hf, haggis_algorithm a) { FILE *f; long len; f = fopen(path, "r"); if (f == NULL) return 2; if (fseek(f, 0, SEEK_END) == -1) { fclose(f); return 2; } len = ftell(f); if (len == -1) { fclose(f); return 2; } hf->len.val = (uint64_t)len; rewind(f); hf->data = malloc((size_t)len); if (fread(hf->data, 1, (size_t)len, f) != (size_t)len) { free(hf->data); fclose(f); return 1; } fclose(f); haggis_init_cksum(hf, a); return 0; } void haggis_file_deinit(haggis_file *f) { if (f->data != NULL) free(f->data); } int haggis_store_file(FILE *stream, haggis_file *file) { if (store_u64(stream, file->len) != 8) return 1; if (haggis_store_cksum(stream, &file->cksum) != 0) return 1; int res = fwrite(file->data, 1, (size_t)file->len.val, stream); if (res != (size_t)file->len.val) return 1; return 0; } int haggis_load_file(FILE *stream, haggis_file *f) { if (load_u64(stream, f->len) != 8) return 1; if (haggis_load_cksum(stream, &f->cksum) != 0) return 1; f->data = malloc((size_t)f->len.val); if (f->data == NULL) return -1; int res = fread(f->data, 1, (size_t)f->len.val, stream); if (res != (size_t)f->len.val) { free(f->data); return 1; } if (haggis_validate_cksum(f)) { free(f->data); return 1; } return 0; } void haggis_filename_init(char *target, haggis_filename *fname) { size_t len; len = strlen(target) - 1; fname->len.val = (uint16_t)len; fname->name = target; } void haggis_filename_deinit(haggis_filename *fname) { if (fname->name != NULL) free(fname->name); } int haggis_load_filename(FILE *stream, haggis_filename *n) { u16 len; char *name; len.val = 0; if (fread(len.bytes, 1, 2, stream) != 2) return 2; n->len = len; name = malloc((size_t)len.val); if (name == NULL) return -1; if (fread(name, 1, (size_t)len.val, stream) != (size_t)len.val) { free(name); return 2; } n->name = name; return 0; } int haggis_store_filename(FILE *stream, haggis_filename *n) { if (fwrite(n->len.bytes, 1, 2, stream) != 2) return 2; if (fwrite(n->name, 1, (size_t)n->len.val, stream) != (size_t)n->len.val) return 2; return 0; } int haggis_load_filetype(FILE *stream, haggis_typeflag tag, haggis_filetype *file) { switch (tag) { case normal: file->tag = 0; return haggis_load_file(stream, &file->f_type.file); case hardlink: return haggis_load_filename(stream, &file->f_type.target); file->tag = 1; case softlink: return haggis_load_filename(stream, &file->f_type.target); file->tag = 2; case directory: file->tag = 3; break; case character: file->tag = 4; return haggis_load_device(stream, &file->f_type.dev); case block: file->tag = 5; return haggis_load_device(stream, &file->f_type.dev); case fifo: file->tag = 6; break; case eof: file->tag = 7; break; } return 0; } int haggis_store_filetype(FILE *stream, haggis_filetype *filetype) { u8 flag; switch (filetype->tag) { case normal: flag = 0; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (haggis_store_file(stream, &filetype->f_type.file) != 0) return 1; break; case hardlink: flag = 1; if (fwrite(&flag, 1, 1, stream) != 1) return 1; return haggis_store_filename(stream, &filetype->f_type.target); case softlink: flag = 2; if (fwrite(&flag, 1, 1, stream) != 1) return 1; return haggis_store_filename(stream, &filetype->f_type.target); case directory: flag = 3; if (fwrite(&flag, 1, 1, stream) != 1) return 1; break; case character: flag = 4; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (haggis_store_device(stream, &filetype->f_type.dev) != 0) return 1; break; case block: flag = 5; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (haggis_store_device(stream, &filetype->f_type.dev) != 0) return 1; break; case fifo: flag = 6; if (fwrite(&flag, 1, 1, stream) != 1) return 1; break; case eof: flag = 7; if (fwrite(&flag, 1, 1, stream) != 1) return 1; break; }; return 0; } haggis_typeflag haggis_filetype_from_mode(u16 mode) { u8 mask = 07 << 5; int filetype = (int)((mode.bytes[0] & mask) >> 5); return filetype; } u16 haggis_derive_mode(u16 raw, haggis_filetype *ft) { u16 mode; mode.val = ((uint16_t)ft->tag << 5) & raw.val; return mode; } void haggis_node_deinit(haggis_node *node) { if (node == NULL) return; if (node->name.name != NULL) haggis_filename_deinit(&node->name); switch (node->filetype.tag) { case normal: if (node->filetype.f_type.file.data != NULL) { free(node->filetype.f_type.file.data); } break; case hardlink: case softlink: if (node->filetype.f_type.target.name != NULL) { haggis_filename_deinit(&node->filetype.f_type.target); } break; case character: case block: case directory: case fifo: case eof: break; }; free(node); } haggis_node* haggis_create_node(char *file, haggis_algorithm a, haggis_hardlink_list *list) { struct stat *st = NULL; u16 mode; char *target; char pathbuf[PATH_MAX]; int res; haggis_node *node; node = malloc(sizeof(haggis_node)); if (node == NULL) return NULL; if (stat(file, st) != 0) { free(node); return NULL; } if (S_ISBLK(st->st_mode)) { node->filetype.tag = block; } else if (S_ISCHR(st->st_mode)) { node->filetype.tag = character; } else if (S_ISDIR(st->st_mode)) { node->filetype.tag = directory; } else if (S_ISFIFO(st->st_mode)) { node->filetype.tag = fifo; } else if (S_ISLNK(st->st_mode)) { node->filetype.tag = softlink; } else if (S_ISREG(st->st_mode)) { node->filetype.tag = normal; } else { free(node); return NULL; } node->uid.val = (uint32_t)st->st_uid; node->gid.val = (uint32_t)st->st_gid; node->mtime.val = (uint64_t)st->st_mtim.tv_sec; mode.val = (uint16_t)(st->st_mode & 07777); node->mode = mode; switch (node->filetype.tag) { case normal: if (st->st_nlink > 1) { target = haggis_linklist_get_or_put(list, st->st_ino, file); if (target != NULL) { node->filetype.tag = hardlink; haggis_filename_init(target, &node->filetype.f_type.target); return node; } } res = haggis_file_init(file, &node->filetype.f_type.file, a); if (res != 0) { haggis_node_deinit(node); return NULL; } break; case block: if (st->st_nlink > 1) { target = haggis_linklist_get_or_put(list, st->st_ino, file); if (target != NULL) { node->filetype.tag = hardlink; haggis_filename_init(target, &node->filetype.f_type.target); return node; } } haggis_device_init(st->st_rdev, &node->filetype.f_type.dev); break; case character: if (st->st_nlink > 1) { target = haggis_linklist_get_or_put(list, st->st_ino, file); if (target != NULL) { node->filetype.tag = hardlink; haggis_filename_init(target, &node->filetype.f_type.target); return node; } } haggis_device_init(st->st_rdev, &node->filetype.f_type.dev); break; case fifo: if (st->st_nlink > 1) { target = haggis_linklist_get_or_put(list, st->st_ino, file); if (target != NULL) { node->filetype.tag = hardlink; haggis_filename_init(target, &node->filetype.f_type.target); return node; } } return node; case directory: case hardlink: case eof: return node; case softlink: node->filetype.tag = softlink; ssize_t res = readlink(file, pathbuf, PATH_MAX); if (res == -1) { haggis_node_deinit(node); return NULL; } target = malloc(res + 1); memcpy(target, pathbuf, (unsigned long)res); haggis_filename_init(target, &node->filetype.f_type.target); return node; } return node; } int haggis_extract_node(FILE *stram, haggis_node *node) { // todo return 0; } int haggis_load_node(FILE *stream, haggis_node *node) { int res; u16 mode; haggis_typeflag tag; mode.val = 0; res = haggis_load_filename(stream, &node->name); if (res) return res; res = load_u32(stream, node->uid); if (res != 4) return 2; res = load_u32(stream, node->gid); if (res != 4) return 2; res = load_u64(stream, node->mtime); if (res != 8) return 2; res = load_u16(stream, mode); if (res != 2) return 2; tag = haggis_filetype_from_mode(mode); node->mode.bytes[0] = mode.bytes[0] & 037; node->mode.bytes[1] = mode.bytes[1]; res = haggis_load_filetype(stream, tag, &node->filetype); if (res) return res; return 0; } int haggis_store_node(FILE *stream, haggis_node *node) { u16 mode; if (haggis_store_filename(stream, &node->name) != (size_t)(node->name.len.val) + 2) return 2; if (store_u32(stream, node->uid) != 4) return 2; if (store_u32(stream, node->gid) != 4) return 2; if (store_u64(stream, node->mtime) != 8) return 2; mode = haggis_derive_mode(node->mode, &node->filetype); if (store_u16(stream, mode) != 2) return 2; return haggis_store_filetype(stream, &node->filetype); }