/* _,.---._ .-._ .--.-. ,--.--------. * _,..---._ ,-.' , - `. /==/ \ .-._/==/ //==/, - , -\ * /==/, - \ /==/_, , - \|==|, \/ /, |==\ -\\==\.-. - ,-./ * |==| _ _\==| .=. |==|- \| | \==\- \`--`\==\- \ * |==| .=. |==|_ : ;=: - |==| , | -| `--`-' \==\_ \ * |==|,| | -|==| , '=' |==| - _ | |==|- | * |==| '=' /\==\ - ,_ /|==| /\ , | |==|, | * |==|-, _`/ '.='. - .' /==/, | |- | /==/ -/ * `-.`.____.' `--`--'' `--`./ `--` `--`--` * _ __ ,---. .-._ .=-.-. _,.----. * .-`.' ,`..--.' \ /==/ \ .-._ /==/_ /.' .' - \ * /==/, - \==\-/\ \ |==|, \/ /, /==|, |/==/ , ,-' * |==| _ .=. /==/-|_\ | |==|- \| ||==| ||==|- | . * |==| , '=',\==\, - \ |==| , | -||==|- ||==|_ `-' \ * |==|- '..'/==/ - ,| |==| - _ ||==| ,||==| _ , | * |==|, | /==/- /\ - \|==| /\ , ||==|- |\==\. / * /==/ - | \==\ _.\=\.-'/==/, | |- |/==/. / `-.`.___.-' * `--`---' `--` `--`./ `--``--`-` * * @(#)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 "haggis.h" #include "mq.h" #include // assert #include // errno #include // dirname #include // PATH_MAX #include // size_t #include // uint_t #if defined(__FreeBSD__) || defined(__DragonFly__) #include #include #include // dev_t, makedev, major, minor #elif defined(__NetBSD__) || defined(__OpenBSD__) #include #include #include // dev_t, makedev, major, minor #elif defined(__linux__) #include #include #include // major, minor, dev_t #endif /* if defined (__FreeBSD__) */ #include #include // fopen, fread, fwrite, FILE #include // free, malloc, calloc #include // memcpy, strnlen, strndup #include // stat, lstat, mkdir, mknod #include // access, dirname, geteuid, readlink #include "haggis_private.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(haggis_device *self, dev_t rdev) { self->major.val = (uint32_t)major(rdev); self->minor.val = (uint32_t)minor(rdev); } int haggis_store_device(haggis_device *self, FILE *stream) { if (fwrite(self->major.bytes, 1, 4, stream) != 4) return 1; if (fwrite(self->minor.bytes, 1, 4, stream) != 4) return 1; return 0; } int haggis_load_device(haggis_device *self, FILE *stream) { if (fread(self->major.bytes, 1, 4, stream) != 4) return 1; if (fread(self->minor.bytes, 1, 4, stream) != 4) return 1; return 0; } int haggis_store_cksum(haggis_checksum *self, FILE *stream) { u8 flag; switch (self->tag) { case md5: flag = 0; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (fwrite(self->md5, 1, 16, stream) != 16) return 1; break; case sha1: flag = 1; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (fwrite(self->sha1, 1, 20, stream) != 20) return 1; break; case sha256: flag = 2; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (fwrite(self->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(haggis_checksum *self, FILE *stream) { u8 flag; if (fread(&flag, 1, 1, stream) != 1) return 1; switch (flag) { case md5: self->tag = 0; if (fread(&self->md5, 1, 16, stream) != 16) return 1; break; case sha1: self->tag = 1; if (fread(&self->sha1, 1, 20, stream) != 20) return 1; break; case sha256: self->tag = 2; if (fread(&self->sha256, 1, 32, stream) != 32) return 1; break; case skip: self->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.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.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.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.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; SHA1Init(&ctx); SHA1Update(&ctx, f->data, (size_t)f->len.val); SHA1Final(f->cksum.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.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.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.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; SHA256Init(&ctx); SHA256Update(&ctx, f->data, (size_t)f->len.val); SHA256Final(f->cksum.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.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(haggis_file *self, char *path, 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; } self->len.val = (uint64_t)len; rewind(f); self->data = calloc(1, (size_t)len); if (fread(self->data, 1, (size_t)len, f) != (size_t)len) { free(self->data); fclose(f); return 1; } fclose(f); haggis_init_cksum(self, a); return 0; } void haggis_file_deinit(haggis_file *f) { if (f->data != NULL) free(f->data); } int haggis_store_file(haggis_file *self, FILE *stream) { if (store_u64(stream, &self->len) != 8) return 1; if (haggis_store_cksum(&self->cksum, stream) != 0) return 1; int res = fwrite(self->data, 1, (size_t)self->len.val, stream); if (res != (size_t)self->len.val) return 1; return 0; } int haggis_load_file(haggis_file *self, FILE *stream) { if (load_u64(stream, &self->len) != 8) return 1; if (haggis_load_cksum(&self->cksum, stream) != 0) return 1; self->data = calloc(1, (size_t)self->len.val); if (self->data == NULL) return -1; int res = fread(self->data, 1, (size_t)self->len.val, stream); if (res != (size_t)self->len.val) { free(self->data); return 1; } if (haggis_validate_cksum(self)) { free(self->data); return 1; } return 0; } void haggis_filename_init(char *target, haggis_filename *fname) { size_t len; len = strlen(target); 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 = calloc(1, (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(&file->file, stream); case hardlink: return haggis_load_filename(stream, &file->target); file->tag = 1; case softlink: return haggis_load_filename(stream, &file->target); file->tag = 2; case directory: file->tag = 3; break; case character: file->tag = 4; return haggis_load_device(&file->dev, stream); case block: file->tag = 5; return haggis_load_device(&file->dev, stream); 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(&filetype->file, stream) != 0) return 1; break; case hardlink: flag = 1; if (fwrite(&flag, 1, 1, stream) != 1) return 1; return haggis_store_filename(stream, &filetype->target); case softlink: flag = 2; if (fwrite(&flag, 1, 1, stream) != 1) return 1; return haggis_store_filename(stream, &filetype->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(&filetype->dev, stream) != 0) return 1; break; case block: flag = 5; if (fwrite(&flag, 1, 1, stream) != 1) return 1; if (haggis_store_device(&filetype->dev, stream) != 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.file.data != NULL) { free(node->filetype.file.data); } break; case hardlink: case softlink: if (node->filetype.target.name != NULL) { haggis_filename_deinit(&node->filetype.target); } break; case character: case block: case directory: case fifo: case eof: break; }; free(node); } int haggis_init_hardlink_node( haggis_node *node, char *target, haggis_linkmap *map, haggis_mq *mq ) { haggis_message_body body; haggis_msg *msg; node->filetype.tag = hardlink; haggis_filename_init(target, &node->filetype.target); body.f_name = strndup(node->name.name, PATH_MAX); msg = haggis_msg_init(NodeCreated, body); if (msg == NULL) return 1; haggis_mq_push(mq, msg); return 0; } int haggis_init_file_node( haggis_node *node, struct stat *st, haggis_algorithm a, haggis_linkmap *map, haggis_mq *mq ) { haggis_message_body body; haggis_msg *msg; char *target; int res; if (st->st_nlink > 1) { target = haggis_linkmap_get_or_add(map, st->st_ino, node->name.name); if (target != NULL) { haggis_init_hardlink_node(node, target, map, mq); return 0; } } node->filetype.tag = normal; res = haggis_file_init(&node->filetype.file, node->name.name, a); if (res != 0) { haggis_node_deinit(node); return 1; } body.f_name = strndup(node->name.name, PATH_MAX); msg = haggis_msg_init(NodeCreated, body); haggis_mq_push(mq, msg); return 0; } int haggis_init_dev_node(haggis_node *node, struct stat *st, haggis_linkmap *map, haggis_mq *mq) { haggis_message_body body; haggis_msg *msg; char *target; if (st->st_nlink > 1) { target = haggis_linkmap_get_or_add(map, st->st_ino, node->name.name); if (target != NULL) { haggis_init_hardlink_node(node, target, map, mq); return 0; } } haggis_device_init(&node->filetype.dev, st->st_rdev); body.f_name = strndup(node->name.name, PATH_MAX); msg = haggis_msg_init(NodeCreated, body); haggis_mq_push(mq, msg); return 0; } haggis_node *haggis_create_node( char *file, haggis_algorithm a, haggis_linkmap *map, haggis_mq *mq ) { u16 mode; char *target; char pathbuf[PATH_MAX]; int res; size_t namlen; haggis_node *node; haggis_message_body body; haggis_msg *msg; struct stat st; node = calloc(1, sizeof(haggis_node)); if (node == NULL) return NULL; if (lstat(file, &st) != 0) { free(node); return NULL; } errno = 0; if (file[0] == '/') { namlen = strnlen(file, PATH_MAX - 1); node->name.name = strndup(&file[1], namlen); } else { namlen = strnlen(file, PATH_MAX) + 1; node->name.name = strndup(file, namlen); } if (errno == ENOMEM) { haggis_node_deinit(node); return NULL; } node->name.len.val = (uint16_t)namlen; 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; if (S_ISBLK(st.st_mode)) { node->filetype.tag = block; res = haggis_init_dev_node(node, &st, map, mq); if (res != 0) return NULL; } else if (S_ISCHR(st.st_mode)) { node->filetype.tag = character; res = haggis_init_dev_node(node, &st, map, mq); if (res != 0) return NULL; } else if (S_ISDIR(st.st_mode)) { node->filetype.tag = directory; body.f_name = strndup(file, PATH_MAX); msg = haggis_msg_init(NodeCreated, body); haggis_mq_push(mq, msg); } else if (S_ISFIFO(st.st_mode)) { node->filetype.tag = fifo; if (st.st_nlink > 1) { target = haggis_linkmap_get_or_add(map, st.st_ino, file); if (target != NULL) { haggis_init_hardlink_node(node, target, map, mq); return node; } } body.f_name = strndup(file, PATH_MAX); msg = haggis_msg_init(NodeCreated, body); haggis_mq_push(mq, msg); } else if (S_ISLNK(st.st_mode)) { 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.target); body.f_name = strndup(file, PATH_MAX); msg = haggis_msg_init(NodeCreated, body); haggis_mq_push(mq, msg); } else if (S_ISREG(st.st_mode)) { res = haggis_init_file_node(node, &st, a, map, mq); if (res != 0) return NULL; } else { free(node); return NULL; } return node; } char *get_full_path(haggis_filename *fname, char *basedir) { char *path; int pathlen; if (basedir == NULL) { path = calloc(1, (int)fname->len.val + 1); if (path == NULL) return NULL; memcpy(path, fname->name, fname->len.val); } else { if (fname->name[0] == '/') { pathlen = strnlen(basedir, PATH_MAX) + (int)fname->len.val + 1; path = calloc(1, pathlen); if (path == NULL) return NULL; snprintf(path, pathlen, "%s%s", basedir, fname->name); } else { pathlen = strnlen(basedir, PATH_MAX) + (int)fname->len.val + 2; path = calloc(1, pathlen); if (path == NULL) return NULL; snprintf(path, pathlen, "%s/%s", basedir, fname->name); } } return path; } int mkdir_p(char *dir) { struct stat st; if (!stat(dir, &st)) return 0; if (mkdir_p(dirname(strndup(dir, PATH_MAX)))) return 1; return mkdir(dir, 0755); } int haggis_extract_dev(haggis_node *node, char *basedir) { dev_t dev; mode_t mode = 0; char *path; int ret, major, minor; assert(geteuid() == 0); assert(node->filetype.tag == block || node->filetype.tag == character); path = get_full_path(&node->name, basedir); if (path == NULL) return errno; if (mkdir_p(dirname(strndup(path, PATH_MAX)))) { free(path); return 2; } major = (int)node->filetype.dev.major.val; minor = (int)node->filetype.dev.minor.val; dev = makedev(major, minor); if (node->filetype.tag == block) { mode = (mode_t)node->mode.val | S_IFBLK; } else if (node->filetype.tag == character) { mode = (mode_t)node->mode.val | S_IFCHR; } if (access(path, F_OK) == 0) unlink(path); ret = mknod(path, mode, dev); free(path); return ret; } int haggis_extract_fifo(haggis_node *node, char *basedir) { mode_t mode; char *path; int ret; assert(node->filetype.tag == fifo); path = get_full_path(&node->name, basedir); if (path == NULL) return 1; if (mkdir_p(dirname(strndup(path, PATH_MAX)))) { free(path); return 2; } mode = (mode_t)node->mode.val; if (access(path, F_OK) == 0) unlink(path); ret = mkfifo(path, mode); free(path); if (ret != 0) return errno; return 0; } int haggis_extract_symlink(haggis_node *node, char *basedir) { char *path, *target; int ret; struct stat st; assert(node->filetype.tag == softlink); path = get_full_path(&node->name, basedir); if (path == NULL) return 1; if (mkdir_p(dirname(strndup(path, PATH_MAX)))) { free(path); return 2; } target = node->filetype.target.name; if (lstat(path, &st) == 0) unlink(path); ret = symlink(target, path); free(path); if (ret != 0) return errno; return 0; } int haggis_extract_hardlink(haggis_node *node, char *basedir) { char *path, *target; int ret; FILE *fd; assert(node->filetype.tag == hardlink); path = get_full_path(&node->name, basedir); if (path == NULL) return 1; if (mkdir_p(dirname(strndup(path, PATH_MAX)))) { free(path); return 2; } target = get_full_path(&node->filetype.target, basedir); if (target == NULL) { free(path); return 1; } if (access(target, F_OK) == -1) { fd = fopen(target, "w"); if (mkdir_p(dirname(strndup(target, PATH_MAX)))) { free(path); return 2; } if (fd == NULL) { free(path); free(target); return errno; } fclose(fd); } if (access(path, F_OK) == 0) unlink(path); ret = link(target, path); free(path); free(target); if (ret != 0) return errno; return 0; } int haggis_extract_dir(haggis_node *node, char *basedir) { char *path; assert(node->filetype.tag == directory); path = get_full_path(&node->name, basedir); if (mkdir_p(path)) { free(path); return errno; } free(path); return 0; } char *haggis_extract_file(haggis_node *node, char *basedir) { char *path; FILE *fd; size_t len; int ret; assert(node->filetype.tag == normal); path = get_full_path(&node->name, basedir); if (path == NULL) { return NULL; } if (mkdir_p(dirname(strndup(path, PATH_MAX)))) { free(path); return NULL; } fd = fopen(path, "w+"); if (fd == NULL) { free(path); return NULL; } len = (size_t)node->filetype.file.len.val; ret = fwrite(node->filetype.file.data, len, 1, fd); if (ret != len) { free(path); fclose(fd); return NULL; } fflush(fd); fclose(fd); return path; } int haggis_extract_node(haggis_node *self, char *basedir, haggis_mq *mq) { haggis_msg *msg; char *path, *dir, *fullpath; int ret = 0; path = get_full_path(&self->name, basedir); dir = dirname(strndup(path, PATH_MAX)); mkdir_p(dir); switch (self->filetype.tag) { case block: case character: if (geteuid() == 0) { ret = haggis_extract_dev(self, basedir); } else { ret = -1; msg = calloc(1, sizeof(haggis_msg)); if (msg == NULL) return 2; msg->tag = DevNodeSkipped; msg->body.f_name = strndup(self->name.name, PATH_MAX); haggis_mq_push(mq, msg); } break; case fifo: ret = haggis_extract_fifo(self, basedir); break; case softlink: ret = haggis_extract_symlink(self, basedir); break; case hardlink: ret = haggis_extract_hardlink(self, basedir); break; case directory: ret = haggis_extract_dir(self, basedir); break; case normal: fullpath = haggis_extract_file(self, basedir); if (fullpath == NULL) { ret = errno; } break; case eof: return 0; } if (ret) return ret; if (geteuid() == 0) { ret = chown(path, (uid_t)self->uid.val, (gid_t)self->gid.val); if (ret != 0) { free(path); return errno; } } if (self->filetype.tag != softlink) ret = chmod(path, (mode_t)self->mode.val); if (ret) return ret; msg = calloc(1, sizeof(haggis_msg)); if (msg == NULL) return 2; msg->tag = NodeExtracted; msg->body.f_name = strndup(self->name.name, PATH_MAX); haggis_mq_push(mq, msg); return ret; } int haggis_load_node(haggis_node *self, FILE *stream) { int res; u16 mode; haggis_typeflag tag; mode.val = 0; res = haggis_load_filename(stream, &self->name); if (res) return res; res = load_u32(stream, &self->uid); if (res != 4) return 2; res = load_u32(stream, &self->gid); if (res != 4) return 2; res = load_u64(stream, &self->mtime); if (res != 8) return 2; res = load_u16(stream, &mode); if (res != 2) return 2; tag = haggis_filetype_from_mode(mode); self->mode.bytes[0] = mode.bytes[0] & 037; self->mode.bytes[1] = mode.bytes[1]; res = haggis_load_filetype(stream, tag, &self->filetype); if (res) return res; return 0; } int haggis_store_node(haggis_node *self, FILE *stream) { u16 mode; if (haggis_store_filename(stream, &self->name) != (size_t)(self->name.len.val) + 2) return 2; if (store_u32(stream, &self->uid) != 4) return 2; if (store_u32(stream, &self->gid) != 4) return 2; if (store_u64(stream, &self->mtime) != 8) return 2; mode = haggis_derive_mode(self->mode, &self->filetype); if (store_u16(stream, &mode) != 2) return 2; return haggis_store_filetype(stream, &self->filetype); }