/* _,.---._ .-._ .--.-. ,--.--------. * _,..---._ ,-.' , - `. /==/ \ .-._/==/ //==/, - , -\ * /==/, - \ /==/_, , - \|==|, \/ /, |==\ -\\==\.-. - ,-./ * |==| _ _\==| .=. |==|- \| | \==\- \`--`\==\- \ * |==| .=. |==|_ : ;=: - |==| , | -| `--`-' \==\_ \ * |==|,| | -|==| , '=' |==| - _ | |==|- | * |==| '=' /\==\ - ,_ /|==| /\ , | |==|, | * |==|-, _`/ '.='. - .' /==/, | |- | /==/ -/ * `-.`.____.' `--`--'' `--`./ `--` `--`--` * _ __ ,---. .-._ .=-.-. _,.----. * .-`.' ,`..--.' \ /==/ \ .-._ /==/_ /.' .' - \ * /==/, - \==\-/\ \ |==|, \/ /, /==|, |/==/ , ,-' * |==| _ .=. /==/-|_\ | |==|- \| ||==| ||==|- | . * |==| , '=',\==\, - \ |==| , | -||==|- ||==|_ `-' \ * |==|- '..'/==/ - ,| |==| - _ ||==| ,||==| _ , | * |==|, | /==/- /\ - \|==| /\ , ||==|- |\==\. / * /==/ - | \==\ _.\=\.-'/==/, | |- |/==/. / `-.`.___.-' * `--`---' `--` `--`./ `--``--`-` * * @(#)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 #include #include #include #if defined(__FreeBSD__) || defined(__DragonFly__) #include #include #include #elif defined(__NetBSD__) || defined(__OpenBSD__) #include #include #include #elif defined(__linux__) #include #include #endif /* if defined (__FreeBSD__) */ #include #include #include #include #include #include #include #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 1; } haggis_device* haggis_device_init(dev_t rdev) { haggis_device *dev; dev = malloc(sizeof(haggis_device)); if (dev == NULL) return NULL; dev->major.val = (uint32_t)major(rdev); dev->minor.val = (uint32_t)minor(rdev); return dev; } void haggis_device_deinit(haggis_device *dev) { free(dev); } 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_ft *ft) { if (fread(ft->dev->major.bytes, 1, 4, stream) != 4) return 1; if (fread(ft->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; } 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__) 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__) 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__) 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__) 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__) */ 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; } haggis_file* haggis_file_init(char *path) { FILE *f; long len; haggis_file *hf; f = fopen(path, "r"); if (f == NULL) return NULL; if (fseek(f, 0, SEEK_END) == -1) { fclose(f); return NULL; } len = ftell(f); if (len == -1) { fclose(f); return NULL; } hf = malloc(sizeof(haggis_file)); if (hf == NULL) return NULL; 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); free(hf); fclose(f); return NULL; } fclose(f); return hf; } 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_ft *ft) { u64 len; len.val = 0; if (load_u64(stream, len) != 8) return 1; ft->file->len = len; if (haggis_load_cksum(stream, ft->file->cksum) != 0) return 1; u8 *data = malloc((size_t)len.val); if (data == NULL) return -1; int res = fread(data, 1, (size_t)ft->file->len.val, stream); if (res != (size_t)ft->file->len.val) { free(ft); return 1; } ft->file->data = data; if (haggis_validate_cksum(ft->file)) { free(ft); return 1; } return 0; } haggis_filename* haggis_filename_init(char *target) { size_t len; haggis_filename *fname; len = strlen(target) - 1; fname = malloc(sizeof(haggis_filename)); if (fname == NULL) { free(target); return NULL; } fname->len.val = (uint16_t)len; fname->name = target; return fname; } void haggis_filename_deinit(haggis_filename *fname) { if (fname->name != NULL) free(fname->name); free(fname); } 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); 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); case block: file->tag = 5; return haggis_load_device(stream, file->f_type); 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 != NULL) free(node->name); switch (node->filetype->tag) { case normal: if (node->filetype->f_type->file != NULL) { free(node->filetype->f_type->file); } break; case hardlink: case softlink: if (node->filetype->f_type->target != NULL) { haggis_filename_deinit(node->filetype->f_type->target); } break; case character: case block: if (node->filetype->f_type->dev != NULL) { haggis_device_deinit(node->filetype->f_type->dev); } break; case directory: case fifo: case eof: break; }; free(node); } haggis_node* haggis_create_node(char *file, haggis_hardlink_list *list) { struct stat *st = NULL; haggis_typeflag tf; u16 mode; u32 uid; u32 gid; u64 mtime; char *target; char pathbuf[PATH_MAX]; haggis_filename *fname; haggis_device *dev; haggis_file *f; haggis_node *node; node = malloc(sizeof(haggis_node)); if (node == NULL) return NULL; node->filetype = malloc(sizeof(haggis_filetype)); if (node->filetype == NULL) return NULL; if (stat(file, st) != 0) { free(node); return NULL; } if (S_ISBLK(st->st_mode)) { tf = block; } else if (S_ISCHR(st->st_mode)) { tf = character; } else if (S_ISDIR(st->st_mode)) { tf = directory; } else if (S_ISFIFO(st->st_mode)) { tf = fifo; } else if (S_ISLNK(st->st_mode)) { tf = softlink; } else if (S_ISREG(st->st_mode)) { tf = normal; } else { free(node); return NULL; } uid.val = (uint32_t)st->st_uid; node->uid = uid; gid.val = (uint32_t)st->st_gid; node->gid = gid; mtime.val = (uint64_t)st->st_mtim.tv_sec; node->mtime = mtime; mode.val = (uint16_t)(st->st_mode & 07777); node->mode = mode; switch (tf) { case normal: target = haggis_linklist_get_or_put(list, st->st_ino, file); if (target == NULL) { node->filetype->tag = normal; f = haggis_file_init(file); if (f == NULL) { haggis_node_deinit(node); return NULL; } } else { node->filetype->tag = hardlink; fname = haggis_filename_init(target); if (fname == NULL) { haggis_node_deinit(node); return NULL; } node->filetype->f_type->target = fname; } break; case block: target = haggis_linklist_get_or_put(list, st->st_ino, file); if (target == NULL) { node->filetype->tag = block; dev = haggis_device_init(st->st_rdev); if (dev == NULL) { haggis_node_deinit(node); return NULL; } node->filetype->f_type->dev = dev; } else { node->filetype->tag = hardlink; fname = haggis_filename_init(target); if (fname == NULL) { haggis_node_deinit(node); return NULL; } node->filetype->f_type->target = fname; } break; case character: target = haggis_linklist_get_or_put(list, st->st_ino, file); if (target == NULL) { node->filetype->tag = character; dev = haggis_device_init(st->st_rdev); if (dev == NULL) { haggis_node_deinit(node); return NULL; } node->filetype->f_type->dev = dev; } else { node->filetype->tag = hardlink; fname = haggis_filename_init(target); if (fname == NULL) { haggis_node_deinit(node); return NULL; } fname->name = target; node->filetype->f_type->target = fname; } break; case fifo: target = haggis_linklist_get_or_put(list, st->st_ino, file); if (target == NULL) { node->filetype->tag = fifo; } else { node->filetype->tag = hardlink; fname = haggis_filename_init(target); if (fname == NULL) { haggis_node_deinit(node); return NULL; } fname->name = target; node->filetype->f_type->target = fname; } break; case directory: node->filetype->tag = directory; break; case hardlink: node->filetype->tag = hardlink; break; case softlink: node->filetype->tag = softlink; ssize_t res = readlink(file, pathbuf, PATH_MAX); if (res == -1) { haggis_node_deinit(node); return NULL; } char *target = malloc(res + 1); memcpy(target, pathbuf, (unsigned long)res); node->filetype->f_type->target = haggis_filename_init(target); if (node->filetype->f_type->target == NULL) { haggis_node_deinit(node); return NULL; } break; case eof: node->filetype->tag = eof; break; } // todo 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); }