## 0.7.0
### Fixed
* **`close()` no longer retries on `EINTR`** — Previously, the internal `close_retry` helper
looped on `EINTR`, which is unsafe on all modern Unixes:
- **Linux**: `close()` always releases the fd *before* returning `EINTR`.
Retrying can close an unrelated fd opened by another thread between attempts.
- **FreeBSD / macOS / other BSDs**: The fd state after `close()` + `EINTR` is
*unspecified* per POSIX 2008+ (Austin Group defect 529), so retrying is equally dangerous.
- The function has been renamed from `close_retry` to `close_once` and now calls `close()`
exactly once, treating both `EINTR` and `EBADF` as success — the same approach used by
Rust's `std::fs::File::drop()`, Go's runtime, and glibc internals.
- **Note**: `open()` and `dup2()` in `redirect_stdio()` still correctly retry on `EINTR`,
as those calls do *not* release resources on interruption.
### Improved
* **`daemon()` return value documentation** — Made it prominent that `daemon()` only ever
returns `Ok(Fork::Child)` or `Err(...)` to the caller; `Ok(Fork::Parent(_))` is never
returned because both parent processes call `_exit(0)` internally. Added recommended
`if let` and `match` usage patterns to the doc comment. Updated `#[must_use]` message
to reflect this guarantee.
### Code Quality
* Added `test_daemon_never_returns_parent` integration test confirming `Fork::Parent` is unreachable
* Renamed `test_close_retry_ok_and_ebadf` to `test_close_once_ok_and_ebadf`
* Replaced fragile `tty` command string-matching in `test_daemon_no_controlling_terminal` with
portable `open("/dev/tty")` check (works reliably across Linux, macOS, and BSDs)
* Replaced fixed 100ms sleep in `test_getppid_after_parent_exits` with a retry loop (up to 1s),
preventing flaky failures on slow CI systems
* Updated tests README to reflect new and renamed tests
## 0.6.0
### Breaking Changes
* **`getpgrp()` signature changed** - Now returns `libc::pid_t` directly instead of `io::Result<libc::pid_t>`
- `getpgrp()` always succeeds per POSIX specification and cannot fail
- **Migration guide:**
- Change `getpgrp()?` to `getpgrp()`
- Change `getpgrp().expect("...")` to `getpgrp()`
- Change `match getpgrp() { Ok(pgid) => ... }` to `let pgid = getpgrp();`
- Rationale: Aligns with POSIX.1 specification and matches `getpid()`/`getppid()` patterns
- Verified on Linux, macOS, FreeBSD, OpenBSD per POSIX.1 specification
- Updated all tests and documentation to reflect this guarantee
### Improved
* **Enhanced documentation** - Comprehensive improvements to library documentation
- Added "Common Patterns" section with practical examples:
- Process supervisor using HashMap with Fork
- Inter-process communication via pipes
- Daemon with PID file creation
- Added "Safety and Best Practices" guidelines
- Added detailed "Common Pitfalls and Safety Considerations" to `fork()`:
- Mutexes and locks (deadlock risks)
- File descriptors (shared state issues)
- Signal handlers (inheritance behavior)
- Async-signal-safety between fork and exec
- Memory usage (copy-on-write behavior)
- Enhanced `Fork` enum documentation with helper method examples
- Added "Platform Compatibility" information
* **Test quality improvements**
- Replaced deprecated `signal()` with `sigaction()` in EINTR tests
- More portable signal handling for cross-platform compatibility
- Renamed `test_getpgrp_returns_io_error_type` to `test_getpgrp_returns_pid_type`
- Updated test README to reflect current test descriptions
### Fixed
* **Documentation warnings** - Resolved doctest warnings about main function wrapping
* **EINTR resilience** - `close_fd` and `redirect_stdio` now retry on `EINTR` for `close/open/dup2`, preventing spurious failures under signal-heavy conditions on Linux, macOS, and BSD
* **Daemon exit safety** - Replaced `std::process::exit` in post-fork parents with `libc::_exit` to avoid running non-async-signal-safe destructors, preventing undefined behavior between `fork()` and `exec()`
### Code Quality
* **Modernized C string handling** - Replaced runtime `CString::new()` allocations with compile-time `c""` string literals (Rust 2024 feature)
- `chdir()` now uses `c"/"` instead of `CString::new("/")`
- `redirect_stdio()` now uses `c"/dev/null"` instead of `CString::new("/dev/null")`
- Benefits: Eliminated dead error handling code, zero runtime overhead, compile-time validation
- No API changes, fully backward compatible
* **Enhanced code clarity** - Added clarifying comments to `redirect_stdio()` error handling logic explaining conditional cleanup of file descriptors
* **Comprehensive test coverage** - Added 12 dedicated tests for `chdir()` function (346 lines)
- Tests idempotent behavior, process isolation, concurrent usage
- Validates modern `c""` string literal implementation
- Tests integration with `setsid()` (daemon pattern)
- Total test count increased from 107 to 119 tests
## 0.5.0
### Breaking Changes
* **`waitpid()` return type changed** - Now returns `io::Result<libc::c_int>` instead of `io::Result<()>`
- Returns the raw status code for inspection with `WIFEXITED`, `WEXITSTATUS`, `WIFSIGNALED`, `WTERMSIG`, etc.
- Migration: Change `waitpid(pid)?` to `let status = waitpid(pid)?; assert!(WIFEXITED(status));`
- Enables proper exit code checking and signal detection
- See updated examples in documentation
### Added
* **Fork helper methods** - Added convenience methods to `Fork` enum
- `is_parent()` - Check if this is the parent process
- `is_child()` - Check if this is the child process
- `child_pid()` - Get child PID if parent, otherwise None
* **Hash trait** - `Fork` now derives `Hash`, enabling use in `HashMap` and `HashSet`
- Useful for process supervisors and tracking multiple children
- Examples: `supervisor.rs` and `supervisor_advanced.rs`
* **must_use attributes** - Added `#[must_use]` to critical functions to prevent accidental misuse
- `fork()` - Must check if parent or child
- `daemon()` - Must check daemon result
- `setsid()` - Must use session ID
- `getpgrp()` - Must use process group ID
* **`waitpid_nohang()` function** - Non-blocking variant of `waitpid()`
- Returns `Ok(Some(status))` if child has exited
- Returns `Ok(None)` if child is still running
- Essential for process supervisors and event loops
- Enables polling patterns without blocking
- Includes 7 comprehensive tests
* **PID helper functions** - Convenience wrappers for getting process IDs
- `getpid()` - Get current process ID (always succeeds, hides unsafe)
- `getppid()` - Get parent process ID (always succeeds, hides unsafe)
* **Status macro re-exports** - Convenient access to status inspection macros
- Re-export `WIFEXITED`, `WEXITSTATUS`, `WIFSIGNALED`, `WTERMSIG` from libc
- Users can now `use fork::{waitpid, WIFEXITED, WEXITSTATUS}` instead of separate libc import
* **Comprehensive test suite** - Added extensive tests covering critical edge cases
- `tests/waitpid_tests.rs` - Exit codes, signals, error handling, and non-blocking waits
- `tests/error_handling_tests.rs` - Error paths and type verification
- `tests/pid_tests.rs` - PID helper functions (getpid, getppid)
- `tests/status_macro_tests.rs` - Status macro re-exports
### Improved
* **Performance** - Added `#[inline]` hints to thin wrapper functions (`chdir`, `setsid`, `getpgrp`, `getpid`, `getppid`)
* **Documentation** - Enhanced with comprehensive examples and safety considerations
- Added doc test for `setsid()` - Session creation example
- Added doc test for `getpgrp()` - Process group query example
- Added doc test for `getpid()` - Current PID example
- Added doc test for `getppid()` - Parent PID example
- Enhanced `fork()` with safety considerations (file descriptors, mutexes, async-signal-safety, signals, memory)
- Enhanced `waitpid()` with status inspection examples
- Added `waitpid_nohang()` with polling patterns and process supervisor examples
* **Daemon correctness** - `daemon()` now performs the full double-fork, exiting the intermediate session leader so only the daemon continues
- Docs clarified the numbered double-fork stages
- Examples updated (`example_daemon.rs`, `example_touch_pid.rs`) to reflect that only the daemon process returns `Fork::Child`
* **waitpid robustness** - Automatic retry on `EINTR` (signal interruption)
- Takes `pid_t` instead of `i32` for better type safety
- Returns raw status code enabling exit code inspection and signal detection
* **Code quality** - Simplified `daemon()` implementation using `?` operator consistently
* **Test coverage** - Comprehensive coverage of all error paths and edge cases
- Error handling: Invalid PID (ECHILD), double-wait, session leader errors (EPERM)
- Exit codes: 0, 1, 42, 127, 255, and multiple code variations
- Signal termination: SIGKILL, SIGTERM, SIGABRT detection
- Status inspection: WIFEXITED vs WIFSIGNALED distinction
- Fork helper methods: `is_parent()`, `is_child()`, `child_pid()`
- io::Error type verification for all functions
* **CI** - GitHub Actions now run tests serially (`RUST_TEST_THREADS=1`) and use the latest checkout action
### Examples
* Added `supervisor.rs` - Basic process supervisor example
* Added `supervisor_advanced.rs` - Production-ready supervisor with restart policies
### Migration Guide (0.4.x → 0.5.0)
#### Before (0.4.x):
```rust
match fork() {
Ok(Fork::Parent(child)) => {
waitpid(child)?; // Just waits, no status
}
Ok(Fork::Child) => exit(0),
Err(e) => eprintln!("Fork failed: {}", e),
}
```
#### After (0.5.0):
```rust
use libc::{WIFEXITED, WEXITSTATUS};
match fork() {
Ok(Fork::Parent(child)) => {
let status = waitpid(child)?; // Returns status code
assert!(WIFEXITED(status), "Child should exit normally");
let exit_code = WEXITSTATUS(status);
println!("Child exited with code: {}", exit_code);
}
Ok(Fork::Child) => exit(0),
Err(e) => eprintln!("Fork failed: {}", e),
}
```
## 0.4.0
### Breaking Changes
* **Improved error handling** - All functions now return `io::Result` instead of `Result<T, i32>`
- `fork()` now returns `io::Result<Fork>` (was `Result<Fork, i32>`)
- `daemon()` now returns `io::Result<Fork>` (was `Result<Fork, i32>`)
- `setsid()` now returns `io::Result<libc::pid_t>` (was `Result<libc::pid_t, i32>`)
- `getpgrp()` now returns `io::Result<libc::pid_t>` (was `Result<libc::pid_t, i32>`)
- `waitpid()` now returns `io::Result<()>` (was `Result<(), i32>`)
- `chdir()` now returns `io::Result<()>` (was `Result<libc::c_int, i32>`)
- `close_fd()` now returns `io::Result<()>` (was `Result<(), i32>`)
### Major Improvements
* **Fixed file descriptor reuse bug** (Issue #2)
- Added `redirect_stdio()` function that redirects stdio to `/dev/null` instead of closing
- Prevents silent file corruption when daemon opens files after stdio is closed
- `daemon()` now uses `redirect_stdio()` instead of `close_fd()`
- Matches industry standard implementations (libuv, systemd, BSD daemon(3))
### Benefits
* **Better error diagnostics** - Errors now capture and preserve `errno` values
* **Rich error messages** - Error display shows descriptive text (e.g., "Permission denied") instead of `-1`
* **Rust idioms** - Integrates seamlessly with `?` operator, `anyhow`, `thiserror`, and other error handling crates
* **Type safety** - Can match on `ErrorKind` variants for specific error handling
* **Debugging** - `.raw_os_error()` provides access to underlying errno when needed
* **Correctness** - No more file descriptor reuse bugs that could corrupt data files
### Added
* `Fork` enum now derives `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq` for better usability
* `redirect_stdio()` function - Safer alternative to `close_fd()`
* Comprehensive tests for stdio redirection (`tests/stdio_redirect_tests.rs`)
- Test demonstrating the fd reuse bug with `close_fd()`
- Tests verifying `redirect_stdio()` prevents fd reuse
- Tests confirming `daemon()` uses correct behavior
### Improved
* Simplified `close_fd()` implementation using iterator pattern
* Enhanced documentation with detailed error descriptions for all functions
* Updated all examples to use proper error handling patterns
* Added warnings to `close_fd()` documentation about fd reuse risks
### Security
* **CRITICAL FIX**: `daemon()` no longer vulnerable to file descriptor reuse bugs
- Previously, files opened after `daemon(false, false)` could get fd 0, 1, or 2
- Any `println!`, `eprintln!`, or panic would write to those files, corrupting them
- Now stdio is redirected to `/dev/null`, keeping fd 0,1,2 occupied
- New files always get fd >= 3
## 0.3.1
* Added comprehensive test coverage for `getpgrp()` function
- Unit tests in `src/lib.rs` (`test_getpgrp`, `test_getpgrp_in_parent`)
- Integration test `test_getpgrp_returns_process_group` in `tests/integration_tests.rs`
* Added `coverage` recipe to `.justfile` for generating coverage reports with grcov
## 0.3.0
### Changed
* Updated Rust edition from 2021 to 2024
* Applied edition 2024 formatting standards (alphabetical import ordering)
### Added
* **Integration tests directory** - Added `tests/` directory with comprehensive integration tests
- `daemon_tests.rs` - 5 tests for daemon functionality (detached process, nochdir, process groups, command execution, no controlling terminal)
- `fork_tests.rs` - 7 tests for fork functionality (basic fork, parent-child communication, multiple children, environment inheritance, command execution, different PIDs, waitpid)
- `integration_tests.rs` - 5 tests for advanced patterns (double-fork daemon, setsid, chdir, process isolation, getpgrp)
### Improved
* Significantly expanded test coverage from 1 to 13 comprehensive unit tests
* Added tests for all public API functions:
- `fork()` - Multiple test scenarios including child execution
- `daemon()` - Daemon pattern tested (double-fork with setsid)
- `waitpid()` - Proper parent-child synchronization
- `setsid()` - Session management and verification
- `getpgrp()` - Process group queries
- `chdir()` - Directory changes with verification
- `close_fd()` - File descriptor management
* Added real-world usage pattern tests:
- Classic double-fork daemon pattern
- Multiple sequential forks
- Command execution in child processes
* Improved test quality with proper cleanup and zombie process prevention
* Enhanced CI/CD integration with LLVM coverage instrumentation
* **Total test count: 35 tests** (13 unit + 17 integration + 5 doc tests)
### Fixed
* Daemon tests now properly test the daemon pattern without calling `daemon()` directly
(which would call `exit(0)` and terminate the test runner)
### Updated
* GitHub Actions: codecov/codecov-action from v4 to v5
## 0.2.0
* Added waitpid(pid: i32)