Skip to content

Modernize flock: CI, bug fixes, comprehensive test suite, and refactoring#43

Merged
josephholsten merged 5 commits intodiscoteq:masterfrom
TwilightCoders:master
Apr 17, 2026
Merged

Modernize flock: CI, bug fixes, comprehensive test suite, and refactoring#43
josephholsten merged 5 commits intodiscoteq:masterfrom
TwilightCoders:master

Conversation

@voltechs
Copy link
Copy Markdown
Contributor

@voltechs voltechs commented Mar 14, 2026

Summary

  • GitHub Actions CI with full test matrix: Ubuntu (GCC + Clang), macOS, and FreeBSD
  • Comprehensive test suite expanded from ~30 timing-sensitive tests to 77 robust tests covering all flags, lock semantics, edge cases, and option combinations
  • Bug fixes for GCC compilation errors, incorrect verbose timing, typos, and unvalidated user input
  • Build improvements making ronn (man page generator) optional so make && make install works without extra dependencies

Bug fixes

  • -h/--help now exits 0 (was EX_USAGE/64) — fixes Add -h/--help option #15
  • Fix %1u format specifier for suseconds_t causing GCC -Werror build failure — fixes Compilation error with GCC #33 (supersedes flock.c: printf: ensure format & param types match #38)
  • Fix verbose timing underflow: elapsed time was only subtracting tv_usec, producing wrong/negative values when lock acquisition crossed a second boundary
  • Fix typo in error message: "waidpid" → "waitpid"
  • Validate -E conflict exit code (was atoi, now rejects non-numeric and out-of-range values)
  • Validate file descriptor argument in FD mode (previously flock abc silently locked stdin)

Already fixed (can be closed)

Test suite

  • Replaced exact timing comparisons ([ "$result" = 0.05 ]) with threshold-based helpers (was_blocked/was_immediate) — fixes convert tests to not be time sensitive #5, addresses make check failures (and what they mean) #42
  • Added setup/teardown with per-test lockfiles and background process cleanup
  • New coverage: -e flag, --nb alias, -c shell features (pipes, variables, subst), child exit propagation, -E with -n/-w, -o/--close, --verbose, option combinations and flag precedence, file with spaces, nonexistent paths

CI & build

Documentation

Code quality

  • Normalized indentation to tabs throughout flock.c
  • Removed unnecessary |= for fl.l_type assignment in fcntl fallback
  • Removed extraneous blank lines and fixed double-tab indentation

Summary by CodeRabbit

  • New Features

    • Added -c/--command, -h/--help, and -V/--version options; improved verbose timing output
    • Added CI that builds/tests across Linux, macOS, FreeBSD and generates man pages
  • Bug Fixes

    • Fixed lock-type handling and corrected error messages
    • Tightened validation for exit-code and descriptor arguments
  • Documentation

    • Expanded manpage OPTIONS and reordered verbosity docs
    • README notes that bats-core is required to run the test suite
  • Tests

    • Significantly expanded and made tests timing-robust; improved setup/teardown and coverage

@voltechs
Copy link
Copy Markdown
Contributor Author

Hi @josephholsten, I did a thing. Hope you find this helpful in modernizing flock. Thanks for writing it. Would love to see a version bump on homebrew in the near future.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a multi-platform GitHub Actions CI workflow, augments Makefile/README doc build behavior, expands the manpage OPTIONS, tightens argument parsing and usage handling in src/flock.c, and greatly expands/rewrites the Bats test suite for timing, concurrency, and new CLI behaviors.

Changes

Cohort / File(s) Summary
CI / Workflows
.github/workflows/ci.yml
Adds a GitHub Actions CI workflow running on push/PR to master with jobs for Linux (gcc/clang matrix), macOS, FreeBSD (via VM), and a docs job that generates man pages; uploads test logs and man-page artifacts on failure/success.
Build system & docs
Makefile.am, README.md
Makefile rules now handle missing ronn conditionally and change doc target to fail unless ronn is present; README documents bats-core requirement and make check.
Man page
man/flock.1.ronn
OPTIONS expanded/reordered: adds -c, --command, -h, --help, -V, --version; revises --verbose description and reorganizes options list.
Core implementation
src/flock.c
Fixes lock-type assignment to overwrite fl.l_type (not OR); changes usage() signature to usage(int) with stdout/stderr selection; replaces atoi/strtol usages with validated strtol checks (range and extra-char validation); adjusts timing/reporting and fixes waitpid error text.
Tests
t/default.bats, t/smartos.bats
Major test-suite refactor: per-test setup/teardown, timing helpers (get_elapsed, was_blocked, etc.), lock orchestration helpers, many new tests covering CLI options, command execution, exit-code semantics, fd-based behaviors, filesystem edge cases, and relaxed threshold-based timing assertions for reliability.

Sequence Diagram(s)

sequenceDiagram
    participant GH as GitHub Actions
    participant Runner as Runner (ubuntu/macos/FreeBSD VM)
    participant Checkout as Checkout
    participant Tooling as Build Tools (autoconf/automake/bats/ronn)
    participant Build as Build/Configure
    participant Tests as Test Suite
    participant Artifacts as Artifact Storage

    GH->>Runner: trigger on push/pull_request to master
    Runner->>Checkout: checkout repo
    Runner->>Tooling: install needed tools/packages
    Tooling-->>Runner: tools available
    Runner->>Build: autoreconf && ./configure (matrix CC)
    Build->>Build: make
    Build->>Tests: make check
    alt tests pass
        Tests-->>Runner: exit 0
        Runner->>GH: report success
    else tests fail
        Tests-->>Runner: exit non-zero
        Runner->>Artifacts: upload test-suite.log and t/*.log
        Runner->>GH: report failure
    end
    Runner->>Artifacts: optionally upload man pages (docs job)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
Tiny paws tap on a key,
CI wakes and runs with glee,
Locks and tests in tidy rows,
Docs that for the future glow.
A rabbit cheers: "Merge it — go!" 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main scope of the PR: it introduces CI infrastructure, includes bug fixes, expands the test suite, and performs code refactoring.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
.github/workflows/ci.yml (1)

18-27: Keep one ronn-enabled path in CI.

Every job currently exercises the “ronn missing” fallback, so a broken man/flock.1.ronn or stale checked-in manpage can still merge green. One small docs leg that installs ronn and runs make doc would preserve the optional-build behavior without dropping coverage.

Also applies to: 41-50, 64-74

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 18 - 27, Add a CI job (or a matrix
variant) that installs ronn and runs the documentation build so we exercise the
ronn-enabled path; modify the existing "Install dependencies" step used by the
job(s) that run autoreconf/configure/make to also install ronn (e.g., apt-get
install ronn or gem install ronn) for one job, then add a "Build docs" step that
runs make doc (or runs `make doc` after the "Build" step) to ensure
man/flock.1.ronn is processed; reference the existing steps named "Install
dependencies", "Bootstrap", "Configure", "Build" and add the new installation
and "Build docs" step only to one CI variant so other jobs continue exercising
the ronn-missing fallback.
Makefile.am (1)

59-65: Don't satisfy man/flock.1 with an empty placeholder.

When ronn is absent and no pre-built page exists, touch $@ creates a zero-byte manual that make install can ship as if it were real. Failing this branch, or explicitly skipping manpage install, is safer than silently publishing blank docs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile.am` around lines 59 - 65, The current Makefile.am rule that tries to
build man/flock.1 uses touch $@ when ronn is missing, producing an empty man
page; replace the touch fallback with a failing exit so builds don't install
blank docs: inside the recipe that references $(RONN), $(RONN_FLAGS), and
man/flock.1.ronn, change the branches that currently do "touch $@" to instead
print a clear error to stderr (mention ronn missing and man/flock.1 not
available) and exit with non‑zero (e.g., echo "Error: ronn not found and no
pre-built man/flock.1" >&2; exit 1) so the build fails rather than creating an
empty $@. Ensure both occurrences of touch $@ in that conditional are updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 23-25: Update the macOS MacPorts section of README.md to either
prepend sudo to the installation command so it reads "sudo port install flock"
or add a short note explaining why sudo is not required; modify the MacPorts
block where "port install flock" appears to use the sudo-prefixed command (or
add the clarification text) to match the Homebrew example's behavior and avoid
permission surprises for users.

In `@src/flock.c`:
- Around line 214-219: The parsing code using strtol in the option handlers
(e.g., the case 'E' branch that sets status_time_conflict and the
file-descriptor parser at lines ~294-298) must reject empty/non-numeric inputs
and detect overflow: ensure endptr != optarg to confirm at least one digit was
parsed, check errno for ERANGE after strtol, and for values stored into int
verify the long is within INT_MIN..INT_MAX before narrowing; for the exit-code
path also enforce the 0..255 range after these checks. Update both the 'E'
handler and the FD parsing code to perform these validations and return the
existing errx(EX_USAGE, ...) on failure.

In `@t/default.bats`:
- Around line 350-354: The test "-c must come after lockfile" currently uses a
vacuous assertion `[ "$status" -ne 0 ] || true` which always succeeds; change it
to assert failure properly by replacing that line with an explicit non-zero
check such as `[ "$status" -ne 0 ]` (or an if that calls fail when `"$status"
-eq 0`) so the invocation `run ${FLOCK} -c "echo 1" ${LOCKFILE}` actually must
fail to pass the test; reference `${FLOCK}`, `${LOCKFILE}`, and the test name
"-c must come after lockfile" to locate and update the assertion.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 18-27: Add a CI job (or a matrix variant) that installs ronn and
runs the documentation build so we exercise the ronn-enabled path; modify the
existing "Install dependencies" step used by the job(s) that run
autoreconf/configure/make to also install ronn (e.g., apt-get install ronn or
gem install ronn) for one job, then add a "Build docs" step that runs make doc
(or runs `make doc` after the "Build" step) to ensure man/flock.1.ronn is
processed; reference the existing steps named "Install dependencies",
"Bootstrap", "Configure", "Build" and add the new installation and "Build docs"
step only to one CI variant so other jobs continue exercising the ronn-missing
fallback.

In `@Makefile.am`:
- Around line 59-65: The current Makefile.am rule that tries to build
man/flock.1 uses touch $@ when ronn is missing, producing an empty man page;
replace the touch fallback with a failing exit so builds don't install blank
docs: inside the recipe that references $(RONN), $(RONN_FLAGS), and
man/flock.1.ronn, change the branches that currently do "touch $@" to instead
print a clear error to stderr (mention ronn missing and man/flock.1 not
available) and exit with non‑zero (e.g., echo "Error: ronn not found and no
pre-built man/flock.1" >&2; exit 1) so the build fails rather than creating an
empty $@. Ensure both occurrences of touch $@ in that conditional are updated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ce9bb1f-21ba-4930-aae6-7623c51c9c85

📥 Commits

Reviewing files that changed from the base of the PR and between b9f6170 and f6f1d14.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • Makefile.am
  • README.md
  • man/flock.1.ronn
  • src/flock.c
  • t/default.bats
  • t/smartos.bats

Comment thread README.md
Comment thread src/flock.c
Comment thread t/default.bats
@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Mar 21, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (1)

Grey Divider


Action required

1. README omits Bats prerequisite📎 Requirement gap ⚙ Maintainability
Description
The project’s test runner uses .bats files, but documentation does not state that
bats/bats-core is required (and configure does not detect it), so make check can fail
without a clear prerequisite. This violates the requirement to document or detect the Bats
dependency for running tests.
Code

README.md[R23-29]

+On macOS using MacPorts:
+
+    port install flock
+
From source:
FLOCK_VERSION=0.4.0
Evidence
Compliance ID 5 requires either documenting Bats as a prerequisite for make check or detecting it
at configure time. The README installation instructions do not mention Bats, configure.ac contains
no Bats detection, and the build system defines the test suite as .bats tests (implying the
dependency).

Declare or detect bats prerequisite for make check
README.md[17-36]
Makefile.am[88-91]
configure.ac[1-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`make check` depends on Bats (`.bats` test files), but the repo neither documents this prerequisite nor detects it in `configure`, so users can hit confusing test failures.
## Issue Context
The test suite is configured via Automake `TESTS` and includes `t/default.bats`/`t/smartos.bats`, which requires `bats`/`bats-core` to be installed.
## Fix Focus Areas
- README.md[17-36]
- configure.ac[1-56]
- Makefile.am[88-91]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Empty man page installed 🐞 Bug ≡ Correctness
Description
man/flock.1 is created via touch when ronn is missing and no prebuilt man page exists,
producing an empty man page. Because man_MANS installs this file, users can end up with a blank
manual silently.
Code

Makefile.am[R59-66]

+	@if command -v $(RONN) >/dev/null 2>&1; then \
+		$(RONN) $(RONN_FLAGS) -r man/flock.1.ronn; \
+	elif [ -f $@ ]; then \
+		touch $@; \
+	else \
+		echo "Warning: ronn not found and no pre-built man page available" >&2; \
+		touch $@; \
+	fi
Evidence
The man/flock.1 rule explicitly touches the target even in the “ronn not found and no pre-built
man page available” branch, which will create an empty file; man_MANS causes that file to be
installed.

Makefile.am[57-66]
Makefile.am[57-58]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The build currently creates an empty `man/flock.1` (via `touch`) when `ronn` is unavailable and there is no prebuilt man page. Since `man_MANS` installs `man/flock.1`, this can silently install a blank manual.
## Issue Context
Goal is to make `ronn` optional without shipping broken docs.
## Fix Focus Areas
- Makefile.am[57-66]
## Suggested approach
- Prefer a configure-time check (e.g., `AC_PATH_PROG([RONN], [ronn], [no])` + `AM_CONDITIONAL([HAVE_RONN], ...)`) and only set `man_MANS` / `doc` targets when `ronn` is available OR a real prebuilt `man/flock.1` is present.
- If you must keep a fallback, generate a minimal *non-empty* stub man page that clearly states documentation wasn’t built (instead of `touch`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. HTML man target missing🐞 Bug ≡ Correctness
Description
When ronn is missing, the man/flock.1.html rule exits successfully but does not create
man/flock.1.html. This leaves html-man/doc targets operating on a missing file (e.g.,
html-man will attempt to open a nonexistent HTML page).
Code

Makefile.am[R68-72]

+	@if command -v $(RONN) >/dev/null 2>&1; then \
+		$(RONN) $(RONN_FLAGS) -5 man/flock.1.ronn; \
+	else \
+		echo "Warning: ronn not found, skipping HTML man page" >&2; \
+	fi
Evidence
The man/flock.1.html recipe’s else branch only prints a warning and does not create $@. Yet
html-man depends on man/flock.1.html and runs $(OPEN) man/flock.1.html, which will
fail/mislead if the file was never generated.

Makefile.am[67-76]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`man/flock.1.html` is not created when `ronn` is missing, but downstream targets still depend on it and try to use it.
## Issue Context
The PR intends to make `ronn` optional; however, optional should not mean producing missing build artifacts while reporting success.
## Fix Focus Areas
- Makefile.am[67-76]
## Suggested approach
- Use the same conditional strategy as for `man/flock.1`: only define `html-man` / `doc` dependencies on the HTML output when `ronn` is available (or when a real prebuilt `man/flock.1.html` exists).
- Alternatively, if HTML is optional, make `man/flock.1.html` rule either (a) create a clearly-labeled stub HTML file, or (b) fail when the HTML target is explicitly requested (so `make html-man` is honest).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. hold_lock() relies on sleep 📎 Requirement gap ≡ Correctness
Description
The test suite still uses elapsed-time thresholds and fixed sleeps (e.g., sleep 0.2) to coordinate
lock acquisition, which can be flaky on slow or heavily loaded machines. This conflicts with the
requirement to make tests non-time-sensitive.
Code

t/default.bats[R17-36]

+# Helper: check whether a command was blocked (waited) or ran immediately.
+# Uses awk to compare elapsed time against a threshold.
+was_blocked() {
+	local elapsed="$1"
+	# "blocked" means elapsed >= 0.3 seconds
+	awk "BEGIN { exit ($elapsed >= 0.3) ? 0 : 1 }"
+}
+
+was_immediate() {
+	local elapsed="$1"
+	# "immediate" means elapsed < 0.3 seconds
+	awk "BEGIN { exit ($elapsed < 0.3) ? 0 : 1 }"
+}
+
+# Hold a lock in the background and wait for it to be acquired
+hold_lock() {
+	local flags="${1:-}"
+	${FLOCK} ${flags} ${LOCKFILE} sleep 2 &
+	sleep 0.2  # ensure background process acquires the lock
+}
Evidence
Compliance ID 6 requires avoiding timing-dependent assertions; however, the new helpers determine
correctness using measured elapsed time (time -p + awk) and fixed sleeps to wait for state
transitions, which remain timing-sensitive.

Make tests non-time-sensitive (robust on slow/small VMs)
t/default.bats[17-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Tests currently infer blocking/immediacy using elapsed-time thresholds and rely on fixed sleeps (e.g., `sleep 0.2`) to ensure a background lock holder has acquired the lock. This can lead to flaky results on slow/small VMs.
## Issue Context
The helpers `was_blocked()`/`was_immediate()` compare `time -p` output against a threshold, and `hold_lock()` uses a fixed delay to assume the lock is held.
## Fix Focus Areas
- t/default.bats[17-40]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Missing strtol digit check🐞 Bug ≡ Correctness
Description
The new strtol validations for -E and fd mode accept inputs with no digits (e.g., an empty
string) as 0 because they only check *endptr and range. fd parsing also casts long to int
without checking fdval > INT_MAX, which can truncate large values to an unexpected fd.
Code

src/flock.c[R214-221]

+		case 'E': {
+			char *endptr;
+			long val = strtol(optarg, &endptr, 10);
+			if (*endptr != '\0' || val < 0 || val > 255)
+				errx(EX_USAGE, "conflict exit code must be 0-255, was '%s'", optarg);
+			status_time_conflict = (int)val;
break;
+		}
Evidence
Both parsers use strtol(..., &endptr, 10) but do not reject the “no conversion performed” case
(endptr == optarg), so empty strings pass. The fd path then assigns fd = (int)fdval with no
upper-bound check, allowing truncation.

src/flock.c[214-221]
src/flock.c[292-299]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`strtol` parsing for `-E` and fd mode does not reject the case where no digits were parsed (e.g., empty string), and fd parsing can truncate large `long` values when casting to `int`.
## Issue Context
The PR aims to improve input validation; these are remaining edge cases in the updated `strtol` paths.
## Fix Focus Areas
- src/flock.c[214-221]
- src/flock.c[292-299]
## Suggested approach
- After `strtol`, validate:
- `endptr != optarg` (at least one digit consumed)
- `*endptr == &#x27;\0&#x27;` (no trailing junk)
- `errno != ERANGE` (set `errno=0` before calling)
- For fd parsing, additionally enforce `0 &lt;= fdval &amp;&amp; fdval &lt;= INT_MAX` (and optionally `fdval &lt; getdtablesize()` where available).
- Apply the same pattern consistently to both `-E` and fd parsing paths.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread README.md
Comment thread Makefile.am
Comment on lines +59 to +66
@if command -v $(RONN) >/dev/null 2>&1; then \
$(RONN) $(RONN_FLAGS) -r man/flock.1.ronn; \
elif [ -f $@ ]; then \
touch $@; \
else \
echo "Warning: ronn not found and no pre-built man page available" >&2; \
touch $@; \
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Empty man page installed 🐞 Bug ✓ Correctness

man/flock.1 is created via touch when ronn is missing and no prebuilt man page exists,
producing an empty man page. Because man_MANS installs this file, users can end up with a blank
manual silently.
Agent Prompt
## Issue description
The build currently creates an empty `man/flock.1` (via `touch`) when `ronn` is unavailable and there is no prebuilt man page. Since `man_MANS` installs `man/flock.1`, this can silently install a blank manual.

## Issue Context
Goal is to make `ronn` optional without shipping broken docs.

## Fix Focus Areas
- Makefile.am[57-66]

## Suggested approach
- Prefer a configure-time check (e.g., `AC_PATH_PROG([RONN], [ronn], [no])` + `AM_CONDITIONAL([HAVE_RONN], ...)`) and only set `man_MANS` / `doc` targets when `ronn` is available OR a real prebuilt `man/flock.1` is present.
- If you must keep a fallback, generate a minimal *non-empty* stub man page that clearly states documentation wasn’t built (instead of `touch`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread Makefile.am
@josephholsten
Copy link
Copy Markdown
Contributor

hey there, it's been a while since I've worked on this so I've recruited some automatic code review.

I'm only concerned about the strtol() check and the || true swallowing a bats assertion.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/flock.c (1)

215-220: ⚠️ Potential issue | 🟠 Major

Empty arguments still slip through both strtol parsers.

Because Lines 219 and 299 only reject trailing characters, strtol("", &endptr, 10) still gets accepted here: -E '' becomes conflict code 0, and flock '' is treated as fd 0. Please add the explicit “no digits parsed” check in both branches and add a regression for the empty-string case.

🐛 Minimal hardening
-			if (errno != 0 || *endptr != '\0' || val < 0 || val > 255)
+			if (endptr == optarg || errno != 0 || *endptr != '\0' || val < 0 || val > 255)
 				errx(EX_USAGE, "conflict exit code must be 0-255, was '%s'", optarg);
...
-		if (errno != 0 || *endptr != '\0' || fdval < 0 || fdval > INT_MAX)
+		if (endptr == argv[optind] || errno != 0 || *endptr != '\0' || fdval < 0 || fdval > INT_MAX)
 			errx(EX_USAGE, "bad file descriptor: %s", argv[optind]);
#!/bin/bash
# Show both numeric guards and confirm the explicit no-digits checks are still absent.
sed -n '215,221p;296,301p' src/flock.c
printf '\nExplicit no-digits guards:\n'
rg -n 'endptr\s*==\s*(optarg|argv\[optind\])' src/flock.c || true

Also applies to: 296-300

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/flock.c` around lines 215 - 220, The strtol checks in the '-E' handling
(the case 'E' block using strtol with variables optarg and endptr) and the
similar fd-parsing branch must reject empty strings by verifying that strtol
actually parsed digits; add an explicit check that endptr != optarg (or endptr
== optarg -> error) after calling strtol and before using val, and keep the
existing errno/trailing-char bounds checks; mirror the same "no digits parsed"
check in the other branch that parses argv[optind] (the second strtol usage
around lines ~296-300); finally add a regression test that calls the binary with
'-E ""' and with an empty fd argument to assert both return the usage/error
path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@t/default.bats`:
- Around line 557-564: The test "-o closes fd before exec so grandchildren don't
inherit lock" currently only runs a short command and never creates a long-lived
grandchild; change it so the command run under ${FLOCK} -o ${LOCKFILE} spawns a
long-lived grandchild (e.g., background sleep or tail) and then exits, and in
the test body immediately attempt to reacquire the same lock with a non-blocking
${FLOCK} -n against ${LOCKFILE} to assert it succeeds while the grandchild is
still alive; this ensures the fd was closed in the exec'd child and not
inherited by the grandchild.
- Around line 571-573: The test "--verbose produces output about lock
acquisition" currently uses a broad glob matching "flock:" which can hide
regressions; update the assertion to explicitly check for the acquisition
message and the timing line instead of just "flock:". Replace the single
assertion ([[ "$result" == *"flock:"* ]]) with two more specific checks that
"$result" contains the acquisition string (e.g., "flock: executing" or the exact
acquisition wording used) and the timing string (e.g., "took" or "took .*
microseconds") — using two [[ ... == *...* ]] checks or grep/egrep with both
patterns so the test fails if either piece is missing.
- Around line 491-502: The test "lock then unlock on file descriptor" currently
can't prove the fd was unlocked before the holder exited; update the test to
either (A) call get_elapsed using a nonblocking lock attempt (pass -n to
${FLOCK}) so the second lock must succeed while the background holder is still
alive and assert it succeeded, referencing ${FLOCK}, ${LOCKFILE} and
get_elapsed, or (B) keep the blocking get_elapsed but tighten the assertion in
was_blocked to include an upper bound (e.g. assert elapsed < 1.5s) so the
measured wait is bounded and cannot be satisfied by waiting until the background
process exits; apply the change inside the test body for "lock then unlock on
file descriptor".
- Around line 31-36: The helper hold_lock() currently uses a fixed sleep which
is racy; replace the sleep-based wait with an explicit readiness check by
polling with a non-blocking probe so you only return once the background holder
truly holds the lock. Concretely: after spawning the background ${FLOCK}
${flags} ${LOCKFILE} sleep 2 &, loop using a non-blocking probe (e.g., attempt
${FLOCK} -n ${LOCKFILE} or equivalent and treat a nonzero/failed acquire as
“lock held”) with a short backoff until the probe indicates contention, then
exit the helper; also update any inline holder snippets that use sleep 0.2 to
the same polling/readiness approach for deterministic tests.

---

Duplicate comments:
In `@src/flock.c`:
- Around line 215-220: The strtol checks in the '-E' handling (the case 'E'
block using strtol with variables optarg and endptr) and the similar fd-parsing
branch must reject empty strings by verifying that strtol actually parsed
digits; add an explicit check that endptr != optarg (or endptr == optarg ->
error) after calling strtol and before using val, and keep the existing
errno/trailing-char bounds checks; mirror the same "no digits parsed" check in
the other branch that parses argv[optind] (the second strtol usage around lines
~296-300); finally add a regression test that calls the binary with '-E ""' and
with an empty fd argument to assert both return the usage/error path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3fdb2602-5ce8-43c2-88e4-0b453ccbc8be

📥 Commits

Reviewing files that changed from the base of the PR and between f6f1d14 and 2b29ba6.

📒 Files selected for processing (2)
  • src/flock.c
  • t/default.bats

Comment thread t/default.bats Outdated
Comment thread t/default.bats
Comment thread t/default.bats Outdated
Comment thread t/default.bats Outdated
voltechs added a commit to TwilightCoders/flock that referenced this pull request Mar 29, 2026
Add endptr == input check to catch empty string input (e.g., -E "" or
flock ""), which strtol silently accepts as 0. Add regression tests.

Addresses coderabbitai review feedback on PR discoteq#43.
voltechs added a commit to TwilightCoders/flock that referenced this pull request Mar 29, 2026
- Check in man/flock.1 so users always get a real man page, even when
  ronn is not installed. Previously, builds without ronn created an
  empty man page via touch, which installed a blank manual.
- Remove man/flock.1 from .gitignore.
- Make 'make doc' fail with a clear error when ronn is absent instead
  of silently producing incomplete output.
- Make 'html-man' fail clearly if man/flock.1.html was not built.

Addresses qodo-code-review feedback on PR discoteq#43.
- Make -h/--help print to stdout and exit 0 instead of stderr/EX_USAGE
  (fixes discoteq#15)
- Fix printf format specifier for suseconds_t: %1u -> %ld to resolve
  GCC -Werror=format build failure (fixes discoteq#33)
- Fix verbose timing: include tv_sec in elapsed calculation to avoid
  underflow across second boundaries; terminate "getting lock" message
  with newline so output isn't left dangling on timeout/error
- Fix typo in error message: "waidpid" -> "waitpid"
- Validate -E exit code with strtol: reject non-numeric, empty, negative,
  >255, and overflow input (was atoi, silently accepted garbage as 0)
- Validate fd argument with strtol: reject non-numeric, empty, negative,
  and overflow input (previously 'flock abc' silently locked stdin)
- Use direct assignment for fl.l_type in fcntl fallback (|= on zero was
  misleading)
- Normalize indentation to tabs throughout; fix double-tab err() calls;
  remove extraneous blank lines
Replace the original ~30 timing-sensitive tests with 86 comprehensive
tests. Key improvements:

- Poll-based wait_for_lock/hold_lock helpers that probe with flock -n
  until the lock is actually held, replacing fixed sleep delays
- Threshold-based was_blocked/was_immediate helpers replacing exact
  timing comparisons like [ "$result" = 0.05 ]
- Per-test setup/teardown with isolated lockfiles and background
  process cleanup

New coverage includes: -e flag, --nb alias, -c shell features (pipes,
variables, command substitution), child exit status propagation (0, 1,
42, 255, nonexistent command), -E validation (non-numeric, empty,
negative, >255, overflow), fd validation (non-numeric, empty, negative,
overflow), -o/--close grandchild fd inheritance, --verbose output
assertions, option combinations and flag precedence (-s -n, -x -n,
-w -E, -n -E 0), file types (spaces in path, nonexistent directory),
and -h/--help exit status.

Fixes discoteq#5. Addresses discoteq#42.
…tions

- Add MacPorts install instructions to README (fixes discoteq#32)
- Document bats-core as a test suite prerequisite in README
- Document -c/--command, -h/--help, -V/--version, and --verbose in
  the man page (these options existed in code but were not documented)
CI test matrix (fixes discoteq#17):
- Ubuntu latest with GCC and Clang
- macOS latest
- FreeBSD 14 via vmactions/freebsd-vm
- Dedicated build-docs job that installs ronn-ng and uploads man pages
  as artifacts (nroff and HTML)
- Test log artifacts uploaded on failure

Build changes:
- Man page build gracefully skips when ronn is not installed: uses
  pre-existing file if available, warns otherwise (fixes discoteq#41)
- make doc and html-man fail with clear errors when ronn is absent
  instead of silently producing incomplete output
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
t/default.bats (2)

167-174: Minor timing dependency in shared lock test.

The sleep 0.2 is used because wait_for_lock (which uses exclusive nonblock probe) can't detect shared locks. This is a minor race condition, but acceptable given that shared lock acquisition is fast. Consider documenting this limitation in a comment if flakiness is observed on slow CI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@t/default.bats` around lines 167 - 174, The test "multiple shared locks can
coexist" relies on a small sleep (sleep 0.2) because wait_for_lock (which uses
an exclusive nonblock probe) cannot detect already-held shared locks,
introducing a minor timing dependency; update the test in the multiple shared
locks can coexist block to add a clear comment explaining this limitation and
why sleep 0.2 is required (mentioning wait_for_lock, the exclusive nonblock
probe, and that shared locks are fast but may race on slow CI), and optionally
note that get_elapsed and was_immediate are used to confirm immediate
acquisition so future maintainers won't remove or change the sleep without
accounting for the probe limitation.

615-619: Consider adding --verbose test for FD mode.

This test only covers --verbose in command mode. Adding a test for FD mode would catch the uninitialized t_l_req bug in flock.c:

`@test` "--verbose in fd mode produces output" {
    (
        ${FLOCK} --verbose 8
    ) 8> ${LOCKFILE} 2>&1 | grep -q "getting lock"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@t/default.bats` around lines 615 - 619, Add a new bats test that exercises
--verbose in FD mode to catch the t_l_req uninitialized bug: create a test named
"--verbose in fd mode produces output" that runs the flock binary with
"--verbose 8" while redirecting FD 8 to the lock file (e.g. run ( ${FLOCK}
--verbose 8 ) 8> ${LOCKFILE} 2>&1 | grep -q "getting lock") so the test asserts
verbose output is produced in FD mode similarly to the existing command-mode
verbose test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/flock.c`:
- Around line 332-337: The verbose timing uses t_l_req even when it was only set
in command mode; fix by ensuring t_l_req is initialized for FD mode too—either
move the initial gettimeofday(&t_l_req, ...) earlier (before the mode dispatch)
so both code paths set t_l_req, or add a separate gettimeofday(&t_l_req, NULL)
in the FD-mode branch before waiting for the lock; alternatively guard the
printf behind a flag that confirms t_l_req was set. Update references to
t_l_req/t_l_acq and the verbose block so elapsed_us is computed only after
t_l_req has been initialized.

---

Nitpick comments:
In `@t/default.bats`:
- Around line 167-174: The test "multiple shared locks can coexist" relies on a
small sleep (sleep 0.2) because wait_for_lock (which uses an exclusive nonblock
probe) cannot detect already-held shared locks, introducing a minor timing
dependency; update the test in the multiple shared locks can coexist block to
add a clear comment explaining this limitation and why sleep 0.2 is required
(mentioning wait_for_lock, the exclusive nonblock probe, and that shared locks
are fast but may race on slow CI), and optionally note that get_elapsed and
was_immediate are used to confirm immediate acquisition so future maintainers
won't remove or change the sleep without accounting for the probe limitation.
- Around line 615-619: Add a new bats test that exercises --verbose in FD mode
to catch the t_l_req uninitialized bug: create a test named "--verbose in fd
mode produces output" that runs the flock binary with "--verbose 8" while
redirecting FD 8 to the lock file (e.g. run ( ${FLOCK} --verbose 8 ) 8>
${LOCKFILE} 2>&1 | grep -q "getting lock") so the test asserts verbose output is
produced in FD mode similarly to the existing command-mode verbose test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7ccfbf0-9587-44eb-885c-ce52b8a4c34e

📥 Commits

Reviewing files that changed from the base of the PR and between b62cc61 and 869738b.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • Makefile.am
  • README.md
  • man/flock.1.ronn
  • src/flock.c
  • t/default.bats
  • t/smartos.bats
✅ Files skipped from review due to trivial changes (2)
  • README.md
  • .github/workflows/ci.yml
🚧 Files skipped from review as they are similar to previous changes (3)
  • t/smartos.bats
  • man/flock.1.ronn
  • Makefile.am

Comment thread src/flock.c
Move gettimeofday(&t_l_req) from command-mode-only block to just
before the flock() loop, so it covers both command and FD paths.
Previously, FD mode with --verbose read uninitialized stack memory,
producing garbage like "took 1774858164251193 microseconds".

Add regression test verifying verbose FD mode timing is sane (<1s).
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/flock.c (1)

207-213: ⚠️ Potential issue | 🟠 Major

Harden -w/--timeout parsing before populating timer.it_value.

Lines 207-213 still use strtod(optarg, NULL), so inputs like 1junk, nan, and inf are accepted, and a tiny positive like 0.0000001 truncates to {0,0}, which disables the timer entirely. Validate full consumption, ERANGE, and finiteness first, then reject or round up sub-microsecond positives before calling setitimer().

Suggested hardening
+#include <math.h>
...
-		case 'w':
+		case 'w': {
+			char *endptr;
 			have_timeout = true;
-			raw_timeval = strtod(optarg, NULL);
-			if (0 >= raw_timeval)
-				errx(EX_USAGE, "timeout must be greater than 0, was %f", raw_timeval);
+			errno = 0;
+			raw_timeval = strtod(optarg, &endptr);
+			if (errno == ERANGE || endptr == optarg || *endptr != '\0' ||
+			    !isfinite(raw_timeval) || raw_timeval <= 0)
+				errx(EX_USAGE, "timeout must be greater than 0, was '%s'", optarg);
 			timer.it_value.tv_sec = (time_t) raw_timeval;
 			timer.it_value.tv_usec = (suseconds_t) ((raw_timeval - timer.it_value.tv_sec) * 1000000);
+			if (timer.it_value.tv_sec == 0 && timer.it_value.tv_usec == 0)
+				timer.it_value.tv_usec = 1;
 			break;
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/flock.c` around lines 207 - 213, The timeout parsing in the case 'w'
branch currently uses raw_timeval = strtod(optarg, NULL) and then assigns
timer.it_value directly; change this to use strtod with an endptr and check
errno for ERANGE, verify endptr consumed the entire optarg (no trailing
characters), and use isfinite() to reject nan/inf; also enforce raw_timeval > 0
and if a positive value would round to {0,0} (sub-microsecond) set
timer.it_value.tv_usec to 1 to avoid disabling the timer; keep the existing
variables have_timeout, raw_timeval, and timer.it_value but perform these
validations and rounding before populating timer.it_value and calling
setitimer().
♻️ Duplicate comments (1)
t/default.bats (1)

232-236: ⚠️ Potential issue | 🟠 Major

Wait for contention in the timeout-success tests.

Lines 232-236, 251-255, and 258-262 still use sleep 0.1 as a proxy for lock acquisition. On a slow runner, the second flock can start before the holder owns ${LOCKFILE}, so these cases pass without ever exercising -w/--timeout. Reuse wait_for_lock here instead of a fixed sleep.

✅ Deterministic replacement
 `@test` "-w succeeds if lock is released before timeout" {
 	${FLOCK} ${LOCKFILE} sleep 0.3 &
-	sleep 0.1
+	wait_for_lock
 	result=$(${FLOCK} -w 3 ${LOCKFILE} echo run || echo err)
 	[ "$result" = run ]
 }
@@
 `@test` "-w with fractional seconds works" {
 	${FLOCK} ${LOCKFILE} sleep 0.2 &
-	sleep 0.1
+	wait_for_lock
 	result=$(${FLOCK} -w 1.5 ${LOCKFILE} echo run || echo err)
 	[ "$result" = run ]
 }
@@
 `@test` "--timeout succeeds if lock is released before timeout" {
 	${FLOCK} ${LOCKFILE} sleep 0.3 &
-	sleep 0.1
+	wait_for_lock
 	result=$(${FLOCK} --timeout 3 ${LOCKFILE} echo run || echo err)
 	[ "$result" = run ]
 }

Also applies to: 251-255, 258-262

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@t/default.bats` around lines 232 - 236, The timeout-success tests (e.g. the
`@test` "-w succeeds if lock is released before timeout" that uses ${FLOCK} and
${LOCKFILE}) rely on a fixed sleep to let the holder acquire the lock; replace
the sleep 0.1 call with the existing wait_for_lock helper to wait
deterministically for the holder to own ${LOCKFILE} before starting the second
${FLOCK} invocation, and apply the same replacement in the other two test blocks
around the same logic (the blocks at the other occurrences) so the -w/--timeout
behavior is actually exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/flock.c`:
- Around line 121-122: Update the usage() help text to document the missing
flags: add a short description for -E (the same as --conflict-exit-code) and
include -v (the same as --verbose) so the printed help matches the parser;
modify the usage() string where the flags are listed to show "-E
--conflict-exit-code    Exit with non-zero code on conflict" (or equivalent) and
"-v --verbose    Increase verbosity" so the option names (E and v) and their
long forms match the parser behavior in usage().

---

Outside diff comments:
In `@src/flock.c`:
- Around line 207-213: The timeout parsing in the case 'w' branch currently uses
raw_timeval = strtod(optarg, NULL) and then assigns timer.it_value directly;
change this to use strtod with an endptr and check errno for ERANGE, verify
endptr consumed the entire optarg (no trailing characters), and use isfinite()
to reject nan/inf; also enforce raw_timeval > 0 and if a positive value would
round to {0,0} (sub-microsecond) set timer.it_value.tv_usec to 1 to avoid
disabling the timer; keep the existing variables have_timeout, raw_timeval, and
timer.it_value but perform these validations and rounding before populating
timer.it_value and calling setitimer().

---

Duplicate comments:
In `@t/default.bats`:
- Around line 232-236: The timeout-success tests (e.g. the `@test` "-w succeeds if
lock is released before timeout" that uses ${FLOCK} and ${LOCKFILE}) rely on a
fixed sleep to let the holder acquire the lock; replace the sleep 0.1 call with
the existing wait_for_lock helper to wait deterministically for the holder to
own ${LOCKFILE} before starting the second ${FLOCK} invocation, and apply the
same replacement in the other two test blocks around the same logic (the blocks
at the other occurrences) so the -w/--timeout behavior is actually exercised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f5d5057-e5fb-4e77-b3e5-53848694f250

📥 Commits

Reviewing files that changed from the base of the PR and between 869738b and ce060da.

📒 Files selected for processing (2)
  • src/flock.c
  • t/default.bats

Comment thread src/flock.c
Comment on lines 121 to 122
-E --conflict-exit-code\n\
--verbose Increase verbosity\n"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Document -E and -v correctly in usage().

Lines 121-122 currently give -E no description, and -v is accepted by the parser but never shown in the help text. That leaves usage() out of sync with the actual CLI.

📝 Suggested help-text fix
- -E --conflict-exit-code\n\
-    --verbose    Increase verbosity\n"
+ -E --conflict-exit-code Set exit code used for conflicts/timeouts\n\
+ -v --verbose    Increase verbosity\n"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-E --conflict-exit-code\n\
--verbose Increase verbosity\n"
-E --conflict-exit-code Set exit code used for conflicts/timeouts\n\
-v --verbose Increase verbosity\n"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/flock.c` around lines 121 - 122, Update the usage() help text to document
the missing flags: add a short description for -E (the same as
--conflict-exit-code) and include -v (the same as --verbose) so the printed help
matches the parser; modify the usage() string where the flags are listed to show
"-E --conflict-exit-code    Exit with non-zero code on conflict" (or equivalent)
and "-v --verbose    Increase verbosity" so the option names (E and v) and their
long forms match the parser behavior in usage().

@voltechs
Copy link
Copy Markdown
Contributor Author

Hey @josephholsten, thanks for the engagement. Sorry it took me so long to circle back to this. I worked through all of the code rabbits comments best I could, but it started to become whackamole. I'd fix everything on its list and the it'd find something else to complain about.

I think it did ultimately give it a pass. Let me know how you're feeling about this. I rebased onto master after your couple merges earlier, which addressed a few things the PR did as well.

@josephholsten josephholsten merged commit 419b608 into discoteq:master Apr 17, 2026
1 check passed
@josephholsten
Copy link
Copy Markdown
Contributor

Sorry about the delay in merging, you got the relevant stuff absolutely. It's trying to critique larger issues in the codebase, and those don't make sense in this changeset. You're awesome, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants