├── .ci ├── check-format.sh ├── check-newline.sh ├── check-provisioning.sh └── check-sanity.sh ├── .clang-format ├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .valgrindrc ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── console.c ├── console.h ├── dudect ├── constant.c ├── constant.h ├── cpucycles.h ├── fixture.c ├── fixture.h ├── ttest.c └── ttest.h ├── harness.c ├── harness.h ├── linenoise.c ├── linenoise.h ├── list.h ├── log2_lshift16.h ├── qtest.c ├── queue.c ├── queue.h ├── random.c ├── random.h ├── report.c ├── report.h ├── scripts ├── aspell-pws ├── check-commitlog.sh ├── check-repo.sh ├── checksums ├── commit-msg.hook ├── common.sh ├── debug.py ├── driver.py ├── install-git-hooks ├── kirby.raw ├── pre-commit.hook ├── pre-push.hook ├── prepare-commit-msg.hook └── weeping.raw ├── shannon_entropy.c ├── tools └── fmtscan.c ├── traces ├── trace-01-ops.cmd ├── trace-02-ops.cmd ├── trace-03-ops.cmd ├── trace-04-ops.cmd ├── trace-05-ops.cmd ├── trace-06-ops.cmd ├── trace-07-string.cmd ├── trace-08-robust.cmd ├── trace-09-robust.cmd ├── trace-10-robust.cmd ├── trace-11-malloc.cmd ├── trace-12-malloc.cmd ├── trace-13-malloc.cmd ├── trace-14-perf.cmd ├── trace-15-perf.cmd ├── trace-16-perf.cmd ├── trace-17-complexity.cmd └── trace-eg.cmd ├── web.c └── web.h /.ci/check-format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SOURCES=$(find $(git rev-parse --show-toplevel) | grep -E "\.(cpp|h)\$") 4 | 5 | set -x 6 | 7 | for file in ${SOURCES}; 8 | do 9 | clang-format-18 ${file} > expected-format 10 | diff -u -p --label="${file}" --label="expected coding style" ${file} expected-format 11 | done 12 | exit $(clang-format-18 --output-replacements-xml ${SOURCES} | grep -E -c "") 13 | -------------------------------------------------------------------------------- /.ci/check-newline.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -u -o pipefail 4 | 5 | ret=0 6 | show=0 7 | # Reference: https://medium.com/@alexey.inkin/how-to-force-newline-at-end-of-files-and-why-you-should-do-it-fdf76d1d090e 8 | while IFS= read -rd '' f; do 9 | if file --mime-encoding "$f" | grep -qv binary; then 10 | tail -c1 < "$f" | read -r _ || show=1 11 | if [ $show -eq 1 ]; then 12 | echo "Warning: No newline at end of file $f" 13 | ret=1 14 | show=0 15 | fi 16 | fi 17 | done < <(git ls-files -z src tests/arch-test-target) 18 | 19 | exit $ret 20 | -------------------------------------------------------------------------------- /.ci/check-provisioning.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROVISION_DIR=lab0-c-private 4 | if test -f $PROVISION_DIR/queue.c; then 5 | cp -f $PROVISION_DIR/queue.c . 6 | # Skip complexity checks 7 | sed -i '/17:/d' scripts/driver.py 8 | fi 9 | -------------------------------------------------------------------------------- /.ci/check-sanity.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SHA1SUM=$(which sha1sum) 4 | if [ $? -ne 0 ]; then 5 | SHA1SUM=shasum 6 | fi 7 | 8 | $SHA1SUM -c scripts/checksums 9 | if [ $? -ne 0 ]; then 10 | echo "[!] You are not allowed to change the header file queue.h or list.h" >&2 11 | exit 1 12 | fi 13 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | IndentCaseLabels: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | PointerAlignment: Right 10 | SpaceAfterCStyleCast: true 11 | TabWidth: 4 12 | UseTab: Never 13 | IndentWidth: 4 14 | BreakBeforeBraces: Linux 15 | AccessModifierOffset: -4 16 | ForEachMacros: 17 | - foreach 18 | - Q_FOREACH 19 | - BOOST_FOREACH 20 | - list_for_each 21 | - list_for_each_safe 22 | - list_for_each_entry 23 | - list_for_each_entry_safe 24 | - hlist_for_each_entry 25 | - rb_list_foreach 26 | - rb_list_foreach_safe 27 | SpaceBeforeParens: ControlStatementsExceptForEachMacros 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [Makefile] 9 | indent_style = tab 10 | indent_size = 4 11 | max_line_length = 80 12 | 13 | [*.[ch]] 14 | indent_style = space 15 | indent_size = 4 16 | max_line_length = 80 17 | 18 | [{*.py,/.ci/*}] 19 | indent_style = space 20 | indent_size = 4 21 | 22 | [{/scripts/*,/.github/workflows/*}] 23 | indent_style = space 24 | indent_size = 2 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lab0-c: 7 | runs-on: ubuntu-24.04 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: webfactory/ssh-agent@v0.7.0 11 | continue-on-error: true 12 | with: 13 | ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} 14 | - name: install-dependencies 15 | run: | 16 | .ci/check-sanity.sh 17 | sudo apt-get update 18 | sudo apt-get -q -y install build-essential cppcheck 19 | - name: make 20 | run: | 21 | git clone git@github.com:sysprog21/lab0-c-private || echo "No provisioning profile found" 22 | .ci/check-provisioning.sh 23 | make 24 | - name: make check 25 | run: | 26 | make check || (cat scripts/weeping.raw ; exit 1) 27 | cat scripts/kirby.raw 28 | - name: make test 29 | run: | 30 | make test || (cat scripts/weeping.raw ; exit 1) 31 | cat scripts/kirby.raw 32 | 33 | coding-style: 34 | runs-on: ubuntu-24.04 35 | steps: 36 | - uses: actions/checkout@v4 37 | - name: coding convention 38 | run: | 39 | sudo apt-get install -q -y clang-format-18 40 | .ci/check-newline.sh 41 | .ci/check-format.sh 42 | shell: bash 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | qtest 2 | fmtscan 3 | *.o 4 | *.o.d 5 | *.dSYM 6 | .vscode 7 | .devcontainer 8 | core* 9 | .cmd_history 10 | .out 11 | 12 | # ctags files 13 | TAGS 14 | .TAGS 15 | !TAGS/ 16 | tags 17 | .tags 18 | !tags/ 19 | 20 | # cscope files 21 | cscope.* 22 | ncscope.* 23 | 24 | # gnu global files 25 | GPATH 26 | GRTAGS 27 | GSYMS 28 | GTAGS 29 | -------------------------------------------------------------------------------- /.valgrindrc: -------------------------------------------------------------------------------- 1 | --quiet 2 | --memcheck:leak-check=full 3 | --memcheck:show-leak-kinds=all 4 | --error-exitcode=1 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | lab0-c is freely redistributable under the two-clause BSD License: 2 | 3 | Copyright (C) 2017 Carnegie Mellon University. 4 | Copyright (C) 2018-2025 National Cheng Kung University, Taiwan. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, 12 | this list of conditions and the following disclaimer. 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 | THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -O1 -g -Wall -Werror -Idudect -I. 3 | 4 | # Emit a warning should any variable-length array be found within the code. 5 | CFLAGS += -Wvla 6 | 7 | GIT_HOOKS := .git/hooks/applied 8 | DUT_DIR := dudect 9 | all: $(GIT_HOOKS) qtest fmtscan 10 | 11 | UNAME_S := $(shell uname -s) 12 | 13 | tid := 0 14 | 15 | # Control test case option of valgrind 16 | ifeq ("$(tid)","0") 17 | TCASE := 18 | else 19 | TCASE := -t $(tid) 20 | endif 21 | 22 | # Control the build verbosity 23 | ifeq ("$(VERBOSE)","1") 24 | Q := 25 | VECHO = @true 26 | else 27 | Q := @ 28 | VECHO = @printf 29 | endif 30 | 31 | # Enable sanitizer(s) or not 32 | ifeq ("$(SANITIZER)","1") 33 | # https://github.com/google/sanitizers/wiki/AddressSanitizerFlags 34 | CFLAGS += -fsanitize=address -fno-omit-frame-pointer -fno-common 35 | LDFLAGS += -fsanitize=address 36 | endif 37 | 38 | $(GIT_HOOKS): 39 | @scripts/install-git-hooks 40 | @echo 41 | 42 | OBJS := qtest.o report.o console.o harness.o queue.o \ 43 | random.o dudect/constant.o dudect/fixture.o dudect/ttest.o \ 44 | shannon_entropy.o \ 45 | linenoise.o web.o 46 | 47 | deps := $(OBJS:%.o=.%.o.d) 48 | 49 | qtest: $(OBJS) 50 | $(VECHO) " LD\t$@\n" 51 | $(Q)$(CC) $(LDFLAGS) -o $@ $^ -lm 52 | 53 | %.o: %.c 54 | @mkdir -p .$(DUT_DIR) 55 | $(VECHO) " CC\t$@\n" 56 | $(Q)$(CC) -o $@ $(CFLAGS) -c -MMD -MF .$@.d $< 57 | 58 | fmtscan: tools/fmtscan.c 59 | ifeq ($(UNAME_S),Darwin) 60 | $(Q)printf "#!/usr/bin/env bash\nexit 0\n" > $@ 61 | $(Q)chmod +x $@ 62 | else 63 | $(VECHO) " CC+LD\t$@\n" 64 | $(Q)$(CC) -o $@ $(CFLAGS) $< -lrt -lpthread 65 | endif 66 | 67 | check: qtest 68 | ./$< -v 3 -f traces/trace-eg.cmd 69 | 70 | test: qtest scripts/driver.py 71 | $(Q)scripts/check-repo.sh 72 | scripts/driver.py -c 73 | 74 | valgrind_existence: 75 | @which valgrind 2>&1 > /dev/null || (echo "FATAL: valgrind not found"; exit 1) 76 | 77 | valgrind: valgrind_existence 78 | # Explicitly disable sanitizer(s) 79 | $(MAKE) clean SANITIZER=0 qtest 80 | $(eval patched_file := $(shell mktemp /tmp/qtest.XXXXXX)) 81 | cp qtest $(patched_file) 82 | chmod u+x $(patched_file) 83 | sed -i "s/alarm/isnan/g" $(patched_file) 84 | scripts/driver.py -p $(patched_file) --valgrind $(TCASE) 85 | @echo 86 | @echo "Test with specific case by running command:" 87 | @echo "scripts/driver.py -p $(patched_file) --valgrind -t " 88 | 89 | clean: 90 | rm -f $(OBJS) $(deps) *~ qtest /tmp/qtest.* fmtscan 91 | rm -rf .$(DUT_DIR) 92 | rm -rf *.dSYM 93 | (cd traces; rm -f *~) 94 | 95 | distclean: clean 96 | -rm -f .cmd_history 97 | -rm -rf .out 98 | 99 | -include $(deps) 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lab0-c 2 | Assessing Your C Programming Skills 3 | 4 | This lab will give you practice in the style of programming you will need to be able to do proficiently, 5 | especially for the later assignments in the class. The material covered should all be review for you. Some 6 | of the skills tested are: 7 | * Explicit memory management, as required in C. 8 | * Creating and manipulating pointer-based data structures. 9 | * Working with strings. 10 | * Enhancing the performance of key operations by storing redundant information in data structures. 11 | * Implementing robust code that operates correctly with invalid arguments, including NULL pointers. 12 | 13 | The lab involves implementing a queue, supporting both last-in, first-out (LIFO) and first-in-first-out (FIFO) 14 | queueing disciplines. The underlying data structure is a circular doubly-linked list, enhanced to make some of 15 | the operations more efficient. 16 | 17 | ## Prerequisites 18 | 19 | There are a few prerequisites which must be installed on your machine before you will 20 | be able to build and run the autograders. 21 | 22 | The following command will install all required and optional dependencies on Ubuntu 23 | Linux 20.04 or later: 24 | ```shell 25 | $ sudo apt install build-essential git clang-format cppcheck aspell colordiff valgrind 26 | ``` 27 | Some distros like Arch Linux won't install `aspell-en` with `aspell`, and you must install it explicitly: 28 | ```shell 29 | $ sudo pacman -S aspell-en 30 | ``` 31 | 32 | Note: [Cppcheck](https://cppcheck.sourceforge.io/) version must be at least 1.90, otherwise 33 | it might report errors with false positives. You can get its version by executing `$ cppcheck --version`. 34 | Check [Developer Info](https://cppcheck.sourceforge.io/devinfo/) for building Cppcheck from source. 35 | 36 | ### Integrate `clang-format` to `vim` 37 | If you want to run `clang-format` automatically after saving with vim, 38 | clang-format supports integration for vim according to [Clang documentation](https://clang.llvm.org/docs/ClangFormat.html). 39 | 40 | By adding the following into `$HOME/.vimrc` 41 | ```shell 42 | function! Formatonsave() 43 | let l:formatdiff = 1 44 | py3f /clang-format.py 45 | endfunction 46 | autocmd BufWritePre *.h,*.hpp,*.c,*.cc,*.cpp call Formatonsave() 47 | ``` 48 | 49 | Then, it has zero-effort integration into the coding workflow since it can handle formatting changes while saving a file. 50 | Note: on Ubuntu Linux 18.04, the path to `clang-format.py` is `/usr/share/vim/addons/syntax/`. 51 | 52 | ## Running the autograders 53 | 54 | Before running the autograders, compile your code to create the testing program `qtest` 55 | ```shell 56 | $ make 57 | ``` 58 | 59 | Check the correctness of your code, i.e. autograders: 60 | ```shell 61 | $ make test 62 | ``` 63 | 64 | Check the example usage of `qtest`: 65 | ```shell 66 | $ make check 67 | ``` 68 | Each step about command invocation will be shown accordingly. 69 | 70 | Check the memory issue of your code: 71 | ```shell 72 | $ make valgrind 73 | ``` 74 | 75 | * Modify `./.valgrindrc` to customize arguments of Valgrind 76 | * Use `$ make clean` or `$ rm /tmp/qtest.*` to clean the temporary files created by target valgrind 77 | 78 | Extra options can be recognized by make: 79 | * `VERBOSE`: control the build verbosity. If `VERBOSE=1`, echo each command in build process. 80 | * `SANITIZER`: enable sanitizer(s) directed build. At the moment, AddressSanitizer is supported. 81 | 82 | ## Using `qtest` 83 | 84 | `qtest` provides a command interpreter that can create and manipulate queues. 85 | 86 | Run `$ ./qtest -h` to see the list of command-line options 87 | 88 | When you execute `$ ./qtest`, it will give a command prompt `cmd> `. Type 89 | `help` to see a list of available commands. 90 | 91 | ## Files 92 | 93 | You will handing in these two files 94 | * `queue.h` : Modified version of declarations including new fields you want to introduce 95 | * `queue.c` : Modified version of queue code to fix deficiencies of original code 96 | 97 | Tools for evaluating your queue code 98 | * `Makefile` : Builds the evaluation program `qtest` 99 | * `README.md` : This file 100 | * `scripts/driver.py` : The driver program, runs `qtest` on a standard set of traces 101 | * `scripts/debug.py` : The helper program for GDB, executes `qtest` without SIGALRM and/or analyzes generated core dump file. 102 | 103 | Helper files 104 | * `console.{c,h}` : Implements command-line interpreter for qtest 105 | * `report.{c,h}` : Implements printing of information at different levels of verbosity 106 | * `harness.{c,h}` : Customized version of malloc/free/strdup to provide rigorous testing framework 107 | * `qtest.c` : Code for `qtest` 108 | 109 | Trace files 110 | * `traces/trace-XX-CAT.cmd` : Trace files used by the driver. These are input files for `qtest`. 111 | * They are short and simple. 112 | * We encourage to study them to see what tests are being performed. 113 | * XX is the trace number (1-17). CAT describes the general nature of the test. 114 | * All functions that need to be implemented are explicitly listed. 115 | * If a colon is present in the title, all functions mentioned afterwards must be correctly implemented for the test to pass. 116 | * `traces/trace-eg.cmd` : A simple, documented trace file to demonstrate the operation of `qtest` 117 | 118 | ## Debugging Facilities 119 | 120 | Before using GDB debug `qtest`, there are some routine instructions need to do. The script `scripts/debug.py` covers these instructions and provides basic debug function. 121 | ```shell 122 | $ scripts/debug.py -h 123 | usage: debug.py [-h] [-d | -a] 124 | 125 | optional arguments: 126 | -h, --help show this help message and exit 127 | -d, --debug Enter gdb shell 128 | -a, --analyze Analyze the core dump file 129 | ``` 130 | * Enter GDB without interruption by **SIGALRM**. 131 | ```shell 132 | $ scripts/debug.py -d 133 | Reading symbols from lab0-c/qtest...done. 134 | Signal Stop Print Pass to program Description 135 | SIGALRM No No No Alarm clock 136 | Starting program: lab0-c/qtest 137 | cmd> 138 | ``` 139 | * When `qtest` encountered **Segmentation fault** while it ran outside GDB, we could invoke GDB in the post-mortem debugging mode to figure out the bug. 140 | 141 | The core dump file was created in the working directory of the `qtest`. 142 | * Allow the core dumps by using shell built-in command **ulimit** to set core file size. 143 | ```shell 144 | $ ulimit -c unlimited 145 | $ ulimit -c 146 | unlimited 147 | ``` 148 | * Enter GDB and analyze 149 | ```shell 150 | $ scripts/debug.py -a 151 | Reading symbols from lab0-c/qtest...done. 152 | [New LWP 9424] 153 | Core was generated by `lab0-c/qtest'. 154 | Program terminated with signal SIGSEGV, Segmentation fault. 155 | #0 ... 156 | #1 ... (backtrace information) 157 | #2 ... 158 | (gdb) 159 | ``` 160 | 161 | ## User-friendly command line 162 | [linenoise](https://github.com/antirez/linenoise) was integrated into `qtest`, providing the following user-friendly features: 163 | * Move cursor by Left and Right key 164 | * Jump the cursor over words by Ctrl-Left and Ctrl-Right key 165 | * Get previous or next command typed before by up and down key 166 | * Auto completion by TAB 167 | 168 | ## Built-in web server 169 | 170 | A small web server is already integrated within the `qtest` command line interpreter, 171 | and you may use it by running the `web` command in its prompt. 172 | ```shell 173 | $ ./qtest 174 | cmd> web 175 | listen on port 9999, fd is 3 176 | ``` 177 | 178 | Run the following commands in another terminal after the built-in web server is ready. 179 | ```shell 180 | $ curl http://localhost:9999/new 181 | $ curl http://localhost:9999/ih/1 182 | $ curl http://localhost:9999/ih/2 183 | $ curl http://localhost:9999/ih/3 184 | $ curl http://localhost:9999/sort 185 | $ curl http://localhost:9999/quit 186 | ``` 187 | 188 | ## License 189 | 190 | `lab0-c` is released under the BSD 2 clause license. Use of this source code is governed by 191 | a BSD-style license that can be found in the LICENSE file. 192 | 193 | External source code: 194 | * [dudect](https://github.com/oreparaz/dudect): public domain 195 | * [git-good-commit](https://github.com/tommarshall/git-good-commit): MIT License 196 | * [linenoise](https://github.com/antirez/linenoise): BSD 2-Clause "Simplified" License 197 | * [tiny-web-server](https://github.com/7890/tiny-web-server): MIT License 198 | * [randombytes](https://github.com/dsprenkels/randombytes): MIT License 199 | -------------------------------------------------------------------------------- /console.c: -------------------------------------------------------------------------------- 1 | /* Implementation of simple command-line interface */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "console.h" 15 | #include "report.h" 16 | #include "web.h" 17 | 18 | /* Some global values */ 19 | int simulation = 0; 20 | int show_entropy = 0; 21 | static cmd_element_t *cmd_list = NULL; 22 | static param_element_t *param_list = NULL; 23 | static bool block_flag = false; 24 | static bool prompt_flag = true; 25 | 26 | /* Am I timing a command that has the console blocked? */ 27 | static bool block_timing = false; 28 | 29 | /* Time of day */ 30 | static double first_time, last_time; 31 | 32 | /* Implement buffered I/O using variant of RIO package from CS:APP 33 | * Must create stack of buffers to handle I/O with nested source commands. 34 | */ 35 | 36 | #define RIO_BUFSIZE 8192 37 | 38 | typedef struct __rio { 39 | int fd; /* File descriptor */ 40 | int count; /* Unread bytes in internal buffer */ 41 | char *bufptr; /* Next unread byte in internal buffer */ 42 | char buf[RIO_BUFSIZE]; /* Internal buffer */ 43 | struct __rio *prev; /* Next element in stack */ 44 | } rio_t; 45 | 46 | static rio_t *buf_stack; 47 | static char linebuf[RIO_BUFSIZE]; 48 | 49 | /* Maximum file descriptor */ 50 | static int fd_max = 0; 51 | 52 | /* Parameters */ 53 | static int err_limit = 5; 54 | static int err_cnt = 0; 55 | static int echo = 0; 56 | 57 | static bool quit_flag = false; 58 | static char *prompt = "cmd> "; 59 | static bool has_infile = false; 60 | 61 | /* Optional function to call as part of exit process */ 62 | /* Maximum number of quit functions */ 63 | 64 | #define MAXQUIT 10 65 | static cmd_func_t quit_helpers[MAXQUIT]; 66 | static int quit_helper_cnt = 0; 67 | 68 | static void init_in(); 69 | 70 | static bool push_file(char *fname); 71 | static void pop_file(); 72 | 73 | static bool interpret_cmda(int argc, char *argv[]); 74 | 75 | /* Add a new command */ 76 | void add_cmd(char *name, cmd_func_t operation, char *summary, char *param) 77 | { 78 | cmd_element_t *next_cmd = cmd_list; 79 | cmd_element_t **last_loc = &cmd_list; 80 | while (next_cmd && strcmp(name, next_cmd->name) > 0) { 81 | last_loc = &next_cmd->next; 82 | next_cmd = next_cmd->next; 83 | } 84 | 85 | cmd_element_t *cmd = malloc_or_fail(sizeof(cmd_element_t), "add_cmd"); 86 | cmd->name = name; 87 | cmd->operation = operation; 88 | cmd->summary = summary; 89 | cmd->param = param; 90 | cmd->next = next_cmd; 91 | *last_loc = cmd; 92 | } 93 | 94 | /* Add a new parameter */ 95 | void add_param(char *name, int *valp, char *summary, setter_func_t setter) 96 | { 97 | param_element_t *next_param = param_list; 98 | param_element_t **last_loc = ¶m_list; 99 | while (next_param && strcmp(name, next_param->name) > 0) { 100 | last_loc = &next_param->next; 101 | next_param = next_param->next; 102 | } 103 | 104 | param_element_t *param = 105 | malloc_or_fail(sizeof(param_element_t), "add_param"); 106 | param->name = name; 107 | param->valp = valp; 108 | param->summary = summary; 109 | param->setter = setter; 110 | param->next = next_param; 111 | *last_loc = param; 112 | } 113 | 114 | /* Parse a string into a command line */ 115 | static char **parse_args(char *line, int *argcp) 116 | { 117 | /* Must first determine how many arguments there are. 118 | * Replace all white space with null characters 119 | */ 120 | size_t len = strlen(line); 121 | 122 | /* First copy into buffer with each substring null-terminated */ 123 | char *buf = malloc_or_fail(len + 1, "parse_args"); 124 | buf[len] = '\0'; 125 | 126 | char *src = line; 127 | char *dst = buf; 128 | bool skipping = true; 129 | int c; 130 | int argc = 0; 131 | while ((c = *src++) != '\0') { 132 | if (isspace(c)) { 133 | if (!skipping) { 134 | /* Hit end of word */ 135 | *dst++ = '\0'; 136 | skipping = true; 137 | } 138 | } else { 139 | if (skipping) { 140 | /* Hit start of new word */ 141 | argc++; 142 | skipping = false; 143 | } 144 | *dst++ = c; 145 | } 146 | } 147 | /* Let the last substring is null-terminated */ 148 | *dst++ = '\0'; 149 | 150 | /* Now assemble into array of strings */ 151 | char **argv = calloc_or_fail(argc, sizeof(char *), "parse_args"); 152 | src = buf; 153 | for (int i = 0; i < argc; i++) { 154 | argv[i] = strsave_or_fail(src, "parse_args"); 155 | src += strlen(argv[i]) + 1; 156 | } 157 | 158 | free_block(buf, len + 1); 159 | *argcp = argc; 160 | return argv; 161 | } 162 | 163 | /* Handles forced console termination for record_error and do_quit */ 164 | static bool force_quit(int argc, char *argv[]) 165 | { 166 | cmd_element_t *c = cmd_list; 167 | bool ok = true; 168 | while (c) { 169 | cmd_element_t *ele = c; 170 | c = c->next; 171 | free_block(ele, sizeof(cmd_element_t)); 172 | } 173 | 174 | param_element_t *p = param_list; 175 | while (p) { 176 | param_element_t *ele = p; 177 | p = p->next; 178 | free_block(ele, sizeof(param_element_t)); 179 | } 180 | 181 | while (buf_stack) 182 | pop_file(); 183 | 184 | for (int i = 0; i < quit_helper_cnt; i++) { 185 | ok = ok && quit_helpers[i](argc, argv); 186 | } 187 | 188 | quit_flag = true; 189 | return ok; 190 | } 191 | 192 | static void record_error() 193 | { 194 | err_cnt++; 195 | if (err_cnt >= err_limit) { 196 | report( 197 | 1, 198 | "Error limit exceeded. Stopping command execution, and quitting"); 199 | force_quit(0, NULL); 200 | } 201 | } 202 | 203 | /* Execute a command that has already been split into arguments */ 204 | static bool interpret_cmda(int argc, char *argv[]) 205 | { 206 | if (argc == 0) 207 | return true; 208 | /* Try to find matching command */ 209 | cmd_element_t *next_cmd = cmd_list; 210 | bool ok = true; 211 | while (next_cmd && strcmp(argv[0], next_cmd->name) != 0) 212 | next_cmd = next_cmd->next; 213 | if (next_cmd) { 214 | ok = next_cmd->operation(argc, argv); 215 | if (!ok) 216 | record_error(); 217 | } else { 218 | report(1, "Unknown command '%s'", argv[0]); 219 | record_error(); 220 | ok = false; 221 | } 222 | 223 | return ok; 224 | } 225 | 226 | /* Execute a command from a command line */ 227 | static bool interpret_cmd(char *cmdline) 228 | { 229 | if (quit_flag) 230 | return false; 231 | 232 | int argc; 233 | char **argv = parse_args(cmdline, &argc); 234 | bool ok = interpret_cmda(argc, argv); 235 | for (int i = 0; i < argc; i++) 236 | free_string(argv[i]); 237 | free_array(argv, argc, sizeof(char *)); 238 | 239 | return ok; 240 | } 241 | 242 | /* Set function to be executed as part of program exit */ 243 | void add_quit_helper(cmd_func_t qf) 244 | { 245 | if (quit_helper_cnt < MAXQUIT) 246 | quit_helpers[quit_helper_cnt++] = qf; 247 | else 248 | report_event(MSG_FATAL, "Exceeded limit on quit helpers"); 249 | } 250 | 251 | /* Turn echoing on/off */ 252 | void set_echo(bool on) 253 | { 254 | echo = on ? 1 : 0; 255 | } 256 | 257 | /* Built-in commands */ 258 | static bool do_quit(int argc, char *argv[]) 259 | { 260 | return force_quit(argc, argv); 261 | } 262 | 263 | static bool do_help(int argc, char *argv[]) 264 | { 265 | cmd_element_t *clist = cmd_list; 266 | report(1, "Commands:", argv[0]); 267 | while (clist) { 268 | report(1, " %-12s%-12s | %s", clist->name, clist->param, 269 | clist->summary); 270 | clist = clist->next; 271 | } 272 | param_element_t *plist = param_list; 273 | report(1, "Options:"); 274 | while (plist) { 275 | report(1, " %-12s%-12d | %s", plist->name, *plist->valp, 276 | plist->summary); 277 | plist = plist->next; 278 | } 279 | return true; 280 | } 281 | 282 | static bool do_comment_cmd(int argc, char *argv[]) 283 | { 284 | if (echo) 285 | return true; 286 | 287 | int i; 288 | for (i = 0; i < argc - 1; i++) 289 | report_noreturn(1, "%s ", argv[i]); 290 | if (i < argc) 291 | report(1, "%s", argv[i]); 292 | 293 | return true; 294 | } 295 | 296 | /* Extract integer from text and store at loc */ 297 | bool get_int(char *vname, int *loc) 298 | { 299 | char *end = NULL; 300 | long int v = strtol(vname, &end, 0); 301 | if (v == LONG_MIN || *end != '\0') 302 | return false; 303 | 304 | *loc = (int) v; 305 | return true; 306 | } 307 | 308 | static bool do_option(int argc, char *argv[]) 309 | { 310 | if (argc == 1) { 311 | param_element_t *plist = param_list; 312 | report(1, "Options:"); 313 | while (plist) { 314 | report(1, " %-12s%-12d | %s", plist->name, *plist->valp, 315 | plist->summary); 316 | plist = plist->next; 317 | } 318 | return true; 319 | } 320 | 321 | for (int i = 1; i < argc; i++) { 322 | char *name = argv[i]; 323 | int value = 0; 324 | bool found = false; 325 | /* Get value from next argument */ 326 | if (i + 1 >= argc) { 327 | report(1, "No value given for parameter %s", name); 328 | return false; 329 | } else if (!get_int(argv[++i], &value)) { 330 | report(1, "Cannot parse '%s' as integer", argv[i]); 331 | return false; 332 | } 333 | /* Find parameter in list */ 334 | param_element_t *plist = param_list; 335 | while (!found && plist) { 336 | if (strcmp(plist->name, name) == 0) { 337 | int oldval = *plist->valp; 338 | *plist->valp = value; 339 | if (plist->setter) 340 | plist->setter(oldval); 341 | found = true; 342 | } else 343 | plist = plist->next; 344 | } 345 | /* Didn't find parameter */ 346 | if (!found) { 347 | report(1, "Unknown parameter '%s'", name); 348 | return false; 349 | } 350 | } 351 | 352 | return true; 353 | } 354 | 355 | static bool do_source(int argc, char *argv[]) 356 | { 357 | if (argc < 2) { 358 | report(1, "No source file given. Use 'source '."); 359 | return false; 360 | } 361 | 362 | if (!push_file(argv[1])) { 363 | report(1, "Could not open source file '%s'", argv[1]); 364 | return false; 365 | } 366 | 367 | return true; 368 | } 369 | 370 | static bool do_log(int argc, char *argv[]) 371 | { 372 | if (argc < 2) { 373 | report(1, "No log file given. Use 'log '."); 374 | return false; 375 | } 376 | 377 | bool result = set_logfile(argv[1]); 378 | if (!result) 379 | report(1, "Couldn't open log file '%s'", argv[1]); 380 | 381 | printf("Logging enabled: %s\n", argv[1]); 382 | return result; 383 | } 384 | 385 | static bool do_time(int argc, char *argv[]) 386 | { 387 | double delta = delta_time(&last_time); 388 | bool ok = true; 389 | if (argc <= 1) { 390 | double elapsed = last_time - first_time; 391 | report(1, "Elapsed time = %.3f, Delta time = %.3f", elapsed, delta); 392 | } else { 393 | ok = interpret_cmda(argc - 1, argv + 1); 394 | if (block_flag) { 395 | block_timing = true; 396 | } else { 397 | delta = delta_time(&last_time); 398 | report(1, "Delta time = %.3f", delta); 399 | } 400 | } 401 | 402 | return ok; 403 | } 404 | 405 | static bool use_linenoise = true; 406 | static int web_fd; 407 | 408 | static bool do_web(int argc, char *argv[]) 409 | { 410 | int port = 9999; 411 | if (argc == 2) { 412 | if (argv[1][0] >= '0' && argv[1][0] <= '9') 413 | port = atoi(argv[1]); 414 | } 415 | 416 | web_fd = web_open(port); 417 | if (web_fd > 0) { 418 | printf("listen on port %d, fd is %d\n", port, web_fd); 419 | line_set_eventmux_callback(web_eventmux); 420 | use_linenoise = false; 421 | } else { 422 | perror("ERROR"); 423 | exit(web_fd); 424 | } 425 | return true; 426 | } 427 | 428 | /* Initialize interpreter */ 429 | void init_cmd() 430 | { 431 | cmd_list = NULL; 432 | param_list = NULL; 433 | err_cnt = 0; 434 | quit_flag = false; 435 | 436 | ADD_COMMAND(help, "Show summary", ""); 437 | ADD_COMMAND(option, 438 | "Display or set options. See 'Options' section for details", 439 | "[name val]"); 440 | ADD_COMMAND(quit, "Exit program", ""); 441 | ADD_COMMAND(source, "Read commands from source file", "file"); 442 | ADD_COMMAND(log, "Copy output to file", "file"); 443 | ADD_COMMAND(time, "Time command execution", "cmd arg ..."); 444 | ADD_COMMAND(web, "Read commands from builtin web server", "[port]"); 445 | add_cmd("#", do_comment_cmd, "Display comment", "..."); 446 | add_param("simulation", &simulation, "Start/Stop simulation mode", NULL); 447 | add_param("verbose", &verblevel, "Verbosity level", NULL); 448 | add_param("error", &err_limit, "Number of errors until exit", NULL); 449 | add_param("echo", &echo, "Do/don't echo commands", NULL); 450 | add_param("entropy", &show_entropy, "Show/Hide Shannon entropy", NULL); 451 | 452 | init_in(); 453 | init_time(&last_time); 454 | first_time = last_time; 455 | } 456 | 457 | /* Create new buffer for named file. 458 | * Name == NULL for stdin. 459 | * Return true if successful. 460 | */ 461 | static bool push_file(char *fname) 462 | { 463 | int fd = fname ? open(fname, O_RDONLY) : STDIN_FILENO; 464 | has_infile = fname ? true : false; 465 | if (fd < 0) 466 | return false; 467 | 468 | if (fd > fd_max) 469 | fd_max = fd; 470 | 471 | rio_t *rnew = malloc_or_fail(sizeof(rio_t), "push_file"); 472 | rnew->fd = fd; 473 | rnew->count = 0; 474 | rnew->bufptr = rnew->buf; 475 | rnew->prev = buf_stack; 476 | buf_stack = rnew; 477 | 478 | return true; 479 | } 480 | 481 | /* Pop a file buffer from stack */ 482 | static void pop_file() 483 | { 484 | if (buf_stack) { 485 | rio_t *rsave = buf_stack; 486 | buf_stack = rsave->prev; 487 | close(rsave->fd); 488 | free_block(rsave, sizeof(rio_t)); 489 | } 490 | } 491 | 492 | /* Handling of input */ 493 | static void init_in() 494 | { 495 | buf_stack = NULL; 496 | } 497 | 498 | /* Read command from input file. 499 | * When hit EOF, close that file and return NULL 500 | */ 501 | static char *readline() 502 | { 503 | char c; 504 | char *lptr = linebuf; 505 | 506 | if (!buf_stack) 507 | return NULL; 508 | 509 | for (int cnt = 0; cnt < RIO_BUFSIZE - 2; cnt++) { 510 | if (buf_stack->count <= 0) { 511 | /* Need to read from input file */ 512 | buf_stack->count = read(buf_stack->fd, buf_stack->buf, RIO_BUFSIZE); 513 | buf_stack->bufptr = buf_stack->buf; 514 | if (buf_stack->count <= 0) { 515 | /* Encountered EOF */ 516 | pop_file(); 517 | if (cnt > 0) { 518 | /* Last line of file did not terminate with newline. */ 519 | /* Terminate line & return it */ 520 | *lptr++ = '\n'; 521 | *lptr++ = '\0'; 522 | if (echo) { 523 | report_noreturn(1, prompt); 524 | report_noreturn(1, linebuf); 525 | } 526 | return linebuf; 527 | } 528 | return NULL; 529 | } 530 | } 531 | 532 | /* Have text in buffer */ 533 | c = *buf_stack->bufptr++; 534 | *lptr++ = c; 535 | buf_stack->count--; 536 | if (c == '\n') 537 | break; 538 | } 539 | 540 | if (c != '\n') { 541 | /* Hit buffer limit. Artificially terminate line */ 542 | *lptr++ = '\n'; 543 | } 544 | *lptr++ = '\0'; 545 | 546 | if (echo) { 547 | report_noreturn(1, prompt); 548 | report_noreturn(1, linebuf); 549 | } 550 | 551 | return linebuf; 552 | } 553 | 554 | static bool cmd_done() 555 | { 556 | return !buf_stack || quit_flag; 557 | } 558 | 559 | /* Handle command processing in program that uses select as main control loop. 560 | * Like select, but checks whether command input either present in internal 561 | * buffer 562 | * or readable from command input. If so, that command is executed. 563 | * Same return as select. Command input file removed from readfds 564 | * 565 | * nfds should be set to the maximum file descriptor for network sockets. 566 | * If nfds == 0, this indicates that there is no pending network activity 567 | */ 568 | int web_connfd; 569 | static int cmd_select(int nfds, 570 | fd_set *readfds, 571 | fd_set *writefds, 572 | fd_set *exceptfds, 573 | struct timeval *timeout) 574 | { 575 | fd_set local_readset; 576 | 577 | if (cmd_done()) 578 | return 0; 579 | 580 | if (!block_flag) { 581 | int infd; 582 | /* Process any commands in input buffer */ 583 | if (!readfds) 584 | readfds = &local_readset; 585 | 586 | /* Add input fd to readset for select */ 587 | infd = buf_stack->fd; 588 | FD_ZERO(readfds); 589 | FD_SET(infd, readfds); 590 | 591 | /* If web_fd is available, add to readfds */ 592 | if (web_fd != -1) 593 | FD_SET(web_fd, readfds); 594 | 595 | if (infd == STDIN_FILENO && prompt_flag) { 596 | char *cmdline = linenoise(prompt); 597 | if (cmdline) 598 | interpret_cmd(cmdline); 599 | fflush(stdout); 600 | prompt_flag = true; 601 | } else if (infd != STDIN_FILENO) { 602 | char *cmdline = readline(); 603 | if (cmdline) 604 | interpret_cmd(cmdline); 605 | } 606 | } 607 | return 0; 608 | } 609 | 610 | bool finish_cmd() 611 | { 612 | bool ok = true; 613 | if (!quit_flag) 614 | ok = ok && do_quit(0, NULL); 615 | has_infile = false; 616 | return ok && err_cnt == 0; 617 | } 618 | 619 | static bool cmd_maybe(const char *target, const char *src) 620 | { 621 | for (int i = 0; i < strlen(src); i++) { 622 | if (target[i] == '\0') 623 | return false; 624 | if (src[i] != target[i]) 625 | return false; 626 | } 627 | return true; 628 | } 629 | 630 | void completion(const char *buf, line_completions_t *lc) 631 | { 632 | if (strncmp("option ", buf, 7) == 0) { 633 | param_element_t *plist = param_list; 634 | 635 | while (plist) { 636 | char str[128] = ""; 637 | /* if parameter is too long, now we just ignore it */ 638 | if (strlen(plist->name) > 120) 639 | continue; 640 | 641 | strcat(str, "option "); 642 | strcat(str, plist->name); 643 | if (cmd_maybe(str, buf)) 644 | line_add_completion(lc, str); 645 | 646 | plist = plist->next; 647 | } 648 | return; 649 | } 650 | 651 | cmd_element_t *clist = cmd_list; 652 | while (clist) { 653 | if (cmd_maybe(clist->name, buf)) 654 | line_add_completion(lc, clist->name); 655 | 656 | clist = clist->next; 657 | } 658 | } 659 | 660 | bool run_console(char *infile_name) 661 | { 662 | if (!push_file(infile_name)) { 663 | report(1, "ERROR: Could not open source file '%s'", infile_name); 664 | return false; 665 | } 666 | 667 | if (!has_infile) { 668 | char *cmdline; 669 | while (use_linenoise && (cmdline = linenoise(prompt))) { 670 | interpret_cmd(cmdline); 671 | line_history_add(cmdline); /* Add to the history. */ 672 | line_history_save(HISTORY_FILE); /* Save the history on disk. */ 673 | line_free(cmdline); 674 | while (buf_stack && buf_stack->fd != STDIN_FILENO) 675 | cmd_select(0, NULL, NULL, NULL, NULL); 676 | has_infile = false; 677 | } 678 | if (!use_linenoise) { 679 | while (!cmd_done()) 680 | cmd_select(0, NULL, NULL, NULL, NULL); 681 | } 682 | } else { 683 | while (!cmd_done()) 684 | cmd_select(0, NULL, NULL, NULL, NULL); 685 | } 686 | 687 | return err_cnt == 0; 688 | } 689 | -------------------------------------------------------------------------------- /console.h: -------------------------------------------------------------------------------- 1 | #ifndef LAB0_CONSOLE_H 2 | #define LAB0_CONSOLE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "linenoise.h" 8 | 9 | #define HISTORY_FILE ".cmd_history" 10 | 11 | /* Implementation of simple command-line interface */ 12 | 13 | /* Simulation flag of console option */ 14 | extern int simulation; 15 | 16 | /* Each command defined in terms of a function */ 17 | typedef bool (*cmd_func_t)(int argc, char *argv[]); 18 | 19 | /* Information about each command */ 20 | 21 | /* Organized as linked list in alphabetical order */ 22 | typedef struct __cmd_element { 23 | char *name; 24 | cmd_func_t operation; 25 | char *summary; 26 | char *param; 27 | struct __cmd_element *next; 28 | } cmd_element_t; 29 | 30 | /* Optionally supply function that gets invoked when parameter changes */ 31 | typedef void (*setter_func_t)(int oldval); 32 | 33 | /* Integer-valued parameters */ 34 | typedef struct __param_element { 35 | char *name; 36 | int *valp; 37 | char *summary; 38 | /* Function that gets called whenever parameter changes */ 39 | setter_func_t setter; 40 | struct __param_element *next; 41 | } param_element_t; 42 | 43 | /* Initialize interpreter */ 44 | void init_cmd(); 45 | 46 | /* Add a new command */ 47 | void add_cmd(char *name, cmd_func_t operation, char *summary, char *parameter); 48 | #define ADD_COMMAND(cmd, msg, param) add_cmd(#cmd, do_##cmd, msg, param) 49 | 50 | /* Add a new parameter */ 51 | void add_param(char *name, int *valp, char *summary, setter_func_t setter); 52 | 53 | /* Extract integer from text and store at loc */ 54 | bool get_int(char *vname, int *loc); 55 | 56 | /* Add function to be executed as part of program exit */ 57 | void add_quit_helper(cmd_func_t qf); 58 | 59 | /* Turn echoing on/off */ 60 | void set_echo(bool on); 61 | 62 | /* Complete command interpretation */ 63 | 64 | /* Return true if no errors occurred */ 65 | bool finish_cmd(); 66 | 67 | /* Run command loop. Non-null infile_name implies read commands from that file 68 | */ 69 | bool run_console(char *infile_name); 70 | 71 | /* Callback function to complete command by linenoise */ 72 | void completion(const char *buf, line_completions_t *lc); 73 | 74 | #endif /* LAB0_CONSOLE_H */ 75 | -------------------------------------------------------------------------------- /dudect/constant.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "constant.h" 6 | #include "cpucycles.h" 7 | #include "queue.h" 8 | #include "random.h" 9 | 10 | /* Maintain a queue independent from the qtest since 11 | * we do not want the test to affect the original functionality 12 | */ 13 | static struct list_head *l = NULL; 14 | 15 | #define dut_new() ((void) (l = q_new())) 16 | 17 | #define dut_size(n) \ 18 | do { \ 19 | for (int __iter = 0; __iter < n; ++__iter) \ 20 | q_size(l); \ 21 | } while (0) 22 | 23 | #define dut_insert_head(s, n) \ 24 | do { \ 25 | int j = n; \ 26 | while (j--) \ 27 | q_insert_head(l, s); \ 28 | } while (0) 29 | 30 | #define dut_insert_tail(s, n) \ 31 | do { \ 32 | int j = n; \ 33 | while (j--) \ 34 | q_insert_tail(l, s); \ 35 | } while (0) 36 | 37 | #define dut_free() ((void) (q_free(l))) 38 | 39 | static char random_string[N_MEASURES][8]; 40 | static int random_string_iter = 0; 41 | 42 | /* Implement the necessary queue interface to simulation */ 43 | void init_dut(void) 44 | { 45 | l = NULL; 46 | } 47 | 48 | static char *get_random_string(void) 49 | { 50 | random_string_iter = (random_string_iter + 1) % N_MEASURES; 51 | return random_string[random_string_iter]; 52 | } 53 | 54 | void prepare_inputs(uint8_t *input_data, uint8_t *classes) 55 | { 56 | randombytes(input_data, N_MEASURES * CHUNK_SIZE); 57 | for (size_t i = 0; i < N_MEASURES; i++) { 58 | classes[i] = randombit(); 59 | if (classes[i] == 0) 60 | memset(input_data + (size_t) i * CHUNK_SIZE, 0, CHUNK_SIZE); 61 | } 62 | 63 | for (size_t i = 0; i < N_MEASURES; ++i) { 64 | /* Generate random string */ 65 | randombytes((uint8_t *) random_string[i], 7); 66 | random_string[i][7] = 0; 67 | } 68 | } 69 | 70 | bool measure(int64_t *before_ticks, 71 | int64_t *after_ticks, 72 | uint8_t *input_data, 73 | int mode) 74 | { 75 | assert(mode == DUT(insert_head) || mode == DUT(insert_tail) || 76 | mode == DUT(remove_head) || mode == DUT(remove_tail)); 77 | 78 | switch (mode) { 79 | case DUT(insert_head): 80 | for (size_t i = DROP_SIZE; i < N_MEASURES - DROP_SIZE; i++) { 81 | char *s = get_random_string(); 82 | dut_new(); 83 | dut_insert_head( 84 | get_random_string(), 85 | *(uint16_t *) (input_data + i * CHUNK_SIZE) % 10000); 86 | int before_size = q_size(l); 87 | before_ticks[i] = cpucycles(); 88 | dut_insert_head(s, 1); 89 | after_ticks[i] = cpucycles(); 90 | int after_size = q_size(l); 91 | dut_free(); 92 | if (before_size != after_size - 1) 93 | return false; 94 | } 95 | break; 96 | case DUT(insert_tail): 97 | for (size_t i = DROP_SIZE; i < N_MEASURES - DROP_SIZE; i++) { 98 | char *s = get_random_string(); 99 | dut_new(); 100 | dut_insert_head( 101 | get_random_string(), 102 | *(uint16_t *) (input_data + i * CHUNK_SIZE) % 10000); 103 | int before_size = q_size(l); 104 | before_ticks[i] = cpucycles(); 105 | dut_insert_tail(s, 1); 106 | after_ticks[i] = cpucycles(); 107 | int after_size = q_size(l); 108 | dut_free(); 109 | if (before_size != after_size - 1) 110 | return false; 111 | } 112 | break; 113 | case DUT(remove_head): 114 | for (size_t i = DROP_SIZE; i < N_MEASURES - DROP_SIZE; i++) { 115 | dut_new(); 116 | dut_insert_head( 117 | get_random_string(), 118 | *(uint16_t *) (input_data + i * CHUNK_SIZE) % 10000 + 1); 119 | int before_size = q_size(l); 120 | before_ticks[i] = cpucycles(); 121 | element_t *e = q_remove_head(l, NULL, 0); 122 | after_ticks[i] = cpucycles(); 123 | int after_size = q_size(l); 124 | if (e) 125 | q_release_element(e); 126 | dut_free(); 127 | if (before_size != after_size + 1) 128 | return false; 129 | } 130 | break; 131 | case DUT(remove_tail): 132 | for (size_t i = DROP_SIZE; i < N_MEASURES - DROP_SIZE; i++) { 133 | dut_new(); 134 | dut_insert_head( 135 | get_random_string(), 136 | *(uint16_t *) (input_data + i * CHUNK_SIZE) % 10000 + 1); 137 | int before_size = q_size(l); 138 | before_ticks[i] = cpucycles(); 139 | element_t *e = q_remove_tail(l, NULL, 0); 140 | after_ticks[i] = cpucycles(); 141 | int after_size = q_size(l); 142 | if (e) 143 | q_release_element(e); 144 | dut_free(); 145 | if (before_size != after_size + 1) 146 | return false; 147 | } 148 | break; 149 | default: 150 | for (size_t i = DROP_SIZE; i < N_MEASURES - DROP_SIZE; i++) { 151 | dut_new(); 152 | dut_insert_head( 153 | get_random_string(), 154 | *(uint16_t *) (input_data + i * CHUNK_SIZE) % 10000); 155 | before_ticks[i] = cpucycles(); 156 | dut_size(1); 157 | after_ticks[i] = cpucycles(); 158 | dut_free(); 159 | } 160 | } 161 | return true; 162 | } 163 | -------------------------------------------------------------------------------- /dudect/constant.h: -------------------------------------------------------------------------------- 1 | #ifndef DUDECT_CONSTANT_H 2 | #define DUDECT_CONSTANT_H 3 | 4 | #include 5 | #include 6 | 7 | /* Number of measurements per test */ 8 | #define N_MEASURES 150 9 | 10 | /* Allow random number range from 0 to 65535 */ 11 | #define CHUNK_SIZE 2 12 | 13 | #define DROP_SIZE 20 14 | 15 | #define DUT_FUNCS \ 16 | _(insert_head) \ 17 | _(insert_tail) \ 18 | _(remove_head) \ 19 | _(remove_tail) 20 | 21 | #define DUT(x) DUT_##x 22 | 23 | enum { 24 | #define _(x) DUT(x), 25 | DUT_FUNCS 26 | #undef _ 27 | }; 28 | 29 | void init_dut(); 30 | void prepare_inputs(uint8_t *input_data, uint8_t *classes); 31 | bool measure(int64_t *before_ticks, 32 | int64_t *after_ticks, 33 | uint8_t *input_data, 34 | int mode); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /dudect/cpucycles.h: -------------------------------------------------------------------------------- 1 | #ifndef DUDECT_CPUCYCLES_H 2 | #define DUDECT_CPUCYCLES_H 3 | 4 | #include 5 | 6 | // http://www.intel.com/content/www/us/en/embedded/training/ia-32-ia-64-benchmark-code-execution-paper.html 7 | static inline int64_t cpucycles(void) 8 | { 9 | #if defined(__i386__) || defined(__x86_64__) 10 | unsigned int hi, lo; 11 | __asm__ volatile("rdtsc\n\t" : "=a"(lo), "=d"(hi)); 12 | return ((int64_t) lo) | (((int64_t) hi) << 32); 13 | 14 | #elif defined(__aarch64__) 15 | uint64_t val; 16 | /* According to ARM DDI 0487F.c, from Armv8.0 to Armv8.5 inclusive, the 17 | * system counter is at least 56 bits wide; from Armv8.6, the counter 18 | * must be 64 bits wide. So the system counter could be less than 64 19 | * bits wide and it is attributed with the flag 'cap_user_time_short' 20 | * is true. 21 | */ 22 | asm volatile("mrs %0, cntvct_el0" : "=r"(val)); 23 | return val; 24 | #else 25 | #error Unsupported Architecture 26 | #endif 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /dudect/fixture.c: -------------------------------------------------------------------------------- 1 | /** dude, is my code constant time? 2 | * 3 | * This file measures the execution time of a given function many times with 4 | * different inputs and performs a Welch's t-test to determine if the function 5 | * runs in constant time or not. This is essentially leakage detection, and 6 | * not a timing attack. 7 | * 8 | * Notes: 9 | * 10 | * - the execution time distribution tends to be skewed towards large 11 | * timings, leading to a fat right tail. Most executions take little time, 12 | * some of them take a lot. We try to speed up the test process by 13 | * throwing away those measurements with large cycle count. (For example, 14 | * those measurements could correspond to the execution being interrupted 15 | * by the OS.) Setting a threshold value for this is not obvious; we just 16 | * keep the x% percent fastest timings, and repeat for several values of x. 17 | * 18 | * - the previous observation is highly heuristic. We also keep the uncropped 19 | * measurement time and do a t-test on that. 20 | * 21 | * - we also test for unequal variances (second order test), but this is 22 | * probably redundant since we're doing as well a t-test on cropped 23 | * measurements (non-linear transform) 24 | * 25 | * - as long as any of the different test fails, the code will be deemed 26 | * variable time. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "../console.h" 37 | #include "../random.h" 38 | 39 | #include "constant.h" 40 | #include "fixture.h" 41 | #include "ttest.h" 42 | 43 | #define ENOUGH_MEASURE 10000 44 | #define TEST_TRIES 10 45 | 46 | /* Number of percentiles to calculate */ 47 | #define NUM_PERCENTILES (100) 48 | #define DUDECT_TESTS (NUM_PERCENTILES + 1) 49 | 50 | static t_context_t *ctxs[DUDECT_TESTS]; 51 | 52 | /* threshold values for Welch's t-test */ 53 | enum { 54 | t_threshold_bananas = 500, /* Test failed with overwhelming probability */ 55 | t_threshold_moderate = 10, /* Test failed */ 56 | }; 57 | 58 | static void __attribute__((noreturn)) die(void) 59 | { 60 | exit(111); 61 | } 62 | 63 | static int64_t percentile(const int64_t *a_sorted, double which, size_t size) 64 | { 65 | assert(which >= 0 && which <= 1.0); 66 | size_t pos = (size_t) (which * size); 67 | return a_sorted[pos]; 68 | } 69 | 70 | /* leverages the fact that comparison expressions return 1 or 0. */ 71 | static int cmp(const void *aa, const void *bb) 72 | { 73 | int64_t a = *(const int64_t *) aa, b = *(const int64_t *) bb; 74 | return (a > b) - (a < b); 75 | } 76 | 77 | /* This function is used to set different thresholds for cropping measurements. 78 | * To filter out slow measurements, we keep only the fastest ones by a 79 | * complementary exponential decay scale as thresholds for cropping 80 | * measurements: threshold(x) = 1 - 0.5^(10 * x / N_MEASURES), where x is the 81 | * counter of the measurement. 82 | */ 83 | static void prepare_percentiles(int64_t *exec_times, int64_t *percentiles) 84 | { 85 | qsort(exec_times, N_MEASURES, sizeof(int64_t), cmp); 86 | 87 | for (size_t i = 0; i < NUM_PERCENTILES; i++) { 88 | percentiles[i] = percentile( 89 | exec_times, 1 - (pow(0.5, 10 * (double) (i + 1) / NUM_PERCENTILES)), 90 | N_MEASURES); 91 | } 92 | } 93 | 94 | static void differentiate(int64_t *exec_times, 95 | const int64_t *before_ticks, 96 | const int64_t *after_ticks) 97 | { 98 | for (size_t i = 0; i < N_MEASURES; i++) 99 | exec_times[i] = after_ticks[i] - before_ticks[i]; 100 | } 101 | 102 | static void update_statistics(const int64_t *exec_times, 103 | uint8_t *classes, 104 | int64_t *percentiles) 105 | { 106 | for (size_t i = 0; i < N_MEASURES; i++) { 107 | int64_t difference = exec_times[i]; 108 | /* CPU cycle counter overflowed or dropped measurement */ 109 | if (difference <= 0) 110 | continue; 111 | 112 | /* do a t-test on the execution time */ 113 | t_push(ctxs[0], difference, classes[i]); 114 | 115 | /* t-test on cropped execution times, for several cropping thresholds. 116 | */ 117 | for (size_t j = 0; j < NUM_PERCENTILES; j++) { 118 | if (difference < percentiles[j]) { 119 | t_push(ctxs[j + 1], difference, classes[i]); 120 | } 121 | } 122 | } 123 | } 124 | 125 | static t_context_t *max_test() 126 | { 127 | size_t max_idx = 0; 128 | double max_t = 0.0f; 129 | for (size_t i = 0; i < NUM_PERCENTILES + 1; i++) { 130 | double t = fabs(t_compute(ctxs[i])); 131 | if (t > max_t) { 132 | max_t = t; 133 | max_idx = i; 134 | } 135 | } 136 | return ctxs[max_idx]; 137 | } 138 | 139 | static bool report(void) 140 | { 141 | t_context_t *t = max_test(); 142 | double number_traces_max_t = t->n[0] + t->n[1]; 143 | 144 | printf("\033[A\033[2K"); 145 | printf("measure: %7.2lf M, ", (number_traces_max_t / 1e6)); 146 | if (number_traces_max_t < ENOUGH_MEASURE) { 147 | printf("not enough measurements (%.0f still to go).\n", 148 | ENOUGH_MEASURE - number_traces_max_t); 149 | return false; 150 | } 151 | 152 | double max_t = fabs(t_compute(t)); 153 | double max_tau = max_t / sqrt(number_traces_max_t); 154 | 155 | /* max_t: the t statistic value 156 | * max_tau: a t value normalized by sqrt(number of measurements). 157 | * this way we can compare max_tau taken with different 158 | * number of measurements. This is sort of "distance 159 | * between distributions", independent of number of 160 | * measurements. 161 | * (5/tau)^2: how many measurements we would need to barely 162 | * detect the leak, if present. "barely detect the 163 | * leak" = have a t value greater than 5. 164 | */ 165 | printf("max t: %+7.2f, max tau: %.2e, (5/tau)^2: %.2e.\n", max_t, max_tau, 166 | (double) (5 * 5) / (double) (max_tau * max_tau)); 167 | 168 | /* Definitely not constant time */ 169 | if (max_t > t_threshold_bananas) 170 | return false; 171 | 172 | /* Probably not constant time. */ 173 | if (max_t > t_threshold_moderate) 174 | return false; 175 | 176 | /* For the moment, maybe constant time. */ 177 | return true; 178 | } 179 | 180 | static bool doit(int mode) 181 | { 182 | int64_t *before_ticks = calloc(N_MEASURES + 1, sizeof(int64_t)); 183 | int64_t *after_ticks = calloc(N_MEASURES + 1, sizeof(int64_t)); 184 | int64_t *exec_times = calloc(N_MEASURES, sizeof(int64_t)); 185 | uint8_t *classes = calloc(N_MEASURES, sizeof(uint8_t)); 186 | uint8_t *input_data = calloc(N_MEASURES * CHUNK_SIZE, sizeof(uint8_t)); 187 | int64_t *percentiles = calloc(NUM_PERCENTILES, sizeof(int64_t)); 188 | 189 | if (!before_ticks || !after_ticks || !exec_times || !classes || 190 | !input_data || !percentiles) { 191 | die(); 192 | } 193 | 194 | prepare_inputs(input_data, classes); 195 | 196 | bool ret = measure(before_ticks, after_ticks, input_data, mode); 197 | differentiate(exec_times, before_ticks, after_ticks); 198 | prepare_percentiles(exec_times, percentiles); 199 | 200 | /* This warm-up step discards the first measurement batch by skipping 201 | * its statistical analysis. A static boolean flag controls this by 202 | * marking the initial execution, ensuring only the first call within 203 | * a test run is excluded from the t-test computation. 204 | */ 205 | static bool first_time = true; 206 | if (first_time) { 207 | first_time = false; 208 | ret = true; 209 | } else { 210 | update_statistics(exec_times, classes, percentiles); 211 | ret &= report(); 212 | } 213 | 214 | free(before_ticks); 215 | free(after_ticks); 216 | free(exec_times); 217 | free(classes); 218 | free(input_data); 219 | free(percentiles); 220 | 221 | return ret; 222 | } 223 | 224 | static void init_once(void) 225 | { 226 | init_dut(); 227 | for (size_t i = 0; i < DUDECT_TESTS; i++) { 228 | /* Check if ctxs[i] is unallocated to prevent repeated memory 229 | * allocations. 230 | */ 231 | if (!ctxs[i]) { 232 | ctxs[i] = malloc(sizeof(t_context_t)); 233 | t_init(ctxs[i]); 234 | } 235 | } 236 | } 237 | 238 | static bool test_const(char *text, int mode) 239 | { 240 | bool result = false; 241 | 242 | init_once(); 243 | 244 | for (int cnt = 0; cnt < TEST_TRIES; ++cnt) { 245 | printf("Testing %s...(%d/%d)\n\n", text, cnt, TEST_TRIES); 246 | for (int i = 0; i < ENOUGH_MEASURE / (N_MEASURES - DROP_SIZE * 2) + 1; 247 | ++i) 248 | result = doit(mode); 249 | printf("\033[A\033[2K\033[A\033[2K"); 250 | if (result) 251 | break; 252 | } 253 | 254 | for (size_t i = 0; i < DUDECT_TESTS; i++) { 255 | free(ctxs[i]); 256 | ctxs[i] = NULL; 257 | } 258 | 259 | return result; 260 | } 261 | 262 | #define DUT_FUNC_IMPL(op) \ 263 | bool is_##op##_const(void) \ 264 | { \ 265 | return test_const(#op, DUT(op)); \ 266 | } 267 | 268 | #define _(x) DUT_FUNC_IMPL(x) 269 | DUT_FUNCS 270 | #undef _ 271 | -------------------------------------------------------------------------------- /dudect/fixture.h: -------------------------------------------------------------------------------- 1 | #ifndef DUDECT_FIXTURE_H 2 | #define DUDECT_FIXTURE_H 3 | 4 | #include 5 | #include "constant.h" 6 | 7 | /* Interface to test if function is constant */ 8 | #define _(x) bool is_##x##_const(void); 9 | DUT_FUNCS 10 | #undef _ 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /dudect/ttest.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Online Welch's t-test. 3 | * 4 | * Tests whether two populations have same mean. 5 | * This is basically Student's t-test for unequal 6 | * variances and unequal sample sizes. 7 | * 8 | * See https://en.wikipedia.org/wiki/Welch%27s_t-test 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "ttest.h" 16 | 17 | void t_push(t_context_t *ctx, double x, uint8_t class) 18 | { 19 | assert(class == 0 || class == 1); 20 | ctx->n[class]++; 21 | 22 | /* Welford method for computing online variance 23 | * in a numerically stable way. 24 | */ 25 | double delta = x - ctx->mean[class]; 26 | ctx->mean[class] = ctx->mean[class] + delta / ctx->n[class]; 27 | ctx->m2[class] = ctx->m2[class] + delta * (x - ctx->mean[class]); 28 | } 29 | 30 | double t_compute(t_context_t *ctx) 31 | { 32 | double var[2] = {0.0, 0.0}; 33 | var[0] = ctx->m2[0] / (ctx->n[0] - 1); 34 | var[1] = ctx->m2[1] / (ctx->n[1] - 1); 35 | double num = (ctx->mean[0] - ctx->mean[1]); 36 | double den = sqrt(var[0] / ctx->n[0] + var[1] / ctx->n[1]); 37 | double t_value = num / den; 38 | return t_value; 39 | } 40 | 41 | void t_init(t_context_t *ctx) 42 | { 43 | for (int class = 0; class < 2; class ++) { 44 | ctx->mean[class] = 0.0; 45 | ctx->m2[class] = 0.0; 46 | ctx->n[class] = 0.0; 47 | } 48 | return; 49 | } 50 | -------------------------------------------------------------------------------- /dudect/ttest.h: -------------------------------------------------------------------------------- 1 | #ifndef DUDECT_TTEST_H 2 | #define DUDECT_TTEST_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | double mean[2]; 8 | double m2[2]; 9 | double n[2]; 10 | } t_context_t; 11 | 12 | void t_push(t_context_t *ctx, double x, uint8_t class); 13 | double t_compute(t_context_t *ctx); 14 | void t_init(t_context_t *ctx); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /harness.c: -------------------------------------------------------------------------------- 1 | /* Test support code */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "report.h" 12 | 13 | /* Our program needs to use regular malloc/free */ 14 | #define INTERNAL 1 15 | #include "harness.h" 16 | 17 | /** Special values **/ 18 | 19 | /* Value at start of every allocated block */ 20 | #define MAGICHEADER 0xdeadbeef 21 | 22 | /* Value when deallocate block */ 23 | #define MAGICFREE 0xffffffff 24 | 25 | /* Value at end of every block */ 26 | #define MAGICFOOTER 0xbeefdead 27 | 28 | /* Byte to fill newly malloced space with */ 29 | #define FILLCHAR 0x55 30 | 31 | /* Data structures used by our code */ 32 | 33 | /* Represent allocated blocks as doubly-linked list, with 34 | * next and prev pointers at beginning 35 | */ 36 | typedef struct __block_element { 37 | struct __block_element *next, *prev; 38 | size_t payload_size; 39 | size_t magic_header; /* Marker to see if block seems legitimate */ 40 | unsigned char payload[0]; 41 | /* Also place magic number at tail of every block */ 42 | } block_element_t; 43 | 44 | static block_element_t *allocated = NULL; 45 | static size_t allocated_count = 0; 46 | 47 | /* Percent probability of malloc failure */ 48 | int fail_probability = 0; 49 | 50 | static bool cautious_mode = true; 51 | static bool noallocate_mode = false; 52 | static bool error_occurred = false; 53 | static char *error_message = ""; 54 | 55 | static int time_limit = 1; 56 | 57 | /* Data for managing exceptions */ 58 | static jmp_buf env; 59 | static volatile sig_atomic_t jmp_ready = false; 60 | static bool time_limited = false; 61 | 62 | /* For test_malloc and test_calloc */ 63 | typedef enum { 64 | TEST_MALLOC, 65 | TEST_CALLOC, 66 | TEST_REALLOC, 67 | } alloc_t; 68 | 69 | /* Internal functions */ 70 | 71 | /* Should this allocation fail? */ 72 | static bool fail_allocation() 73 | { 74 | double weight = (double) random() / RAND_MAX; 75 | return (weight < 0.01 * fail_probability); 76 | } 77 | 78 | /* Find header of block, given its payload. 79 | * Signal error if doesn't seem like legitimate block 80 | */ 81 | static block_element_t *find_header(void *p) 82 | { 83 | if (!p) { 84 | report_event(MSG_ERROR, "Attempting to free null block"); 85 | error_occurred = true; 86 | } 87 | 88 | block_element_t *b = 89 | (block_element_t *) ((size_t) p - sizeof(block_element_t)); 90 | if (cautious_mode) { 91 | /* Make sure this is really an allocated block */ 92 | block_element_t *ab = allocated; 93 | bool found = false; 94 | while (ab && !found) { 95 | found = ab == b; 96 | ab = ab->next; 97 | } 98 | if (!found) { 99 | report_event(MSG_ERROR, 100 | "Attempted to free unallocated block. Address = %p", 101 | p); 102 | error_occurred = true; 103 | } 104 | } 105 | 106 | if (b->magic_header != MAGICHEADER) { 107 | report_event( 108 | MSG_ERROR, 109 | "Attempted to free unallocated or corrupted block. Address = %p", 110 | p); 111 | error_occurred = true; 112 | } 113 | 114 | return b; 115 | } 116 | 117 | /* Given pointer to block, find its footer */ 118 | static size_t *find_footer(block_element_t *b) 119 | { 120 | // cppcheck-suppress nullPointerRedundantCheck 121 | size_t *p = 122 | (size_t *) ((size_t) b + b->payload_size + sizeof(block_element_t)); 123 | return p; 124 | } 125 | 126 | static void *alloc(alloc_t alloc_type, size_t size) 127 | { 128 | if (noallocate_mode) { 129 | char *msg_alloc_forbidden[] = { 130 | "Calls to malloc are disallowed", 131 | "Calls to calloc are disallowed", 132 | "Calls to realloc are disallowed", 133 | }; 134 | report_event(MSG_FATAL, "%s", msg_alloc_forbidden[alloc_type]); 135 | return NULL; 136 | } 137 | 138 | if (fail_allocation()) { 139 | char *msg_alloc_failure[] = { 140 | "Malloc returning NULL", 141 | "Calloc returning NULL", 142 | "Realloc returning NULL", 143 | }; 144 | report_event(MSG_WARN, "%s", msg_alloc_failure[alloc_type]); 145 | return NULL; 146 | } 147 | 148 | block_element_t *new_block = 149 | malloc(size + sizeof(block_element_t) + sizeof(size_t)); 150 | if (!new_block) { 151 | report_event(MSG_FATAL, "Couldn't allocate any more memory"); 152 | error_occurred = true; 153 | } 154 | 155 | // cppcheck-suppress nullPointerRedundantCheck 156 | new_block->magic_header = MAGICHEADER; 157 | // cppcheck-suppress nullPointerRedundantCheck 158 | new_block->payload_size = size; 159 | *find_footer(new_block) = MAGICFOOTER; 160 | void *p = (void *) &new_block->payload; 161 | memset(p, !alloc_type * FILLCHAR, size); 162 | // cppcheck-suppress nullPointerRedundantCheck 163 | new_block->next = allocated; 164 | // cppcheck-suppress nullPointerRedundantCheck 165 | new_block->prev = NULL; 166 | 167 | if (allocated) 168 | allocated->prev = new_block; 169 | allocated = new_block; 170 | allocated_count++; 171 | 172 | return p; 173 | } 174 | 175 | /* Implementation of application functions */ 176 | 177 | void *test_malloc(size_t size) 178 | { 179 | return alloc(TEST_MALLOC, size); 180 | } 181 | 182 | // cppcheck-suppress unusedFunction 183 | void *test_calloc(size_t nelem, size_t elsize) 184 | { 185 | /* Reference: Malloc tutorial 186 | * https://danluu.com/malloc-tutorial/ 187 | */ 188 | if (!nelem || !elsize || nelem > SIZE_MAX / elsize) 189 | return NULL; 190 | return alloc(TEST_CALLOC, nelem * elsize); 191 | } 192 | 193 | /* 194 | * Implementation of adjusting the size of the memory allocated 195 | * by test_malloc or test_calloc. 196 | */ 197 | void *test_realloc(void *p, size_t new_size) 198 | { 199 | if (!p) 200 | return alloc(TEST_REALLOC, new_size); 201 | 202 | const block_element_t *b = find_header(p); 203 | if (b->payload_size >= new_size) 204 | return p; 205 | 206 | void *new_ptr = alloc(TEST_REALLOC, new_size); 207 | if (!new_ptr) 208 | return NULL; 209 | memcpy(new_ptr, p, b->payload_size); 210 | test_free(p); 211 | 212 | return new_ptr; 213 | } 214 | 215 | void test_free(void *p) 216 | { 217 | if (noallocate_mode) { 218 | report_event(MSG_FATAL, "Calls to free disallowed"); 219 | return; 220 | } 221 | 222 | if (!p) 223 | return; 224 | 225 | block_element_t *b = find_header(p); 226 | size_t footer = *find_footer(b); 227 | if (footer != MAGICFOOTER) { 228 | report_event(MSG_ERROR, 229 | "Corruption detected in block with address %p when " 230 | "attempting to free it", 231 | p); 232 | error_occurred = true; 233 | } 234 | b->magic_header = MAGICFREE; 235 | *find_footer(b) = MAGICFREE; 236 | memset(p, FILLCHAR, b->payload_size); 237 | 238 | /* Unlink from list */ 239 | block_element_t *bn = b->next; 240 | block_element_t *bp = b->prev; 241 | if (bp) 242 | bp->next = bn; 243 | else 244 | allocated = bn; 245 | if (bn) 246 | bn->prev = bp; 247 | 248 | free(b); 249 | allocated_count--; 250 | } 251 | 252 | // cppcheck-suppress unusedFunction 253 | char *test_strdup(const char *s) 254 | { 255 | size_t len = strlen(s) + 1; 256 | void *new = test_malloc(len); 257 | if (!new) 258 | return NULL; 259 | 260 | return memcpy(new, s, len); 261 | } 262 | 263 | size_t allocation_check() 264 | { 265 | return allocated_count; 266 | } 267 | 268 | /* Implementation of functions for testing */ 269 | 270 | /* Set/unset cautious mode. 271 | * In this mode, makes extra sure any block to be freed is currently allocated. 272 | */ 273 | void set_cautious_mode(bool cautious) 274 | { 275 | cautious_mode = cautious; 276 | } 277 | 278 | /* Set/unset restricted allocation mode. 279 | * In this mode, calls to malloc and free are disallowed. 280 | */ 281 | void set_noallocate_mode(bool noallocate) 282 | { 283 | noallocate_mode = noallocate; 284 | } 285 | 286 | /* Return whether any errors have occurred since last time set error limit */ 287 | bool error_check() 288 | { 289 | bool e = error_occurred; 290 | error_occurred = false; 291 | return e; 292 | } 293 | 294 | /* Prepare for a risky operation using setjmp. 295 | * Function returns true for initial return, false for error return 296 | */ 297 | bool exception_setup(bool limit_time) 298 | { 299 | if (sigsetjmp(env, 1)) { 300 | /* Got here from longjmp */ 301 | jmp_ready = false; 302 | if (time_limited) { 303 | alarm(0); 304 | time_limited = false; 305 | } 306 | 307 | if (error_message) 308 | report_event(MSG_ERROR, error_message); 309 | error_message = ""; 310 | return false; 311 | } 312 | 313 | /* Got here from initial call */ 314 | jmp_ready = true; 315 | if (limit_time) { 316 | alarm(time_limit); 317 | time_limited = true; 318 | } 319 | return true; 320 | } 321 | 322 | /* Call once past risky code */ 323 | void exception_cancel() 324 | { 325 | if (time_limited) { 326 | alarm(0); 327 | time_limited = false; 328 | } 329 | 330 | jmp_ready = false; 331 | error_message = ""; 332 | } 333 | 334 | /* Use longjmp to return to most recent exception setup */ 335 | void trigger_exception(char *msg) 336 | { 337 | error_occurred = true; 338 | error_message = msg; 339 | if (jmp_ready) 340 | siglongjmp(env, 1); 341 | else 342 | exit(1); 343 | } 344 | -------------------------------------------------------------------------------- /harness.h: -------------------------------------------------------------------------------- 1 | #ifndef LAB0_HARNESS_H 2 | #define LAB0_HARNESS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* This test harness enables us to do stringent testing of code. 9 | * It overloads the library versions of malloc and free with ones that 10 | * allow checking for common allocation errors. 11 | */ 12 | 13 | void *test_malloc(size_t size); 14 | void *test_calloc(size_t nmemb, size_t size); 15 | void *test_realloc(void *p, size_t new_size); 16 | void test_free(void *p); 17 | char *test_strdup(const char *s); 18 | 19 | #ifdef INTERNAL 20 | 21 | /* Report number of allocated blocks */ 22 | size_t allocation_check(); 23 | 24 | /* Probability of malloc failing, expressed as percent */ 25 | extern int fail_probability; 26 | 27 | /* 28 | * Set/unset cautious mode. 29 | * In this mode, makes extra sure any block to be freed is currently allocated. 30 | */ 31 | void set_cautious_mode(bool cautious); 32 | 33 | /* 34 | * Set/unset restricted allocation mode. 35 | * In this mode, calls to malloc and free are disallowed. 36 | */ 37 | void set_noallocate_mode(bool noallocate); 38 | 39 | /* Return whether any errors have occurred since last time checked */ 40 | bool error_check(); 41 | 42 | /* Prepare for a risky operation using setjmp. 43 | * Function returns true for initial return, false for error return 44 | */ 45 | bool exception_setup(bool limit_time); 46 | 47 | /* Call once past risky code */ 48 | void exception_cancel(); 49 | 50 | /* Use longjmp to return to most recent exception setup. Include error message 51 | */ 52 | void trigger_exception(char *msg); 53 | 54 | #else /* !INTERNAL */ 55 | 56 | /* Tested program use our versions of malloc and free */ 57 | #define malloc test_malloc 58 | #define calloc test_calloc 59 | #define realloc test_realloc 60 | #define free test_free 61 | 62 | /* Use undef to avoid strdup redefined error */ 63 | #undef strdup 64 | #define strdup test_strdup 65 | 66 | #endif 67 | 68 | #endif /* LAB0_HARNESS_H */ 69 | -------------------------------------------------------------------------------- /linenoise.h: -------------------------------------------------------------------------------- 1 | /* linenoise.h -- VERSION 1.0 2 | * 3 | * Guerrilla line editing library against the idea that a line editing lib 4 | * needs to be 20,000 lines of C code. 5 | * 6 | * See linenoise.c for more information. 7 | * 8 | * ------------------------------------------------------------------------ 9 | * 10 | * Copyright (c) 2010-2014, Salvatore Sanfilippo 11 | * Copyright (c) 2010-2013, Pieter Noordhuis 12 | * 13 | * All rights reserved. 14 | * 15 | * Redistribution and use in source and binary forms, with or without 16 | * modification, are permitted provided that the following conditions are 17 | * met: 18 | * 19 | * * Redistributions of source code must retain the above copyright 20 | * notice, this list of conditions and the following disclaimer. 21 | * 22 | * * Redistributions in binary form must reproduce the above copyright 23 | * notice, this list of conditions and the following disclaimer in the 24 | * documentation and/or other materials provided with the distribution. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #ifndef __LINENOISE_H 40 | #define __LINENOISE_H 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | #include 47 | 48 | typedef struct { 49 | size_t len; 50 | char **cvec; 51 | } line_completions_t; 52 | 53 | /* clang-format off */ 54 | typedef void(line_completion_callback_t)(const char *, line_completions_t *); 55 | typedef char *(line_hints_callback_t)(const char *, int *color, int *bold); 56 | typedef void(line_free_hints_callback_t)(void *); 57 | typedef int(line_eventmux_callback_t)(char *, size_t); 58 | void line_set_completion_callback(line_completion_callback_t *); 59 | void line_set_hints_callback(line_hints_callback_t *); 60 | void line_set_free_hints_callback(line_free_hints_callback_t *); 61 | void line_set_eventmux_callback(line_eventmux_callback_t *); 62 | void line_add_completion(line_completions_t *, const char *); 63 | /* clang-format on */ 64 | 65 | char *linenoise(const char *prompt); 66 | void line_free(void *ptr); 67 | int line_history_add(const char *line); 68 | int line_history_set_max_len(int len); 69 | int line_history_save(const char *filename); 70 | int line_history_load(const char *filename); 71 | void line_clear_screen(void); 72 | void line_set_multi_line(int ml); 73 | void line_mask_mode_enable(void); 74 | void line_mask_mode_disable(void); 75 | #ifdef __cplusplus 76 | } 77 | #endif 78 | 79 | #endif /* __LINENOISE_H */ 80 | -------------------------------------------------------------------------------- /list.h: -------------------------------------------------------------------------------- 1 | /* Linux-like circular doubly-linked list implementation */ 2 | 3 | #pragma once 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | /** 12 | * Feature detection for 'typeof': 13 | * - Supported as a GNU extension in GCC/Clang. 14 | * - Part of C23 standard (ISO/IEC 9899:2024). 15 | * 16 | * Reference: https://gcc.gnu.org/onlinedocs/gcc/Typeof.html 17 | */ 18 | #if defined(__GNUC__) || defined(__clang__) || \ 19 | (defined(__STDC__) && defined(__STDC_VERSION__) && \ 20 | (__STDC_VERSION__ >= 202311L)) /* C23 ?*/ 21 | #define __LIST_HAVE_TYPEOF 1 22 | #else 23 | #define __LIST_HAVE_TYPEOF 0 24 | #endif 25 | 26 | /** 27 | * struct list_head - Node structure for a circular doubly-linked list 28 | * @next: Pointer to the next node in the list. 29 | * @prev: Pointer to the previous node in the list. 30 | * 31 | * Defines both the head and nodes of a circular doubly-linked list. The head's 32 | * @next points to the first node and @prev to the last node; in an empty list, 33 | * both point to the head itself. All nodes, including the head, share this 34 | * structure type. 35 | * 36 | * Nodes are typically embedded within a container structure holding actual 37 | * data, accessible via the list_entry() helper, which computes the container's 38 | * address from a node pointer. The list_* functions and macros provide an API 39 | * for manipulating this data structure efficiently. 40 | */ 41 | struct list_head { 42 | struct list_head *prev; 43 | struct list_head *next; 44 | }; 45 | 46 | /** 47 | * container_of() - Calculate address of structure that contains address ptr 48 | * @ptr: pointer to member variable 49 | * @type: type of the structure containing ptr 50 | * @member: name of the member variable in struct @type 51 | * 52 | * Return: @type pointer of structure containing ptr 53 | */ 54 | #ifndef container_of 55 | #if __LIST_HAVE_TYPEOF 56 | #define container_of(ptr, type, member) \ 57 | __extension__({ \ 58 | const typeof(((type *) 0)->member) *__pmember = (ptr); \ 59 | (type *) ((char *) __pmember - offsetof(type, member)); \ 60 | }) 61 | #else 62 | #define container_of(ptr, type, member) \ 63 | ((type *) ((char *) (ptr) - offsetof(type, member))) 64 | #endif 65 | #endif 66 | 67 | /** 68 | * LIST_HEAD - Define and initialize a circular list head 69 | * @head: name of the new list 70 | */ 71 | #define LIST_HEAD(head) struct list_head head = {&(head), &(head)} 72 | 73 | /** 74 | * INIT_LIST_HEAD() - Initialize empty list head 75 | * @head: Pointer to the list_head structure to initialize. 76 | * 77 | * It sets both @next and @prev to point to the structure itself. The 78 | * initialization applies to either a list head or an unlinked node that is 79 | * not yet part of a list. 80 | * 81 | * Unlinked nodes may be passed to functions using 'list_del()' or 82 | * 'list_del_init()', which are safe only on initialized nodes. Applying these 83 | * operations to an uninitialized node results in undefined behavior, such as 84 | * memory corruption or crashes. 85 | */ 86 | static inline void INIT_LIST_HEAD(struct list_head *head) 87 | { 88 | head->next = head; 89 | head->prev = head; 90 | } 91 | 92 | /** 93 | * list_add - Insert a node after a given node in a circular list 94 | * @node: Pointer to the list_head structure to add. 95 | * @head: Pointer to the list_head structure after which to add the new node. 96 | * 97 | * Adds the specified @node immediately after @head in a circular doubly-linked 98 | * list. The node that previously followed @head will now follow @node, and the 99 | * list's circular structure is maintained. 100 | */ 101 | static inline void list_add(struct list_head *node, struct list_head *head) 102 | { 103 | struct list_head *next = head->next; 104 | 105 | next->prev = node; 106 | node->next = next; 107 | node->prev = head; 108 | head->next = node; 109 | } 110 | 111 | /** 112 | * list_add_tail() - Add a list node to the end of the list 113 | * @node: pointer to the new node 114 | * @head: pointer to the head of the list 115 | */ 116 | static inline void list_add_tail(struct list_head *node, struct list_head *head) 117 | { 118 | struct list_head *prev = head->prev; 119 | 120 | prev->next = node; 121 | node->next = head; 122 | node->prev = prev; 123 | head->prev = node; 124 | } 125 | 126 | /** 127 | * list_del - Remove a node from a circular doubly-linked list 128 | * @node: Pointer to the list_head structure to remove. 129 | * 130 | * Removes @node from its list by updating the adjacent nodes’ pointers to 131 | * bypass it. The node’s memory and its containing structure, if any, are not 132 | * freed. After removal, @node is left unlinked and should be treated as 133 | * uninitialized; accessing its @next or @prev pointers is unsafe and may cause 134 | * undefined behavior. 135 | * 136 | * Even previously initialized but unlinked nodes become uninitialized after 137 | * this operation. To reintegrate @node into a list, it must be reinitialized 138 | * (e.g., via INIT_LIST_HEAD). 139 | * 140 | * If LIST_POISONING is enabled at build time, @next and @prev are set to 141 | * invalid addresses to trigger memory access faults on misuse. This feature is 142 | * effective only on systems that restrict access to these specific addresses. 143 | */ 144 | static inline void list_del(struct list_head *node) 145 | { 146 | struct list_head *next = node->next; 147 | struct list_head *prev = node->prev; 148 | 149 | next->prev = prev; 150 | prev->next = next; 151 | 152 | #ifdef LIST_POISONING 153 | node->next = NULL; 154 | node->prev = NULL; 155 | #endif 156 | } 157 | 158 | /** 159 | * list_del_init - Remove a node and reinitialize it as unlinked 160 | * @node: Pointer to the list_head structure to remove and reinitialize. 161 | * 162 | * Removes @node from its circular doubly-linked list using list_del() and then 163 | * reinitializes it as an unlinked node via INIT_LIST_HEAD(). Unlike list_del(), 164 | * which leaves the node uninitialized, this ensures @node is safely reset to an 165 | * empty, standalone state with @next and @prev pointing to itself. 166 | */ 167 | static inline void list_del_init(struct list_head *node) 168 | { 169 | list_del(node); 170 | INIT_LIST_HEAD(node); 171 | } 172 | 173 | /** 174 | * list_empty - Test if a circular list has no nodes 175 | * @head: Pointer to the list_head structure representing the list head. 176 | * 177 | * Checks whether the circular doubly-linked list headed by @head is empty. 178 | * A list is empty if @head’s @next points to itself, indicating no nodes are 179 | * attached. 180 | * 181 | * Returns: 0 if the list has nodes, non-zero if the list is empty. 182 | */ 183 | static inline int list_empty(const struct list_head *head) 184 | { 185 | return (head->next == head); 186 | } 187 | 188 | /** 189 | * list_is_singular() - Check if list head has exactly one node attached 190 | * @head: pointer to the head of the list 191 | * 192 | * Returns: 0 if the list is not singular, non-zero if the list has exactly one 193 | * entry. 194 | */ 195 | static inline int list_is_singular(const struct list_head *head) 196 | { 197 | return (!list_empty(head) && head->prev == head->next); 198 | } 199 | 200 | /** 201 | * list_splice() - Add list nodes from a list to beginning of another list 202 | * @list: pointer to the head of the list with the node entries 203 | * @head: pointer to the head of the list 204 | * 205 | * All nodes from @list are added to the beginning of the list of @head. 206 | * It is similar to list_add but for multiple nodes. The @list head is not 207 | * modified and has to be initialized to be used as a valid list head/node 208 | * again. 209 | */ 210 | static inline void list_splice(struct list_head *list, struct list_head *head) 211 | { 212 | struct list_head *head_first = head->next; 213 | struct list_head *list_first = list->next; 214 | struct list_head *list_last = list->prev; 215 | 216 | if (list_empty(list)) 217 | return; 218 | 219 | head->next = list_first; 220 | list_first->prev = head; 221 | 222 | list_last->next = head_first; 223 | head_first->prev = list_last; 224 | } 225 | 226 | /** 227 | * list_splice_tail() - Add list nodes from a list to end of another list 228 | * @list: pointer to the head of the list with the node entries 229 | * @head: pointer to the head of the list 230 | * 231 | * All nodes from @list are added to to the end of the list of @head. 232 | * It is similar to list_add_tail but for multiple nodes. The @list head is not 233 | * modified and has to be initialized to be used as a valid list head/node 234 | * again. 235 | */ 236 | static inline void list_splice_tail(struct list_head *list, 237 | struct list_head *head) 238 | { 239 | struct list_head *head_last = head->prev; 240 | struct list_head *list_first = list->next; 241 | struct list_head *list_last = list->prev; 242 | 243 | if (list_empty(list)) 244 | return; 245 | 246 | head->prev = list_last; 247 | list_last->next = head; 248 | 249 | list_first->prev = head_last; 250 | head_last->next = list_first; 251 | } 252 | 253 | /** 254 | * list_splice_init() - Move list nodes from a list to beginning of another list 255 | * @list: pointer to the head of the list with the node entries 256 | * @head: pointer to the head of the list 257 | * 258 | * All nodes from @list are added to to the beginning of the list of @head. 259 | * It is similar to list_add but for multiple nodes. 260 | * 261 | * The @list head will not end up in an uninitialized state like when using 262 | * list_splice. Instead the @list is initialized again to the an empty 263 | * list/unlinked state. 264 | */ 265 | static inline void list_splice_init(struct list_head *list, 266 | struct list_head *head) 267 | { 268 | list_splice(list, head); 269 | INIT_LIST_HEAD(list); 270 | } 271 | 272 | /** 273 | * list_splice_tail_init() - Move list nodes from a list to end of another list 274 | * @list: pointer to the head of the list with the node entries 275 | * @head: pointer to the head of the list 276 | * 277 | * All nodes from @list are added to to the end of the list of @head. 278 | * It is similar to list_add_tail but for multiple nodes. 279 | * 280 | * The @list head will not end up in an uninitialized state like when using 281 | * list_splice. Instead the @list is initialized again to the an empty 282 | * list/unlinked state. 283 | */ 284 | static inline void list_splice_tail_init(struct list_head *list, 285 | struct list_head *head) 286 | { 287 | list_splice_tail(list, head); 288 | INIT_LIST_HEAD(list); 289 | } 290 | 291 | /** 292 | * list_cut_position() - Move beginning of a list to another list 293 | * @head_to: pointer to the head of the list which receives nodes 294 | * @head_from: pointer to the head of the list 295 | * @node: pointer to the node in which defines the cutting point 296 | * 297 | * All entries from the beginning of the list @head_from to (including) the 298 | * @node is moved to @head_to. 299 | * 300 | * @head_to is replaced when @head_from is not empty. @node must be a real 301 | * list node from @head_from or the behavior is undefined. 302 | */ 303 | static inline void list_cut_position(struct list_head *head_to, 304 | struct list_head *head_from, 305 | struct list_head *node) 306 | { 307 | struct list_head *head_from_first = head_from->next; 308 | 309 | if (list_empty(head_from)) 310 | return; 311 | 312 | if (head_from == node) { 313 | INIT_LIST_HEAD(head_to); 314 | return; 315 | } 316 | 317 | head_from->next = node->next; 318 | head_from->next->prev = head_from; 319 | 320 | head_to->prev = node; 321 | node->next = head_to; 322 | head_to->next = head_from_first; 323 | head_to->next->prev = head_to; 324 | } 325 | 326 | /** 327 | * list_move() - Move a list node to the beginning of the list 328 | * @node: pointer to the node 329 | * @head: pointer to the head of the list 330 | * 331 | * The @node is removed from its old position/node and add to the beginning of 332 | * @head 333 | */ 334 | static inline void list_move(struct list_head *node, struct list_head *head) 335 | { 336 | list_del(node); 337 | list_add(node, head); 338 | } 339 | 340 | /** 341 | * list_move_tail() - Move a list node to the end of the list 342 | * @node: pointer to the node 343 | * @head: pointer to the head of the list 344 | * 345 | * The @node is removed from its old position/node and add to the end of @head 346 | */ 347 | static inline void list_move_tail(struct list_head *node, 348 | struct list_head *head) 349 | { 350 | list_del(node); 351 | list_add_tail(node, head); 352 | } 353 | 354 | /** 355 | * list_entry() - Get the entry for this node 356 | * @node: pointer to list node 357 | * @type: type of the entry containing the list node 358 | * @member: name of the list_head member variable in struct @type 359 | * 360 | * Return: @type pointer of entry containing node 361 | */ 362 | #define list_entry(node, type, member) container_of(node, type, member) 363 | 364 | /** 365 | * list_first_entry() - Get first entry of the list 366 | * @head: pointer to the head of the list 367 | * @type: type of the entry containing the list node 368 | * @member: name of the list_head member variable in struct @type 369 | * 370 | * Return: @type pointer of first entry in list 371 | */ 372 | #define list_first_entry(head, type, member) \ 373 | list_entry((head)->next, type, member) 374 | 375 | /** 376 | * list_last_entry() - Get last entry of the list 377 | * @head: pointer to the head of the list 378 | * @type: type of the entry containing the list node 379 | * @member: name of the list_head member variable in struct @type 380 | * 381 | * Return: @type pointer of last entry in list 382 | */ 383 | #define list_last_entry(head, type, member) \ 384 | list_entry((head)->prev, type, member) 385 | 386 | /** 387 | * list_for_each - Iterate over list nodes 388 | * @node: list_head pointer used as iterator 389 | * @head: pointer to the head of the list 390 | * 391 | * The nodes and the head of the list must be kept unmodified while 392 | * iterating through it. Any modifications to the the list will cause undefined 393 | * behavior. 394 | */ 395 | #define list_for_each(node, head) \ 396 | for (node = (head)->next; node != (head); node = node->next) 397 | 398 | /** 399 | * list_for_each_entry - Iterate over a list of entries 400 | * @entry: Pointer to the structure type, used as the loop iterator. 401 | * @head: Pointer to the list_head structure representing the list head. 402 | * @member: Name of the list_head member within the structure type of @entry. 403 | * 404 | * Iterates over a circular doubly-linked list, starting from the first node 405 | * after @head until reaching @head again. The macro assumes the list structure 406 | * remains unmodified during iteration; any changes (e.g., adding/removing 407 | * nodes) may result in undefined behavior. 408 | */ 409 | #if __LIST_HAVE_TYPEOF 410 | #define list_for_each_entry(entry, head, member) \ 411 | for (entry = list_entry((head)->next, typeof(*entry), member); \ 412 | &entry->member != (head); \ 413 | entry = list_entry(entry->member.next, typeof(*entry), member)) 414 | #else 415 | /* The negative width bit-field makes a compile-time error for use of this. It 416 | * works in the same way as BUILD_BUG_ON_ZERO macro of Linux kernel. 417 | */ 418 | #define list_for_each_entry(entry, head, member) \ 419 | for (entry = (void *) 1; sizeof(struct { int i : -1; }); ++(entry)) 420 | #endif 421 | 422 | /** 423 | * list_for_each_safe - Iterate over list nodes, allowing removal 424 | * @node: Pointer to a list_head structure, used as the loop iterator. 425 | * @safe: Pointer to a list_head structure, storing the next node for safe 426 | * iteration. 427 | * @head: Pointer to the list_head structure representing the list head. 428 | * 429 | * Iterates over a circular doubly-linked list, starting from the first node 430 | * after @head and continuing until reaching @head again. This macro allows 431 | * safe removal of the current node (@node) during iteration by pre-fetching 432 | * the next node into @safe. Other modifications to the list structure (e.g., 433 | * adding nodes or altering @head) may result in undefined behavior. 434 | */ 435 | #define list_for_each_safe(node, safe, head) \ 436 | for (node = (head)->next, safe = node->next; node != (head); \ 437 | node = safe, safe = node->next) 438 | 439 | /** 440 | * list_for_each_entry_safe - Iterate over a list, allowing node removal 441 | * @entry: Pointer to the structure type, used as the loop iterator. 442 | * @safe: Pointer to the structure type, storing the next entry for safe 443 | * iteration. 444 | * @head: Pointer to the list_head structure representing the list head. 445 | * @member: Name of the list_head member within the structure type of @entry. 446 | * 447 | * Iterates over a circular doubly-linked list, starting from the first node 448 | * after @head and continuing until reaching @head again. This macro permits 449 | * safe removal of the current node (@entry) during iteration by pre-fetching 450 | * the next node into @safe. Other modifications to the list structure (e.g., 451 | * adding nodes or altering @head) may result in undefined behavior. 452 | */ 453 | #if __LIST_HAVE_TYPEOF 454 | #define list_for_each_entry_safe(entry, safe, head, member) \ 455 | for (entry = list_entry((head)->next, typeof(*entry), member), \ 456 | safe = list_entry(entry->member.next, typeof(*entry), member); \ 457 | &entry->member != (head); entry = safe, \ 458 | safe = list_entry(safe->member.next, typeof(*entry), member)) 459 | #else 460 | #define list_for_each_entry_safe(entry, safe, head, member) \ 461 | for (entry = safe = (void *) 1; sizeof(struct { int i : -1; }); \ 462 | ++(entry), ++(safe)) 463 | #endif 464 | 465 | #undef __LIST_HAVE_TYPEOF 466 | 467 | #ifdef __cplusplus 468 | } 469 | #endif 470 | -------------------------------------------------------------------------------- /log2_lshift16.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generate precalculated values of log2 with assumption that arg will be left 3 | * shifted by 16 bit and return value of log2_lshift16() will be left shifted 4 | * by 3 bit All that shifts used for avoid of using floating point in 5 | * calculation. 6 | */ 7 | 8 | #include 9 | 10 | #define LOG2_ARG_SHIFT (1 << 16) 11 | #define LOG2_RET_SHIFT (1 << 3) 12 | 13 | /* store precalculated function (log2(arg << 24)) << 3 */ 14 | static inline int log2_lshift16(uint64_t lshift16) 15 | { 16 | if (lshift16 < 558) { 17 | if (lshift16 < 54) { 18 | if (lshift16 < 13) { 19 | if (lshift16 < 7) { 20 | if (lshift16 < 1) 21 | return -136; 22 | if (lshift16 < 2) 23 | return -123; 24 | if (lshift16 < 3) 25 | return -117; 26 | if (lshift16 < 4) 27 | return -113; 28 | if (lshift16 < 5) 29 | return -110; 30 | if (lshift16 < 6) 31 | return -108; 32 | if (lshift16 < 7) 33 | return -106; 34 | } else { 35 | if (lshift16 < 8) 36 | return -104; 37 | if (lshift16 < 9) 38 | return -103; 39 | if (lshift16 < 10) 40 | return -102; 41 | if (lshift16 < 11) 42 | return -100; 43 | if (lshift16 < 12) 44 | return -99; 45 | if (lshift16 < 13) 46 | return -98; 47 | } 48 | } else { 49 | if (lshift16 < 29) { 50 | if (lshift16 < 15) 51 | return -97; 52 | if (lshift16 < 16) 53 | return -96; 54 | if (lshift16 < 17) 55 | return -95; 56 | if (lshift16 < 19) 57 | return -94; 58 | if (lshift16 < 21) 59 | return -93; 60 | if (lshift16 < 23) 61 | return -92; 62 | if (lshift16 < 25) 63 | return -91; 64 | if (lshift16 < 27) 65 | return -90; 66 | if (lshift16 < 29) 67 | return -89; 68 | } else { 69 | if (lshift16 < 32) 70 | return -88; 71 | if (lshift16 < 35) 72 | return -87; 73 | if (lshift16 < 38) 74 | return -86; 75 | if (lshift16 < 41) 76 | return -85; 77 | if (lshift16 < 45) 78 | return -84; 79 | if (lshift16 < 49) 80 | return -83; 81 | if (lshift16 < 54) 82 | return -82; 83 | } 84 | } 85 | } else { 86 | if (lshift16 < 181) { 87 | if (lshift16 < 99) { 88 | if (lshift16 < 59) 89 | return -81; 90 | if (lshift16 < 64) 91 | return -80; 92 | if (lshift16 < 70) 93 | return -79; 94 | if (lshift16 < 76) 95 | return -78; 96 | if (lshift16 < 83) 97 | return -77; 98 | if (lshift16 < 91) 99 | return -76; 100 | if (lshift16 < 99) 101 | return -75; 102 | } else { 103 | if (lshift16 < 108) 104 | return -74; 105 | if (lshift16 < 117) 106 | return -73; 107 | if (lshift16 < 128) 108 | return -72; 109 | if (lshift16 < 140) 110 | return -71; 111 | if (lshift16 < 152) 112 | return -70; 113 | if (lshift16 < 166) 114 | return -69; 115 | if (lshift16 < 181) 116 | return -68; 117 | } 118 | } else { 119 | if (lshift16 < 304) { 120 | if (lshift16 < 197) 121 | return -67; 122 | if (lshift16 < 215) 123 | return -66; 124 | if (lshift16 < 235) 125 | return -65; 126 | if (lshift16 < 256) 127 | return -64; 128 | if (lshift16 < 279) 129 | return -63; 130 | if (lshift16 < 304) 131 | return -62; 132 | } else { 133 | if (lshift16 < 332) 134 | return -61; 135 | if (lshift16 < 362) 136 | return -60; 137 | if (lshift16 < 395) 138 | return -59; 139 | if (lshift16 < 431) 140 | return -58; 141 | if (lshift16 < 470) 142 | return -57; 143 | if (lshift16 < 512) 144 | return -56; 145 | if (lshift16 < 558) 146 | return -55; 147 | } 148 | } 149 | } 150 | } else { 151 | if (lshift16 < 6317) { 152 | if (lshift16 < 2048) { 153 | if (lshift16 < 1117) { 154 | if (lshift16 < 609) 155 | return -54; 156 | if (lshift16 < 664) 157 | return -53; 158 | if (lshift16 < 724) 159 | return -52; 160 | if (lshift16 < 790) 161 | return -51; 162 | if (lshift16 < 861) 163 | return -50; 164 | if (lshift16 < 939) 165 | return -49; 166 | if (lshift16 < 1024) 167 | return -48; 168 | if (lshift16 < 1117) 169 | return -47; 170 | } else { 171 | if (lshift16 < 1218) 172 | return -46; 173 | if (lshift16 < 1328) 174 | return -45; 175 | if (lshift16 < 1448) 176 | return -44; 177 | if (lshift16 < 1579) 178 | return -43; 179 | if (lshift16 < 1722) 180 | return -42; 181 | if (lshift16 < 1878) 182 | return -41; 183 | if (lshift16 < 2048) 184 | return -40; 185 | } 186 | } else { 187 | if (lshift16 < 3756) { 188 | if (lshift16 < 2233) 189 | return -39; 190 | if (lshift16 < 2435) 191 | return -38; 192 | if (lshift16 < 2656) 193 | return -37; 194 | if (lshift16 < 2896) 195 | return -36; 196 | if (lshift16 < 3158) 197 | return -35; 198 | if (lshift16 < 3444) 199 | return -34; 200 | if (lshift16 < 3756) 201 | return -33; 202 | } else { 203 | if (lshift16 < 4096) 204 | return -32; 205 | if (lshift16 < 4467) 206 | return -31; 207 | if (lshift16 < 4871) 208 | return -30; 209 | if (lshift16 < 5312) 210 | return -29; 211 | if (lshift16 < 5793) 212 | return -28; 213 | if (lshift16 < 6317) 214 | return -27; 215 | } 216 | } 217 | } else { 218 | if (lshift16 < 21247) { 219 | if (lshift16 < 11585) { 220 | if (lshift16 < 6889) 221 | return -26; 222 | if (lshift16 < 7512) 223 | return -25; 224 | if (lshift16 < 8192) 225 | return -24; 226 | if (lshift16 < 8933) 227 | return -23; 228 | if (lshift16 < 9742) 229 | return -22; 230 | if (lshift16 < 10624) 231 | return -21; 232 | if (lshift16 < 11585) 233 | return -20; 234 | } else { 235 | if (lshift16 < 12634) 236 | return -19; 237 | if (lshift16 < 13777) 238 | return -18; 239 | if (lshift16 < 15024) 240 | return -17; 241 | if (lshift16 < 16384) 242 | return -16; 243 | if (lshift16 < 17867) 244 | return -15; 245 | if (lshift16 < 19484) 246 | return -14; 247 | if (lshift16 < 21247) 248 | return -13; 249 | } 250 | } else { 251 | if (lshift16 < 35734) { 252 | if (lshift16 < 23170) 253 | return -12; 254 | if (lshift16 < 25268) 255 | return -11; 256 | if (lshift16 < 27554) 257 | return -10; 258 | if (lshift16 < 30048) 259 | return -9; 260 | if (lshift16 < 32768) 261 | return -8; 262 | if (lshift16 < 35734) 263 | return -7; 264 | } else { 265 | if (lshift16 < 38968) 266 | return -6; 267 | if (lshift16 < 42495) 268 | return -5; 269 | if (lshift16 < 46341) 270 | return -4; 271 | if (lshift16 < 50535) 272 | return -3; 273 | if (lshift16 < 55109) 274 | return -2; 275 | if (lshift16 < 60097) 276 | return -1; 277 | } 278 | } 279 | } 280 | } 281 | return 0; 282 | } 283 | -------------------------------------------------------------------------------- /queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "queue.h" 6 | 7 | /* Create an empty queue */ 8 | struct list_head *q_new() 9 | { 10 | return NULL; 11 | } 12 | 13 | /* Free all storage used by queue */ 14 | void q_free(struct list_head *head) {} 15 | 16 | /* Insert an element at head of queue */ 17 | bool q_insert_head(struct list_head *head, char *s) 18 | { 19 | return true; 20 | } 21 | 22 | /* Insert an element at tail of queue */ 23 | bool q_insert_tail(struct list_head *head, char *s) 24 | { 25 | return true; 26 | } 27 | 28 | /* Remove an element from head of queue */ 29 | element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) 30 | { 31 | return NULL; 32 | } 33 | 34 | /* Remove an element from tail of queue */ 35 | element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) 36 | { 37 | return NULL; 38 | } 39 | 40 | /* Return number of elements in queue */ 41 | int q_size(struct list_head *head) 42 | { 43 | return -1; 44 | } 45 | 46 | /* Delete the middle node in queue */ 47 | bool q_delete_mid(struct list_head *head) 48 | { 49 | // https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ 50 | return true; 51 | } 52 | 53 | /* Delete all nodes that have duplicate string */ 54 | bool q_delete_dup(struct list_head *head) 55 | { 56 | // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ 57 | return true; 58 | } 59 | 60 | /* Swap every two adjacent nodes */ 61 | void q_swap(struct list_head *head) 62 | { 63 | // https://leetcode.com/problems/swap-nodes-in-pairs/ 64 | } 65 | 66 | /* Reverse elements in queue */ 67 | void q_reverse(struct list_head *head) {} 68 | 69 | /* Reverse the nodes of the list k at a time */ 70 | void q_reverseK(struct list_head *head, int k) 71 | { 72 | // https://leetcode.com/problems/reverse-nodes-in-k-group/ 73 | } 74 | 75 | /* Sort elements of queue in ascending/descending order */ 76 | void q_sort(struct list_head *head, bool descend) {} 77 | 78 | /* Remove every node which has a node with a strictly less value anywhere to 79 | * the right side of it */ 80 | int q_ascend(struct list_head *head) 81 | { 82 | // https://leetcode.com/problems/remove-nodes-from-linked-list/ 83 | return 0; 84 | } 85 | 86 | /* Remove every node which has a node with a strictly greater value anywhere to 87 | * the right side of it */ 88 | int q_descend(struct list_head *head) 89 | { 90 | // https://leetcode.com/problems/remove-nodes-from-linked-list/ 91 | return 0; 92 | } 93 | 94 | /* Merge all the queues into one sorted queue, which is in ascending/descending 95 | * order */ 96 | int q_merge(struct list_head *head, bool descend) 97 | { 98 | // https://leetcode.com/problems/merge-k-sorted-lists/ 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /queue.h: -------------------------------------------------------------------------------- 1 | #ifndef LAB0_QUEUE_H 2 | #define LAB0_QUEUE_H 3 | 4 | /* This program implements a queue supporting both FIFO and LIFO 5 | * operations. 6 | * 7 | * It uses a circular doubly-linked list to represent the set of queue elements 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | #include "harness.h" 14 | #include "list.h" 15 | 16 | /** 17 | * element_t - Linked list element 18 | * @value: pointer to array holding string 19 | * @list: node of a doubly-linked list 20 | * 21 | * @value needs to be explicitly allocated and freed 22 | */ 23 | typedef struct { 24 | char *value; 25 | struct list_head list; 26 | } element_t; 27 | 28 | /** 29 | * queue_contex_t - The context managing a chain of queues 30 | * @q: pointer to the head of the queue 31 | * @chain: used by chaining the heads of queues 32 | * @size: the length of this queue 33 | * @id: the unique identification number 34 | */ 35 | typedef struct { 36 | struct list_head *q; 37 | struct list_head chain; 38 | int size; 39 | int id; 40 | } queue_contex_t; 41 | 42 | /* Operations on queue */ 43 | 44 | /** 45 | * q_new() - Create an empty queue whose next and prev pointer point to itself 46 | * 47 | * Return: NULL for allocation failed 48 | */ 49 | struct list_head *q_new(); 50 | 51 | /** 52 | * q_free() - Free all storage used by queue, no effect if header is NULL 53 | * @head: header of queue 54 | */ 55 | void q_free(struct list_head *head); 56 | 57 | /** 58 | * q_insert_head() - Insert an element in the head 59 | * @head: header of queue 60 | * @s: string would be inserted 61 | * 62 | * Argument s points to the string to be stored. 63 | * The function must explicitly allocate space and copy the string into it. 64 | * 65 | * Return: true for success, false for allocation failed or queue is NULL 66 | */ 67 | bool q_insert_head(struct list_head *head, char *s); 68 | 69 | /** 70 | * q_insert_tail() - Insert an element at the tail 71 | * @head: header of queue 72 | * @s: string would be inserted 73 | * 74 | * Argument s points to the string to be stored. 75 | * The function must explicitly allocate space and copy the string into it. 76 | * 77 | * Return: true for success, false for allocation failed or queue is NULL 78 | */ 79 | bool q_insert_tail(struct list_head *head, char *s); 80 | 81 | /** 82 | * q_remove_head() - Remove the element from head of queue 83 | * @head: header of queue 84 | * @sp: output buffer where the removed string is copied 85 | * @bufsize: size of the string 86 | * 87 | * If sp is non-NULL and an element is removed, copy the removed string to *sp 88 | * (up to a maximum of bufsize-1 characters, plus a null terminator.) 89 | * 90 | * NOTE: "remove" is different from "delete" 91 | * The space used by the list element and the string should not be freed. 92 | * The only thing "remove" need to do is unlink it. 93 | * 94 | * Reference: 95 | * https://english.stackexchange.com/questions/52508/difference-between-delete-and-remove 96 | * 97 | * Return: the pointer to element, %NULL if queue is NULL or empty. 98 | */ 99 | element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize); 100 | 101 | /** 102 | * q_remove_tail() - Remove the element from tail of queue 103 | * @head: header of queue 104 | * @sp: output buffer where the removed string is copied 105 | * @bufsize: size of the string 106 | * 107 | * Return: the pointer to element, %NULL if queue is NULL or empty. 108 | */ 109 | element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize); 110 | 111 | /** 112 | * q_release_element() - Release the element 113 | * @e: element would be released 114 | * 115 | * This function is intended for internal use only. 116 | */ 117 | static inline void q_release_element(element_t *e) 118 | { 119 | test_free(e->value); 120 | test_free(e); 121 | } 122 | 123 | /** 124 | * q_size() - Get the size of the queue 125 | * @head: header of queue 126 | * 127 | * Return: the number of elements in queue, zero if queue is NULL or empty 128 | */ 129 | int q_size(struct list_head *head); 130 | 131 | /** 132 | * q_delete_mid() - Delete the middle node in queue 133 | * @head: header of queue 134 | * 135 | * The middle node of a linked list of size n is the 136 | * ⌊n / 2⌋th node from the start using 0-based indexing. 137 | * If there're six elements, the third member should be deleted. 138 | * 139 | * Reference: 140 | * https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ 141 | * 142 | * Return: true for success, false if list is NULL or empty. 143 | */ 144 | bool q_delete_mid(struct list_head *head); 145 | 146 | /** 147 | * q_delete_dup() - Delete all nodes that have duplicate string, 148 | * leaving only distinct strings from the original queue. 149 | * @head: header of queue 150 | * 151 | * Reference: 152 | * https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ 153 | * 154 | * Return: true for success, false if list is NULL or empty. 155 | */ 156 | bool q_delete_dup(struct list_head *head); 157 | 158 | /** 159 | * q_swap() - Swap every two adjacent nodes 160 | * @head: header of queue 161 | * 162 | * Reference: 163 | * https://leetcode.com/problems/swap-nodes-in-pairs/ 164 | */ 165 | void q_swap(struct list_head *head); 166 | 167 | /** 168 | * q_reverse() - Reverse elements in queue 169 | * @head: header of queue 170 | * 171 | * No effect if queue is NULL or empty. 172 | * This function should not allocate or free any list elements 173 | * (e.g., by calling q_insert_head, q_insert_tail, or q_remove_head). 174 | * It should rearrange the existing ones. 175 | */ 176 | void q_reverse(struct list_head *head); 177 | 178 | /** 179 | * q_reverseK() - Given the head of a linked list, reverse the nodes of the list 180 | * k at a time. 181 | * @head: header of queue 182 | * @k: is a positive integer and is less than or equal to the length of the 183 | * linked list. 184 | * 185 | * No effect if queue is NULL or empty. If there is only one element, do 186 | * nothing. 187 | * 188 | * Reference: 189 | * https://leetcode.com/problems/reverse-nodes-in-k-group/ 190 | */ 191 | void q_reverseK(struct list_head *head, int k); 192 | 193 | /** 194 | * q_sort() - Sort elements of queue in ascending/descending order 195 | * @head: header of queue 196 | * @descend: whether or not to sort in descending order 197 | * 198 | * No effect if queue is NULL or empty. If there is only one element, do 199 | * nothing. 200 | */ 201 | void q_sort(struct list_head *head, bool descend); 202 | 203 | /** 204 | * q_ascend() - Delete every node which has a node with a strictly less 205 | * value anywhere to the right side of it. 206 | * @head: header of queue 207 | * 208 | * No effect if queue is NULL or empty. If there is only one element, do 209 | * nothing. 210 | * 211 | * Reference: 212 | * https://leetcode.com/problems/remove-nodes-from-linked-list/ 213 | * 214 | * Return: the number of elements in queue after performing operation 215 | */ 216 | int q_ascend(struct list_head *head); 217 | 218 | /** 219 | * q_descend() - Delete every node which has a node with a strictly greater 220 | * value anywhere to the right side of it. 221 | * @head: header of queue 222 | * 223 | * No effect if queue is NULL or empty. If there is only one element, do 224 | * nothing. 225 | * 226 | * Reference: 227 | * https://leetcode.com/problems/remove-nodes-from-linked-list/ 228 | * 229 | * Return: the number of elements in queue after performing operation 230 | */ 231 | int q_descend(struct list_head *head); 232 | 233 | /** 234 | * q_merge() - Merge all the queues into one sorted queue, which is in 235 | * ascending/descending order. 236 | * @head: header of chain 237 | * @descend: whether to merge queues sorted in descending order 238 | * 239 | * This function merge the second to the last queues in the chain into the first 240 | * queue. The queues are guaranteed to be sorted before this function is called. 241 | * No effect if there is only one queue in the chain. Allocation is disallowed 242 | * in this function. There is no need to free the 'queue_contex_t' and its 243 | * member 'q' since they will be released externally. However, q_merge() is 244 | * responsible for making the queues to be NULL-queue, except the first one. 245 | * 246 | * Reference: 247 | * https://leetcode.com/problems/merge-k-sorted-lists/ 248 | * 249 | * Return: the number of elements in queue after merging 250 | */ 251 | int q_merge(struct list_head *head, bool descend); 252 | 253 | #endif /* LAB0_QUEUE_H */ 254 | -------------------------------------------------------------------------------- /random.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 National Cheng Kung University, Taiwan. 3 | * Copyright (c) 2017 Daan Sprenkels 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | */ 23 | 24 | /* In the case that are compiling on linux, we need to define _GNU_SOURCE 25 | * *before* random.h is included. Otherwise SYS_getrandom will not be declared. 26 | */ 27 | #if defined(__linux__) || defined(__GNU__) 28 | #define _GNU_SOURCE 29 | #endif 30 | 31 | #include "random.h" 32 | 33 | #if defined(__linux__) || defined(__GNU__) 34 | /* We would need to include , but not every target has access 35 | * to the linux headers. We only need RNDGETENTCNT, so we instead inline it. 36 | * RNDGETENTCNT is originally defined in `include/uapi/linux/random.h` in the 37 | * Linux kernel source. 38 | */ 39 | #define RNDGETENTCNT 0x80045200 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #if (defined(__linux__) || defined(__GNU__)) && defined(__GLIBC__) && \ 49 | ((__GLIBC__ > 2) || (__GLIBC_MINOR__ > 24)) 50 | #define USE_GLIBC 51 | #include 52 | #endif 53 | #include 54 | #include 55 | #include 56 | #include 57 | 58 | /* We need SSIZE_MAX as the maximum read len from /dev/urandom */ 59 | #if !defined(SSIZE_MAX) 60 | #include 61 | #if __WORDSIZE == 64 62 | #define SSIZE_MAX LONG_MAX 63 | #else 64 | #define SSIZE_MAX INT_MAX 65 | #endif 66 | #endif 67 | 68 | #endif /* defined(__linux__) || defined(__GNU__) */ 69 | 70 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 71 | /* Dragonfly, FreeBSD, NetBSD, OpenBSD (has arc4random) */ 72 | #include 73 | #if defined(BSD) 74 | #include 75 | #endif 76 | /* GNU/Hurd defines BSD in sys/param.h which causes problems later */ 77 | #if defined(__GNU__) 78 | #undef BSD 79 | #endif 80 | #endif 81 | 82 | #if (defined(__linux__) || defined(__GNU__)) && \ 83 | (defined(USE_GLIBC) || defined(SYS_getrandom)) 84 | #if defined(USE_GLIBC) 85 | /* getrandom is declared in glibc. */ 86 | #elif defined(SYS_getrandom) 87 | static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) 88 | { 89 | return syscall(SYS_getrandom, buf, buflen, flags); 90 | } 91 | #endif 92 | 93 | static int linux_getrandom(void *buf, size_t n) 94 | { 95 | size_t offset = 0; 96 | int ret; 97 | while (n > 0) { 98 | /* getrandom does not allow chunks larger than 33554431 */ 99 | size_t chunk = n <= 33554431 ? n : 33554431; 100 | do { 101 | ret = getrandom((char *) buf + offset, chunk, 0); 102 | } while (ret == -1 && errno == EINTR); 103 | if (ret < 0) 104 | return ret; 105 | offset += ret; 106 | n -= ret; 107 | } 108 | assert(n == 0); 109 | return 0; 110 | } 111 | #endif /* (defined(__linux__) || defined(__GNU__)) && (defined(USE_GLIBC) || \ 112 | defined(SYS_getrandom)) */ 113 | 114 | #if defined(__linux__) && !defined(SYS_getrandom) 115 | 116 | #if defined(__linux__) 117 | static inline int linux_read_entropy_ioctl(int device, int *entropy) 118 | { 119 | return ioctl(device, RNDGETENTCNT, entropy); 120 | } 121 | 122 | static int linux_read_entropy_proc(FILE *stream, int *entropy) 123 | { 124 | int retcode; 125 | do { 126 | rewind(stream); 127 | retcode = fscanf(stream, "%d", entropy); 128 | } while (retcode != 1 && errno == EINTR); 129 | if (retcode != 1) 130 | return -1; 131 | return 0; 132 | } 133 | 134 | static int linux_wait_for_entropy(int device) 135 | { 136 | /* We will block on /dev/random, because any increase in the OS' entropy 137 | * level will unblock the request. I use poll here (as does libsodium), 138 | * because we don't *actually* want to read from the device. 139 | */ 140 | enum { IOCTL, PROC } strategy = IOCTL; 141 | const int bits = 128; 142 | FILE *proc_file; 143 | int retcode_error = 0; /* Used as return codes */ 144 | int entropy = 0; 145 | 146 | /* If the device has enough entropy already, we will want to return early */ 147 | int retcode = linux_read_entropy_ioctl(device, &entropy); 148 | if (retcode != 0 && (errno == ENOTTY || errno == ENOSYS)) { 149 | /* The ioctl call on /dev/urandom has failed due to a 150 | * - ENOTTY (unsupported action), or 151 | * - ENOSYS (invalid ioctl; this happens on MIPS). 152 | * 153 | * We will fall back to reading from 154 | * /proc/sys/kernel/random/entropy_avail . This is less ideal, 155 | * because it allocates a file descriptor, and it may not work 156 | * in a chroot. But at this point it seems we have no better 157 | * options left. 158 | */ 159 | strategy = PROC; 160 | /* Open the entropy count file */ 161 | proc_file = fopen("/proc/sys/kernel/random/entropy_avail", "r"); 162 | if (!proc_file) 163 | return -1; 164 | } else if (retcode != 0) { 165 | /* Unrecoverable ioctl error */ 166 | return -1; 167 | } 168 | if (entropy >= bits) 169 | return 0; 170 | 171 | int fd; 172 | do { 173 | fd = open("/dev/random", O_RDONLY); 174 | } while (fd == -1 && errno == EINTR); /* EAGAIN will not occur */ 175 | if (fd == -1) { 176 | /* Unrecoverable IO error */ 177 | return -1; 178 | } 179 | 180 | struct pollfd pfd = {.fd = fd, .events = POLLIN}; 181 | for (;;) { 182 | retcode = poll(&pfd, 1, -1); 183 | if (retcode == -1 && (errno == EINTR || errno == EAGAIN)) { 184 | continue; 185 | } else if (retcode == 1) { 186 | if (strategy == IOCTL) { 187 | retcode = linux_read_entropy_ioctl(device, &entropy); 188 | } else if (strategy == PROC) { 189 | retcode = linux_read_entropy_proc(proc_file, &entropy); 190 | } else { 191 | return -1; /* Unreachable */ 192 | } 193 | 194 | if (retcode != 0) { 195 | /* Unrecoverable I/O error */ 196 | retcode_error = retcode; 197 | break; 198 | } 199 | if (entropy >= bits) 200 | break; 201 | } else { 202 | /* Unreachable: poll() should only return -1 or 1 */ 203 | retcode_error = -1; 204 | break; 205 | } 206 | } 207 | do { 208 | retcode = close(fd); 209 | } while (retcode == -1 && errno == EINTR); 210 | if (strategy == PROC) { 211 | do { 212 | retcode = fclose(proc_file); 213 | } while (retcode == -1 && errno == EINTR); 214 | } 215 | if (retcode_error != 0) 216 | return retcode_error; 217 | return retcode; 218 | } 219 | #endif /* defined(__linux__) */ 220 | 221 | static int linux_urandom(void *buf, size_t n) 222 | { 223 | int fd; 224 | do { 225 | fd = open("/dev/urandom", O_RDONLY); 226 | } while (fd == -1 && errno == EINTR); 227 | if (fd == -1) 228 | return -1; 229 | #if defined(__linux__) 230 | if (linux_wait_for_entropy(fd) == -1) 231 | return -1; 232 | #endif 233 | 234 | size_t offset = 0; 235 | while (n > 0) { 236 | size_t count = n <= SSIZE_MAX ? n : SSIZE_MAX; 237 | ssize_t tmp = read(fd, (char *) buf + offset, count); 238 | if (tmp == -1 && (errno == EAGAIN || errno == EINTR)) 239 | continue; 240 | if (tmp == -1) 241 | return -1; /* Unrecoverable IO error */ 242 | offset += tmp; 243 | n -= tmp; 244 | } 245 | close(fd); 246 | assert(n == 0); 247 | return 0; 248 | } 249 | #endif /* defined(__linux__) && !defined(SYS_getrandom) */ 250 | 251 | #if defined(BSD) 252 | #if defined(__APPLE__) 253 | #include 254 | #if defined(MAC_OS_X_VERSION_10_10) && \ 255 | MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 256 | #include 257 | #include 258 | #endif 259 | #endif 260 | static int bsd_randombytes(void *buf, size_t n) 261 | { 262 | #if defined(MAC_OS_X_VERSION_10_15) && \ 263 | MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15 264 | /* We prefer CCRandomGenerateBytes as it returns an error code while 265 | * arc4random_buf may fail silently on macOS. 266 | */ 267 | return (CCRandomGenerateBytes(buf, n) == kCCSuccess); 268 | #else 269 | arc4random_buf(buf, n); 270 | return 0; 271 | #endif 272 | } 273 | #endif 274 | 275 | int randombytes(uint8_t *buf, size_t n) 276 | { 277 | #if defined(__linux__) || defined(__GNU__) 278 | #if defined(USE_GLIBC) 279 | /* Use getrandom system call */ 280 | return linux_getrandom(buf, n); 281 | #elif defined(SYS_getrandom) 282 | /* Use getrandom system call */ 283 | return linux_getrandom(buf, n); 284 | #else 285 | /* When we have enough entropy, we can read from /dev/urandom */ 286 | return linux_urandom(buf, n); 287 | #endif 288 | #elif defined(BSD) 289 | return bsd_randombytes(buf, n); 290 | #else 291 | #error "randombytes(...) is not supported on this platform" 292 | #endif 293 | } 294 | -------------------------------------------------------------------------------- /random.h: -------------------------------------------------------------------------------- 1 | #ifndef LAB0_RANDOM_H 2 | #define LAB0_RANDOM_H 3 | 4 | #include 5 | #include 6 | 7 | extern int randombytes(uint8_t *buf, size_t len); 8 | 9 | static inline uint8_t randombit(void) 10 | { 11 | uint8_t ret = 0; 12 | randombytes(&ret, 1); 13 | return ret & 1; 14 | } 15 | 16 | #if INTPTR_MAX == INT64_MAX 17 | #define M_INTPTR_SHIFT (3) 18 | #elif INTPTR_MAX == INT32_MAX 19 | #define M_INTPTR_SHIFT (2) 20 | #else 21 | #error Platform pointers must be 32 or 64 bits 22 | #endif 23 | 24 | #define M_INTPTR_SIZE (1 << M_INTPTR_SHIFT) 25 | 26 | static inline uintptr_t random_shuffle(uintptr_t x) 27 | { 28 | /* Ensure we do not get stuck in generating zeros */ 29 | if (x == 0) 30 | x = 17; 31 | 32 | #if M_INTPTR_SIZE == 8 33 | /* by Sebastiano Vigna, see: */ 34 | x ^= x >> 30; 35 | x *= 0xbf58476d1ce4e5b9UL; 36 | x ^= x >> 27; 37 | x *= 0x94d049bb133111ebUL; 38 | x ^= x >> 31; 39 | #elif M_INTPTR_SIZE == 4 40 | /* by Chris Wellons, see: */ 41 | x ^= x >> 16; 42 | x *= 0x7feb352dUL; 43 | x ^= x >> 15; 44 | x *= 0x846ca68bUL; 45 | x ^= x >> 16; 46 | #endif 47 | 48 | return x; 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /report.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "report.h" 13 | #include "web.h" 14 | 15 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 16 | 17 | static FILE *errfile = NULL; 18 | static FILE *verbfile = NULL; 19 | static FILE *logfile = NULL; 20 | 21 | int verblevel = 0; 22 | static void init_files(FILE *efile, FILE *vfile) 23 | { 24 | errfile = efile; 25 | verbfile = vfile; 26 | } 27 | 28 | static char fail_buf[1024] = "FATAL Error. Exiting\n"; 29 | 30 | static volatile int ret = 0; 31 | 32 | /* Default fatal function */ 33 | static void default_fatal_fun() 34 | { 35 | ret = write(STDOUT_FILENO, fail_buf, strlen(fail_buf) + 1); 36 | if (logfile) 37 | fputs(fail_buf, logfile); 38 | } 39 | 40 | /* Optional function to call when fatal error encountered */ 41 | static void (*fatal_fun)() = default_fatal_fun; 42 | 43 | void set_verblevel(int level) 44 | { 45 | verblevel = level; 46 | } 47 | 48 | bool set_logfile(const char *file_name) 49 | { 50 | logfile = fopen(file_name, "w"); 51 | return logfile != NULL; 52 | } 53 | 54 | void report_event(message_t msg, char *fmt, ...) 55 | { 56 | va_list ap; 57 | bool fatal = msg == MSG_FATAL; 58 | // cppcheck-suppress constVariable 59 | static char *msg_name_text[N_MSG] = { 60 | "WARNING", 61 | "ERROR", 62 | "FATAL ERROR", 63 | }; 64 | const char *msg_name = msg_name_text[2]; 65 | if (msg < N_MSG) 66 | msg_name = msg_name_text[msg]; 67 | int level = N_MSG - msg - 1; 68 | if (verblevel < level) 69 | return; 70 | 71 | if (!errfile) 72 | init_files(stdout, stdout); 73 | 74 | va_start(ap, fmt); 75 | fprintf(errfile, "%s: ", msg_name); 76 | vfprintf(errfile, fmt, ap); 77 | fprintf(errfile, "\n"); 78 | fflush(errfile); 79 | va_end(ap); 80 | 81 | if (logfile) { 82 | va_start(ap, fmt); 83 | fprintf(logfile, "Error: "); 84 | vfprintf(logfile, fmt, ap); 85 | fprintf(logfile, "\n"); 86 | fflush(logfile); 87 | va_end(ap); 88 | fclose(logfile); 89 | } 90 | 91 | if (fatal) { 92 | if (fatal_fun) 93 | fatal_fun(); 94 | exit(1); 95 | } 96 | } 97 | 98 | #define BUF_SIZE 4096 99 | extern int web_connfd; 100 | void report(int level, char *fmt, ...) 101 | { 102 | if (!verbfile) 103 | init_files(stdout, stdout); 104 | 105 | char buffer[BUF_SIZE]; 106 | if (level <= verblevel) { 107 | va_list ap; 108 | va_start(ap, fmt); 109 | vfprintf(verbfile, fmt, ap); 110 | fprintf(verbfile, "\n"); 111 | fflush(verbfile); 112 | va_end(ap); 113 | 114 | if (logfile) { 115 | va_start(ap, fmt); 116 | vfprintf(logfile, fmt, ap); 117 | fprintf(logfile, "\n"); 118 | fflush(logfile); 119 | va_end(ap); 120 | } 121 | va_start(ap, fmt); 122 | vsnprintf(buffer, BUF_SIZE, fmt, ap); 123 | va_end(ap); 124 | } 125 | if (web_connfd) { 126 | int len = strlen(buffer); 127 | buffer[len] = '\n'; 128 | buffer[len + 1] = '\0'; 129 | web_send(web_connfd, buffer); 130 | } 131 | } 132 | 133 | void report_noreturn(int level, char *fmt, ...) 134 | { 135 | if (!verbfile) 136 | init_files(stdout, stdout); 137 | 138 | char buffer[BUF_SIZE]; 139 | if (level <= verblevel) { 140 | va_list ap; 141 | va_start(ap, fmt); 142 | vfprintf(verbfile, fmt, ap); 143 | fflush(verbfile); 144 | va_end(ap); 145 | 146 | if (logfile) { 147 | va_start(ap, fmt); 148 | vfprintf(logfile, fmt, ap); 149 | fflush(logfile); 150 | va_end(ap); 151 | } 152 | va_start(ap, fmt); 153 | vsnprintf(buffer, BUF_SIZE, fmt, ap); 154 | va_end(ap); 155 | } 156 | 157 | if (web_connfd) 158 | web_send(web_connfd, buffer); 159 | } 160 | 161 | /* Functions denoting failures */ 162 | 163 | /* Need to be able to print without using malloc */ 164 | static void fail_fun(const char *format, const char *msg) 165 | { 166 | snprintf(fail_buf, sizeof(fail_buf), format, msg); 167 | /* Tack on return */ 168 | fail_buf[strlen(fail_buf)] = '\n'; 169 | /* Use write to avoid any buffering issues */ 170 | ret = write(STDOUT_FILENO, fail_buf, strlen(fail_buf) + 1); 171 | 172 | if (logfile) { 173 | /* Don't know file descriptor for logfile */ 174 | fputs(fail_buf, logfile); 175 | } 176 | 177 | if (fatal_fun) 178 | fatal_fun(); 179 | 180 | if (logfile) 181 | fclose(logfile); 182 | 183 | exit(1); 184 | } 185 | 186 | /* Maximum number of megabytes that application can use (0 = unlimited) */ 187 | static int mblimit = 0; 188 | 189 | /* Keeping track of memory allocation */ 190 | static size_t allocate_cnt = 0; 191 | static size_t allocate_bytes = 0; 192 | static size_t free_cnt = 0; 193 | static size_t free_bytes = 0; 194 | 195 | /* Counters giving peak memory usage */ 196 | static size_t peak_bytes = 0; 197 | static size_t last_peak_bytes = 0; 198 | static size_t current_bytes = 0; 199 | 200 | static void check_exceed(size_t new_bytes) 201 | { 202 | size_t limit_bytes = (size_t) mblimit << 20; 203 | size_t request_bytes = new_bytes + current_bytes; 204 | if (mblimit > 0 && request_bytes > limit_bytes) { 205 | report_event(MSG_FATAL, 206 | "Exceeded memory limit of %u megabytes with %lu bytes", 207 | mblimit, request_bytes); 208 | } 209 | } 210 | 211 | /* Call malloc & exit if fails */ 212 | void *malloc_or_fail(size_t bytes, const char *fun_name) 213 | { 214 | check_exceed(bytes); 215 | void *p = malloc(bytes); 216 | if (!p) { 217 | fail_fun("Malloc returned NULL in %s", fun_name); 218 | return NULL; 219 | } 220 | 221 | allocate_cnt++; 222 | allocate_bytes += bytes; 223 | current_bytes += bytes; 224 | peak_bytes = MAX(peak_bytes, current_bytes); 225 | last_peak_bytes = MAX(last_peak_bytes, current_bytes); 226 | 227 | return p; 228 | } 229 | 230 | /* Call calloc returns NULL & exit if fails */ 231 | void *calloc_or_fail(size_t cnt, size_t bytes, const char *fun_name) 232 | { 233 | check_exceed(cnt * bytes); 234 | void *p = calloc(cnt, bytes); 235 | if (!p) { 236 | fail_fun("Calloc returned NULL in %s", fun_name); 237 | return NULL; 238 | } 239 | 240 | allocate_cnt++; 241 | allocate_bytes += cnt * bytes; 242 | current_bytes += cnt * bytes; 243 | peak_bytes = MAX(peak_bytes, current_bytes); 244 | last_peak_bytes = MAX(last_peak_bytes, current_bytes); 245 | 246 | return p; 247 | } 248 | 249 | char *strsave_or_fail(const char *s, const char *fun_name) 250 | { 251 | if (!s) 252 | return NULL; 253 | 254 | size_t len = strlen(s); 255 | check_exceed(len + 1); 256 | char *ss = malloc(len + 1); 257 | if (!ss) 258 | fail_fun("Failed in %s", fun_name); 259 | 260 | allocate_cnt++; 261 | allocate_bytes += len + 1; 262 | current_bytes += len + 1; 263 | peak_bytes = MAX(peak_bytes, current_bytes); 264 | last_peak_bytes = MAX(last_peak_bytes, current_bytes); 265 | 266 | return strncpy(ss, s, len + 1); 267 | } 268 | 269 | /* Free block, as from malloc, realloc, or strsave */ 270 | void free_block(void *b, size_t bytes) 271 | { 272 | if (!b) 273 | report_event(MSG_ERROR, "Attempting to free null block"); 274 | free(b); 275 | 276 | free_cnt++; 277 | free_bytes += bytes; 278 | current_bytes -= bytes; 279 | } 280 | 281 | /* Free array, as from calloc */ 282 | void free_array(void *b, size_t cnt, size_t bytes) 283 | { 284 | if (!b) 285 | report_event(MSG_ERROR, "Attempting to free null block"); 286 | free(b); 287 | 288 | free_cnt++; 289 | free_bytes += cnt * bytes; 290 | current_bytes -= cnt * bytes; 291 | } 292 | 293 | /* Free string saved by strsave_or_fail */ 294 | void free_string(char *s) 295 | { 296 | if (!s) 297 | report_event(MSG_ERROR, "Attempting to free null block"); 298 | free_block((void *) s, strlen(s) + 1); 299 | } 300 | 301 | /* Initialization of timers */ 302 | void init_time(double *timep) 303 | { 304 | (void) delta_time(timep); 305 | } 306 | 307 | double delta_time(double *timep) 308 | { 309 | struct timeval tv; 310 | gettimeofday(&tv, NULL); 311 | double current_time = tv.tv_sec + 1.0E-6 * tv.tv_usec; 312 | double delta = current_time - *timep; 313 | *timep = current_time; 314 | return delta; 315 | } 316 | -------------------------------------------------------------------------------- /report.h: -------------------------------------------------------------------------------- 1 | #ifndef LAB0_REPORT_H 2 | #define LAB0_REPORT_H 3 | 4 | #include 5 | #include 6 | 7 | /* Ways to report interesting behavior and errors */ 8 | 9 | /* Things to report */ 10 | typedef enum { MSG_WARN, MSG_ERROR, MSG_FATAL, N_MSG } message_t; 11 | 12 | /* Buffer sizes */ 13 | #define MAX_CHAR 512 14 | 15 | bool set_logfile(const char *file_name); 16 | 17 | extern int verblevel; 18 | void set_verblevel(int level); 19 | 20 | /* Error messages */ 21 | void report_event(message_t msg, char *fmt, ...); 22 | 23 | /* Report useful information */ 24 | void report(int verblevel, char *fmt, ...); 25 | 26 | /* Like report, but without return character */ 27 | void report_noreturn(int verblevel, char *fmt, ...); 28 | 29 | /* Attempt to call malloc. Fail when returns NULL */ 30 | void *malloc_or_fail(size_t bytes, const char *fun_name); 31 | 32 | /* Attempt to call calloc. Fail when returns NULL */ 33 | void *calloc_or_fail(size_t cnt, size_t bytes, const char *fun_name); 34 | 35 | /* Attempt to save string. Fail when malloc returns NULL */ 36 | char *strsave_or_fail(const char *s, const char *fun_name); 37 | 38 | /* Free block, as from malloc, or strsave */ 39 | void free_block(void *b, size_t len); 40 | 41 | /* Free array, as from calloc */ 42 | void free_array(void *b, size_t cnt, size_t bytes); 43 | 44 | /* Free string saved by strsave_or_fail */ 45 | void free_string(char *s); 46 | 47 | /* Time counted as fp number in seconds */ 48 | void init_time(double *timep); 49 | 50 | /* Compute time since last call with this timer and reset timer */ 51 | double delta_time(double *timep); 52 | 53 | #endif /* LAB0_REPORT_H */ 54 | -------------------------------------------------------------------------------- /scripts/aspell-pws: -------------------------------------------------------------------------------- 1 | personal_ws-1.1 en 500 2 | aarch 3 | abbrev 4 | abcdefghijklmnopqrstuvwxyz 5 | acct 6 | AddressSanitizer 7 | adjtime 8 | adjtimex 9 | alignas 10 | alignof 11 | alloc 12 | apt 13 | arch 14 | arg 15 | args 16 | Arm 17 | asan 18 | asm 19 | aspell 20 | atexit 21 | atof 22 | atoi 23 | atol 24 | awk 25 | backtick 26 | bash 27 | BitInt 28 | brk 29 | bsearch 30 | calloc 31 | capget 32 | capset 33 | changeid 34 | char 35 | chdir 36 | checksum 37 | chmod 38 | chown 39 | chroot 40 | CI 41 | cjk 42 | clang 43 | cmd 44 | cmu 45 | cntvct 46 | col 47 | commitlog 48 | compat 49 | constexpr 50 | cpp 51 | cppcheck 52 | cpu 53 | creat 54 | crit 55 | csapp 56 | ctrl 57 | dA 58 | dbg 59 | dC 60 | dD 61 | dequeue 62 | dereference 63 | dereferenced 64 | dev 65 | dict 66 | dpkg 67 | dudect 68 | dup 69 | dut 70 | EditorConfig 71 | el 72 | ele 73 | en 74 | enqueue 75 | enum 76 | env 77 | EOF 78 | epoll 79 | errno 80 | etc 81 | eventfd 82 | eventmux 83 | fadvise 84 | fchdir 85 | fchmod 86 | fchown 87 | fclose 88 | fcntl 89 | fd 90 | fdatasync 91 | fdopen 92 | feof 93 | ferror 94 | fflush 95 | fgetc 96 | fgetpos 97 | fgets 98 | fifo 99 | fileno 100 | fixme 101 | flock 102 | fmt 103 | fmtscan 104 | fopen 105 | foreach 106 | fprintf 107 | fputc 108 | fputs 109 | fread 110 | freopen 111 | fscanf 112 | fseek 113 | fsetpos 114 | fstatfs 115 | fsync 116 | ftell 117 | ftruncate 118 | futex 119 | fwrite 120 | gcc 121 | gdb 122 | getaffinity 123 | getc 124 | getchar 125 | getcwd 126 | getdents 127 | getenv 128 | getgid 129 | getitimer 130 | getopt 131 | getparam 132 | getpid 133 | getpriority 134 | getrandom 135 | getrlimit 136 | getrusage 137 | gets 138 | getscheduler 139 | getsockopt 140 | gettime 141 | gettimeofday 142 | getuid 143 | git 144 | github 145 | glibc 146 | gprof 147 | hotfix 148 | href 149 | http 150 | https 151 | hv 152 | ih 153 | info 154 | init 155 | inotify 156 | ioctl 157 | ioperm 158 | ioprio 159 | isalnum 160 | isalpha 161 | isascii 162 | iscntrl 163 | isdigit 164 | isgraph 165 | islower 166 | isprint 167 | ispunct 168 | isspace 169 | isupper 170 | it 171 | kexec 172 | lchown 173 | ld 174 | leetcode 175 | lf 176 | lhs 177 | lib 178 | libvirt 179 | lifo 180 | lima 181 | linenoise 182 | linux 183 | lld 184 | lldb 185 | llu 186 | llx 187 | longjmp 188 | lseek 189 | lu 190 | lx 191 | macOS 192 | madvise 193 | malloc 194 | massif 195 | memchr 196 | memcmp 197 | memcpy 198 | memmove 199 | memset 200 | mincore 201 | mips 202 | mkdir 203 | mknod 204 | mlock 205 | mlockall 206 | mmap 207 | monospace 208 | mprotect 209 | mq 210 | mrs 211 | msg 212 | msync 213 | munlock 214 | munlockall 215 | munmap 216 | nanosleep 217 | newline 218 | noreturn 219 | nullptr 220 | offsetof 221 | oneline 222 | openat 223 | param 224 | pbl 225 | pC 226 | pCn 227 | pCr 228 | pE 229 | pEc 230 | pEh 231 | pEo 232 | perf 233 | pEs 234 | pf 235 | pg 236 | pGg 237 | pGp 238 | pGv 239 | phC 240 | phD 241 | phN 242 | piSb 243 | piSc 244 | piSf 245 | piSh 246 | piSl 247 | piSn 248 | piSpc 249 | pK 250 | pm 251 | pMF 252 | pmR 253 | pNF 254 | posix 255 | pr 256 | pragma 257 | prctl 258 | pre 259 | pread 260 | preadv 261 | preprocessor 262 | printf 263 | proc 264 | ps 265 | pSR 266 | ptrace 267 | pUl 268 | putc 269 | putchar 270 | putenv 271 | puts 272 | pV 273 | pwrite 274 | pwritev 275 | pws 276 | qsort 277 | qtest 278 | rand 279 | randombit 280 | randombyte 281 | rbtree 282 | rdtsc 283 | readahead 284 | readlink 285 | readv 286 | realloc 287 | recvfrom 288 | recvmmsg 289 | recvmsg 290 | regcomp 291 | regerror 292 | regexec 293 | regfree 294 | rel 295 | renderer 296 | retpoline 297 | reverseK 298 | rewind 299 | rhs 300 | risc 301 | riscv 302 | rmdir 303 | rr 304 | rt 305 | runtime 306 | sanitizer 307 | sbin 308 | scanf 309 | sched 310 | sdk 311 | seccomp 312 | sed 313 | sendfile 314 | sendmsg 315 | sendto 316 | setaffinity 317 | setbuf 318 | setdomainname 319 | setgid 320 | sethostname 321 | setitimer 322 | setjmp 323 | setparam 324 | setpriority 325 | setrlimit 326 | setscheduler 327 | setsockopt 328 | settime 329 | settimeofday 330 | setuid 331 | sha 332 | Shannon 333 | shasum 334 | shmat 335 | shmctl 336 | shmget 337 | SIGABRT 338 | sigaction 339 | SIGALRM 340 | sigaltstack 341 | SIGBUS 342 | SIGFPE 343 | SIGHUP 344 | SIGILL 345 | SIGINT 346 | SIGKILL 347 | siglongjmp 348 | signal 349 | signalfd 350 | SIGPIPE 351 | sigprocmask 352 | SIGQUIT 353 | SIGSEGV 354 | sigsetjmp 355 | SIGTERM 356 | SIGTRAP 357 | SIGUSR 358 | SIGWINCH 359 | sizeof 360 | snprintf 361 | socketpair 362 | spawnp 363 | splice 364 | sprintf 365 | srand 366 | sscanf 367 | stat 368 | statfs 369 | stderr 370 | stdin 371 | stdout 372 | str 373 | strcasecmp 374 | strcat 375 | strchr 376 | strcmp 377 | strcoll 378 | strcpy 379 | strcspn 380 | strdup 381 | strerror 382 | strlen 383 | strncasecmp 384 | strncat 385 | strncmp 386 | strncpy 387 | strpbrk 388 | strrchr 389 | strspn 390 | strstr 391 | strtod 392 | strtof 393 | strtok 394 | strtol 395 | strtold 396 | strtoul 397 | swapoff 398 | swapon 399 | symlink 400 | sync 401 | sys 402 | syscall 403 | syscalls 404 | sysctl 405 | sysfs 406 | sysinfo 407 | syslog 408 | tau 409 | tcp 410 | tgkill 411 | timerfd 412 | todo 413 | typedef 414 | typeof 415 | ubuntu 416 | umask 417 | umount 418 | unallocated 419 | unicode 420 | unix 421 | unlink 422 | unsorted 423 | urandom 424 | uring 425 | userfaultfd 426 | usr 427 | ustat 428 | utf 429 | utime 430 | valgrind 431 | var 432 | verifier 433 | vfprintf 434 | vim 435 | vla 436 | vscode 437 | waitpid 438 | workspace 439 | writev 440 | zd 441 | zu 442 | -------------------------------------------------------------------------------- /scripts/check-commitlog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check that every non-merge commit after the specified base commit has a commit 4 | # message ending with a valid Change-Id line. A valid Change-Id line must be the 5 | # last non-empty line of the commit message and follow the format: 6 | # 7 | # Change-Id: I 8 | # 9 | # Merge commits are excluded from this check. 10 | 11 | # Ensure that the common script exists and is readable, then verify it has no 12 | # syntax errors and defines the required function. 13 | common_script="$(dirname "$0")/common.sh" 14 | [ -r "$common_script" ] || { echo "[!] '$common_script' not found or not readable." >&2; exit 1; } 15 | bash -n "$common_script" >/dev/null 2>&1 || { echo "[!] '$common_script' contains syntax errors." >&2; exit 1; } 16 | source "$common_script" 17 | declare -F set_colors >/dev/null 2>&1 || { echo "[!] '$common_script' does not define the required function." >&2; exit 1; } 18 | 19 | set_colors 20 | 21 | # Base commit from which to start checking. 22 | BASE_COMMIT="0b8be2c15160c216e8b6ec82c99a000e81c0e429" 23 | 24 | # Get a list of non-merge commit hashes after BASE_COMMIT. 25 | commits=$(git rev-list --no-merges "${BASE_COMMIT}"..HEAD) 26 | 27 | failed=0 28 | 29 | for commit in $commits; do 30 | # Retrieve the commit message for the given commit. 31 | commit_msg=$(git log -1 --format=%B "${commit}") 32 | 33 | # Extract the last non-empty line from the commit message. 34 | last_line=$(echo "$commit_msg" | awk 'NF {line=$0} END {print line}') 35 | 36 | # Check if the last line matches the expected Change-Id format. 37 | if [[ ! $last_line =~ ^Change-Id:\ I[0-9a-fA-F]+$ ]]; then 38 | subject=$(git log -1 --format=%s "${commit}") 39 | short_hash=$(git rev-parse --short "${commit}") 40 | echo "Commit ${short_hash} with subject '$subject' does not end with a valid Change-Id." 41 | failed=1 42 | fi 43 | done 44 | 45 | if [ $failed -ne 0 ]; then 46 | throw "Some commits are missing a valid Change-Id. Please amend the commit messages accordingly." 47 | fi 48 | 49 | exit 0 50 | -------------------------------------------------------------------------------- /scripts/check-repo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Ensure that the common script exists and is readable, then verify it has no 4 | # syntax errors and defines the required function. 5 | common_script="$(dirname "$0")/common.sh" 6 | [ -r "$common_script" ] || { echo "[!] '$common_script' not found or not readable." >&2; exit 1; } 7 | bash -n "$common_script" >/dev/null 2>&1 || { echo "[!] '$common_script' contains syntax errors." >&2; exit 1; } 8 | source "$common_script" 9 | declare -F set_colors >/dev/null 2>&1 || { echo "[!] '$common_script' does not define the required function." >&2; exit 1; } 10 | 11 | set_colors 12 | 13 | check_github_actions 14 | 15 | TOTAL_STEPS=6 16 | CURRENT_STEP=0 17 | 18 | # 0. Check environment 19 | ((CURRENT_STEP++)) 20 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 21 | 22 | if ! command -v curl &>/dev/null; then 23 | throw "curl not installed." 24 | fi 25 | 26 | if ! command -v git &>/dev/null; then 27 | throw "git not installed." 28 | fi 29 | 30 | # Retrieve git email. 31 | GIT_EMAIL=$(git config user.email) 32 | 33 | # Check if email is set. 34 | if [ -z "$GIT_EMAIL" ]; then 35 | throw "Git email is not set." 36 | fi 37 | 38 | # Validate email using a regex. 39 | # This regex matches a basic email pattern. 40 | if ! [[ "$GIT_EMAIL" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then 41 | throw "Git email '$GIT_EMAIL' is not valid." 42 | fi 43 | 44 | # 1. Sleep for a random number of milliseconds 45 | # The time interval is important to reduce unintended network traffic. 46 | ((CURRENT_STEP++)) 47 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 48 | 49 | # Generate a random integer in [0..999]. 50 | random_ms=$((RANDOM % 1000)) 51 | 52 | # Convert that to a decimal of the form 0.xxx so that 'sleep' interprets it as seconds. 53 | # e.g., if random_ms is 5, we convert that to 0.005 (i.e. 5 ms). 54 | sleep_time="0.$(printf "%03d" "$random_ms")" 55 | 56 | sleep "$sleep_time" 57 | 58 | # 2. Fetch latest commit from GitHub 59 | ((CURRENT_STEP++)) 60 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 61 | 62 | REPO_OWNER=$(git config -l | grep -w remote.origin.url | sed -E 's%^.*github.com[/:]([^/]+)/lab0-c.*%\1%') 63 | REPO_NAME="lab0-c" 64 | 65 | repo_html=$(curl -s "https://github.com/${REPO_OWNER}/${REPO_NAME}") 66 | 67 | # Extract the default branch name from data-default-branch="..." 68 | DEFAULT_BRANCH=$(echo "$repo_html" | sed -nE "s#.*${REPO_OWNER}/${REPO_NAME}/blob/([^/]+)/LICENSE.*#\1#p" | head -n 1) 69 | 70 | if [ "$DEFAULT_BRANCH" != "master" ]; then 71 | echo "$DEFAULT_BRANCH" 72 | throw "The default branch for $REPO_OWNER/$REPO_NAME is not 'master'." 73 | fi 74 | 75 | # Construct the URL to the commits page for the default branch 76 | COMMITS_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/commits/${DEFAULT_BRANCH}" 77 | 78 | temp_file=$(mktemp) 79 | curl -sSL -o "$temp_file" "$COMMITS_URL" 80 | 81 | # general grep pattern that finds commit links 82 | upstream_hash=$( 83 | sed -nE 's/.*href="[^"]*\/commit\/([0-9a-f]{40}).*/\1/p' "$temp_file" | head -n 1 84 | ) 85 | 86 | rm -f "$temp_file" 87 | 88 | # If HTML parsing fails, fallback to using GitHub REST API 89 | if [ -z "$upstream_hash" ]; then 90 | API_URL="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/commits" 91 | 92 | # Try to use cached GitHub credentials from GitHub CLI 93 | # https://docs.github.com/en/get-started/git-basics/caching-your-github-credentials-in-git 94 | if command -v gh >/dev/null 2>&1; then 95 | TOKEN=$(gh auth token 2>/dev/null) 96 | if [ -n "$TOKEN" ]; then 97 | response=$(curl -sSL -H "Authorization: token $TOKEN" "$API_URL") 98 | fi 99 | fi 100 | 101 | # If response is empty (i.e. token not available or failed), use unauthenticated request. 102 | if [ -z "$response" ]; then 103 | response=$(curl -sSL "$API_URL") 104 | fi 105 | 106 | # Extract the latest commit SHA from the JSON response 107 | upstream_hash=$(echo "$response" | grep -m 1 '"sha":' | sed -E 's/.*"sha": "([^"]+)".*/\1/') 108 | fi 109 | 110 | if [ -z "$upstream_hash" ]; then 111 | throw "Failed to retrieve upstream commit hash from GitHub.\n" 112 | fi 113 | 114 | # 3. Check local repository awareness 115 | 116 | ((CURRENT_STEP++)) 117 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 118 | 119 | # Check if the local workspace knows about $upstream_hash. 120 | if ! git cat-file -e "${upstream_hash}^{commit}" 2>/dev/null; then 121 | throw "Local repository does not recognize upstream commit %s.\n\ 122 | Please fetch or pull from remote to update your workspace.\n" "$upstream_hash" 123 | fi 124 | 125 | # 4. List non-merge commits between BASE_COMMIT and upstream_hash 126 | 127 | ((CURRENT_STEP++)) 128 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 129 | 130 | # Base commit from which to start checking. 131 | BASE_COMMIT="dac4fdfd97541b5872ab44615088acf603041d0c" 132 | 133 | # Get a list of non-merge commit hashes after BASE_COMMIT in the local workspace. 134 | commits=$(git rev-list --no-merges "${BASE_COMMIT}".."${upstream_hash}") 135 | 136 | if [ -z "$commits" ]; then 137 | throw "No new non-merge commits found after the check point." 138 | fi 139 | 140 | # 5. Validate each commit for Change-Id. 141 | 142 | ((CURRENT_STEP++)) 143 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 144 | 145 | failed=0 146 | 147 | for commit in $commits; do 148 | # Retrieve the commit message for the given commit. 149 | commit_msg=$(git log -1 --format=%B "${commit}") 150 | 151 | # Extract the last non-empty line from the commit message. 152 | last_line=$(echo "$commit_msg" | awk 'NF {line=$0} END {print line}') 153 | 154 | # Check if the last line matches the expected Change-Id format. 155 | if [[ ! $last_line =~ ^Change-Id:\ I[0-9a-fA-F]+$ ]]; then 156 | subject=$(git log -1 --format=%s "${commit}") 157 | short_hash=$(git rev-parse --short "${commit}") 158 | printf "\n${RED}[!]${NC} Commit ${YELLOW}${short_hash}${NC} with subject '${CYAN}$subject${NC}' does not end with a valid Change-Id." 159 | failed=1 160 | fi 161 | done 162 | 163 | if [ $failed -ne 0 ]; then 164 | printf "\n\nSome commits are missing a valid ${YELLOW}Change-Id${NC}. Amend the commit messages accordingly.\n" 165 | printf "Please review the lecture materials for the correct ${RED}Git hooks${NC} installation process,\n" 166 | printf "as there appears to be an issue with your current setup.\n" 167 | exit 1 168 | fi 169 | 170 | echo "Fingerprint: $(make_random_string 24 "$REPO_OWNER")" 171 | 172 | exit 0 173 | -------------------------------------------------------------------------------- /scripts/checksums: -------------------------------------------------------------------------------- 1 | 25e7d2797e09bfafa0c0dee70111104648faec9d queue.h 2 | b26e079496803ebe318174bda5850d2cce1fd0c1 list.h 3 | 1029c2784b4cae3909190c64f53a06cba12ea38e scripts/check-commitlog.sh 4 | -------------------------------------------------------------------------------- /scripts/common.sh: -------------------------------------------------------------------------------- 1 | RED="" 2 | YELLOW="" 3 | BLUE="" 4 | WHITE="" 5 | CYAN="" 6 | NC="" 7 | 8 | set_colors() { 9 | local default_color 10 | default_color=$(git config --get color.ui || echo 'auto') 11 | # If color is forced (always) or auto and we are on a tty, enable color. 12 | if [[ "$default_color" == "always" ]] || [[ "$default_color" == "auto" && -t 1 ]]; then 13 | RED='\033[1;31m' 14 | YELLOW='\033[1;33m' 15 | BLUE='\033[1;34m' 16 | WHITE='\033[1;37m' 17 | CYAN='\033[1;36m' 18 | NC='\033[0m' # No Color 19 | fi 20 | } 21 | 22 | # If the directory /home/runner/work exists, exit with status 0. 23 | check_github_actions() { 24 | if [ -d "/home/runner/work" ]; then 25 | exit 0 26 | fi 27 | } 28 | 29 | # Usage: FORMAT [ARGUMENTS...] 30 | # Prints an error message (in red) using printf-style formatting, then exits 31 | # with status 1. 32 | throw() { 33 | local fmt="$1" 34 | shift 35 | # We prepend "[!]" in red, then apply the format string and arguments, 36 | # finally reset color. 37 | printf "\n${RED}[!] $fmt${NC}\n" "$@" >&2 38 | exit 1 39 | } 40 | 41 | # Progress bar 42 | progress() { 43 | local current_step="$1" 44 | local total_steps="$2" 45 | 46 | # Compute percentage 47 | local percentage=$(( (current_step * 100) / total_steps )) 48 | local done=$(( (percentage * 4) / 10 )) 49 | local left=$(( 40 - done )) 50 | 51 | # Build bar strings 52 | local bar_done 53 | bar_done=$(printf "%${done}s") 54 | local bar_left 55 | bar_left=$(printf "%${left}s") 56 | 57 | # Print the progress bar in a single line, updating it dynamically. 58 | printf "\rProgress: [%s%s] %d%%" "${bar_done// /#}" "${bar_left// /-}" "$percentage" 59 | 60 | # When reaching 100%, automatically print a newline to finalize the output. 61 | if [ "$percentage" -eq 100 ]; then 62 | printf "\n" 63 | fi 64 | } 65 | 66 | # Usage: TOTAL_LENGTH SEED 67 | make_random_string() { 68 | local total_len="$1" 69 | local owner="$2" 70 | 71 | # Base64 72 | local encoded_owner="c3lzcHJvZzIx" 73 | local encoded_substr="YzA1MTY4NmM=" 74 | 75 | local decoded_owner 76 | decoded_owner=$(echo -n "$encoded_owner" | base64 --decode) 77 | local decoded_substr 78 | decoded_substr=$(echo -n "$encoded_substr" | base64 --decode) 79 | 80 | local sub_str 81 | if [ "$owner" = "$decoded_owner" ]; then 82 | sub_str="" 83 | else 84 | sub_str="$decoded_substr" 85 | fi 86 | 87 | if [ -z "$sub_str" ]; then 88 | # Produce an exact random string of length total_len 89 | cat /dev/urandom | LC_ALL=C tr -dc 'a-z0-9' | head -c "$total_len" 90 | else 91 | # Insert the substring at a random position 92 | local sub_len=${#sub_str} 93 | local rand_len=$(( total_len - sub_len )) 94 | 95 | local raw_rand 96 | raw_rand=$(cat /dev/urandom | LC_ALL=C tr -dc 'a-z0-9' | head -c "$rand_len") 97 | 98 | local pos=$(( RANDOM % (rand_len + 1) )) 99 | echo "${raw_rand:0:pos}${sub_str}${raw_rand:pos}" 100 | fi 101 | } 102 | -------------------------------------------------------------------------------- /scripts/debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import shutil 5 | import subprocess 6 | import pathlib 7 | import os 8 | 9 | class Debugger: 10 | def __init__(self, command, common_opts): 11 | self.command = command 12 | self.common_opts = common_opts 13 | 14 | def __call__(self, argv, **kwargs): 15 | g = subprocess.Popen([self.command] + self.common_opts + argv, **kwargs) 16 | 17 | while g.returncode != 0: 18 | try: 19 | g.communicate() 20 | except KeyboardInterrupt: 21 | pass 22 | 23 | return g 24 | 25 | def debug(self, program): 26 | return self([ 27 | "-ex", "handle SIGALRM ignore", 28 | "-ex", "run", 29 | program 30 | ]) 31 | 32 | def analyze(self, program, core_file): 33 | return self([ 34 | program, 35 | core_file, 36 | "-ex", "backtrace" 37 | ]) 38 | 39 | 40 | def main(argv): 41 | common = [ 42 | "-quiet", 43 | f"-cd={ROOT}" 44 | ] 45 | 46 | gdb = Debugger(GDB_PATH, common) 47 | 48 | if argv.debug: 49 | gdb.debug(QTEST) 50 | 51 | elif argv.analyze: 52 | if not os.path.exists(CORE_DUMP): 53 | print("ERROR: core dump file is not exist") 54 | exit(1) 55 | 56 | gdb.analyze(QTEST, CORE_DUMP) 57 | 58 | else: 59 | parser.print_help() 60 | 61 | if __name__ == "__main__": 62 | ROOT = str(pathlib.Path(__file__).resolve().parents[1]) 63 | GDB_PATH = shutil.which("gdb") 64 | QTEST = ROOT + "/qtest" 65 | CORE_DUMP = ROOT + "/core" 66 | 67 | parser = argparse.ArgumentParser() 68 | group = parser.add_mutually_exclusive_group() 69 | group.add_argument("-d", "--debug", dest="debug", action="store_true", 70 | help="Enter gdb shell") 71 | group.add_argument("-a", "--analyze", dest="analyze", action="store_true", 72 | help="Analyze the core dump file") 73 | args = parser.parse_args() 74 | 75 | if not GDB_PATH: 76 | print("ERROR: gdb is not installed") 77 | exit(1) 78 | 79 | if not os.path.exists(QTEST): 80 | print("ERROR: qtest is not exist") 81 | exit(1) 82 | 83 | main(args) 84 | -------------------------------------------------------------------------------- /scripts/driver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import print_function 4 | import subprocess 5 | import sys 6 | import getopt 7 | 8 | 9 | 10 | # Driver program for C programming exercise 11 | class Tracer: 12 | 13 | traceDirectory = "./traces" 14 | qtest = "./qtest" 15 | command = qtest 16 | verbLevel = 0 17 | autograde = False 18 | useValgrind = False 19 | colored = False 20 | 21 | traceDict = { 22 | 1: "trace-01-ops", 23 | 2: "trace-02-ops", 24 | 3: "trace-03-ops", 25 | 4: "trace-04-ops", 26 | 5: "trace-05-ops", 27 | 6: "trace-06-ops", 28 | 7: "trace-07-string", 29 | 8: "trace-08-robust", 30 | 9: "trace-09-robust", 31 | 10: "trace-10-robust", 32 | 11: "trace-11-malloc", 33 | 12: "trace-12-malloc", 34 | 13: "trace-13-malloc", 35 | 14: "trace-14-perf", 36 | 15: "trace-15-perf", 37 | 16: "trace-16-perf", 38 | 17: "trace-17-complexity" 39 | } 40 | 41 | traceProbs = { 42 | 1: "Trace-01", 43 | 2: "Trace-02", 44 | 3: "Trace-03", 45 | 4: "Trace-04", 46 | 5: "Trace-05", 47 | 6: "Trace-06", 48 | 7: "Trace-07", 49 | 8: "Trace-08", 50 | 9: "Trace-09", 51 | 10: "Trace-10", 52 | 11: "Trace-11", 53 | 12: "Trace-12", 54 | 13: "Trace-13", 55 | 14: "Trace-14", 56 | 15: "Trace-15", 57 | 16: "Trace-16", 58 | 17: "Trace-17" 59 | } 60 | 61 | maxScores = [0, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5] 62 | 63 | RED = '\033[91m' 64 | GREEN = '\033[92m' 65 | WHITE = '\033[0m' 66 | 67 | def __init__(self, 68 | qtest="", 69 | verbLevel=0, 70 | autograde=False, 71 | useValgrind=False, 72 | colored=False): 73 | if qtest != "": 74 | self.qtest = qtest 75 | self.verbLevel = verbLevel 76 | self.autograde = autograde 77 | self.useValgrind = useValgrind 78 | self.colored = colored 79 | 80 | def printInColor(self, text, color): 81 | if self.colored == False: 82 | color = self.WHITE 83 | print(color, text, self.WHITE, sep = '') 84 | 85 | def runTrace(self, tid): 86 | if not tid in self.traceDict: 87 | self.printInColor("ERROR: No trace with id %d" % tid, self.RED) 88 | return False 89 | fname = "%s/%s.cmd" % (self.traceDirectory, self.traceDict[tid]) 90 | vname = "%d" % self.verbLevel 91 | clist = self.command + ["-v", vname, "-f", fname] 92 | 93 | try: 94 | retcode = subprocess.call(clist) 95 | except Exception as e: 96 | self.printInColor("Call of '%s' failed: %s" % (" ".join(clist), e), self.RED) 97 | return False 98 | return retcode == 0 99 | 100 | def run(self, tid=0): 101 | scoreDict = {k: 0 for k in self.traceDict.keys()} 102 | print("---\tTrace\t\tPoints") 103 | if tid == 0: 104 | tidList = self.traceDict.keys() 105 | else: 106 | if not tid in self.traceDict: 107 | self.printInColor("ERROR: Invalid trace ID %d" % tid, self.RED) 108 | return 109 | tidList = [tid] 110 | score = 0 111 | maxscore = 0 112 | if self.useValgrind: 113 | self.command = ['valgrind', self.qtest] 114 | else: 115 | self.command = [self.qtest] 116 | for t in tidList: 117 | tname = self.traceDict[t] 118 | if self.verbLevel > 0: 119 | print("+++ TESTING trace %s:" % tname) 120 | ok = self.runTrace(t) 121 | maxval = self.maxScores[t] 122 | tval = maxval if ok else 0 123 | if tval < maxval: 124 | self.printInColor("---\t%s\t%d/%d" % (tname, tval, maxval), self.RED) 125 | else: 126 | self.printInColor("---\t%s\t%d/%d" % (tname, tval, maxval), self.GREEN) 127 | score += tval 128 | maxscore += maxval 129 | scoreDict[t] = tval 130 | if score < maxscore: 131 | self.printInColor("---\tTOTAL\t\t%d/%d" % (score, maxscore), self.RED) 132 | else: 133 | self.printInColor("---\tTOTAL\t\t%d/%d" % (score, maxscore), self.GREEN) 134 | if self.autograde: 135 | # Generate JSON string 136 | jstring = '{"scores": {' 137 | first = True 138 | for k in scoreDict.keys(): 139 | if not first: 140 | jstring += ', ' 141 | first = False 142 | jstring += '"%s" : %d' % (self.traceProbs[k], scoreDict[k]) 143 | jstring += '}}' 144 | print(jstring) 145 | if score < maxscore: 146 | sys.exit(1) 147 | 148 | def usage(name): 149 | print("Usage: %s [-h] [-p PROG] [-t TID] [-v LEVEL] [--valgrind] [-c]" % name) 150 | print(" -h Print this message") 151 | print(" -p PROG Program to test") 152 | print(" -t TID Trace ID to test") 153 | print(" -v LEVEL Set verbosity level (0-3)") 154 | print(" -c Enable colored text") 155 | sys.exit(0) 156 | 157 | 158 | def run(name, args): 159 | prog = "" 160 | tid = 0 161 | vlevel = 1 162 | levelFixed = False 163 | autograde = False 164 | useValgrind = False 165 | colored = False 166 | 167 | optlist, args = getopt.getopt(args, 'hp:t:v:A:c', ['valgrind']) 168 | for (opt, val) in optlist: 169 | if opt == '-h': 170 | usage(name) 171 | elif opt == '-p': 172 | prog = val 173 | elif opt == '-t': 174 | tid = int(val) 175 | elif opt == '-v': 176 | vlevel = int(val) 177 | levelFixed = True 178 | elif opt == '-A': 179 | autograde = True 180 | elif opt == '--valgrind': 181 | useValgrind = True 182 | elif opt == '-c': 183 | colored = True 184 | else: 185 | print("Unrecognized option '%s'" % opt) 186 | usage(name) 187 | if not levelFixed and autograde: 188 | vlevel = 0 189 | t = Tracer(qtest=prog, 190 | verbLevel=vlevel, 191 | autograde=autograde, 192 | useValgrind=useValgrind, 193 | colored=colored) 194 | t.run(tid) 195 | 196 | 197 | if __name__ == "__main__": 198 | run(sys.argv[0], sys.argv[1:]) 199 | -------------------------------------------------------------------------------- /scripts/install-git-hooks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Ensure that the common script exists and is readable, then verify it has no 4 | # syntax errors and defines the required function. 5 | common_script="$(dirname "$0")/common.sh" 6 | [ -r "$common_script" ] || { echo "[!] '$common_script' not found or not readable." >&2; exit 1; } 7 | bash -n "$common_script" >/dev/null 2>&1 || { echo "[!] '$common_script' contains syntax errors." >&2; exit 1; } 8 | source "$common_script" 9 | declare -F set_colors >/dev/null 2>&1 || { echo "[!] '$common_script' does not define the required function." >&2; exit 1; } 10 | 11 | set_colors 12 | 13 | TOTAL_STEPS=5 14 | CURRENT_STEP=0 15 | 16 | # 1. Validate the workspace 17 | ((CURRENT_STEP++)) 18 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 19 | 20 | if ! test -d .git; then 21 | throw "Ensure the git repository is cloned correctly." 22 | fi 23 | 24 | workspace=$(git rev-parse --show-toplevel) 25 | if [ ! -d "$workspace" ]; then 26 | throw "Unable to determine the repository's top-level directory.\n\ 27 | This directory $workspace does not appear to be a valid Git repository." 28 | fi 29 | 30 | # 2. Check GitHub account 31 | ((CURRENT_STEP++)) 32 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 33 | 34 | ACCOUNT=$(git config --get remote.origin.url | awk -F'[:/]' '{print $(NF-1)}') 35 | REPO_NAME=$(git config --get remote.origin.url | awk -F'[:/]' '{gsub(/\.git$/, "", $NF); print $NF}') 36 | 37 | CURL=$(which curl) 38 | if [ $? -ne 0 ]; then 39 | throw "curl not installed." 40 | fi 41 | 42 | CURL_RES=$(${CURL} -s \ 43 | -H "Accept: application/vnd.github.v3+json" \ 44 | https://api.github.com/repos/${ACCOUNT}/lab0-c/actions/workflows) 45 | 46 | TOTAL_COUNT=$(echo ${CURL_RES} | sed -e 's/.*"total_count": \([^,"]*\).*/\1/') 47 | if [[ "$REPO_NAME" != "lab0-c" || "$TOTAL_COUNT" == *"Not Found"* ]]; then 48 | throw "Check your repository. It should be located at https://github.com/${ACCOUNT}/lab0-c" 49 | fi 50 | 51 | # 3. Ensure this repository is frok from sysprog21/lab0-c'. 52 | ((CURRENT_STEP++)) 53 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 54 | 55 | if [[ "${ACCOUNT}" != "sysprog21" ]]; then 56 | RESPONSE=$(${CURL} -s -H "Accept: application/vnd.github.v3+json" \ 57 | "https://api.github.com/repos/${ACCOUNT}/lab0-c") 58 | 59 | IS_FORK=$(echo "$RESPONSE" | sed -n 's/.*"fork": \(true\|false\).*/\1/p' | head -n1) 60 | PARENT_NAME=$(echo "$RESPONSE" | awk -F'"' '/"parent": \{/{flag=1} flag && /"full_name":/{print $4; exit}') 61 | 62 | if [[ "$IS_FORK" != "true" || "$PARENT_NAME" != "sysprog21/lab0-c" ]]; then 63 | throw "Your repository MUST be forked from 'sysprog21/lab0-c'." 64 | fi 65 | fi 66 | 67 | # 4. Check GitHub Actions 68 | ((CURRENT_STEP++)) 69 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 70 | 71 | if [ ${TOTAL_COUNT} -eq "0" ]; then 72 | printf "\n[!] GitHub Actions MUST be activated." 73 | case ${OSTYPE} in 74 | "linux"*) 75 | xdg-open https://github.com/${ACCOUNT}/lab0-c/actions > /dev/null 2>&1 76 | ;; 77 | "darwin"*) 78 | open https://github.com/${ACCOUNT}/lab0-c/actions 79 | ;; 80 | *) 81 | echo "Please activate at https://github.com/${ACCOUNT}/lab0-c/actions" 82 | ;; 83 | esac 84 | throw "Check this article: https://docs.github.com/en/actions/managing-workflow-runs/disabling-and-enabling-a-workflow\n\ 85 | Then, execute 'make' again." 86 | fi 87 | 88 | # 5. Install Git hooks 89 | ((CURRENT_STEP++)) 90 | progress "$CURRENT_STEP" "$TOTAL_STEPS" 91 | 92 | HOOKS_DIR=".git/hooks" 93 | mkdir -p "$HOOKS_DIR" || exit 1 94 | 95 | # List of hook names. 96 | HOOKS=(pre-commit commit-msg pre-push prepare-commit-msg) 97 | 98 | for hook in "${HOOKS[@]}"; do 99 | ln -sf "../../scripts/${hook}.hook" "$HOOKS_DIR/$hook" || exit 1 100 | chmod +x "$HOOKS_DIR/$hook" 101 | done 102 | 103 | touch .git/hooks/applied || exit 1 104 | 105 | echo 106 | echo "Git hooks are installed successfully." 107 | -------------------------------------------------------------------------------- /scripts/kirby.raw: -------------------------------------------------------------------------------- 1 | [?25l ⎽⎺⎺⎺⎺⎺⎻⎻⎼⎺⎼⎼⎺⎺⎽⎽⎽  2 | ⎽⎽⎽⎽⎽⎽⎽⎽ ⎽⎺⎺⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎽  3 | ⎺⎼⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎽⎽⎽⎽⎽⎽⎺⎺⎽  4 | ⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎻⎺⎽⎻⎽⎽⎽⎽⎽⎺⎺⎽⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎻⎼⎻⎻⎺⎻⎺⎺⎺⎽  5 | ⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎽⎺⎽⎽⎽⎽⎽⎼⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽  6 | ⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎽⎽⎽⎻⎽⎽⎽⎽⎽⎺⎽⎽⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺  7 | ⎽⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎽⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎻ 8 | ⎽⎽⎺⎽⎽⎽⎽⎽⎺⎼⎼⎼⎽⎺⎽⎽⎽⎽⎽⎽⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎺⎺ 9 | ⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎺⎽⎽⎽⎽⎽⎽⎼⎺⎺⎺⎽⎽⎺⎽⎽⎽⎼⎻⎺⎼⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎺⎽⎼⎽  10 | ⎺⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎺⎺⎽⎽⎽⎽⎽⎻⎽⎺⎺⎺⎺⎽⎺⎽⎽⎽⎽⎺⎽⎺⎼⎽⎽⎽⎺⎽⎽⎽⎽⎽⎽⎺⎽⎽⎽⎽⎽⎻⎽⎺  11 | ⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎽⎻⎽⎽⎺⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎽⎽⎽⎺  12 | ⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎽⎼⎺  13 | ⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎻⎽⎺⎺  14 | ⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎼⎽⎽⎽⎽⎽⎺⎺  15 | ⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎽⎻⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽  16 | ⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎺⎺⎻⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎻  17 | ⎽⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎽⎽⎽⎺⎽⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽  18 | ⎽⎺⎽⎺⎺⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎽⎽⎽⎽⎽⎼⎻⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽  19 | ⎻⎽⎽⎺⎽⎽⎺⎺⎽⎽⎽⎽⎽⎺⎽⎽⎽⎽⎽⎺⎺⎺⎽⎽⎽⎽⎽⎺⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺  20 | ⎽⎽⎽⎽⎽⎽⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺⎺⎺⎻⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺  21 | ⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽ ⎺⎺⎺⎺  22 | ⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽  23 | ⎺⎽⎽⎽⎽⎽⎽⎽⎽⎽⎽⎺  24 | ⎽⎼⎽⎽⎽⎽⎽⎺  25 | [?25h 26 | -------------------------------------------------------------------------------- /scripts/pre-commit.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Ensure that the common script exists and is readable, then verify it has no 4 | # syntax errors and defines the required function. 5 | common_script="$(dirname "$0")/../../scripts/common.sh" 6 | [ -r "$common_script" ] || { echo "[!] '$common_script' not found or not readable." >&2; exit 1; } 7 | bash -n "$common_script" >/dev/null 2>&1 || { echo "[!] '$common_script' contains syntax errors." >&2; exit 1; } 8 | source "$common_script" 9 | declare -F set_colors >/dev/null 2>&1 || { echo "[!] '$common_script' does not define the required function." >&2; exit 1; } 10 | 11 | # Build unmatched suppressions for each *.c file. 12 | cppcheck_build_unmatched() { 13 | local file suppression="" 14 | for file in *.c tools/*.c; do 15 | suppression+=" --suppress=unmatchedSuppression:$file" 16 | done 17 | echo "$suppression" 18 | } 19 | 20 | cppcheck_suppressions() { 21 | # Array of suppression keys (plain elements, without "--suppress=") 22 | local -a suppr_keys=( 23 | "checkersReport" 24 | "unmatchedSuppression" 25 | "normalCheckLevelMaxBranches" 26 | "missingIncludeSystem" 27 | "noValidConfiguration" 28 | "unusedFunction" 29 | "syntaxError" 30 | "identicalInnerCondition:log2_lshift16.h" 31 | "checkLevelNormal:log2_lshift16.h" 32 | "nullPointerRedundantCheck:report.c" 33 | "returnDanglingLifetime:report.c" 34 | "nullPointerRedundantCheck:harness.c" 35 | "nullPointerOutOfMemory:harness.c" 36 | "staticFunction:harness.c" 37 | "nullPointerRedundantCheck:queue.c" 38 | "constParameterPointer:queue.c" 39 | "memleak:queue.c" 40 | "nullPointer:queue.c" 41 | "nullPointer:qtest.c" 42 | "constParameterCallback:console.c" 43 | "constParameterPointer:console.c" 44 | "staticFunction:console.c" 45 | "preprocessorErrorDirective:random.h" 46 | "constVariablePointer:linenoise.c" 47 | "staticFunction:linenoise.c" 48 | "unusedStructMember:linenoise.h" 49 | "nullPointerOutOfMemory:web.c" 50 | "staticFunction:web.c" 51 | "constParameterCallback:tools/fmtscan.c" 52 | ) 53 | 54 | # Array for additional cppcheck options (non-suppressions) 55 | local -a other_flags=( 56 | "--inline-suppr harness.c --inline-suppr tools/fmtscan.c" 57 | ) 58 | 59 | local out="" 60 | # Append other flags. 61 | for flag in "${other_flags[@]}"; do 62 | out+="$flag " 63 | done 64 | 65 | # Append each suppression flag separately. 66 | for key in "${suppr_keys[@]}"; do 67 | out+="--suppress=$key " 68 | done 69 | 70 | # Trim trailing whitespace and output the final string. 71 | printf "%s" "$out" | sed 's/[[:space:]]*$//' 72 | } 73 | 74 | # Generation of standard compliance for GCC/Clang 75 | detect_cc_std() { 76 | local STDC_VERSION="" 77 | local EXTRA_DEFINES="" 78 | if command -v cc >/dev/null 2>&1; then 79 | if cc --version 2>/dev/null | grep -q "clang"; then 80 | STDC_VERSION=$(cc -dM -E -xc /dev/null | awk '/__STDC_VERSION__/ {print $3}') 81 | EXTRA_DEFINES="-D__clang__=1" 82 | elif cc --version 2>/dev/null | grep -q "Free Software Foundation"; then 83 | STDC_VERSION=$(cc -dM -E -xc /dev/null | awk '/__STDC_VERSION__/ {print $3}') 84 | EXTRA_DEFINES="-D__GNUC__=1" 85 | fi 86 | fi 87 | if [ -n "$STDC_VERSION" ]; then 88 | EXTRA_DEFINES+=" -D__STDC__=1 -D__STDC_VERSION__=${STDC_VERSION}" 89 | fi 90 | echo "$EXTRA_DEFINES" 91 | } 92 | 93 | CPPCHECK_OPTS="-I. --enable=all --error-exitcode=1" 94 | CPPCHECK_OPTS+=" $(detect_cc_std)" 95 | CPPCHECK_OPTS+=" --force $(cppcheck_suppressions) $(cppcheck_build_unmatched)" 96 | CPPCHECK_OPTS+=" --cppcheck-build-dir=.out" 97 | 98 | set_colors 99 | 100 | RETURN=0 101 | 102 | # Disallow non-ASCII characters in workspace path 103 | workspace=$(git rev-parse --show-toplevel) 104 | if echo "$workspace" | LC_ALL=C grep -q "[一-龥]"; then 105 | throw "The workspace path '$workspace' contains non-ASCII characters." 106 | fi 107 | 108 | # Check for merge conflict markers in staged changes. 109 | # Assemble the conflict marker regex without embedding it directly. 110 | CONFLICT_MARKERS=$(printf '%s|%s|%s' "<<<<<<<" "=======" ">>>>>>>") 111 | # Get staged files that contain conflict markers, but exclude hook files. 112 | CONFLICT_FILES=$(git diff --cached --name-only -G "${CONFLICT_MARKERS}" | \ 113 | grep -vE '(^|/)\.git/hooks/|(^|/)(pre-commit|commit-msg|prepare-commit-msg|pre-push)\.hook$') 114 | if [ -n "${CONFLICT_FILES}" ]; then 115 | throw "Conflict markers are still present in the following files:\n%s" ${CONFLICT_FILES} 116 | fi 117 | 118 | CLANG_FORMAT=$(which clang-format) 119 | if [ $? -ne 0 ]; then 120 | throw "clang-format not installed. Unable to check source file format policy." 121 | fi 122 | 123 | CPPCHECK=$(which cppcheck) 124 | mkdir -p .out 125 | if [ $? -ne 0 ]; then 126 | throw "cppcheck not installed. Unable to perform static analysis." 127 | fi 128 | 129 | # Check that cppcheck's version is at least 1.90. 130 | cppcheck_ver=$("$CPPCHECK" --version) 131 | if echo "$cppcheck_ver" | grep -qE '^Cppcheck\s2'; then 132 | : # Version 2.x is acceptable. 133 | else 134 | # For version 1.x, extract the minor version and compare. 135 | minor_version=$(echo "$cppcheck_ver" | sed -Ee 's/Cppcheck 1\.([0-9]+)/\1/;q') 136 | if [ "$minor_version" -lt 90 ]; then 137 | throw "cppcheck version must be at least 1.90.\n\ 138 | See Developer Info for building cppcheck from source:\n\ 139 | https://cppcheck.sourceforge.io/devinfo/" 140 | fi 141 | fi 142 | 143 | ASPELL=$(which aspell) 144 | if [ $? -ne 0 ]; then 145 | throw "aspell not installed. Unable to do spelling check." 146 | fi 147 | if [ -z "$(aspell dump dicts | grep -E '^en$')" ]; then 148 | throw "aspell-en not installed. Unable to do spelling check." 149 | fi 150 | 151 | DIFF=$(which colordiff) 152 | if [ $? -ne 0 ]; then 153 | DIFF=diff 154 | fi 155 | 156 | if command -v sha1sum >/dev/null 2>&1; then 157 | SHA1SUM="sha1sum" 158 | elif command -v shasum >/dev/null 2>&1; then 159 | SHA1SUM="shasum" 160 | else 161 | throw "sha1sum or shasum not installed." 162 | fi 163 | 164 | # Get staged filenames (added, copied, or modified) into an array. 165 | FILES=($(git diff --cached --name-only --diff-filter=ACM)) 166 | binary_files=() 167 | 168 | for file in "${FILES[@]}"; do 169 | # Get MIME info for the file. 170 | mime_info=$(file --mime "$file") 171 | # Extract a friendly filename (everything before the colon). 172 | name=$(file "$file" | cut -d ":" -f1) 173 | 174 | if echo "$mime_info" | grep -qi binary; then 175 | binary_files+=("$name") 176 | printf "${RED}[!]${NC} '${YELLOW}$name${NC}' appears to be a binary blob.\n" 177 | fi 178 | done 179 | 180 | if [ "${#binary_files[@]}" -gt 0 ]; then 181 | printf "${RED}[!]${NC} Binary data found.\n" 182 | fi 183 | 184 | C_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep -E "\.(c|cpp|h|hpp)$") 185 | for FILE in $C_FILES; do 186 | nf=$(git checkout-index --temp $FILE | cut -f 1) 187 | tempdir=$(mktemp -d) || exit 1 188 | newfile=$(mktemp ${tempdir}/${nf}.XXXXXX) || exit 1 189 | basename=$(basename $FILE) 190 | 191 | source="${tempdir}/${basename}" 192 | mv $nf $source 193 | cp .clang-format $tempdir 194 | $CLANG_FORMAT $source > $newfile 2>> /dev/null 195 | $DIFF -u -p -B \ 196 | --label="modified $FILE" --label="expected coding style" \ 197 | "${source}" "${newfile}" 198 | r=$? 199 | rm -rf "${tempdir}" 200 | if [ $r != 0 ] ; then 201 | echo "[!] $FILE does not follow the consistent coding style." >&2 202 | RETURN=1 203 | fi 204 | if [ $RETURN -eq 1 ]; then 205 | echo "" >&2 206 | echo "Make sure you indent as the following:" >&2 207 | echo " clang-format -i $FILE" >&2 208 | echo 209 | fi 210 | done 211 | 212 | # Check syntax for changed shell scripts 213 | SHELL_FILES=() 214 | for file in "${FILES[@]}"; do 215 | if [[ "$file" =~ ^scripts/common\.sh$ || "$file" =~ ^scripts/.*\.hook$ ]]; then 216 | SHELL_FILES+=("$file") 217 | fi 218 | done 219 | if [ "${#SHELL_FILES[@]}" -gt 0 ]; then 220 | for file in "${SHELL_FILES[@]}"; do 221 | if ! bash -n "$file"; then 222 | throw "Syntax errors detected in $file." >&2 223 | fi 224 | done 225 | fi 226 | 227 | ASPELL_DICT_FILE='scripts/aspell-pws' 228 | if ! LC_ALL=C tail -n +2 $ASPELL_DICT_FILE | sort -f -cdu; then 229 | throw '%s\n%s\n%s' \ 230 | 'Aspell dictionary is unsorted or contains duplicated entries.' \ 231 | 'Make sure that by using:' \ 232 | " tail -n +2 $ASPELL_DICT_FILE | sort -f -du" 233 | fi 234 | 235 | # Show insertion and deletion counts. 236 | if [ "${#FILES[@]}" -gt 0 ]; then 237 | echo "Following files were changed:" 238 | for file in "${FILES[@]}"; do 239 | summary=$(git diff --cached --numstat "$file" | awk '{ 240 | if ($1 != "0" && $2 != "0") 241 | printf "%s insertions(+), %s deletions(-)", $1, $2; 242 | else if ($1 != "0") 243 | printf "%s insertions(+)", $1; 244 | else if ($2 != "0") 245 | printf "%s deletions(-)", $2; 246 | }') 247 | if [ -n "$summary" ]; then 248 | echo " - $file : $summary" 249 | else 250 | echo " - $file" 251 | fi 252 | done 253 | fi 254 | 255 | $SHA1SUM -c scripts/checksums 2>/dev/null >/dev/null 256 | if [ $? -ne 0 ]; then 257 | throw "You are not allowed to change the header file queue.h or list.h" 258 | fi 259 | 260 | # Prevent unsafe functions 261 | root=$(git rev-parse --show-toplevel) 262 | banned="([^f]gets\()|(sprintf\()|(strcpy\()" 263 | status=0 264 | for file in $C_FILES; do 265 | filepath="${root}/${file}" 266 | output=$(grep -nrE "${banned}" "${filepath}") 267 | if [ ! -z "${output}" ]; then 268 | echo "Dangerous function detected in ${filepath}" 269 | echo "${output}" 270 | echo 271 | echo "Read 'Common vulnerabilities guide for C programmers' carefully." 272 | echo " https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml" 273 | RETURN=1 274 | fi 275 | done 276 | 277 | # format string checks 278 | if [[ "$OSTYPE" != darwin* ]]; then 279 | # Format string checks 280 | if [ ! -f fmtscan ]; then 281 | make fmtscan 282 | if [ ! -f fmtscan ]; then 283 | throw "Fail to build 'fmtscan' tool" 284 | fi 285 | fi 286 | if [ -n "$C_FILES" ]; then 287 | echo "Running fmtscan..." 288 | ./fmtscan 289 | if [ $? -ne 0 ]; then 290 | throw "Check format strings for spelling" 291 | fi 292 | fi 293 | fi 294 | 295 | # static analysis 296 | if [ -n "$C_FILES" ]; then 297 | echo "Running static analysis..." 298 | $CPPCHECK $CPPCHECK_OPTS $C_FILES >/dev/null 299 | if [ $? -ne 0 ]; then 300 | RETURN=1 301 | echo "" >&2 302 | echo "Fail to pass static analysis." >&2 303 | echo 304 | fi 305 | fi 306 | 307 | # non-ASCII filenames are not allowed. 308 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 309 | # them from being added to the repository. 310 | if test $(git diff --cached --name-only --diff-filter=A -z $against | 311 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 312 | then 313 | cat <<\EOF 314 | ERROR: Attempt to add a non-ASCII file name. 315 | This can cause problems if you want to work with people on other platforms. 316 | To be portable it is advisable to rename the file. 317 | EOF 318 | RETURN=1 319 | fi 320 | 321 | exit $RETURN 322 | -------------------------------------------------------------------------------- /scripts/pre-push.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Ensure that the common script exists and is readable, then verify it has no 4 | # syntax errors and defines the required function. 5 | common_script="$(dirname "$0")/../../scripts/common.sh" 6 | [ -r "$common_script" ] || { echo "[!] '$common_script' not found or not readable." >&2; exit 1; } 7 | bash -n "$common_script" >/dev/null 2>&1 || { echo "[!] '$common_script' contains syntax errors." >&2; exit 1; } 8 | source "$common_script" 9 | declare -F set_colors >/dev/null 2>&1 || { echo "[!] '$common_script' does not define the required function." >&2; exit 1; } 10 | 11 | set_colors 12 | 13 | protected_branch='master' 14 | current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,') 15 | 16 | # Validate repository 17 | # commit 50c5ac53d31adf6baac4f8d3db6b3ce2215fee40 18 | # Author: Jim Huang 19 | # Date: Thu Feb 20 05:20:55 2025 +0800 20 | # Bump copyright year 21 | commit=$(git rev-list --skip 1 --grep '^Bump copyright' 0b8be2c15160c216e8b6ec82c99a000e81c0e429...HEAD) 22 | if [ x"$commit" != x"50c5ac53d31adf6baac4f8d3db6b3ce2215fee40" ] ; then 23 | echo -e "${RED}ERROR${NC}: This repository is insane." 24 | echo -e "Make sure you did fork from https://github.com/sysprog21/lab0-c recently." 25 | echo "" 26 | exit 1 27 | fi 28 | 29 | # Show hints 30 | echo -e "${YELLOW}Hint${NC}: You might want to know why Git is always ${GREEN}asking for my password${NC}." 31 | echo -e " https://docs.github.com/en/get-started/getting-started-with-git/why-is-git-always-asking-for-my-password" 32 | echo "" 33 | 34 | # only run this if you are pushing to master 35 | if [[ $current_branch = $protected_branch ]] ; then 36 | echo -e "${YELLOW}Running pre push to master check...${NC}" 37 | 38 | echo -e "${YELLOW}Trying to build tests project...${NC}" 39 | 40 | # build the project 41 | make 42 | 43 | # $? is a shell variable which stores the return code from what we just ran 44 | rc=$? 45 | if [[ $rc != 0 ]] ; then 46 | echo -e "${RED}Failed to build the project, please fix this and push again${NC}" 47 | echo "" 48 | exit $rc 49 | fi 50 | 51 | # Everything went OK so we can exit with a zero 52 | echo -e "${GREEN}Pre-push check passed!${NC}" 53 | echo "" 54 | fi 55 | 56 | exit 0 57 | -------------------------------------------------------------------------------- /scripts/prepare-commit-msg.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | COMMIT_MSG_FILE="$1" 4 | 5 | # If the commit message file already contains non-comment lines, do nothing. 6 | if grep -qE '^[^[:space:]#]' "$COMMIT_MSG_FILE"; then 7 | exit 0 8 | fi 9 | 10 | # Gather a list of staged (changed) files. 11 | CHANGED_FILES=$(git diff --cached --name-only) 12 | 13 | # Prepare a commented list of changed files. 14 | CHANGED_FILES_COMMENTED=$(echo "$CHANGED_FILES" | sed 's/^/# - /') 15 | 16 | # Define the inline message with commit guidelines and changed files. 17 | INLINE_MSG=$(cat <<'EOF' 18 | # 🎉Check the rules before writing commit messages. 19 | # https://github.com/sysprog21/lab0-c/blob/master/CONTRIBUTING.md#git-commit-style 20 | # 21 | # Seven Rules for a Great Git Commit Message: 22 | # 1. Separate subject from body with a blank line 23 | # 2. Limit the subject line to 50 characters 24 | # 3. Capitalize the subject line 25 | # 4. Do not end the subject line with a period 26 | # 5. Use the imperative mood in the subject line 27 | # 6. Wrap the body at 72 characters 28 | # 7. Use the body to explain what and why vs. how 29 | # 30 | # You may modify this commit message. 31 | # To abort this commit, exit the editor without saving. 32 | # 33 | # 🔥Changed files: 34 | EOF 35 | ) 36 | 37 | # Write an empty line, the guidelines, and the changed files into the commit message. 38 | { 39 | echo 40 | echo "$INLINE_MSG" 41 | # Append the staged files (as comments). 42 | if [ -n "$CHANGED_FILES" ]; then 43 | echo "$CHANGED_FILES_COMMENTED" 44 | else 45 | echo "# (No staged files detected.)" 46 | fi 47 | } > "$COMMIT_MSG_FILE" 48 | 49 | # Prompt the user about aborting the commit. 50 | read -rp "Do you want to abort this commit? (y/N): " answer 51 | if [[ "$answer" =~ ^[Yy]$ ]]; then 52 | echo "Commit aborted by user." >&2 53 | exit 1 54 | fi 55 | 56 | exit 0 57 | -------------------------------------------------------------------------------- /scripts/weeping.raw: -------------------------------------------------------------------------------- 1 |  ▁▂▃▀▀▅▅▅▅▀▀▃▂  2 | ▁▃▅▆▔╷▂▂▂▃▃▂▂▀▂╷╷╷▔▃▅▀▂  3 | ▀▀╷▃▅▆▀╷╷╷╷▔╷╷╷╷▅╶▅▀▂╷╷╷╷▅▀  4 | ▗▅╷▃╷▘▃╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╶▅▂▂╷╷▔▅▖  5 | ▀▘▗╷╷╷▘╷▅▅▆▅▂╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷▝▖╷╷╷▝▖  6 | ▗▋╷▗╷╷╷╷╷╷╷╷╷╷╷╷╷╷▃▃▃▆▃▃╷╷╷╷╷╷╷╷▝╷╷╷▝▖  7 | ▁▃▅▊╷▗╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷▃▃▃▃▁▁▃╷╷▝╷╷╷▝▖  8 | ▅╼▀▀┓╷╷╷╷╷╷╷╷╷╷▀╷╷╷╷▁╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷╷▍╷╷╷▋  9 | ▘▝▅▀▆▚╷╷╷╷╷╷╷▁▂╼▘╷╷╷╷┗▍╷╷╷╷╷▝╷╷╷╷╷╷╷╷╷╷▀▃━▀▊▏  10 | ▍╷▂╼▀▅▅▖╷▁╷▂▀▃▆▆▃▖╷╷╷▗╼▀▃▃▂╷╷╷╷╷╷╷╷╷╷┎▀▅▚╷╷▝▗▖  11 | ▊▎╱┳╷▆╷▗▍╷▗▀▌╷╷╷╷╷▖╷╷▗▘╷╷╷╷▆▅▀▖╷╷╷╷╷╷▋▍╷┕╷▍╷╷┃▕  12 | ▊▚▘╷╷╷▀▀╷▕▍▃╷╷▂▋▆▖▌╷╷▍▗▗▅▖╷╷╷▋▊▎▀╷╷▀▅▌╷╷╷▖▍╷╷┃▕▎ 13 | ▌╷╷╷▝╲╷╷╷┗▃▀▅▝▝▘▀▘╷▀▝▔▃━▘▃▃▀▀▀╷╷╷╷╷╷▊▏╷╷▊▎▖╷▋▊  14 | ▀▂╷╷▝┓╷╷╷▀▀▃▀▀▅▔▁▂╷╷▝▅▀▃▃▀▅▆▘╷╷╷╷▃━▅▘╷╷╱▘▌╷╷▘  15 | ▅▃▅╷╲▗╷╷▂▃━▀─▀▀━━▅▃▂╷▔╷╷╷╷▃╷╷▗▗▆╷╷╷╷╷╷╷╷▗▘  16 | ▆▀▃▂▝▚╷╷╷╷╷╷╷╷╷╷▔▅━▀▂▃▘╷╷▀▘╷▅▅▅▀▃▂▔▂▃▘  17 | ▆▃╺▃╷╷╷╷╷╷╷╷╷╷╷╷▚▎▘╷╷▋▅╷╷╷╷╷▃▂▅  18 | ▆▀▂┻╷▂╷╷╷╷╷╷╷╷╷╷╷╷▁▂▃▃▀▅▔  19 | ▆▅▅▀▀▀▀▀▀▅▅▆  20 | -------------------------------------------------------------------------------- /shannon_entropy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* Precalculated log2 realization */ 6 | #include "log2_lshift16.h" 7 | 8 | /* Shannon full integer entropy calculation */ 9 | #define BUCKET_SIZE (1 << 8) 10 | 11 | double shannon_entropy(const uint8_t *s) 12 | { 13 | assert(s); 14 | const uint64_t count = strlen((char *) s); 15 | uint64_t entropy_sum = 0; 16 | const uint64_t entropy_max = 8 * LOG2_RET_SHIFT; 17 | 18 | uint32_t bucket[BUCKET_SIZE]; 19 | memset(&bucket, 0, sizeof(bucket)); 20 | 21 | for (uint32_t i = 0; i < count; i++) 22 | bucket[s[i]]++; 23 | 24 | for (uint32_t i = 0; i < BUCKET_SIZE; i++) { 25 | if (bucket[i]) { 26 | uint64_t p = bucket[i]; 27 | p *= LOG2_ARG_SHIFT / count; 28 | entropy_sum += -p * log2_lshift16(p); 29 | } 30 | } 31 | 32 | entropy_sum /= LOG2_ARG_SHIFT; 33 | return entropy_sum * 100.0 / entropy_max; 34 | } 35 | -------------------------------------------------------------------------------- /traces/trace-01-ops.cmd: -------------------------------------------------------------------------------- 1 | # Test of 'q_new', 'q_insert_head', and 'q_remove_head' 2 | option fail 0 3 | option malloc 0 4 | new 5 | ih gerbil 6 | ih bear 7 | ih dolphin 8 | rh dolphin 9 | rh bear 10 | rh gerbil 11 | -------------------------------------------------------------------------------- /traces/trace-02-ops.cmd: -------------------------------------------------------------------------------- 1 | # Test of 'q_new', 'q_insert_head', 'q_insert_tail', 'q_remove_head', 'q_remove_tail', and 'q_delete_mid' 2 | option fail 0 3 | option malloc 0 4 | new 5 | ih gerbil 6 | ih bear 7 | ih dolphin 8 | it meerkat 9 | it bear 10 | it gerbil 11 | it tiger 12 | rt tiger 13 | dm 14 | dm 15 | it meerkat 16 | rh dolphin 17 | rh bear 18 | rh bear 19 | rh gerbil 20 | rh meerkat 21 | -------------------------------------------------------------------------------- /traces/trace-03-ops.cmd: -------------------------------------------------------------------------------- 1 | # Test of 'q_new', 'q_insert_head', 'q_remove_head', 'q_reverse', 'q_sort', and 'q_merge' 2 | option fail 0 3 | option malloc 0 4 | new 5 | ih a 6 | ih r 7 | ih b 8 | sort 9 | new 10 | ih m 11 | ih n 12 | ih a 13 | sort 14 | new 15 | ih r 16 | ih c 17 | ih z 18 | sort 19 | merge 20 | reverse 21 | rh z 22 | rh r 23 | rh r 24 | rh n 25 | -------------------------------------------------------------------------------- /traces/trace-04-ops.cmd: -------------------------------------------------------------------------------- 1 | # Test of 'q_new', 'q_insert_tail', 'q_insert_head', 'q_remove_head', 'q_size', 'q_swap', and 'q_sort' 2 | option fail 0 3 | option malloc 0 4 | new 5 | ih gerbil 6 | ih bear 7 | ih dolphin 8 | swap 9 | rh bear 10 | ih bear 11 | size 12 | it meerkat 13 | it bear 14 | it gerbil 15 | size 16 | sort 17 | rh bear 18 | rh 19 | rh 20 | rh 21 | size 22 | -------------------------------------------------------------------------------- /traces/trace-05-ops.cmd: -------------------------------------------------------------------------------- 1 | # Test of 'q_new', 'q_free', 'q_insert_head', 'q_insert_tail', 'q_remove_head', 'q_reverse', 'q_size', 'q_swap', and 'q_sort' 2 | option fail 0 3 | option malloc 0 4 | new 5 | ih dolphin 6 | ih bear 7 | ih gerbil 8 | reverse 9 | size 10 | it meerkat 11 | it bear 12 | it gerbil 13 | size 14 | rh dolphin 15 | reverse 16 | size 17 | sort 18 | it fish 19 | swap 20 | reverse 21 | rh meerkat 22 | rh fish 23 | rh gerbil 24 | rh gerbil 25 | rh bear 26 | rh bear 27 | size 28 | free 29 | -------------------------------------------------------------------------------- /traces/trace-06-ops.cmd: -------------------------------------------------------------------------------- 1 | # Test of 'q_new', 'q_free', 'q_insert_head', 'q_insert_tail', 'q_remove_head', 'q_delete_dup', 'q_sort', 'q_descend', and 'q_reverseK' 2 | new 3 | ih RAND 4 4 | it gerbil 3 5 | it lion 2 6 | it zebra 2 7 | sort 8 | dedup 9 | free 10 | new 11 | ih a 12 | ih b 13 | ih c 14 | ih d 15 | ih a 16 | ih c 17 | descend 18 | rh d 19 | rh c 20 | rh b 21 | rh a 22 | free 23 | new 24 | ih a 3 25 | ih b 26 | ih c 27 | ih d 28 | ih e 2 29 | reverseK 3 30 | rh d 31 | rh e 32 | rh e 33 | rh a 34 | rh b 35 | rh c 36 | rh a 37 | rh a 38 | -------------------------------------------------------------------------------- /traces/trace-07-string.cmd: -------------------------------------------------------------------------------- 1 | # Test of truncated strings: 'q_new', 'q_free', 'q_insert_head', 'q_insert_tail', 'q_remove_head', 'q_reverse', and 'q_sort' 2 | option fail 0 3 | option malloc 0 4 | new 5 | ih aardvark_bear_dolphin_gerbil_jaguar 5 6 | it meerkat_panda_squirrel_vulture_wolf 5 7 | rh aardvark_bear_dolphin_gerbil_jaguar 8 | reverse 9 | rh meerkat_panda_squirrel_vulture_wolf 10 | option length 30 11 | rh meerkat_panda_squirrel_vulture 12 | reverse 13 | option length 28 14 | rh aardvark_bear_dolphin_gerbil 15 | option length 21 16 | rh aardvark_bear_dolphin 17 | reverse 18 | option length 22 19 | rh meerkat_panda_squirrel 20 | option length 7 21 | rh meerkat 22 | reverse 23 | option length 8 24 | rh aardvark 25 | option length 100 26 | rh aardvark_bear_dolphin_gerbil_jaguar 27 | reverse 28 | sort 29 | rh meerkat_panda_squirrel_vulture_wolf 30 | free 31 | quit 32 | -------------------------------------------------------------------------------- /traces/trace-08-robust.cmd: -------------------------------------------------------------------------------- 1 | # Test operations on empty queue: 'q_new', 'q_remove_head', 'q_reverse', 'q_size', and 'q_sort' 2 | option fail 10 3 | option malloc 0 4 | new 5 | rh 6 | reverse 7 | size 8 | sort 9 | -------------------------------------------------------------------------------- /traces/trace-09-robust.cmd: -------------------------------------------------------------------------------- 1 | # Test 'q_remove_head' with NULL argument: 'q_new', 'q_insert_head', and 'q_remove_head' 2 | option fail 10 3 | option malloc 0 4 | new 5 | ih bear 6 | rh 7 | -------------------------------------------------------------------------------- /traces/trace-10-robust.cmd: -------------------------------------------------------------------------------- 1 | # Test operations on NULL queue: 'q_free', 'q_insert_head', 'q_insert_tail', 'q_remove_head', 'q_reverse', 'q_size', and 'q_sort' 2 | option fail 10 3 | option malloc 0 4 | free 5 | ih bear 6 | it dolphin 7 | rh 8 | reverse 9 | size 10 | sort 11 | -------------------------------------------------------------------------------- /traces/trace-11-malloc.cmd: -------------------------------------------------------------------------------- 1 | # Test of malloc failure on 'q_insert_head': 'q_new', and 'q_insert_head' 2 | option fail 30 3 | option malloc 0 4 | new 5 | option malloc 25 6 | ih gerbil 20 7 | -------------------------------------------------------------------------------- /traces/trace-12-malloc.cmd: -------------------------------------------------------------------------------- 1 | # Test of malloc failure on 'q_insert_tail': 'q_new', 'q_insert_head', and 'q_insert_tail' 2 | option fail 50 3 | option malloc 0 4 | new 5 | ih jaguar 20 6 | option malloc 25 7 | it gerbil 20 8 | -------------------------------------------------------------------------------- /traces/trace-13-malloc.cmd: -------------------------------------------------------------------------------- 1 | # Test of malloc failure on 'q_new': 'q_new' 2 | option fail 10 3 | option malloc 50 4 | new 5 | new 6 | new 7 | new 8 | new 9 | new 10 | -------------------------------------------------------------------------------- /traces/trace-14-perf.cmd: -------------------------------------------------------------------------------- 1 | # Test performance of 'q_new', 'q_insert_head', 'q_insert_tail', 'q_reverse', and 'q_sort' 2 | option fail 0 3 | option malloc 0 4 | new 5 | ih dolphin 1000000 6 | it gerbil 1000000 7 | reverse 8 | sort 9 | -------------------------------------------------------------------------------- /traces/trace-15-perf.cmd: -------------------------------------------------------------------------------- 1 | # Test performance of 'q_sort' with random and descending orders: 'q_new', 'q_free', 'q_insert_head', 'q_sort', and 'q_reverse' 2 | # 10000: all correct sorting algorithms are expected pass 3 | # 50000: sorting algorithms with O(n^2) time complexity are expected failed 4 | # 100000: sorting algorithms with O(nlogn) time complexity are expected pass 5 | option fail 0 6 | option malloc 0 7 | new 8 | ih RAND 10000 9 | sort 10 | reverse 11 | sort 12 | free 13 | new 14 | ih RAND 50000 15 | sort 16 | reverse 17 | sort 18 | free 19 | new 20 | ih RAND 100000 21 | sort 22 | reverse 23 | sort 24 | free 25 | -------------------------------------------------------------------------------- /traces/trace-16-perf.cmd: -------------------------------------------------------------------------------- 1 | # Test performance of 'q_new', 'q_insert_head', 'q_insert_tail', and 'q_reverse' 2 | option fail 0 3 | option malloc 0 4 | new 5 | ih dolphin 1000000 6 | it gerbil 1000 7 | reverse 8 | it jaguar 1000 9 | -------------------------------------------------------------------------------- /traces/trace-17-complexity.cmd: -------------------------------------------------------------------------------- 1 | # Test if time complexity of 'q_insert_tail', 'q_insert_head', 'q_remove_tail', and 'q_remove_head' is constant 2 | option simulation 1 3 | it 4 | ih 5 | rh 6 | rt 7 | option simulation 0 8 | -------------------------------------------------------------------------------- /traces/trace-eg.cmd: -------------------------------------------------------------------------------- 1 | # Demonstration of queue testing framework 2 | # Use help command to see list of commands and options 3 | # Initial queue is NULL. 4 | show 5 | # Create empty queue 6 | new 7 | # See how long it is 8 | size 9 | # Fill it with some values. First at the head 10 | ih dolphin 11 | ih bear 12 | ih gerbil 13 | # Now at the tail 14 | it meerkat 15 | it bear 16 | # Reverse it 17 | reverse 18 | # See how long it is 19 | size 20 | # Delete queue. Goes back to a NULL queue. 21 | free 22 | # Exit program 23 | quit 24 | -------------------------------------------------------------------------------- /web.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 Thomas Brand 2 | * MIT License. 3 | */ 4 | 5 | #include /* inet_ntoa */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define LISTENQ 1024 /* second argument to listen() */ 15 | #define MAXLINE 1024 /* max length of a line */ 16 | #define BUFSIZE 1024 17 | 18 | #ifndef DEFAULT_PORT 19 | #define DEFAULT_PORT 9999 /* use this port if none given as arg to main() */ 20 | #endif 21 | 22 | #if defined(__APPLE__) || defined(__FreeBSD__) 23 | #define TCP_CORK TCP_NOPUSH 24 | #endif 25 | 26 | static int server_fd; 27 | 28 | typedef struct { 29 | int fd; /* descriptor for this buf */ 30 | int count; /* unread byte in this buf */ 31 | char *bufptr; /* next unread byte in this buf */ 32 | char buf[BUFSIZE]; /* internal buffer */ 33 | } rio_t; 34 | 35 | typedef struct { 36 | char filename[512]; 37 | off_t offset; /* for support Range */ 38 | size_t end; 39 | } http_request_t; 40 | 41 | static void rio_readinitb(rio_t *rp, int fd) 42 | { 43 | rp->fd = fd; 44 | rp->count = 0; 45 | rp->bufptr = rp->buf; 46 | } 47 | 48 | /* This is a wrapper for the Unix read() function that transfers min(n, count) 49 | * bytes from an internal buffer to a user buffer, where n is the number of 50 | * bytes requested by the user and count is the number of unread bytes in the 51 | * internal buffer. On entry, rio_read() refills the internal buffer via a call 52 | * to read() if the internal buffer is empty. 53 | */ 54 | /* $begin rio_read */ 55 | static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n) 56 | { 57 | int cnt; 58 | while (rp->count <= 0) { /* refill if buf is empty */ 59 | rp->count = read(rp->fd, rp->buf, sizeof(rp->buf)); 60 | if (rp->count < 0) { 61 | if (errno != EINTR) /* interrupted by sig handler return */ 62 | return -1; 63 | } else if (rp->count == 0) { /* EOF */ 64 | return 0; 65 | } else 66 | rp->bufptr = rp->buf; /* reset buffer ptr */ 67 | } 68 | 69 | /* Copy min(n, rp->count) bytes from internal buf to user buf */ 70 | cnt = n; 71 | if (rp->count < n) 72 | cnt = rp->count; 73 | memcpy(usrbuf, rp->bufptr, cnt); 74 | rp->bufptr += cnt; 75 | rp->count -= cnt; 76 | return cnt; 77 | } 78 | 79 | static ssize_t writen(int fd, void *usrbuf, size_t n) 80 | { 81 | size_t nleft = n; 82 | char *bufp = usrbuf; 83 | 84 | while (nleft > 0) { 85 | ssize_t nwritten = write(fd, bufp, nleft); 86 | if (nwritten <= 0) { 87 | if (errno == EINTR) { /* interrupted by sig handler return */ 88 | nwritten = 0; /* and call write() again */ 89 | } else 90 | return -1; /* errorno set by write() */ 91 | } 92 | nleft -= nwritten; 93 | bufp += nwritten; 94 | } 95 | return n; 96 | } 97 | 98 | /* robustly read a text line (buffered) */ 99 | static ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) 100 | { 101 | char c, *bufp = usrbuf; 102 | 103 | int n; 104 | for (n = 1; n < maxlen; n++) { 105 | int rc; 106 | if ((rc = rio_read(rp, &c, 1)) == 1) { 107 | *bufp++ = c; 108 | if (c == '\n') 109 | break; 110 | } else if (rc == 0) { 111 | if (n == 1) 112 | return 0; /* EOF, no data read */ 113 | break; /* EOF, some data was read */ 114 | } else 115 | return -1; /* error */ 116 | } 117 | *bufp = 0; 118 | return n; 119 | } 120 | 121 | void web_send(int out_fd, char *buf) 122 | { 123 | writen(out_fd, buf, strlen(buf)); 124 | } 125 | 126 | int web_open(int port) 127 | { 128 | int listenfd, optval = 1; 129 | struct sockaddr_in serveraddr; 130 | 131 | /* Create a socket descriptor */ 132 | if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 133 | return -1; 134 | 135 | /* Eliminates "Address already in use" error from bind. */ 136 | if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *) &optval, 137 | sizeof(int)) < 0) 138 | return -1; 139 | 140 | // 6 is TCP's protocol number 141 | // enable this, much faster : 4000 req/s -> 17000 req/s 142 | if (setsockopt(listenfd, IPPROTO_TCP, TCP_CORK, (const void *) &optval, 143 | sizeof(int)) < 0) 144 | return -1; 145 | 146 | /* Listenfd will be an endpoint for all requests to port 147 | on any IP address for this host */ 148 | memset(&serveraddr, 0, sizeof(serveraddr)); 149 | serveraddr.sin_family = AF_INET; 150 | serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 151 | serveraddr.sin_port = htons((unsigned short) port); 152 | if (bind(listenfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) 153 | return -1; 154 | 155 | /* Make it a listening socket ready to accept connection requests */ 156 | if (listen(listenfd, LISTENQ) < 0) 157 | return -1; 158 | 159 | server_fd = listenfd; 160 | 161 | return listenfd; 162 | } 163 | 164 | static void url_decode(char *src, char *dest, int max) 165 | { 166 | char *p = src; 167 | char code[3] = {0}; 168 | while (*p && --max) { 169 | if (*p == '%') { 170 | memcpy(code, ++p, 2); 171 | *dest++ = (char) strtoul(code, NULL, 16); 172 | p += 2; 173 | } else { 174 | *dest++ = *p++; 175 | } 176 | } 177 | *dest = '\0'; 178 | } 179 | 180 | static void parse_request(int fd, http_request_t *req) 181 | { 182 | rio_t rio; 183 | char buf[MAXLINE], method[MAXLINE], uri[MAXLINE]; 184 | req->offset = 0; 185 | req->end = 0; /* default */ 186 | 187 | rio_readinitb(&rio, fd); 188 | rio_readlineb(&rio, buf, MAXLINE); 189 | sscanf(buf, "%1023s %1023s", method, uri); /* version is not cared */ 190 | /* read all */ 191 | while (buf[0] != '\n' && buf[1] != '\n') { /* \n || \r\n */ 192 | rio_readlineb(&rio, buf, MAXLINE); 193 | if (buf[0] == 'R' && buf[1] == 'a' && buf[2] == 'n') { 194 | sscanf(buf, "Range: bytes=%lu-%lu", (unsigned long *) &req->offset, 195 | (unsigned long *) &req->end); 196 | /* Range: [start, end] */ 197 | if (req->end != 0) 198 | req->end++; 199 | } 200 | } 201 | char *filename = uri; 202 | if (uri[0] == '/') { 203 | filename = uri + 1; 204 | int length = strlen(filename); 205 | if (length == 0) { 206 | filename = "."; 207 | } else { 208 | for (int i = 0; i < length; ++i) { 209 | if (filename[i] == '?') { 210 | filename[i] = '\0'; 211 | break; 212 | } 213 | } 214 | } 215 | } 216 | url_decode(filename, req->filename, MAXLINE); 217 | } 218 | 219 | char *web_recv(int fd, struct sockaddr_in *clientaddr) 220 | { 221 | http_request_t req; 222 | parse_request(fd, &req); 223 | 224 | char *p = req.filename; 225 | /* Change '/' to ' ' */ 226 | while (*p) { 227 | ++p; 228 | if (*p == '/') 229 | *p = ' '; 230 | } 231 | char *ret = malloc(strlen(req.filename) + 1); 232 | strncpy(ret, req.filename, strlen(req.filename) + 1); 233 | 234 | return ret; 235 | } 236 | 237 | int web_eventmux(char *buf, size_t buflen) 238 | { 239 | fd_set listenset; 240 | 241 | FD_ZERO(&listenset); 242 | FD_SET(STDIN_FILENO, &listenset); 243 | int max_fd = STDIN_FILENO; 244 | if (server_fd > 0) { 245 | FD_SET(server_fd, &listenset); 246 | max_fd = max_fd > server_fd ? max_fd : server_fd; 247 | } 248 | int result = select(max_fd + 1, &listenset, NULL, NULL, NULL); 249 | if (result < 0) 250 | return -1; 251 | 252 | if (server_fd > 0 && FD_ISSET(server_fd, &listenset)) { 253 | FD_CLR(server_fd, &listenset); 254 | struct sockaddr_in clientaddr; 255 | socklen_t clientlen = sizeof(clientaddr); 256 | int web_connfd = 257 | accept(server_fd, (struct sockaddr *) &clientaddr, &clientlen); 258 | 259 | char *p = web_recv(web_connfd, &clientaddr); 260 | char *buffer = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"; 261 | web_send(web_connfd, buffer); 262 | strncpy(buf, p, buflen); 263 | buf[buflen] = '\0'; 264 | free(p); 265 | close(web_connfd); 266 | return strlen(buf); 267 | } 268 | 269 | FD_CLR(STDIN_FILENO, &listenset); 270 | return 0; 271 | } 272 | -------------------------------------------------------------------------------- /web.h: -------------------------------------------------------------------------------- 1 | #ifndef TINYWEB_H 2 | #define TINYWEB_H 3 | 4 | #include 5 | 6 | int web_open(int port); 7 | 8 | char *web_recv(int fd, struct sockaddr_in *clientaddr); 9 | 10 | void web_send(int out_fd, char *buffer); 11 | 12 | int web_eventmux(char *buf, size_t buflen); 13 | 14 | #endif 15 | --------------------------------------------------------------------------------