aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgithub-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com>2022-01-24 22:06:00 +0000
committergithub-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com>2022-01-24 22:06:00 +0000
commit851ae290b2d24057f8426b42f97c9924e658a18d (patch)
tree2f3adf22b7ae76e2fa67e0ddc7dde8728fb0d24b
Initial commit
-rw-r--r--.clang-format93
-rw-r--r--.github/classroom/autograding.json22
-rw-r--r--.github/workflows/classroom.yml16
-rw-r--r--.gitignore13
-rw-r--r--Makefile34
-rwxr-xr-xcheck-files.py38
-rw-r--r--ext2fs.c399
-rw-r--r--ext2fs.h48
-rw-r--r--ext2fs_defs.h226
-rw-r--r--ext2fuse.c205
-rw-r--r--ext2list.c116
-rw-r--r--ext2test.c311
-rw-r--r--files.sha25616
-rwxr-xr-xgrade.py193
-rw-r--r--listfs.c86
-rw-r--r--md5.h48
-rw-r--r--md5c.c323
-rwxr-xr-xrun-clang-format.sh20
18 files changed, 2207 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..e366c93
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,93 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 2
+ContinuationIndentWidth: 2
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+ForEachMacros: [ TAILQ_FOREACH, SPLAY_FOREACH, RB_FOREACH, WITH_MTX_LOCK,
+ WITH_SPIN_LOCK, WITH_RW_LOCK, SET_FOREACH, LIST_FOREACH,
+ TAILQ_FOREACH_REVERSE, TAILQ_FOREACH_SAFE,
+ LIST_FOREACH_SAFE, WITH_VM_MAP_LOCK ]
+IncludeCategories:
+ - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
+ Priority: 2
+ - Regex: '^(<|"(gtest|isl|json)/)'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 1
+IndentCaseLabels: true
+IndentWidth: 2
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Right
+ReflowComments: true
+SortIncludes: false
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 8
+UseTab: Never
+...
+
diff --git a/.github/classroom/autograding.json b/.github/classroom/autograding.json
new file mode 100644
index 0000000..53606f3
--- /dev/null
+++ b/.github/classroom/autograding.json
@@ -0,0 +1,22 @@
+{
+ "tests": [
+ {
+ "name": "Build",
+ "run": "make",
+ "input": "",
+ "output": "",
+ "comparison": "included",
+ "timeout": 10,
+ "points": 1
+ },
+ {
+ "name": "Grade solution",
+ "run": "make grade",
+ "input": "",
+ "output": "",
+ "comparison": "included",
+ "timeout": 10,
+ "points": 1
+ }
+ ]
+}
diff --git a/.github/workflows/classroom.yml b/.github/workflows/classroom.yml
new file mode 100644
index 0000000..ba27568
--- /dev/null
+++ b/.github/workflows/classroom.yml
@@ -0,0 +1,16 @@
+name: GitHub Classroom Workflow
+
+on: [push]
+
+jobs:
+ build:
+ name: Autograding
+ runs-on: ubuntu-latest
+ container: cahirwpz/ii-so:latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Check code formatting
+ run: ./run-clang-format.sh
+ - name: Check for unauthorized modifications
+ run: ./check-files.py
+ - uses: education/autograding@v1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c05f602
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+# Compiled languages
+*.o
+*.so
+*.a
+.*.d
+ext2fuse
+ext2test
+ext2list
+listfs
+
+# Vim
+*.swp
+*~
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..89e5214
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,34 @@
+CC = gcc -fsanitize=address -g
+CPPFLAGS += -DSTUDENT
+CFLAGS = -Og -Wall -Wextra -Werror
+
+all: ext2test ext2list listfs
+
+ext2fs.o: ext2fs.c ext2fs.h ext2fs_defs.h
+md5c.o: md5c.c md5.h
+
+ext2fuse: ext2fuse.o ext2fs.o
+ext2fuse: LDLIBS += $(shell pkg-config --libs fuse)
+ext2fuse.o: ext2fuse.c ext2fs.h ext2fs_defs.h
+ext2fuse.o: CFLAGS += $(shell pkg-config --cflags fuse)
+
+ext2test: ext2test.o ext2fs.o
+ext2test: LDLIBS += -lreadline
+ext2test.o: ext2test.c ext2fs.h ext2fs_defs.h
+
+ext2list: ext2list.o ext2fs.o md5c.o
+ext2list.o: ext2test.c ext2fs.h ext2fs_defs.h md5.h
+
+listfs: listfs.o md5c.o
+listfs.o: listfs.c md5.h
+
+grade:
+ ./grade.py
+
+format:
+ clang-format -i *.c *.h
+
+clean:
+ rm -f *~ *.o ext2fuse ext2test ext2list listfs
+
+# vim: ts=8 sw=8 noet
diff --git a/check-files.py b/check-files.py
new file mode 100755
index 0000000..c993d88
--- /dev/null
+++ b/check-files.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+import hashlib
+import io
+import subprocess
+
+STUDENT_CODE = ['ext2fs.c']
+
+
+def remove_solution(path):
+ drop = False
+ lines = []
+
+ for line in open(path).readlines():
+ if line.startswith('#endif /* !STUDENT */'):
+ drop = False
+ if not drop:
+ lines.append(line)
+ if line.startswith('#ifdef STUDENT'):
+ drop = True
+
+ return ''.join(lines).encode('utf-8')
+
+
+if __name__ == '__main__':
+ for line in open('files.sha256').readlines():
+ sha_orig, path = line.split()
+ if path in STUDENT_CODE:
+ contents = remove_solution(path)
+ else:
+ contents = open(path, 'rb').read()
+ sha_new = hashlib.sha256(contents).hexdigest()
+ if sha_orig != sha_new:
+ raise SystemExit(
+ f'Unauthorized modification of {path} file!\n'
+ f'SHA sum: {sha_new} vs {sha_orig} (original)')
+
+ print('No unauthorized changes to source files.')
diff --git a/ext2fs.c b/ext2fs.c
new file mode 100644
index 0000000..176742e
--- /dev/null
+++ b/ext2fs.c
@@ -0,0 +1,399 @@
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdalign.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ext2fs_defs.h"
+#include "ext2fs.h"
+
+/* If you want debugging output, use the following macro. When you hand
+ * in, remove the #define DEBUG line. */
+#undef DEBUG
+#ifdef DEBUG
+#define debug(...) printf(__VA_ARGS__)
+#else
+#define debug(...)
+#endif
+
+/* Call this function when an unfixable error has happened. */
+static noreturn void panic(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+/* Number of lists containing buffered blocks. */
+#define NBUCKETS 16
+
+/* Since majority of files in a filesystem are small, `idx` values will be
+ * usually low. Since ext2fs tends to allocate blocks at the beginning of each
+ * block group, `ino` values are less predictable. */
+#define BUCKET(ino, idx) (((ino) + (idx)) % NBUCKETS)
+
+/* That should give us around 64kB worth of buffers. */
+#define NBLOCKS (NBUCKETS * 4)
+
+/* Structure that is used to manage buffer of single block. */
+typedef struct blk {
+ TAILQ_ENTRY(blk) b_hash;
+ TAILQ_ENTRY(blk) b_link;
+ uint32_t b_blkaddr; /* block address on the block device */
+ uint32_t b_inode; /* i-node number of file this buffer refers to */
+ uint32_t b_index; /* block index from the beginning of file */
+ uint32_t b_refcnt; /* if zero then block can be reused */
+ void *b_data; /* raw data from this buffer */
+} blk_t;
+
+typedef TAILQ_HEAD(blk_list, blk) blk_list_t;
+
+/* BLK_ZERO is a special value that reflect the fact that block 0 may be used to
+ * represent a block filled with zeros. You must not dereference the value! */
+#define BLK_ZERO ((blk_t *)-1L)
+
+/* All memory for buffers and buffer management is allocated statically.
+ * Using malloc for these would introduce unnecessary complexity. */
+static alignas(BLKSIZE) char blkdata[NBLOCKS][BLKSIZE];
+static blk_t blocks[NBLOCKS];
+static blk_list_t buckets[NBUCKETS]; /* all blocks with valid data */
+static blk_list_t lrulst; /* free blocks with valid data */
+static blk_list_t freelst; /* free blocks that are empty */
+
+/* File descriptor that refers to ext2 filesystem image. */
+static int fd_ext2 = -1;
+
+/* How many i-nodes fit into one block? */
+#define BLK_INODES (BLKSIZE / sizeof(ext2_inode_t))
+
+/* How many block pointers fit into one block? */
+#define BLK_POINTERS (BLKSIZE / sizeof(uint32_t))
+
+/* Properties extracted from a superblock and block group descriptors. */
+static size_t inodes_per_group; /* number of i-nodes in block group */
+static size_t blocks_per_group; /* number of blocks in block group */
+static size_t group_desc_count; /* numbre of block group descriptors */
+static size_t block_count; /* number of blocks in the filesystem */
+static size_t inode_count; /* number of i-nodes in the filesystem */
+static size_t first_data_block; /* first block managed by block bitmap */
+static ext2_groupdesc_t *group_desc; /* block group descriptors in memory */
+
+/*
+ * Buffering routines.
+ */
+
+/* Opens filesystem image file and initializes block buffers. */
+static int blk_init(const char *fspath) {
+ if ((fd_ext2 = open(fspath, O_RDONLY)) < 0)
+ return errno;
+
+ /* Initialize list structures. */
+ TAILQ_INIT(&lrulst);
+ TAILQ_INIT(&freelst);
+ for (int i = 0; i < NBUCKETS; i++)
+ TAILQ_INIT(&buckets[i]);
+
+ /* Initialize all blocks and put them on free list. */
+ for (int i = 0; i < NBLOCKS; i++) {
+ blocks[i].b_data = blkdata[i];
+ TAILQ_INSERT_TAIL(&freelst, &blocks[i], b_link);
+ }
+
+ return 0;
+}
+
+/* Allocates new block buffer. */
+static blk_t *blk_alloc(void) {
+ blk_t *blk = NULL;
+
+ /* Initially every empty block is on free list. */
+ if (!TAILQ_EMPTY(&freelst)) {
+#ifdef STUDENT
+ /* TODO */
+#endif /* !STUDENT */
+ return blk;
+ }
+
+ /* Eventually free list will become exhausted.
+ * Then we'll take the last recently used entry from LRU list. */
+ if (!TAILQ_EMPTY(&lrulst)) {
+#ifdef STUDENT
+ /* TODO */
+#endif /* !STUDENT */
+ return blk;
+ }
+
+ /* No buffers!? Have you forgot to release some? */
+ panic("Free buffers pool exhausted!");
+}
+
+/* Acquires a block buffer for file identified by `ino` i-node and block index
+ * `idx`. When `ino` is zero the buffer refers to filesystem metadata (i.e.
+ * superblock, block group descriptors, block & i-node bitmap, etc.) and `off`
+ * offset is given from the start of block device. */
+static blk_t *blk_get(uint32_t ino, uint32_t idx) {
+ blk_list_t *bucket = &buckets[BUCKET(ino, idx)];
+ blk_t *blk = NULL;
+
+ /* Locate a block in the buffer and return it if found. */
+#ifdef STUDENT
+ /* TODO */
+#endif /* !STUDENT */
+
+ long blkaddr = ext2_blkaddr_read(ino, idx);
+ debug("ext2_blkaddr_read(%d, %d) -> %ld\n", ino, idx, blkaddr);
+ if (blkaddr == -1)
+ return NULL;
+ if (blkaddr == 0)
+ return BLK_ZERO;
+ if (ino > 0 && !ext2_block_used(blkaddr))
+ panic("Attempt to read block %d that is not in use!", blkaddr);
+
+ blk = blk_alloc();
+ blk->b_inode = ino;
+ blk->b_index = idx;
+ blk->b_blkaddr = blkaddr;
+ blk->b_refcnt = 1;
+
+ ssize_t nread =
+ pread(fd_ext2, blk->b_data, BLKSIZE, blk->b_blkaddr * BLKSIZE);
+ if (nread != BLKSIZE)
+ panic("Attempt to read past the end of filesystem!");
+
+ TAILQ_INSERT_HEAD(bucket, blk, b_hash);
+ return blk;
+}
+
+/* Releases a block buffer. If reference counter hits 0 the buffer can be
+ * reused to cache another block. The buffer is put at the beginning of LRU list
+ * of unused blocks. */
+static void blk_put(blk_t *blk) {
+ if (--blk->b_refcnt > 0)
+ return;
+
+ TAILQ_INSERT_HEAD(&lrulst, blk, b_link);
+}
+
+/*
+ * Ext2 filesystem routines.
+ */
+
+/* Reads block bitmap entry for `blkaddr`. Returns 0 if the block is free,
+ * 1 if it's in use, and EINVAL if `blkaddr` is out of range. */
+int ext2_block_used(uint32_t blkaddr) {
+ if (blkaddr >= block_count)
+ return EINVAL;
+ int used = 0;
+#ifdef STUDENT
+ /* TODO */
+#endif /* !STUDENT */
+ return used;
+}
+
+/* Reads i-node bitmap entry for `ino`. Returns 0 if the i-node is free,
+ * 1 if it's in use, and EINVAL if `ino` value is out of range. */
+int ext2_inode_used(uint32_t ino) {
+ if (!ino || ino >= inode_count)
+ return EINVAL;
+ int used = 0;
+#ifdef STUDENT
+ /* TODO */
+#endif /* !STUDENT */
+ return used;
+}
+
+/* Reads i-node identified by number `ino`.
+ * Returns 0 on success. If i-node is not allocated returns ENOENT. */
+static int ext2_inode_read(off_t ino, ext2_inode_t *inode) {
+#ifdef STUDENT
+ /* TODO */
+ (void)ino;
+ (void)inode;
+ return ENOENT;
+#endif /* !STUDENT */
+ return 0;
+}
+
+/* Returns block pointer `blkidx` from block of `blkaddr` address. */
+static uint32_t ext2_blkptr_read(uint32_t blkaddr, uint32_t blkidx) {
+#ifdef STUDENT
+ /* TODO */
+ (void)blkaddr;
+ (void)blkidx;
+#endif /* !STUDENT */
+ return 0;
+}
+
+/* Translates i-node number `ino` and block index `idx` to block address.
+ * Returns -1 on failure, otherwise block address. */
+long ext2_blkaddr_read(uint32_t ino, uint32_t blkidx) {
+ /* No translation for filesystem metadata blocks. */
+ if (ino == 0)
+ return blkidx;
+
+ ext2_inode_t inode;
+ if (ext2_inode_read(ino, &inode))
+ return -1;
+
+ /* Read direct pointers or pointers from indirect blocks. */
+#ifdef STUDENT
+ /* TODO */
+ (void)ext2_blkptr_read;
+#endif /* !STUDENT */
+ return -1;
+}
+
+/* Reads exactly `len` bytes starting from `pos` position from any file (i.e.
+ * regular, directory, etc.) identified by `ino` i-node. Returns 0 on success,
+ * EINVAL if `pos` and `len` would have pointed past the last block of file.
+ *
+ * WARNING: This function assumes that `ino` i-node pointer is valid! */
+int ext2_read(uint32_t ino, void *data, size_t pos, size_t len) {
+#ifdef STUDENT
+ /* TODO */
+ (void)ino;
+ (void)data;
+ (void)pos;
+ (void)len;
+ (void)blk_get;
+ (void)blk_put;
+#endif /* !STUDENT */
+ return EINVAL;
+}
+
+/* Reads a directory entry at position stored in `off_p` from `ino` i-node that
+ * is assumed to be a directory file. The entry is stored in `de` and
+ * `de->de_name` must be NUL-terminated. Assumes that entry offset is 0 or was
+ * set by previous call to `ext2_readdir`. Returns 1 on success, 0 if there are
+ * no more entries to read. */
+#define de_name_offset offsetof(ext2_dirent_t, de_name)
+
+int ext2_readdir(uint32_t ino, uint32_t *off_p, ext2_dirent_t *de) {
+#ifdef STUDENT
+ /* TODO */
+ (void)ino;
+ (void)off_p;
+ (void)de;
+#endif /* !STUDENT */
+ return 0;
+}
+
+/* Read the target of a symbolic link identified by `ino` i-node into buffer
+ * `buf` of size `buflen`. Returns 0 on success, EINVAL if the file is not a
+ * symlink or read failed. */
+int ext2_readlink(uint32_t ino, char *buf, size_t buflen) {
+ int error;
+
+ ext2_inode_t inode;
+ if ((error = ext2_inode_read(ino, &inode)))
+ return error;
+
+ /* Check if it's a symlink and read it. */
+#ifdef STUDENT
+ /* TODO */
+ (void)buf;
+ (void)buflen;
+#endif /* !STUDENT */
+ return ENOTSUP;
+}
+
+/* Read metadata from file identified by `ino` i-node and convert it to
+ * `struct stat`. Returns 0 on success, or error if i-node could not be read. */
+int ext2_stat(uint32_t ino, struct stat *st) {
+ int error;
+
+ ext2_inode_t inode;
+ if ((error = ext2_inode_read(ino, &inode)))
+ return error;
+
+ /* Convert the metadata! */
+#ifdef STUDENT
+ /* TODO */
+ (void)st;
+#endif /* !STUDENT */
+ return ENOTSUP;
+}
+
+/* Reads file identified by `ino` i-node as directory and performs a lookup of
+ * `name` entry. If an entry is found, its i-inode number is stored in `ino_p`
+ * and its type in stored in `type_p`. On success returns 0, or EINVAL if `name`
+ * is NULL or zero length, or ENOTDIR is `ino` file is not a directory, or
+ * ENOENT if no entry was found. */
+int ext2_lookup(uint32_t ino, const char *name, uint32_t *ino_p,
+ uint8_t *type_p) {
+ int error;
+
+ if (name == NULL || !strlen(name))
+ return EINVAL;
+
+ ext2_inode_t inode;
+ if ((error = ext2_inode_read(ino, &inode)))
+ return error;
+
+#ifdef STUDENT
+ /* TODO */
+ (void)ino_p;
+ (void)type_p;
+#endif /* !STUDENT */
+
+ return ENOENT;
+}
+
+/* Initializes ext2 filesystem stored in `fspath` file.
+ * Returns 0 on success, otherwise an error. */
+int ext2_mount(const char *fspath) {
+ int error;
+
+ if ((error = blk_init(fspath)))
+ return error;
+
+ /* Read superblock and verify we support filesystem's features. */
+ ext2_superblock_t sb;
+ ext2_read(0, &sb, EXT2_SBOFF, sizeof(ext2_superblock_t));
+
+ debug(">>> super block\n"
+ "# of inodes : %d\n"
+ "# of blocks : %d\n"
+ "block size : %ld\n"
+ "blocks per group : %d\n"
+ "inodes per group : %d\n"
+ "inode size : %d\n",
+ sb.sb_icount, sb.sb_bcount, 1024UL << sb.sb_log_bsize, sb.sb_bpg,
+ sb.sb_ipg, sb.sb_inode_size);
+
+ if (sb.sb_magic != EXT2_MAGIC)
+ panic("'%s' cannot be identified as ext2 filesystem!", fspath);
+
+ if (sb.sb_rev != EXT2_REV1)
+ panic("Only ext2 revision 1 is supported!");
+
+ size_t blksize = 1024UL << sb.sb_log_bsize;
+ if (blksize != BLKSIZE)
+ panic("ext2 filesystem with block size %ld not supported!", blksize);
+
+ if (sb.sb_inode_size != sizeof(ext2_inode_t))
+ panic("The only i-node size supported is %d!", sizeof(ext2_inode_t));
+
+ /* Load interesting data from superblock into global variables.
+ * Read group descriptor table into memory. */
+#ifdef STUDENT
+ /* TODO */
+ (void)inodes_per_group;
+ (void)blocks_per_group;
+ (void)group_desc_count;
+ (void)block_count;
+ (void)inode_count;
+ (void)first_data_block;
+ (void)group_desc;
+#endif /* !STUDENT */
+ return ENOTSUP;
+}
diff --git a/ext2fs.h b/ext2fs.h
new file mode 100644
index 0000000..7d19b21
--- /dev/null
+++ b/ext2fs.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <sys/cdefs.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include "ext2fs_defs.h"
+
+#ifndef min
+#define min(a, b) \
+ ({ \
+ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
+ _a < _b ? _a : _b; \
+ })
+#endif
+
+#ifndef howmany
+#define howmany(x, y) (((x) + (y)-1) / (y))
+#endif
+
+#ifndef __unused
+#define __unused __attribute__((unused))
+#endif
+
+#define BLKSIZE 1024UL /* size of data stored in the buffer */
+
+/*
+ * Extended filesystem 2 types and functions.
+ */
+
+/* Low-level functions. */
+int ext2_block_used(uint32_t blkaddr);
+int ext2_inode_used(uint32_t ino);
+long ext2_blkaddr_read(uint32_t ino, uint32_t blkidx);
+
+/* High-level functions. */
+int ext2_read(uint32_t ino, void *data, size_t pos, size_t len);
+int ext2_readdir(uint32_t ino, uint32_t *offp, ext2_dirent_t *de);
+int ext2_readlink(uint32_t ino, char *buf, size_t buflen);
+int ext2_stat(uint32_t ino, struct stat *st);
+int ext2_lookup(uint32_t ino, const char *name, uint32_t *ino_p,
+ uint8_t *type_p);
+int ext2_mount(const char *imgpath);
diff --git a/ext2fs_defs.h b/ext2fs_defs.h
new file mode 100644
index 0000000..4f6cc9b
--- /dev/null
+++ b/ext2fs_defs.h
@@ -0,0 +1,226 @@
+#pragma once
+
+/* Based on NetBSD's header files from /sys/ufs/ext2fs,
+ * namely: ext2fs.h, ext2fs_dinode.h, ext2fs_dir.h */
+
+#include <sys/cdefs.h>
+#include <stdint.h>
+
+/*
+ * The first super block and block group descriptors offsets are given in
+ * absolute disk addresses.
+ */
+#define EXT2_SBOFF ((off_t)1024)
+#define EXT2_GDOFF ((off_t)2048)
+
+/*
+ * Filesystem identification
+ */
+#define EXT2_MAGIC 0xEF53 /* the ext2fs magic number */
+#define EXT2_REV0 0 /* GOOD_OLD revision */
+#define EXT2_REV1 1 /* Support compat/incompat features */
+
+/*
+ * File system super block.
+ */
+typedef struct ext2_superblock {
+ uint32_t sb_icount; /* Inode count */
+ uint32_t sb_bcount; /* blocks count */
+ uint32_t sb_rbcount; /* reserved blocks count */
+ uint32_t sb_fbcount; /* free blocks count */
+ uint32_t sb_ficount; /* free inodes count */
+ uint32_t sb_first_dblock; /* first data block */
+ uint32_t sb_log_bsize; /* bsize = 1024*(2^sb_log_bsize) */
+ uint32_t sb_fsize; /* fragment size */
+ uint32_t sb_bpg; /* blocks per group */
+ uint32_t sb_fpg; /* frags per group */
+ uint32_t sb_ipg; /* inodes per group */
+ uint32_t sb_mtime; /* mount time */
+ uint32_t sb_wtime; /* write time */
+ uint16_t sb_mnt_count; /* mount count */
+ uint16_t sb_max_mnt_count; /* max mount count */
+ uint16_t sb_magic; /* magic number */
+ uint16_t sb_state; /* file system state */
+ uint16_t sb_beh; /* behavior on errors */
+ uint16_t sb_minrev; /* minor revision level */
+ uint32_t sb_lastfsck; /* time of last fsck */
+ uint32_t sb_fsckintv; /* max time between fscks */
+ uint32_t sb_creator; /* creator OS */
+ uint32_t sb_rev; /* revision level */
+ uint16_t sb_ruid; /* default uid for reserved blocks */
+ uint16_t sb_rgid; /* default gid for reserved blocks */
+ /* EXT2_DYNAMIC_REV superblocks */
+ uint32_t sb_first_ino; /* first non-reserved inode */
+ uint16_t sb_inode_size; /* size of inode structure */
+ uint16_t sb_block_group_nr; /* block grp number of this sblk */
+ uint32_t sb_features_compat; /* compatible feature set */
+ uint32_t sb_features_incompat; /* incompatible feature set */
+ uint32_t sb_features_rocompat; /* RO-compatible feature set */
+ uint8_t sb_uuid[16]; /* 128-bit uuid for volume */
+ char sb_vname[16]; /* volume name */
+ char sb_fsmnt[64]; /* name mounted on */
+ uint32_t sb_algo; /* For compression */
+ uint8_t sb_prealloc; /* # of blocks to preallocate */
+ uint8_t sb_dir_prealloc; /* # of blocks to preallocate for dir */
+ uint16_t sb_reserved_ngdb; /* # of reserved gd blocks for resize */
+} ext2_superblock_t;
+
+#define ext2_blksize(sb) (1024UL << (sb)->sb_log_bsize)
+
+/*
+ * File system block group descriptor.
+ */
+typedef struct ext2_groupdesc {
+ uint32_t gd_b_bitmap; /* blocks bitmap block */
+ uint32_t gd_i_bitmap; /* inodes bitmap block */
+ uint32_t gd_i_tables; /* first inodes table block */
+ uint16_t gd_nbfree; /* number of free blocks */
+ uint16_t gd_nifree; /* number of free inodes */
+ uint16_t gd_ndirs; /* number of directories */
+
+ /*
+ * Following only valid when either GDT_CSUM or METADATA_CKSUM feature is on.
+ */
+ uint16_t gd_flags; /* ext4 bg flags (INODE_UNINIT, ...)*/
+ uint32_t gd_exclude_bitmap_lo; /* snapshot exclude bitmap */
+ uint16_t gd_block_bitmap_csum_lo; /* Low block bitmap checksum */
+ uint16_t gd_inode_bitmap_csum_lo; /* Low inode bitmap checksum */
+ uint16_t gd_itable_unused_lo; /* Low unused inode offset */
+ uint16_t gd_checksum; /* Group desc checksum */
+} ext2_groupdesc_t;
+
+/*
+ * A block group has backup copy of the superblock and block group descriptors
+ * only if it's 1, a power of 3, 5 or 7
+ */
+static inline int ext2_gd_has_backup(int i) {
+ int a3, a5, a7;
+
+ if (i == 0 || i == 1)
+ return 1;
+ for (a3 = 3, a5 = 5, a7 = 7; a3 <= i || a5 <= i || a7 <= i;
+ a3 *= 3, a5 *= 5, a7 *= 7)
+ if (i == a3 || i == a5 || i == a7)
+ return 1;
+ return 0;
+}
+
+/*
+ * A dinode contains all the meta-data associated with a file. This structure
+ * defines the on-disk format of a dinode. Since this structure describes an
+ * on-disk structure, all its fields are defined by types with precise widths.
+ */
+
+#define EXT2_NDADDR 12 /* Direct addresses in inode. */
+#define EXT2_NIADDR 3 /* Indirect addresses in inode. */
+#define EXT2_NADDR (EXT2_NDADDR + EXT2_NIADDR)
+
+#define EXT2_MAXSYMLINKLEN ((EXT2_NDADDR + EXT2_NIADDR) * sizeof(uint32_t))
+
+/*
+ * The root inode is the root of the file system. Inode 0 can't be used for
+ * normal purposes and bad blocks are normally linked to inode 1, thus
+ * the root inode is 2.
+ * Inode 3 to 10 are reserved in ext2fs.
+ */
+#define EXT2_BADBLKINO ((ino_t)1)
+#define EXT2_ROOTINO ((ino_t)2)
+#define EXT2_ACLIDXINO ((ino_t)3)
+#define EXT2_ACLDATAINO ((ino_t)4)
+#define EXT2_BOOTLOADERINO ((ino_t)5)
+#define EXT2_UNDELDIRINO ((ino_t)6)
+#define EXT2_RESIZEINO ((ino_t)7)
+#define EXT2_JOURNALINO ((ino_t)8)
+#define EXT2_FIRSTINO ((ino_t)11)
+
+/*
+ * Structure of an inode on the disk.
+ */
+typedef struct ext2_inode {
+ uint16_t i_mode; /* 0: IFMT, permissions; see below. */
+ uint16_t i_uid; /* 2: Owner UID */
+ uint32_t i_size; /* 4: Size (in bytes) */
+ uint32_t i_atime; /* 8: Access time */
+ uint32_t i_ctime; /* 12: Change time */
+ uint32_t i_mtime; /* 16: Modification time */
+ uint32_t i_dtime; /* 20: Deletion time */
+ uint16_t i_gid; /* 24: Owner GID */
+ uint16_t i_nlink; /* 26: File link count */
+ uint32_t i_nblock; /* 28: Blocks count */
+ uint32_t i_flags; /* 32: Status flags (chflags) */
+ uint32_t i_version; /* 36: Low 32 bits inode version */
+ uint32_t i_blocks[EXT2_NADDR]; /* 40: disk blocks */
+ uint32_t i_gen; /* 100: generation number */
+ uint32_t i_facl; /* 104: Low EA block */
+ uint32_t i_size_high; /* 108: Upper bits of file size */
+ uint32_t i_faddr; /* 112: Fragment address (obsolete) */
+ uint16_t i_nblock_high; /* 116: Blocks count bits 47:32 */
+ uint16_t i_facl_high; /* 118: File EA bits 47:32 */
+ uint16_t i_uid_high; /* 120: Owner UID top 16 bits */
+ uint16_t i_gid_high; /* 122: Owner GID top 16 bits */
+ uint16_t i_chksum_lo; /* 124: Lower inode checksum */
+ uint16_t i_lx_reserved; /* 126: Unused */
+} ext2_inode_t;
+
+_Static_assert(sizeof(ext2_inode_t) == 128,
+ "size of ext2 i-node must be 128 bytes");
+
+/* File permissions. */
+#define EXT2_IEXEC 0000100 /* Executable. */
+#define EXT2_IWRITE 0000200 /* Writable. */
+#define EXT2_IREAD 0000400 /* Readable. */
+#define EXT2_ISVTX 0001000 /* Sticky bit. */
+#define EXT2_ISGID 0002000 /* Set-gid. */
+#define EXT2_ISUID 0004000 /* Set-uid. */
+
+/* File types. */
+#define EXT2_IFMT 0170000 /* Mask of file type. */
+#define EXT2_IFIFO 0010000 /* Named pipe (fifo). */
+#define EXT2_IFCHR 0020000 /* Character device. */
+#define EXT2_IFDIR 0040000 /* Directory file. */
+#define EXT2_IFBLK 0060000 /* Block device. */
+#define EXT2_IFREG 0100000 /* Regular file. */
+#define EXT2_IFLNK 0120000 /* Symbolic link. */
+#define EXT2_IFSOCK 0140000 /* UNIX domain socket. */
+
+/*
+ * A directory consists of some number of blocks of e2fs_blksize bytes.
+ *
+ * Each block contains some number of directory entry structures, which are of
+ * variable length. Each directory entry has a struct direct at the front of
+ * it, containing its inode number, the length of the entry, and the length of
+ * the name contained in the entry. These are followed by the name padded to a
+ * 4 byte boundary with null bytes. All names are guaranteed null terminated.
+ * The maximum length of a name in a directory is EXT2_MAXNAMLEN.
+ */
+
+/* Ext2 directory file types. */
+enum {
+ EXT2_FT_UNKNOWN = 0,
+ EXT2_FT_REG = 1,
+ EXT2_FT_DIR = 2,
+ EXT2_FT_CHRDEV = 3,
+ EXT2_FT_BLKDEV = 4,
+ EXT2_FT_FIFO = 5,
+ EXT2_FT_SOCK = 6,
+ EXT2_FT_SYMLINK = 7,
+};
+
+/*
+ * The EXT2_DIRSIZE macro gives the minimum record length which will hold
+ * the directory entry for a name of length `len` (without the terminating null
+ * byte). This requires the amount of space in struct direct without the
+ * de_name field, plus enough space for the name without a terminating null
+ * byte, rounded up to a 4 byte boundary.
+ */
+#define EXT2_DIRSIZE(len) roundup2(8 + len, 4)
+
+#define EXT2_MAXNAMLEN 255
+
+typedef struct ext2_dirent {
+ uint32_t de_ino; /* inode number of entry */
+ uint16_t de_reclen; /* length of this record */
+ uint8_t de_namelen; /* length of string in de_name */
+ uint8_t de_type; /* file type */
+ char de_name[EXT2_MAXNAMLEN + 1]; /* name with length <= EXT2_MAXNAMLEN */
+} ext2_dirent_t;
diff --git a/ext2fuse.c b/ext2fuse.c
new file mode 100644
index 0000000..166fbc9
--- /dev/null
+++ b/ext2fuse.c
@@ -0,0 +1,205 @@
+#define FUSE_USE_VERSION 30
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ext2fs.h"
+
+typedef struct fuse_file_info fuse_file_info_t;
+
+static void e2fs_getattr(fuse_req_t req, fuse_ino_t ino,
+ fuse_file_info_t *fi __unused) {
+ struct stat st;
+ int error;
+
+ if (ino == 1)
+ ino = EXT2_ROOTINO;
+
+ memset(&st, 0, sizeof(st));
+ if ((error = ext2_stat(ino, &st))) {
+ fuse_reply_err(req, error);
+ return;
+ }
+
+ fuse_reply_attr(req, &st, 1.0);
+}
+
+static void e2fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) {
+ uint32_t ino;
+ int error;
+
+ if (parent == 1)
+ parent = EXT2_ROOTINO;
+
+ if ((error = ext2_lookup(parent, name, &ino, NULL))) {
+ fuse_reply_err(req, error);
+ return;
+ }
+
+ struct fuse_entry_param e;
+ memset(&e, 0, sizeof(e));
+ e.ino = ino;
+ e.attr_timeout = 1.0;
+ e.entry_timeout = 1.0;
+
+ if ((error = ext2_stat(e.ino, &e.attr))) {
+ fuse_reply_err(req, error);
+ return;
+ }
+
+ fuse_reply_entry(req, &e);
+}
+
+static void e2fs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t _off, fuse_file_info_t *fi __unused) {
+ int error;
+
+ if (ino == 1)
+ ino = EXT2_ROOTINO;
+
+ ext2_dirent_t de;
+ uint32_t off = _off;
+ if (!ext2_readdir(ino, &off, &de)) {
+ fuse_reply_buf(req, NULL, 0);
+ return;
+ }
+
+ void *buf = malloc(size);
+ assert(buf != NULL);
+ struct stat st;
+ if ((error = ext2_stat(de.de_ino, &st))) {
+ fuse_reply_err(req, error);
+ return;
+ }
+ size = fuse_add_direntry(req, buf, size, de.de_name, &st, off);
+ error = fuse_reply_buf(req, buf, size);
+ assert(error == 0);
+ free(buf);
+}
+
+static void e2fs_readlink(fuse_req_t req, fuse_ino_t ino) {
+ int error;
+
+ if (ino == 1)
+ ino = EXT2_ROOTINO;
+
+ struct stat st;
+ if ((error = ext2_stat(ino, &st))) {
+ fuse_reply_err(req, error);
+ return;
+ }
+
+ char symlink[st.st_size + 1];
+ if ((error = ext2_readlink(ino, symlink, st.st_size))) {
+ fuse_reply_err(req, error);
+ return;
+ }
+
+ symlink[st.st_size] = '\0';
+ fuse_reply_readlink(req, symlink);
+}
+
+static void e2fs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info_t *fi) {
+ int error;
+
+ if (ino == 1)
+ ino = EXT2_ROOTINO;
+
+ struct stat st;
+ if ((error = ext2_stat(ino, &st))) {
+ fuse_reply_err(req, error);
+ return;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fuse_reply_err(req, EISDIR);
+ return;
+ }
+
+ if ((fi->flags & 3) != O_RDONLY) {
+ fuse_reply_err(req, EACCES);
+ return;
+ }
+
+ fuse_reply_open(req, fi);
+}
+
+static void e2fs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ fuse_file_info_t *fi __unused) {
+ int error;
+
+ if (ino == 1)
+ ino = EXT2_ROOTINO;
+
+ struct stat st;
+ if ((error = ext2_stat(ino, &st))) {
+ fuse_reply_err(req, error);
+ return;
+ }
+
+ if (!S_ISREG(st.st_mode) || (off > st.st_size)) {
+ fuse_reply_err(req, EINVAL);
+ return;
+ }
+
+ size = min(size, (size_t)(st.st_size - off));
+
+ void *buf = malloc(size);
+ assert(buf != NULL);
+ ext2_read(ino, buf, off, size);
+ error = fuse_reply_buf(req, buf, size);
+ assert(error == 0);
+ free(buf);
+}
+
+static void e2fs_statfs(fuse_req_t req, fuse_ino_t ino __unused) {
+ struct statvfs statfs;
+ memset(&statfs, 0, sizeof(statfs));
+ fuse_reply_statfs(req, &statfs);
+}
+
+static struct fuse_lowlevel_ops e2fs_oper = {
+ .lookup = e2fs_lookup,
+ .getattr = e2fs_getattr,
+ .readdir = e2fs_readdir,
+ .readlink = e2fs_readlink,
+ .open = e2fs_open,
+ .read = e2fs_read,
+ .statfs = e2fs_statfs,
+};
+
+int main(int argc, char *argv[]) {
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+ struct fuse_chan *ch;
+ struct fuse_session *se;
+ char *mountpoint;
+ int multithreaded, foreground, err = -1;
+
+ if ((err = ext2_mount("debian9-ext2.img"))) {
+ fprintf(stderr, "Cannot open 'debian9-ext2.img': %s!\n", strerror(err));
+ return EXIT_FAILURE;
+ }
+
+ if (!fuse_parse_cmdline(&args, &mountpoint, &multithreaded, &foreground) &&
+ (ch = fuse_mount(mountpoint, &args))) {
+ if ((se = fuse_lowlevel_new(&args, &e2fs_oper, sizeof(e2fs_oper), NULL))) {
+ if (fuse_set_signal_handlers(se) != -1) {
+ fuse_session_add_chan(se, ch);
+ err = fuse_session_loop(se);
+ fuse_remove_signal_handlers(se);
+ fuse_session_remove_chan(ch);
+ }
+ fuse_session_destroy(se);
+ }
+ fuse_unmount(mountpoint, ch);
+ }
+ fuse_opt_free_args(&args);
+
+ return err ? 1 : 0;
+}
diff --git a/ext2list.c b/ext2list.c
new file mode 100644
index 0000000..d7da6f6
--- /dev/null
+++ b/ext2list.c
@@ -0,0 +1,116 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "md5.h"
+#include "ext2fs.h"
+
+static void showfile(const char *path, uint32_t ino) {
+ struct stat sb[1];
+
+ memset(sb, 0, sizeof(struct stat));
+ ext2_stat(ino, sb);
+
+ printf("path=%s ino=%ld mode=%o nlink=%ld uid=%d gid=%d "
+ "size=%ld atime=%ld mtime=%ld ctime=%ld",
+ path, sb->st_ino, sb->st_mode, sb->st_nlink, sb->st_uid, sb->st_gid,
+ sb->st_size, sb->st_atime, sb->st_mtime, sb->st_ctime);
+
+ switch (sb->st_mode & S_IFMT) {
+ case S_IFREG: {
+ uint8_t data[BLKSIZE];
+ size_t pos = 0;
+ size_t len = sb->st_size;
+ MD5_CTX ctx;
+
+ MD5Init(&ctx);
+
+ while (len > 0) {
+ size_t cnt = min(len, BLKSIZE);
+ if (ext2_read(ino, data, pos, cnt))
+ break;
+ MD5Update(&ctx, data, cnt);
+ len -= cnt;
+ pos += cnt;
+ }
+
+ char md5sum[MD5_DIGEST_STRING_LENGTH];
+ MD5End(&ctx, md5sum);
+ printf(" md5=%s", md5sum);
+ break;
+ }
+ case S_IFLNK: {
+ char symlink[sb->st_size + 1];
+ if (ext2_readlink(ino, symlink, sb->st_size)) {
+ strcpy(symlink, "?");
+ } else {
+ symlink[sb->st_size] = '\0';
+ }
+ printf(" target=%s", symlink);
+ break;
+ }
+ default:
+ break;
+ }
+ putchar('\n');
+}
+
+static void listdir(const char *path, uint32_t ino) {
+ char newpath[strlen(path) + EXT2_MAXNAMLEN + 2];
+
+ ext2_dirent_t de;
+ uint32_t off = 0;
+ while (ext2_readdir(ino, &off, &de)) {
+ if (strncmp(de.de_name, ".", de.de_namelen) == 0)
+ continue;
+ if (strncmp(de.de_name, "..", de.de_namelen) == 0)
+ continue;
+
+ strcpy(newpath, path);
+ strcat(newpath, "/");
+ strcat(newpath, de.de_name);
+
+ showfile(newpath, de.de_ino);
+
+ if (de.de_type == EXT2_FT_DIR)
+ listdir(newpath, de.de_ino);
+ }
+}
+
+static void count_used_inodes(void) {
+ unsigned used = 0;
+
+ for (uint32_t ino = 1;; ino++) {
+ int res = ext2_inode_used(ino);
+ if (res == EINVAL)
+ break;
+ used += res;
+ }
+
+ fprintf(stderr, "used inodes: %u\n", used);
+}
+
+static void count_used_blocks(void) {
+ unsigned used = 0;
+
+ for (uint32_t blk = 1;; blk++) {
+ int res = ext2_block_used(blk);
+ if (res == EINVAL)
+ break;
+ used += res;
+ }
+
+ fprintf(stderr, "used blocks: %u\n", used);
+}
+
+int main(void) {
+ if (ext2_mount("debian9-ext2.img"))
+ exit(EXIT_FAILURE);
+
+ showfile(".", EXT2_ROOTINO);
+ listdir(".", EXT2_ROOTINO);
+ count_used_blocks();
+ count_used_inodes();
+ exit(EXIT_SUCCESS);
+}
diff --git a/ext2test.c b/ext2test.c
new file mode 100644
index 0000000..0be6318
--- /dev/null
+++ b/ext2test.c
@@ -0,0 +1,311 @@
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include "ext2fs.h"
+
+/* clang-format off */
+static const char *ext2_ft_name[] = {
+ [EXT2_FT_UNKNOWN] = "???",
+ [EXT2_FT_REG] = "reg",
+ [EXT2_FT_DIR] = "dir",
+ [EXT2_FT_CHRDEV] = "cdev",
+ [EXT2_FT_BLKDEV] = "bdev",
+ [EXT2_FT_FIFO] = "fifo",
+ [EXT2_FT_SOCK] = "sock",
+ [EXT2_FT_SYMLINK] = "slnk",
+};
+/* clang-format on */
+
+typedef int (*func_t)(char *arg);
+
+typedef struct {
+ const char *name;
+ func_t func;
+} command_t;
+
+static uint32_t curdir = EXT2_ROOTINO;
+
+static int do_chdir(char *arg) {
+ uint32_t ino;
+ uint8_t type;
+ int error;
+
+ if ((error = ext2_lookup(curdir, arg, &ino, &type)))
+ return error;
+
+ if (type != EXT2_FT_DIR)
+ return ENOTDIR;
+
+ curdir = ino;
+ return 0;
+}
+
+static int do_list(char *arg) {
+ uint32_t ino = curdir;
+ int error;
+
+ if (arg != NULL) {
+ uint8_t type;
+
+ if ((error = ext2_lookup(curdir, arg, &ino, &type)))
+ return error;
+
+ if (type != EXT2_FT_DIR)
+ return ENOTDIR;
+ }
+
+ ext2_dirent_t de;
+ uint32_t off = 0;
+ while (ext2_readdir(ino, &off, &de)) {
+ printf("%8d %4s '%.*s'\n", de.de_ino, ext2_ft_name[de.de_type],
+ de.de_namelen, de.de_name);
+ }
+
+ return 0;
+}
+
+static void hexdump(uint8_t *data, size_t offset, size_t size) {
+ bool was_empty = false;
+ bool is_empty;
+
+ size = min(size, BLKSIZE);
+
+ for (unsigned i = 0; i < size; i += 16, was_empty = is_empty) {
+ is_empty = true;
+
+ for (unsigned j = i; j < min(size, i + 16); j++) {
+ if (data[j]) {
+ is_empty = false;
+ break;
+ }
+ }
+
+ if (is_empty) {
+ if (!was_empty)
+ puts("*");
+ continue;
+ }
+
+ printf("%08lx:", i + offset);
+ for (unsigned j = i; j < i + 16; j++) {
+ if (j % 8 == 0)
+ putchar(' ');
+ if (j < size)
+ printf(" %02x", data[j]);
+ else
+ printf(" ");
+ }
+ printf(" |");
+ for (unsigned j = i; j < min(size, i + 16); j++) {
+ uint8_t c = data[j];
+ putchar(isprint(c) ? c : '.');
+ }
+ printf("|\n");
+ }
+}
+
+static int do_read(char *arg) {
+ uint32_t ino;
+ int error;
+
+ if ((error = ext2_lookup(curdir, arg, &ino, NULL)))
+ return error;
+
+ struct stat st;
+ ext2_stat(ino, &st);
+
+ if (!(st.st_mode & S_IFREG))
+ return EINVAL;
+
+ uint8_t data[BLKSIZE];
+ size_t pos = 0;
+ size_t len = st.st_size;
+ while (len > 0) {
+ size_t cnt = min(len, BLKSIZE);
+ if ((error = ext2_read(ino, data, pos, cnt)))
+ return error;
+ hexdump(data, pos, cnt);
+ len -= cnt;
+ pos += cnt;
+ }
+
+ return 0;
+}
+
+static int do_stat(char *arg) {
+ uint32_t ino;
+ int error;
+
+ if ((error = ext2_lookup(curdir, arg, &ino, NULL)))
+ return error;
+
+ struct stat st;
+ ext2_stat(ino, &st);
+
+ mode_t mode = st.st_mode;
+ const char *type = "???";
+
+ if (S_ISDIR(mode))
+ type = "directory";
+ else if (S_ISCHR(mode))
+ type = "character device";
+ else if (S_ISBLK(mode))
+ type = "block device";
+ else if (S_ISREG(mode))
+ type = "regular file";
+ else if (S_ISLNK(mode))
+ type = "symbolic link";
+
+ char modestr[10] = "rwxrwxrwx\0";
+ char mtim[26]; /* 26 as in manual page */
+ char atim[26];
+ char ctim[26];
+
+ for (int i = 0; i < 9; i++)
+ if (!(st.st_mode & (1 << (8 - i))))
+ modestr[i] = '-';
+
+ ctime_r(&st.st_ctime, (char *)&ctim);
+ ctime_r(&st.st_atime, (char *)&atim);
+ ctime_r(&st.st_mtime, (char *)&mtim);
+
+ printf("i-node : %ld\n"
+ "type : %s\n"
+ "mode : %s\n"
+ "user : %d\n"
+ "group : %d\n"
+ "size : %ld\n"
+ "blocks : %ld\n"
+ "links : %ld\n"
+ "ctime : %s"
+ "atime : %s"
+ "mtime : %s",
+ st.st_ino, type, modestr, st.st_uid, st.st_gid, st.st_size,
+ st.st_blocks, st.st_nlink, ctim, atim, mtim);
+ return 0;
+}
+
+static int do_readlink(char *arg) {
+ uint32_t ino;
+ int error;
+
+ if ((error = ext2_lookup(curdir, arg, &ino, NULL)))
+ return error;
+
+ struct stat st;
+ ext2_stat(ino, &st);
+
+ char symlink[st.st_size + 1];
+ if ((error = ext2_readlink(ino, symlink, st.st_size)))
+ return error;
+
+ symlink[st.st_size] = '\0';
+ printf("%s -> %s\n", arg, symlink);
+ return 0;
+}
+
+/* Print all number of data blocks for given i-node. */
+static int do_blocks(char *arg) {
+ uint32_t ino;
+ uint8_t type;
+ int error;
+
+ if ((error = ext2_lookup(curdir, arg, &ino, &type)))
+ return error;
+
+ if (type != EXT2_FT_DIR && type != EXT2_FT_REG)
+ return ENOTSUP;
+
+ struct stat st;
+ ext2_stat(ino, &st);
+
+ uint32_t blkidx = 0;
+ while ((blkidx * BLKSIZE < (uint32_t)st.st_size))
+ printf("%ld ", ext2_blkaddr_read(ino, blkidx++));
+ puts("");
+
+ return 0;
+}
+
+/* Test if i-node is in use. */
+static int do_testi(char *arg) {
+ if (!arg)
+ return EINVAL;
+ uint32_t ino = strtol(arg, NULL, 10);
+ int res = ext2_inode_used(ino);
+ if (res == EINVAL)
+ return res;
+ printf("i-node %d is %s\n", ino, res ? "used" : "free");
+ return 0;
+}
+
+/* Test if block is in use. */
+static int do_testb(char *arg) {
+ if (!arg)
+ return EINVAL;
+ uint32_t blk = strtol(arg, NULL, 10);
+ int res = ext2_block_used(blk);
+ if (res == EINVAL)
+ return res;
+ printf("block %d is %s\n", blk, res ? "used" : "free");
+ return 0;
+}
+
+/* clang-format off */
+static command_t commands[] = {
+ {"cd", do_chdir},
+ {"ls", do_list},
+ {"stat", do_stat},
+ {"read", do_read},
+ {"readlink", do_readlink},
+ {"blocks", do_blocks},
+ {"testi", do_testi},
+ {"testb", do_testb},
+ {NULL, NULL},
+};
+/* clang-format on */
+
+static int doit(char *line) {
+ char *name = strsep(&line, " ");
+
+ if (line && !strlen(line))
+ line = NULL;
+
+ for (command_t *cmd = commands; cmd->name; cmd++) {
+ if (strcmp(name, cmd->name))
+ continue;
+ return cmd->func(line);
+ }
+
+ return ENOTSUP;
+}
+
+int main(void) {
+ int error;
+
+ if ((error = ext2_mount("debian9-ext2.img")))
+ return error;
+
+ while (true) {
+ char *line = readline("# ");
+
+ if (line == NULL)
+ break;
+
+ if (strlen(line)) {
+ add_history(line);
+ if ((error = doit(line)))
+ fprintf(stderr, "ERROR: %s!\n", strerror(error));
+ }
+ free(line);
+ }
+
+ return 0;
+}
diff --git a/files.sha256 b/files.sha256
new file mode 100644
index 0000000..0c03bea
--- /dev/null
+++ b/files.sha256
@@ -0,0 +1,16 @@
+dd16d5a60d6923565628b05c6bbaa78f86cb47366fc83ffa6168931b28034d96 .clang-format
+eb8f0887af4317e9df0dd302f34c2dd30efc4fdcab3ded1a0646c85f01b42c32 .github/classroom/autograding.json
+2e015f1dc9a4cc2d044cd6629d66f6aaea3bd83c2fb242f0b5e5b7b5eeabf458 .github/workflows/classroom.yml
+99656309552b6bf4d8ff20c2b06cf93ba7c3dda99fff86c03c6893c578e8aa45 check-files.py
+ca271adde44b5fe121f4e3e71cee702885444cd2b1876480f1a60fc8311cfd4e ext2fs.c
+3bba687490ccf9568763f28e36aefd4837af7853c1b87bd1a308305ab09d4512 ext2fs_defs.h
+424b7c445861a803332b8c2ded10f21b7d6d01871cd98e3e154c21d88be20332 ext2fs.h
+82cdb09e04b98264a9b17226149aedd0a4649c3fc7ec87967a7710c04668f81c ext2fuse.c
+5f1308d1bd4b3a744912e47a5c10af4399b17f46c91db622b51f211b730ec0b3 ext2list.c
+155979c50ad28ec7ca139368733a9794702c3452102cc6e5322741b2e284c07f ext2test.c
+ce059de4843a0dfdd599d270566b388f96642eb25f0e1e3e6490608c93a3bb6d grade.py
+353b7457ca1233c3eeef3028097b763d8c491892923bb2df80b83e11472bf995 listfs.c
+8598d502e136c912972217c451762071cc21b558856b1f9c9fe45d377ad1fc39 Makefile
+c31bc4d543e5f7625b8266e8c99271035b8cef1d87d87f15953c1418b4302f07 md5c.c
+6c72bce0de85d6bc7c9653ed8f8655591a34d66101eb567ca3ae4486cb3afcd6 md5.h
+1886db3d4d1b8361bd692ee13aac3c276ae9eb11536b527e44a111b620a02e52 run-clang-format.sh
diff --git a/grade.py b/grade.py
new file mode 100755
index 0000000..976b4ee
--- /dev/null
+++ b/grade.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+
+import os
+import os.path
+import shutil
+import signal
+import subprocess
+from collections import namedtuple, OrderedDict
+from pprint import pprint
+
+PROG = './ext2list'
+IMAGE = 'debian9-ext2'
+Group = namedtuple('Group', 'b_bitmap i_bitmap i_tables nbfree nifree ndirs')
+
+
+def signame(n):
+ for k, v in signal.Signals.__members__.items():
+ if v.value == n:
+ return k
+
+
+def maybe_int(s):
+ try:
+ return int(s)
+ except ValueError:
+ return s
+
+
+def parse_ext2(fsimg):
+ # XXX: debugfs reports indirect blocks and ext2test does not,
+ # hence I cannot compare output of blocks command :(
+
+ debugfs = subprocess.run(
+ ['/sbin/debugfs', '-R', 'stats', 'debian9-ext2.img'],
+ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True)
+
+ lines = debugfs.stdout.decode('utf-8').splitlines()
+
+ sb = dict()
+ groups = []
+
+ while lines:
+ line = lines.pop(0)
+
+ key, val = line.split(':', 1)
+ key = key.strip()
+ val = val.strip()
+
+ if 'Group' in key:
+ val += ', ' + lines.pop(0).strip()
+ (b_bitmap, i_bitmap, i_tables, nbfree, nifree, ndirs) = \
+ map(str.split, val.split(', '))
+ groups.append(Group(int(b_bitmap[-1]), int(i_bitmap[-1]),
+ int(i_tables[-1]), int(nbfree[0]),
+ int(nifree[0]), int(ndirs[0])))
+ else:
+ sb[key] = maybe_int(val)
+
+ return sb, groups
+
+
+def check_used(sb, count_lines):
+ expected_used_blocks = (sb['Block count'] - sb['Free blocks'] -
+ sb['First block'])
+ expected_used_inodes = sb['Inode count'] - sb['Free inodes']
+
+ blocks_str, inodes_str = count_lines
+ used_blocks = int(blocks_str.split(':')[1])
+ used_inodes = int(inodes_str.split(':')[1])
+
+ if used_blocks != expected_used_blocks:
+ raise SystemExit(f'Used blocks value is {used_blocks}, '
+ f'expected {expected_used_blocks}!')
+ else:
+ print(f'Used blocks: {used_blocks}')
+
+ if used_inodes != expected_used_inodes:
+ raise SystemExit(f'Used inodes value is {used_inodes}, '
+ f'expected {expected_used_inodes}!')
+ else:
+ print(f'Used inodes: {used_inodes}')
+
+
+def build_index(lines):
+ index = dict()
+
+ for line in lines:
+ od = OrderedDict([fs.split('=') for fs in line.split()])
+ path = od['path']
+ del od['path']
+ if path in index:
+ raise SystemExit(f'File "{path}" reported twice!?')
+ index[path] = od
+
+ return index
+
+
+def records_match(name, got, exp):
+ got_keys = set(got.keys())
+ exp_keys = set(exp.keys())
+ errors = []
+
+ if got_keys ^ exp_keys:
+ missing = exp_keys - got_keys
+ if missing:
+ for key in missing:
+ errors.append(f' - missing property: \'{key}\'')
+
+ extra = got_keys - exp_keys
+ if extra:
+ for key in extra:
+ errors.append(f' - unexpected property: \'{key}\'')
+
+ if any(got[k] != exp[k] for k in got_keys):
+ for key in got_keys:
+ if got[key] != exp[key]:
+ errors.append(f' - property \'{key}\' has value \'{got[key]}\''
+ f' (expected \'{exp[key]}\')')
+
+ if errors:
+ print(f'File \'{name}\':')
+ for err in errors:
+ print(err)
+
+ return not bool(errors)
+
+
+def compare_list(files_lines, expected_lines):
+ errors = False
+
+ files_index = build_index(files_lines)
+ expected_index = build_index(expected_lines)
+
+ # check if student reported extra files that do not exist
+ files_keys = set(files_index.keys())
+ expected_keys = set(expected_index.keys())
+ keys = files_keys - expected_keys
+ if keys:
+ for key in sorted(keys):
+ print(f'File "{key}" reported,'
+ f'but it does not exists in the filesystem!')
+ errors = True
+
+ # now check index file by file and verify attributes are the same
+ for name, expected in sorted(expected_index.items()):
+ if name not in files_index:
+ print(f'File "{name}" has not been found!')
+ errors = True
+ continue
+
+ if not records_match(name, files_index[name], expected):
+ errors = True
+
+ if errors:
+ raise SystemExit('Solution is incorrect!')
+
+ print('Solution seems to be ok!')
+
+
+if __name__ == '__main__':
+ os.environ['PATH'] = '/usr/bin:/sbin:/bin'
+ os.environ['LC_ALL'] = 'C'
+
+ if os.path.isdir('/__w'):
+ os.symlink(f'/{IMAGE}.img', f'{IMAGE}.img')
+ os.symlink(f'/{IMAGE}.kern.log', f'{IMAGE}.kern.log')
+
+ sb, groups = parse_ext2(f'{IMAGE}.img')
+
+ ext2list = subprocess.run([PROG],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding='utf-8')
+ if ext2list.returncode:
+ print(ext2list.stdout)
+ print(ext2list.stderr)
+
+ if ext2list.returncode > 0:
+ raise SystemExit(f'{PROG}: exited abnormally with code: '
+ f'{ext2list.returncode}!')
+ else:
+ raise SystemExit(f'{PROG}: was terminated by ' +
+ signame(-ext2list.returncode) + ' signal!')
+
+ files = ext2list.stdout.splitlines()
+ count = ext2list.stderr.splitlines()
+
+ check_used(sb, count)
+
+ with open(f'{IMAGE}.kern.log', 'r') as f:
+ files_expected = f.read().splitlines()
+
+ compare_list(files, files_expected)
diff --git a/listfs.c b/listfs.c
new file mode 100644
index 0000000..5741efb
--- /dev/null
+++ b/listfs.c
@@ -0,0 +1,86 @@
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "md5.h"
+
+static int showfile(const char *path) {
+ struct stat sb[1];
+
+ memset(sb, 0, sizeof(struct stat));
+
+ (void)lstat(path, sb);
+
+ printf("path=%s ino=%ld mode=%o nlink=%ld uid=%d gid=%d "
+ "size=%ld atime=%ld mtime=%ld ctime=%ld",
+ path, sb->st_ino, sb->st_mode, sb->st_nlink, sb->st_uid, sb->st_gid,
+ sb->st_size, sb->st_atime, sb->st_mtime, sb->st_ctime);
+
+ switch (sb->st_mode & S_IFMT) {
+ case S_IFREG: {
+ char *md5sum = MD5File(path, NULL);
+ printf(" md5=%s", md5sum);
+ free(md5sum);
+ break;
+ }
+ case S_IFLNK: {
+ char symlink[PATH_MAX + 1] = "?";
+ ssize_t size;
+ if ((size = readlink(path, symlink, PATH_MAX)) > 0)
+ symlink[size] = '\0';
+ printf(" target=%s", symlink);
+ break;
+ }
+ default:
+ break;
+ }
+ putchar('\n');
+
+ return 0;
+}
+
+static int listdir(const char *path) {
+ char newpath[strlen(path) + MAXNAMLEN + 2];
+
+ DIR *dirp = opendir(path);
+ if (dirp == NULL)
+ return ENOENT;
+
+ struct dirent *dp;
+ while ((dp = readdir(dirp))) {
+ if (strcmp(dp->d_name, ".") == 0)
+ continue;
+ if (strcmp(dp->d_name, "..") == 0)
+ continue;
+
+ strcpy(newpath, path);
+ strcat(newpath, "/");
+ strcat(newpath, dp->d_name);
+ showfile(newpath);
+
+ if (dp->d_type == DT_DIR)
+ listdir(newpath);
+ }
+
+ (void)closedir(dirp);
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ if (argc == 2) {
+ if (chdir(argv[1])) {
+ perror("chdir");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ (void)showfile(".");
+ (void)listdir(".");
+ exit(EXIT_SUCCESS);
+}
diff --git a/md5.h b/md5.h
new file mode 100644
index 0000000..691690a
--- /dev/null
+++ b/md5.h
@@ -0,0 +1,48 @@
+/*-
+ SPDX-License-Identifier: RSA-MD
+
+ Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+ */
+
+#ifndef _SYS_MD5_H_
+#define _SYS_MD5_H_
+
+#define MD5_BLOCK_LENGTH 64
+#define MD5_DIGEST_LENGTH 16
+#define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH * 2 + 1)
+
+/* MD5 context. */
+typedef struct MD5Context {
+ u_int32_t state[4]; /* state (ABCD) */
+ u_int32_t count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ unsigned char buffer[64]; /* input buffer */
+} MD5_CTX;
+
+#include <sys/cdefs.h>
+
+void MD5Init(MD5_CTX *);
+void MD5Update(MD5_CTX *, const void *, unsigned int);
+void MD5Final(unsigned char[static(MD5_DIGEST_LENGTH)], MD5_CTX *);
+char *MD5End(MD5_CTX *, char *);
+char *MD5File(const char *, char *);
+
+#endif /* _SYS_MD5_H_ */
diff --git a/md5c.c b/md5c.c
new file mode 100644
index 0000000..c082005
--- /dev/null
+++ b/md5c.c
@@ -0,0 +1,323 @@
+/*-
+ * SPDX-License-Identifier: RSA-MD
+ *
+ * MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ *
+ * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ * rights reserved.
+ *
+ * License to copy and use this software is granted provided that it
+ * is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ * Algorithm" in all material mentioning or referencing this software
+ * or this function.
+ *
+ * License is also granted to make and use derivative works provided
+ * that such works are identified as "derived from the RSA Data
+ * Security, Inc. MD5 Message-Digest Algorithm" in all material
+ * mentioning or referencing the derived work.
+ *
+ * RSA Data Security, Inc. makes no representations concerning either
+ * the merchantability of this software or the suitability of this
+ * software for any particular purpose. It is provided "as is"
+ * without express or implied warranty of any kind.
+ *
+ * These notices must be retained in any copies of any part of this
+ * documentation and/or software.
+ *
+ * This code is the same as the code published by RSA Inc. It has been
+ * edited for clarity and style only.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/cdefs.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "md5.h"
+
+static void MD5Transform(u_int32_t[4], const unsigned char[64]);
+
+static unsigned char PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+/* F, G, H and I are basic MD5 functions. */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits. */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+/*
+ * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+ * Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) \
+ { \
+ (a) += F((b), (c), (d)) + (x) + (u_int32_t)(ac); \
+ (a) = ROTATE_LEFT((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) \
+ { \
+ (a) += G((b), (c), (d)) + (x) + (u_int32_t)(ac); \
+ (a) = ROTATE_LEFT((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) \
+ { \
+ (a) += H((b), (c), (d)) + (x) + (u_int32_t)(ac); \
+ (a) = ROTATE_LEFT((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) \
+ { \
+ (a) += I((b), (c), (d)) + (x) + (u_int32_t)(ac); \
+ (a) = ROTATE_LEFT((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context. */
+
+void MD5Init(MD5_CTX *context) {
+ context->count[0] = context->count[1] = 0;
+
+ /* Load magic initialization constants. */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/*
+ * MD5 block update operation. Continues an MD5 message-digest
+ * operation, processing another message block, and updating the
+ * context.
+ */
+
+void MD5Update(MD5_CTX *context, const void *in, unsigned int inputLen) {
+ unsigned int i, idx, partLen;
+ const unsigned char *input = in;
+
+ /* Compute number of bytes mod 64 */
+ idx = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((u_int32_t)inputLen << 3)) <
+ ((u_int32_t)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((u_int32_t)inputLen >> 29);
+
+ partLen = 64 - idx;
+
+ /* Transform as many times as possible. */
+ if (inputLen >= partLen) {
+ memcpy((void *)&context->buffer[idx], (const void *)input, partLen);
+ MD5Transform(context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform(context->state, &input[i]);
+
+ idx = 0;
+ } else
+ i = 0;
+
+ /* Buffer remaining input */
+ memcpy((void *)&context->buffer[idx], (const void *)&input[i], inputLen - i);
+}
+
+/*
+ * MD5 padding. Adds padding followed by original length.
+ */
+
+static void MD5Pad(MD5_CTX *context) {
+ unsigned char bits[8];
+ unsigned int idx, padLen;
+
+ /* Save number of bits */
+ memcpy(bits, context->count, 8);
+
+ /* Pad out to 56 mod 64. */
+ idx = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (idx < 56) ? (56 - idx) : (120 - idx);
+ MD5Update(context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ MD5Update(context, bits, 8);
+}
+
+/*
+ * MD5 finalization. Ends an MD5 message-digest operation, writing the
+ * the message digest and zeroizing the context.
+ */
+
+void MD5Final(unsigned char digest[16], MD5_CTX *context) {
+ /* Do padding. */
+ MD5Pad(context);
+
+ /* Store state in digest */
+ memcpy(digest, context->state, 16);
+
+ /* Zeroize sensitive information. */
+ explicit_bzero(context, sizeof(*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block. */
+
+static void MD5Transform(u_int32_t state[4], const unsigned char block[64]) {
+ u_int32_t a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+ memcpy(x, block, 64);
+
+ /* Round 1 */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+ FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */
+ FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */
+ FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */
+ FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */
+ FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */
+ FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */
+ FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */
+ FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */
+ FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */
+ FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */
+ FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+ GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */
+ GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */
+ GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */
+ GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */
+ GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */
+ GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */
+ GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */
+ GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */
+ GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */
+ GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */
+ GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+ HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */
+ HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */
+ HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */
+ HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */
+ HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */
+ HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */
+ HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */
+ HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */
+ HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */
+ HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+ II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */
+ II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */
+ II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */
+ II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */
+ II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */
+ II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */
+ II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */
+ II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */
+ II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */
+ II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information. */
+ memset((void *)x, 0, sizeof(x));
+}
+
+char *MD5End(MD5_CTX *ctx, char *buf) {
+ int i;
+ unsigned char digest[16];
+ static const char hex[] = "0123456789abcdef";
+
+ if (buf == NULL)
+ buf = malloc(33);
+ if (buf == NULL)
+ return NULL;
+
+ MD5Final(digest, ctx);
+
+ for (i = 0; i < 16; i++) {
+ buf[i + i] = hex[(u_int32_t)digest[i] >> 4];
+ buf[i + i + 1] = hex[digest[i] & 0x0f];
+ }
+
+ buf[i + i] = '\0';
+ return buf;
+}
+
+#ifndef BUFSIZ
+#define BUFSIZ 1024
+#endif
+
+char *MD5File(const char *filename, char *buf) {
+ unsigned char buffer[BUFSIZ];
+ MD5_CTX ctx;
+ int f, j;
+ ssize_t i;
+
+ MD5Init(&ctx);
+ f = open(filename, O_RDONLY | O_CLOEXEC, 0666);
+ if (f < 0)
+ return NULL;
+
+ while ((i = read(f, buffer, sizeof(buffer))) > 0)
+ MD5Update(&ctx, buffer, (unsigned int)i);
+
+ j = errno;
+ close(f);
+ errno = j;
+
+ if (i < 0)
+ return NULL;
+
+ return MD5End(&ctx, buf);
+}
diff --git a/run-clang-format.sh b/run-clang-format.sh
new file mode 100755
index 0000000..4411dba
--- /dev/null
+++ b/run-clang-format.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+PAGER=cat
+
+# Use make format to cleanup the copied tree
+if ! make format > /dev/null; then
+ echo "Check if clang-format is installed!"
+ exit 1
+fi
+
+# See if there are any changes compared to checked out sources.
+if ! git diff --check --exit-code >/dev/null; then
+ echo "Formatting incorrect for C files!"
+ echo "Please run 'make format' before committing your changes,"
+ echo "or manually apply the changes listed above."
+ git diff
+ exit 1
+fi
+
+echo "Formatting correct for C files."