1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//
// Syd: rock-solid application kernel
// src/utils/syd-ofd.rs: Take a lock on a file, then execute into another program
//
// Copyright (c) 2025, 2026 Ali Polatel <alip@chesswob.org>
// Based in part upon s6-setlock utility of skarnet s6 suite which is:
// Copyright (c) 2011-2025 Laurent Bercot <ska-skaware@skarnet.org>
// SPDX-License-Identifier: ISC
//
// SPDX-License-Identifier: GPL-3.0
// SAFETY: This module has (almost) been liberated from unsafe code!
// 1. We call into sigaction(2) to set SIGALRM handler with -t timeout which needs unsafe.
// Use deny rather than forbid so we can allow this case.
#![deny(unsafe_code)]
use std::{
os::{
fd::{AsRawFd, RawFd},
unix::ffi::OsStrExt,
},
process::ExitCode,
};
use btoi::btoi;
use nix::{
errno::Errno,
fcntl::{OFlag, AT_FDCWD},
sys::{
signal::{sigaction, SaFlags, SigAction, SigHandler, Signal},
signalfd::SigSet,
stat::Mode,
},
};
use syd::{
compat::{dup3, openat2, OpenHow, ResolveFlag},
confine::run_syd_shell,
eprintfln,
err::err2exit,
fd::set_cloexec,
ofd::lock_fd,
path::XPathBuf,
printfln,
retry::retry_on_eintr,
timer::AlarmTimer,
};
// Set global allocator to GrapheneOS allocator.
#[cfg(all(
not(target_os = "android"),
not(target_arch = "loongarch64"),
not(target_arch = "riscv64"),
target_page_size_4k,
target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;
// Signal handler function for SIGALRM.
extern "C" fn handle_sigalrm(_: libc::c_int) {}
syd::main! {
use lexopt::prelude::*;
// Set SIGPIPE handler to default.
syd::set_sigpipe_dfl()?;
// Parse CLI options.
//
// Note, option parsing is POSIXly correct:
// POSIX recommends that no more options are parsed after the first
// positional argument. The other arguments are then all treated as
// positional arguments.
// See: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
let mut opt_block = true;
let mut opt_fdset = None;
let mut opt_tmout = None;
let mut opt_plock = None;
let mut opt_wlock = true;
let mut opt_cmd = None;
let mut opt_arg = Vec::new();
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next()? {
match arg {
Short('h') => {
help()?;
return Ok(ExitCode::SUCCESS);
}
Short('n') => opt_block = false,
Short('N') => opt_block = true,
Short('r' | 's') => opt_wlock = false,
Short('w' | 'x') => opt_wlock = true,
Short('d') => opt_fdset = Some(btoi::<RawFd>(parser.value()?.as_bytes())?),
Short('t') => opt_tmout = Some(btoi::<u64>(parser.value()?.as_bytes())?),
Value(lock) => {
opt_plock = Some(XPathBuf::from(lock));
let mut raw = parser.raw_args()?;
if let Some(cmd) = raw.next() {
opt_cmd = Some(cmd);
opt_arg.extend(raw);
}
}
_ => return Err(arg.unexpected().into()),
}
}
let opt_plock = if let Some(opt_plock) = opt_plock {
opt_plock
} else {
eprintfln!("syd-ofd: Lock path is required!")?;
return Err(Errno::ENOENT.into());
};
if opt_plock.has_parent_dot() {
eprintfln!("syd-ofd: Parent directory (..) components aren't permitted in lock path!")?;
return Err(Errno::EACCES.into());
}
// Open the lock file:
// 1. Exclusive lock opens for write+create with mode 0600.
// 2. Shared lock open for read+create with mode 0600.
//
// SAFETY:
// 1. Do not follow symlinks in any of the path components.
// 2. Do not follow symlinks in last path component.
// 3. Do not acquire a controlling TTY.
// 4. Do not block on FIFOs.
let mode = Mode::from_bits_truncate(0o600);
let mut flags = OFlag::O_CREAT | OFlag::O_CLOEXEC | OFlag::O_NONBLOCK | OFlag::O_NOCTTY | OFlag::O_NOFOLLOW;
if opt_wlock {
flags.insert(OFlag::O_WRONLY);
} else {
flags.insert(OFlag::O_RDONLY);
}
let how = OpenHow::new()
.flags(flags)
.mode(mode)
.resolve(ResolveFlag::RESOLVE_NO_MAGICLINKS | ResolveFlag::RESOLVE_NO_SYMLINKS);
#[expect(clippy::disallowed_methods)]
let mut fd = retry_on_eintr(|| openat2(AT_FDCWD, &opt_plock, how))?;
// Create a timer as necessary and hold a reference to it,
// because timer_delete(2) is called on Drop.
let timer = if let Some(tmout) = opt_tmout {
// Ensure -t timeout uses blocking call regardless of -n.
opt_block = true;
// Set up the signal handler for SIGALRM.
let sig_action = SigAction::new(
SigHandler::Handler(handle_sigalrm),
SaFlags::empty(),
SigSet::empty(),
);
// SAFETY: Register the handler for SIGALRM.
// This handler is per-process.
#[expect(unsafe_code)]
unsafe { sigaction(Signal::SIGALRM, &sig_action) }?;
// Set up an alarm timer and start it.
let mut timer = AlarmTimer::from_milliseconds(tmout)?;
timer.start()?;
Some(timer)
} else {
None
};
// Lock file descriptor.
//
// We do NOT retry on EINTR because it's the AlarmTimer.
lock_fd(&fd, opt_wlock, opt_block)?;
// Delete the timer which is no longer needed.
drop(timer);
// Prepare to pass fd to the child process.
if let Some(opt_fdset) = opt_fdset {
if opt_fdset != fd.as_raw_fd() {
// Atomically duplicate onto the exact fd number.
// Note we move the old fd into the function so it's dropped on return.
fd = dup3(fd.as_raw_fd(), opt_fdset, OFlag::O_CLOEXEC.bits())?;
}
}
set_cloexec(&fd, false)?;
// Execute command, Syd shell by default.
Ok(err2exit(run_syd_shell(opt_cmd, opt_arg)))
}
fn help() -> Result<(), Errno> {
printfln!(
"Usage: syd-ofd [-n | -N] [-t timeout] [-d fd] [-s=-r | -x=-w] file {{command [arg...]}}"
)?;
printfln!("Take a lock on a file, then execute into another program.")?;
printfln!("Use -n to take a nonblocking lock.")?;
printfln!("Use -N to take a blocking lock. This is the default.")?;
printfln!("Use -t timeout to specify a timeout in milliseconds.")?;
printfln!("Use -s or -r to take a shared lock.")?;
printfln!("Use -x or -w to take an exclusive lock. This is the default.")?;
printfln!("Use -d fd to make the lock visible to program on file descriptor fd.")?;
Ok(())
}