Skip to content

Implement shell I/O redirection runtime#890

Merged
dburkart merged 2 commits into
mainfrom
m879-io-redirections
May 5, 2026
Merged

Implement shell I/O redirection runtime#890
dburkart merged 2 commits into
mainfrom
m879-io-redirections

Conversation

@dburkart
Copy link
Copy Markdown
Owner

@dburkart dburkart commented May 5, 2026

Closes #879

Summary

  • Add base/sh/src/redirect.rs implementing POSIX.1-2024 §2.7 I/O redirection runtime
  • Supports all redirection operators: >, >>, <, <>, N>, N<, N>>, >&N, <&N, >&-, << (here-docs), and <<- (tab-stripping here-docs)
  • Redirections applied left-to-right per POSIX, with SavedFds for undoing redirections on builtins
  • Runtime uses extern C declarations for open/close/dup/dup2/pipe/write/fcntl provided by vibix_libc through the std toolchain
  • 37 host-side unit tests covering fd resolution, open flag computation, dup target parsing, heredoc tab stripping, ordering semantics, error display, and SavedFds

Test plan

  • cargo test in base/sh/ — 218 tests pass (37 new redirect tests)
  • cargo xtask build — clean build
  • cargo xtask test — passes (only pre-existing rwlock_concurrent_readers flake)

Add the redirect module that applies parsed Redirect AST nodes to the
process fd table at runtime, conforming to POSIX.1-2024 section 2.7.

Implemented:
- Output redirection: > (truncate), >> (append)
- Input redirection: <
- Read-write redirection: <>
- Fd-specific redirections: N>, N<, N>>
- Fd duplication: >&N, <&N, >&- (close fd)
- Here-documents via pipe: <<DELIM body materialized through pipe()
- Tab stripping for <<- variant (strip_heredoc_tabs)
- SavedFds for undoing redirections (builtin support)
- Left-to-right application order per POSIX

Runtime uses extern C declarations for open/close/dup/dup2/pipe/write/
fcntl, provided by vibix_libc through the std toolchain. Pure logic
(fd resolution, flag computation, dup target parsing, heredoc tab
stripping) is fully tested via 37 host-side unit tests.

Closes #879

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4435432-f97a-4dab-b8ac-c8b342b9543b

📥 Commits

Reviewing files that changed from the base of the PR and between 252eed2 and 3dfa139.

📒 Files selected for processing (1)
  • base/sh/src/redirect.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • base/sh/src/redirect.rs

📝 Walkthrough

Walkthrough

A new internal module redirect is added and integrated via mod redirect;. The module implements POSIX-style runtime I/O redirections (file opens, fd duplication/closing, here-docs), fd snapshot/restore logic, syscall bindings, helpers, and a comprehensive unit test suite.

Changes

Shell I/O Redirection Runtime

Layer / File(s) Summary
Type & Constants
base/sh/src/redirect.rs
Adds RawFd, POSIX open flags (O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND), DupTarget type.
Errors & Display
base/sh/src/redirect.rs
Adds RedirectError enum and Display impl for syscall-style messages.
FD Snapshot
base/sh/src/redirect.rs
Adds SavedFds struct and restore() (no-op in tests; real dup2/close restore in non-test builds).
Syscall Bindings & Errno
base/sh/src/redirect.rs
Declares extern "C" bindings: open, close, dup, dup2, pipe, write, fcntl, __errno_location; adds get_errno() (non-test).
Helpers
base/sh/src/redirect.rs
Adds default_fd(), open_flags(), parse_dup_target(), resolve_fd(), strip_heredoc_tabs() implementing fd defaults, open-mode flags, dup-target parsing, and <<- tab stripping.
Core Runtime
base/sh/src/redirect.rs
Adds apply_redirects() (non-test) applying redirects left-to-right: save original fds, open files, dup2 file/pipe fds onto targets, handle dup/close redirect semantics, and here-doc materialization via apply_heredoc().
FD Save Helper
base/sh/src/redirect.rs
Adds save_fd() which checks openness with fcntl, duplicates via dup(), and sets FD_CLOEXEC on the copy.
Tests & Validation
base/sh/src/redirect.rs
Comprehensive unit tests covering helpers, dup ordering semantics, SavedFds behavior, and RedirectError display strings.
Module Integration
base/sh/src/main.rs
Adds mod redirect; to include the new module in the crate.

Sequence Diagram

sequenceDiagram
    actor Caller
    participant apply_redirects
    participant save_fd
    participant open_syscall as open()
    participant dup2_syscall as dup2()
    participant apply_heredoc
    participant pipe_syscall as pipe()
    participant write_syscall as write()

    Caller->>apply_redirects: apply_redirects(&[Redirect])
    activate apply_redirects

    loop each redirect (left-to-right)
        apply_redirects->>apply_redirects: resolve_fd()
        alt file redirection
            apply_redirects->>save_fd: save_fd(target_fd)
            save_fd->>dup (syscall): dup(target_fd)
            save_fd->>save_fd: set FD_CLOEXEC
            save_fd-->>apply_redirects: saved copy
            apply_redirects->>open_syscall: open(path, flags, mode)
            open_syscall-->>apply_redirects: opened_fd
            apply_redirects->>dup2_syscall: dup2(opened_fd, target_fd)
        else dup/close redirection
            apply_redirects->>apply_redirects: parse_dup_target()
            apply_redirects->>dup2_syscall: dup2(source_fd, target_fd) or close(target_fd)
        else here-doc
            apply_redirects->>save_fd: save_fd(target_fd)
            apply_redirects->>apply_heredoc: apply_heredoc(target_fd, body)
            apply_heredoc->>pipe_syscall: pipe()
            pipe_syscall-->>apply_heredoc: r,w fds
            apply_heredoc->>write_syscall: write(w_fd, body)
            apply_heredoc->>dup2_syscall: dup2(r_fd, target_fd)
        end
    end

    apply_redirects-->>Caller: SavedFds
    deactivate apply_redirects

    Caller->>SavedFds: restore()
    activate SavedFds
    loop reverse saved entries
        SavedFds->>dup2_syscall: dup2(saved_copy, original_fd)
        SavedFds->>SavedFds: close(saved_copy)
    end
    SavedFds-->>Caller: Ok
    deactivate SavedFds
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • dburkart/vibix#887: Runtime redirection implementation complements parser/AST changes that produce Redirect/RedirectOp nodes.

Poem

🐰 I nudge fds left-to-right with cheer,

Pipes and dup2 bring I/O near,
Here-docs tucked in tidy streams,
Shells now weave their redirect dreams.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Implement shell I/O redirection runtime' directly and clearly describes the main change: adding the redirect.rs module for POSIX I/O redirection functionality.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the new redirect.rs module, supported operators, testing approach, and linking to issue #879.
Linked Issues check ✅ Passed The PR implementation satisfies all coding objectives from #879: all redirection operators (>, >>, <, <>, N>, N<, N>>, >&N, <&N, >&-), here-docs with tab-stripping, left-to-right ordering, and 37 unit tests provided.
Out of Scope Changes check ✅ Passed All changes are in-scope: a single mod declaration in main.rs and the comprehensive redirect.rs implementation directly address the I/O redirection objectives from issue #879.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch m879-io-redirections

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: 1

🧹 Nitpick comments (1)
base/sh/src/redirect.rs (1)

531-537: 💤 Low value

Consider rejecting negative fd numbers in parse_dup_target.

The test documents that "-1" parses as DupTarget::Fd(-1), which will fail at runtime with EBADF. While this is caught at runtime, validating upfront provides clearer error messages to users (e.g., "invalid fd target: -1" instead of a cryptic "dup2(-1, 2) failed: errno 9").

💡 Optional: Reject negative fds during parsing
 pub fn parse_dup_target(target: &str) -> Result<DupTarget, RedirectError> {
     if target == "-" {
         Ok(DupTarget::Close)
     } else {
         target
-            .parse::<RawFd>()
-            .map(DupTarget::Fd)
+            .parse::<u32>()
+            .map(|n| DupTarget::Fd(n as RawFd))
             .map_err(|_| RedirectError::InvalidFdTarget {
                 target: target.to_string(),
             })
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/sh/src/redirect.rs` around lines 531 - 537, The test and comment
indicate parse_dup_target currently accepts negative integers and returns
DupTarget::Fd(-1); change parse_dup_target to validate parsed integers and
reject negative fd numbers by returning an Err with a clear message (e.g.,
"invalid fd target: -1") instead of producing DupTarget::Fd(-1), update any
tests that expect DupTarget::Fd(-1) to assert an error, and ensure the error
path is produced from the same function (parse_dup_target) so callers get a
user-friendly parse error rather than an EBADF at dup time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@base/sh/src/redirect.rs`:
- Around line 229-236: strip_heredoc_tabs currently uses body.lines() which
drops a trailing newline; update strip_heredoc_tabs to preserve the final
newline by either iterating with body.split_inclusive('\n') and trimming leading
tabs on each chunk (so existing newlines are kept), or keep the current lines()
approach but append a '\n' to the joined result when body.ends_with('\n'); apply
this change inside the strip_heredoc_tabs function to ensure here-document
bodies that end with a newline still end with a newline after processing.

---

Nitpick comments:
In `@base/sh/src/redirect.rs`:
- Around line 531-537: The test and comment indicate parse_dup_target currently
accepts negative integers and returns DupTarget::Fd(-1); change parse_dup_target
to validate parsed integers and reject negative fd numbers by returning an Err
with a clear message (e.g., "invalid fd target: -1") instead of producing
DupTarget::Fd(-1), update any tests that expect DupTarget::Fd(-1) to assert an
error, and ensure the error path is produced from the same function
(parse_dup_target) so callers get a user-friendly parse error rather than an
EBADF at dup time.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44d8a868-d407-4df4-beb3-14ffbf0885ac

📥 Commits

Reviewing files that changed from the base of the PR and between 35bf5b0 and 252eed2.

📒 Files selected for processing (2)
  • base/sh/src/main.rs
  • base/sh/src/redirect.rs

Comment thread base/sh/src/redirect.rs
Address CodeRabbit review finding — body.lines() drops a trailing
newline, which matters for here-document bodies. Append '\n' back
when the input ended with one. Add two tests covering both cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dburkart
Copy link
Copy Markdown
Owner Author

dburkart commented May 5, 2026

Addressed the trailing-newline bug in strip_heredoc_tabs — good catch. The negative-fd nit (parsing -1 as a valid fd) is deferred to future work; at runtime the kernel would reject it with EBADF, and the error message is clear enough for now.

@dburkart dburkart merged commit 6a10deb into main May 5, 2026
15 checks passed
@dburkart dburkart deleted the m879-io-redirections branch May 5, 2026 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement shell I/O redirections for /bin/sh

2 participants