diff options
author | github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com> | 2022-01-24 22:06:00 +0000 |
---|---|---|
committer | github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com> | 2022-01-24 22:06:00 +0000 |
commit | 851ae290b2d24057f8426b42f97c9924e658a18d (patch) | |
tree | 2f3adf22b7ae76e2fa67e0ddc7dde8728fb0d24b |
Initial commit
-rw-r--r-- | .clang-format | 93 | ||||
-rw-r--r-- | .github/classroom/autograding.json | 22 | ||||
-rw-r--r-- | .github/workflows/classroom.yml | 16 | ||||
-rw-r--r-- | .gitignore | 13 | ||||
-rw-r--r-- | Makefile | 34 | ||||
-rwxr-xr-x | check-files.py | 38 | ||||
-rw-r--r-- | ext2fs.c | 399 | ||||
-rw-r--r-- | ext2fs.h | 48 | ||||
-rw-r--r-- | ext2fs_defs.h | 226 | ||||
-rw-r--r-- | ext2fuse.c | 205 | ||||
-rw-r--r-- | ext2list.c | 116 | ||||
-rw-r--r-- | ext2test.c | 311 | ||||
-rw-r--r-- | files.sha256 | 16 | ||||
-rwxr-xr-x | grade.py | 193 | ||||
-rw-r--r-- | listfs.c | 86 | ||||
-rw-r--r-- | md5.h | 48 | ||||
-rw-r--r-- | md5c.c | 323 | ||||
-rwxr-xr-x | run-clang-format.sh | 20 |
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); +} @@ -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_ */ @@ -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." |