diff --git a/.agents/rules/coding-style.md b/.agents/skills/coding-style/SKILL.md similarity index 74% rename from .agents/rules/coding-style.md rename to .agents/skills/coding-style/SKILL.md index 28d6a98..2999406 100644 --- a/.agents/rules/coding-style.md +++ b/.agents/skills/coding-style/SKILL.md @@ -1,10 +1,12 @@ --- -trigger: model_decision -description: After generating or editing code +name: coding-style +description: Coding style and formatting rules for F3. Use when writing, modifying, refactoring, or reviewing C code in the F3 project. --- # F3 Coding Style +When generating, modifying, or reviewing C code in the F3 project, follow these coding style guidelines and conventions. + ## 1. General Principles - **Language**: C (C17 standard as specified in the Makefile). - **Line Length**: Maximum 80 columns. The only exception is for literal strings to facilitate grep-ability of output messages. @@ -12,12 +14,12 @@ description: After generating or editing code ## 2. Formatting and Indentation - **Indentation**: Uses **Tabs** for indentation. -- **Braces**: K&R style. Opening braces `{` are on the same line as the statement (`if`, `while`, `for`, `switch`, `struct`, and function definitions). +- **Braces**: K&R style. Opening braces `{` are on the same line for control statements (`if`, `while`, `for`, `switch`) and `struct` definitions, but on a new line at column 0 for function definitions. - **Whitespace**: - - Space after keywords (`if`, `while`, `for`, `switch`, `do`). - - No space between function name and the opening parenthesis `(`. - - Pointers: `char *ptr` (space before the asterisk, not after). - - Alignment: Struct member assignments and function parameters are often aligned using tabs when spanning multiple lines. + - Space after keywords (`if`, `while`, `for`, `switch`, `do`). + - No space between function name and the opening parenthesis `(`. + - Pointers: `char *ptr` (space before the asterisk, not after). + - Alignment: Struct member assignments and function parameters are often aligned using tabs when spanning multiple lines. ## 3. Naming Conventions - **Files**: Lowercase with `snake_case` (e.g., `f3read.c`, `libutils.h`). @@ -48,4 +50,4 @@ description: After generating or editing code ## 8. Comments - **Style**: Uses C-style comments `/* ... */`. -- **Placement**: Comments are placed above the code they describe or as trailing comments aligned with tabs. \ No newline at end of file +- **Placement**: Comments are placed above the code they describe or as trailing comments aligned with tabs. diff --git a/.agents/skills/commit-message/SKILL.md b/.agents/skills/commit-message/SKILL.md new file mode 100644 index 0000000..70e4541 --- /dev/null +++ b/.agents/skills/commit-message/SKILL.md @@ -0,0 +1,148 @@ +--- +name: commit-message +description: Rules and guidelines for generating Git commit messages in F3. Use when writing, formatting, or reviewing Git commit messages for this repository. +--- + +All commit messages in F3 must follow the project's established conventions. + +```text +: + + + + +``` + +1. **Subsystem Prefix (``)**: + - Matches the affected tool, library, directory, or configuration in lowercase. + - **Applications / Binaries**: + Use the binary name (e.g. `f3write`, `f3read`, `f3probe`, `f3brew`, `f3fix`). + When a change touches multiple related tools, join them with a slash (e.g. `f3write/f3read`, `f3write/f3read/f3brew`, `f3brew/f3probe`). + - **Libraries**: + Use the library name (e.g. `libflow`, `libprobe`, `libutils`, `libdevs`, `libfile`). + - **Build and CI**: + Use `Makefile`, `GitHub Actions` (or `GitHub`). + - **Documentation and Metadata**: + Use `README`, `doc`, `man` (or specific manual page, e.g. `f3read.1`), `changelog`, `.gitignore`. + - **Developer Tools and Workspace Configuration**: + Use `agents`, `scripts`, `zed`. + - **Project-Wide Changes**: + Repository-wide restructurings or standard bumps may occasionally omit the subsystem prefix (e.g. `Move codebase from C99 to C17`, `Bump version to 10.0`, `Reorganize codebase with new directories`), but scoped changes should always use the subsystem prefix. + +2. **Subject Line**: + - **Format**: `: ` (separated by a colon and a single space). + - **Casing**: Start the summary with a lowercase letter (e.g. `libflow: drop a magic number`, `f3write: track per-file min/max speeds`), unless the first word is a case-sensitive code identifier, macro, or proper noun (e.g. `README: improve instructions for FreeBSD`, `libutils: add generic macro MIN()`). + - **Mood**: Use the imperative mood (e.g. `add`, `fix`, `drop`, `make`, `avoid`, `replace`, `adopt`, `employ`, `update`, `simplify`, `standardize`, `rename`, `convert`, `tighten`, `generalize`, `remove`, not `added`, `fixes`, `updating`). + - **Punctuation**: Do not end the subject line with a period. + - **Length**: Keep the subject line concise (under 72 characters, ideally 50–60 characters). + - **Function and Symbol References**: When mentioning functions or commands in the subject, adhere to the reference rules below (e.g. `f3brew: drop assert() in validate_block()`, `libprobe: make find_first_bad_block() report more information`). + +3. **Function, Command, and Symbol References**: + - **Internal / Project Functions**: + Always append empty parentheses `()` to function names and function-like macros: + - Examples: `init_flow()`, `validate_block()`, `calc_avg_speed()`, `find_first_bad_block()`, `end_measurement()`, `assert()`, `MIN()`, `DIM()`. + - **Standard Library Functions, System Calls, and System Commands**: + Always append the manual section number in parentheses after the name of standard library functions, POSIX functions, system calls, and system administration commands: + - **Section 1 (User Commands)**: `ls(1)`, `grep(1)` + - **Section 2 (System Calls)**: `clock_gettime(2)`, `fdatasync(2)`, `read(2)`, `write(2)`, `getrandom(2)` + - **Section 3 (C / Library Functions)**: `free(3)`, `aligned_alloc(3)`, `snprintf(3)`, `perror(3)` + - **Section 8 (System Administration Commands)**: `losetup(8)`, `mount(8)` + - **Types, Structs, and Macros**: + Mention types with their C specifiers (e.g. `struct flow`, `struct perf_device`, `struct block_stats`, `uint64_t`), enum values in lowercase with prefix (e.g. `bs_changed`, `bs_good`, `bs_overwritten`), and macros/constants in uppercase (e.g. `SECTOR_ORDER`, `MEGABYTE_ORDER`, `UNUSED()`, `DIM()`, `FW_STEADY`). + - **Parameters and Flags**: + Mention CLI flags as written (e.g. `--max-write-rate`, `--verbose`, `--destructive`, `--fix-cmd`). Mention function parameters by name (e.g. `parameter measurement_boundary`, `parameter processed_blocks`). + - **Plain Text Style**: + Do not use Markdown backticks in commit messages. Write code identifiers, function names, file names, compiler options (e.g. `-O2`), and directives (e.g. `#include `) as plain text. Single quotes (`'...'`) or double quotes (`"..."`) may be used for quoting exact phrases or compiler messages when clarity is needed. + +4. **Message Body**: + - **Separation**: Separate the subject from the body with a blank line. + - **Line Wrapping**: Hard-wrap all body lines at 72 characters. + - **Structure**: + - State the problem, limitation, or background context first (e.g. what fails, what distortion happens, or why the current behavior is inadequate). + - State the solution and technical rationale (what changed and why). + - **Lists**: When breaking down multiple steps or changes, use numbered lists (`1. ...`, `2. ...`) or bullet points (`- ...`). + - **Diagnostics**: Compiler warnings, error messages, and log snippets may be included verbatim in the body to document the issue clearly. + +5. **Issue References**: + - **Closing Issues**: When closing an issue, use the project's standard formula at the end of the body (separated by a blank line): + ```text + This commit closes # + ``` + or: + ```text + This patch closes # + ``` + - **Closing Multiple Issues**: + ```text + This commit closes #, closes # + ``` + - **Non-Closing References**: When referencing an issue without closing it: + ```text + See issue # for an example. + ``` + or: + ```text + This patch addresses issue #. + ``` + +### Example 1: Function name with `()` and parameter rationale +```text +libflow: add parameter measurement_boundary to end_measurement() + +Introduce parameter measurement_boundary to end_measurement(), +pass true in f3write.c and f3read.c since they make measurements +on files (i.e., a file is a measurement boundary), and pass +false for everyone else. + +Reaching a boundary forces any leftover processed_blocks and +acc_delay_ns to be committed to the global statistics. +This prevents measurement data from bleeding across boundaries. +``` + +### Example 2: System call with section number and issue resolution +```text +f3write: gracefully handle failures of fdatasync(2) + +According to issue #102, calls to fdatasync(2) might take +a long time (e.g. 3s or 4s) and distort the measurement of +the average write speed. This patch addresses it by only updating +the measurements when delays are within a tolerance. + +This patch closes #102 +``` + +### Example 3: CLI flag with motivation and issue reference +```text +f3brew: add flag --fix-cmd + +When flag --fix-cmd is passed, f3brew shows how to call f3fix on +the largest good region identified. +``` + +### Example 4: Compiler warning / diagnostics verbatim in body +```text +f3probe: avoid compiler warning + +When using -O2, GCC was issuing the following warning: + +f3probe.c: In function 'main': +f3probe.c:446:13: warning: 'sdev' may be used uninitialized in this function [-Wmaybe-uninitialized] + sdev_flush(sdev); + ^ + +GCC could not deduce that args->save being true implied +sdev was not NULL. + +This patch addresses issue #34. +``` + +### Example 5: Multi-tool change with numbered list +```text +f3write/f3read/f3brew: standardize report of I/O speeds + +This commit makes f3write, f3read, and f3brew report I/O speeds +analogously to f3probe: +1. Emphasizing that these are sequential measurements. +2. Including the number of blocks and time measured. +3. Using "write" and "read" instead of "writing" and "reading". +``` diff --git a/.zed/debug.json b/.zed/debug.json new file mode 100644 index 0000000..02e4412 --- /dev/null +++ b/.zed/debug.json @@ -0,0 +1,68 @@ +[ + // Use adapter "CodeLLDB" because "GDB" is not working on gdb versions + // before gdb 17. + // See https://github.com/zed-industries/zed/issues/41753 for more details. + { + "label": "f3brew test", + "adapter": "CodeLLDB", + "request": "launch", + "program": "$ZED_WORKTREE_ROOT/build/f3brew", + "args": ["--debug", "--fix-cmd", "test"] + }, + { + "label": "f3probe unit-test", + "adapter": "CodeLLDB", + "request": "launch", + "program": "$ZED_WORKTREE_ROOT/build/f3probe", + "args": ["--debug-unit-test", "test"] + }, + { + "label": "f3probe simple test", + "adapter": "CodeLLDB", + "request": "launch", + "program": "$ZED_WORKTREE_ROOT/build/f3probe", + "args": ["--debug", "--destructive", "--verbose", "test"] + }, + { + "label": "f3probe test", + "adapter": "CodeLLDB", + "request": "launch", + "program": "$ZED_WORKTREE_ROOT/build/f3probe", + "args": [ + "--debug-real-size=1G", + "--debug-fake-size=1T", + "--debug-wrap=40", + "--debug-cache-order=21", + "--debug-strict-cache", + "--destructive", + "--verbose", + "test" + ] + // Uncomment the following line to run under sudo: + // "gdb_path": "$ZED_WORKTREE_ROOT/.zed/gdb-sudo.sh" + }, + { + "label": "f3write a", + "adapter": "CodeLLDB", + "request": "launch", + "build": { + "command": "mkdir", + // -p creates the directory if needed and avoids failing if it already exists. + "args": ["-p", "a"] + }, + "program": "$ZED_WORKTREE_ROOT/build/f3write", + "args": ["--end-at=5", "a"] + }, + { + "label": "f3read a", + "adapter": "CodeLLDB", + "request": "launch", + "build": { + "command": "mkdir", + // -p creates the directory if needed and avoids failing if it already exists. + "args": ["-p", "a"] + }, + "program": "$ZED_WORKTREE_ROOT/build/f3read", + "args": ["a"] + } +] diff --git a/.zed/gdb-sudo.sh b/.zed/gdb-sudo.sh new file mode 100755 index 0000000..6a7e5f5 --- /dev/null +++ b/.zed/gdb-sudo.sh @@ -0,0 +1,3 @@ +#!/bin/bash +#sudo /usr/bin/gdb "$@" +pkexec /usr/bin/gdb "$@" diff --git a/.zed/tasks.json b/.zed/tasks.json new file mode 100644 index 0000000..b1099c4 --- /dev/null +++ b/.zed/tasks.json @@ -0,0 +1,13 @@ +[ + { + "label": "F3: Build", + "command": "make", + "args": ["all", "extra"], + "save": "all" + }, + { + "label": "F3: Clean", + "command": "make", + "args": ["clean"] + } +] diff --git a/Makefile b/Makefile index 829eb5a..b213557 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC ?= gcc -CFLAGS += -std=c17 -Wall -Wextra -pedantic -MMD -ggdb +CFLAGS += -std=c17 -Wall -Wextra -pedantic -Wdeclaration-after-statement -MMD -ggdb BUILD_DIR = build SRC_DIR = src diff --git a/src/f3fix.c b/src/f3fix.c index 38f3dac..62cf19a 100644 --- a/src/f3fix.c +++ b/src/f3fix.c @@ -238,7 +238,7 @@ static int fix_disk(PedDevice *dev, PedDiskType *type, return ret; } -int main (int argc, char *argv[]) +int main(int argc, char *argv[]) { struct args args = { /* Defaults. */ diff --git a/src/f3read.c b/src/f3read.c index 63ca104..0320f69 100644 --- a/src/f3read.c +++ b/src/f3read.c @@ -314,6 +314,8 @@ static void validate_file(struct flow *fw, struct dynamic_buffer *dbuf, } else if (file_time_ns > 0) { const uint64_t blocks_read = stats->bytes_read >> block_order; + const char *unit; + assert((stats->bytes_read & (block_size - 1)) == 0); if (file_tot_blocks == blocks_read && file_tot_time_ns > 0) { @@ -321,7 +323,7 @@ static void validate_file(struct flow *fw, struct dynamic_buffer *dbuf, } file_avg_speed = calc_avg_speed(block_order, blocks_read, file_time_ns); - const char *unit = adjust_unit(&file_avg_speed); + unit = adjust_unit(&file_avg_speed); printf(" Avg: %.2f %s/s", file_avg_speed, unit); } } diff --git a/src/f3write.c b/src/f3write.c index bc06ed2..facd8cc 100644 --- a/src/f3write.c +++ b/src/f3write.c @@ -280,13 +280,15 @@ static int create_and_fill_file(struct flow *fw, struct dynamic_buffer *dbuf, } else if (file_time_ns > 0) { const uint64_t blocks_written = total_file_blocks - remaining_blocks; + const char *unit; + if (file_tot_blocks == blocks_written && file_tot_time_ns > 0) { file_time_ns = file_tot_time_ns; } file_avg_speed = calc_avg_speed(block_order, blocks_written, file_time_ns); - const char *unit = adjust_unit(&file_avg_speed); + unit = adjust_unit(&file_avg_speed); printf("OK! Avg: %.2f %s/s\n", file_avg_speed, unit); } else { diff --git a/src/libutils.h b/src/libutils.h index 938e241..e09333a 100644 --- a/src/libutils.h +++ b/src/libutils.h @@ -64,10 +64,9 @@ int nsec_to_str(uint64_t nsec, char *str); /* * The functions align_head() and align_mem() are used to align pointers. * - * The following example allocates two block on stack and makes sure that + * The following example allocates two blocks on stack and makes sure that * the blocks are aligned with the block size. * - * // The number 2 below means two blocks. * char stack[align_head(block_order) + (2 << block_order)]; * char *stamp_blk, *probe_blk; * stamp_blk = align_mem(stack, block_order);