feat: relibc S1 — sem_open refcounting + glibc cross-reference assessment
Phase S1 (Critical Correctness): - sem_open/sem_close: global refcounting via BTreeMap + AtomicUsize - sem_close: decrements refcount, munmaps only at zero - sem_open: reuses existing mapping, O_EXCL returns EEXIST - sem_unlink: marks entry for removal before shm_unlink - va_list parsing: reads mode_t and value from stack after oflag - All 11 sem_* functions verified in libc.so T Phase S2-S4 (Designed, documented): - eventfd() function, signalfd read path, EINTR handling - name canonicalization, cancellation safety - Full plan in local/docs/RELIBC-AGAINST-GLIBC-ASSESSMENT.md Reference: glibc 2.41 cloned to local/reference/glibc/ Boot verified: greeter ready on VT 3 with refcounted semaphores
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
# Relibc vs GNU libc — Cross-Reference Assessment
|
||||
|
||||
**Date:** 2026-05-05
|
||||
**Reference:** glibc 2.41 (2026-05-05 clone from sourceware.org)
|
||||
**Relibc pinned:** commit 861bbb0 with Red Bear patch chain (26 patches)
|
||||
|
||||
---
|
||||
|
||||
## 1. eventfd
|
||||
|
||||
### glibc reference
|
||||
|
||||
```c
|
||||
// sysdeps/unix/sysv/linux/eventfd.c (not cloned yet — syscall wrapper)
|
||||
// bits/eventfd.h:
|
||||
EFD_SEMAPHORE = 00000001 // octal 1
|
||||
EFD_CLOEXEC = 02000000 // octal 0x80000
|
||||
EFD_NONBLOCK = 00004000 // octal 0x800
|
||||
```
|
||||
|
||||
glibc calls `INLINE_SYSCALL(eventfd2, 2, initval, flags)` — a kernel syscall. The kernel creates an anonymous file descriptor for event notification. Supports `EFD_SEMAPHORE` (semaphore-like counting), `EFD_CLOEXEC`, `EFD_NONBLOCK`.
|
||||
|
||||
### relibc current state
|
||||
|
||||
```rust
|
||||
// src/header/sys_eventfd/mod.rs — 8 lines
|
||||
pub const EFD_SEMAPHORE: c_int = 1;
|
||||
pub const EFD_CLOEXEC: c_int = 0x80000;
|
||||
pub const EFD_NONBLOCK: c_int = 0x800;
|
||||
// No eventfd() function — constants only
|
||||
```
|
||||
|
||||
**Constants match** ✅
|
||||
**No implementation** ❌ — libwayland provides inline `eventfd()` via `/scheme/event`
|
||||
|
||||
### Gaps
|
||||
|
||||
| Gap | Severity | Detail |
|
||||
|-----|----------|--------|
|
||||
| No `eventfd()` in relibc | Medium | libwayland has its own inline, but relibc should be canonical |
|
||||
| No `eventfd_read()`/`eventfd_write()` | Low | POSIX-adjacent convenience wrappers (glibc provides them) |
|
||||
|
||||
---
|
||||
|
||||
## 2. signalfd
|
||||
|
||||
### glibc reference
|
||||
|
||||
```c
|
||||
// sysdeps/unix/sysv/linux/signalfd.c
|
||||
int signalfd(int fd, const sigset_t *mask, int flags) {
|
||||
return INLINE_SYSCALL(signalfd4, 4, fd, mask, __NSIG_BYTES, flags);
|
||||
}
|
||||
|
||||
// bits/signalfd.h:
|
||||
SFD_CLOEXEC = 02000000 // octal 0x80000
|
||||
SFD_NONBLOCK = 00004000 // octal 0x800
|
||||
```
|
||||
|
||||
glibc is a thin syscall wrapper. Kernel handles signal mask, fd management, and non-blocking reads returning `struct signalfd_siginfo`.
|
||||
|
||||
### relibc current state
|
||||
|
||||
```rust
|
||||
// src/header/signal/signalfd.rs — 103 lines
|
||||
// Full implementation: opens /scheme/event, applies CLOEXEC/NONBLOCK via fcntl,
|
||||
// calls sigprocmask(SIG_BLOCK, mask), returns fd.
|
||||
// signalfd4 supports modifying existing fd's flags.
|
||||
```
|
||||
|
||||
**Flags match glibc** ✅
|
||||
**signalfd_siginfo struct matches** ✅
|
||||
**signalfd4 with existing fd** ✅ (fcntl-based flag modification)
|
||||
|
||||
### Prowess vs glibc
|
||||
|
||||
| Aspect | glibc | relibc |
|
||||
|--------|-------|--------|
|
||||
| Implementation | Syscall wrapper (5 lines) | Userspace via `/scheme/event` (100 lines) |
|
||||
| Existing fd support | Kernel handles | fcntl O_CLOEXEC/O_NONBLOCK modification ✅ |
|
||||
| Errno mapping | Kernel errno | Wraps in Errno, proper EINVAL/EFAULT |
|
||||
| Signal blocking | Kernel auto-blocks on read | `sigprocmask(SIG_BLOCK, mask)` called explicitly ✅ |
|
||||
|
||||
### Gaps
|
||||
|
||||
| Gap | Severity | Detail |
|
||||
|-----|----------|--------|
|
||||
| No read path | High | Nothing reads `signalfd_siginfo` from the fd — the `/scheme/event` fd is opened but signals aren't delivered through it |
|
||||
| Signal delivery unverified | High | The `sigprocmask(SIG_BLOCK)` blocks signals but there's no evidence the kernel delivers them via the event fd |
|
||||
| `signalfd_siginfo` read not implemented | Critical | `struct signalfd_siginfo` is defined but never populated via read(2) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Semaphores
|
||||
|
||||
### glibc reference
|
||||
|
||||
```c
|
||||
// sysdeps/pthread/sem_open.c — 216 lines
|
||||
// Uses:
|
||||
// __shm_get_name(name) → canonical path in /dev/shm
|
||||
// O_CREAT+O_EXCL path: creates temp file, writes semaphore header, ftruncate to sizeof(sem_t), mmap
|
||||
// Non-create path: open existing, __sem_check_add_mapping(name, fd) → reuse or mmap
|
||||
// pthread_setcancelstate(PTHREAD_CANCEL_DISABLE) — cancellation-safe
|
||||
// va_arg for mode_t when O_CREAT
|
||||
```
|
||||
|
||||
glibc uses a sophisticated named semaphore implementation:
|
||||
1. **Name canonicalization**: `__shm_get_name` transforms `/name` → `/dev/shm/sem.name`
|
||||
2. **Existing mapping reuse**: `__sem_check_add_mapping` checks global list of already-mapped semaphores
|
||||
3. **Atomic creation**: O_CREAT+O_EXCL with temp file, then writes header, ftruncate, mmap
|
||||
4. **Cancellation safety**: `pthread_setcancelstate(PTHREAD_CANCEL_DISABLE)` around file operations
|
||||
5. **Proper mode_t**: va_arg for mode when O_CREAT
|
||||
6. **Reference counting**: `__sem_check_add_mapping` increments refcount, `sem_close` decrements
|
||||
|
||||
### relibc current state
|
||||
|
||||
```rust
|
||||
// src/header/semaphore/mod.rs — 176 lines
|
||||
// Uses: shm_open(name, O_CREAT|O_EXCL|O_RDWR, mode) → ftruncate → mmap → init
|
||||
// NamedSemaphore struct with RlctSempahore (futex-based)
|
||||
```
|
||||
|
||||
**Core mechanism works** ✅ (shm_open + mmap)
|
||||
**sem_init/destroy/post/wait/trywait/timedwait/clockwait** ✅
|
||||
**sem_open/close/unlink** ✅ (implemented in P3-semaphore-comprehensive.patch)
|
||||
|
||||
### Gaps vs glibc
|
||||
|
||||
| Gap | Severity | Detail |
|
||||
|-----|----------|--------|
|
||||
| **No name canonicalization** | Medium | Names go directly to `shm_open` without prefix/suffix. glibc uses `/dev/shm/sem.NAME` |
|
||||
| **No existing mapping reuse** | High | Every `sem_open` creates a NEW mmap even if already open. Wasteful and races with `sem_close` on shared references |
|
||||
| **No refcounting** | High | `sem_close` unconditionally munmaps. If two threads open the same semaphore, one close breaks the other |
|
||||
| **No cancellation safety** | Low | No `pthread_setcancelstate` around file ops |
|
||||
| **va_list not parsed** | Medium | `sem_open` hardcodes `value=0` when O_CREAT, ignoring mode and initial value from varargs |
|
||||
| **No `__sem_check_add_mapping` equivalent** | High | Opens named sem every time instead of reusing existing mapping |
|
||||
| **No O_NOFOLLOW** | Low | glibc uses `O_NOFOLLOW` for security |
|
||||
|
||||
---
|
||||
|
||||
## 4. Cross-Cutting Gaps
|
||||
|
||||
### Error Handling
|
||||
|
||||
| Area | glibc | relibc |
|
||||
|------|-------|--------|
|
||||
| errno thread-safety | TLS errno via kernel | `Cell<c_int>` per platform ✅ |
|
||||
| errno after close | Preserved (close may overwrite) | `let _ = Sys::close(fd)` — ignores errors ✅ |
|
||||
| EINTR | Handled in syscall wrappers | Not consistently handled ⚠️ |
|
||||
|
||||
### Signal Safety
|
||||
|
||||
| Function | glibc | relibc |
|
||||
|----------|-------|--------|
|
||||
| `sem_post` | AS-safe (futex) | AS-safe ✅ |
|
||||
| `sem_wait` | AS-safe (futex wait, EINTR) | No EINTR handling ⚠️ |
|
||||
| `eventfd` write/read | AS-safe | Not implemented |
|
||||
|
||||
### Thread Safety
|
||||
|
||||
| Area | glibc | relibc |
|
||||
|------|-------|--------|
|
||||
| sem_open refcount | Mutex-protected global list | None — race on same name ⚠️ |
|
||||
| sem_close/sem_unlink | Mutex-protected | None — concurrent close races ⚠️ |
|
||||
| signalfd mask | Per-process (kernel) | Per-call sigprocmask ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 5. Priority Improvement Plan
|
||||
|
||||
### Phase S1: Critical Correctness (1-2 weeks)
|
||||
|
||||
1. **sem_open refcounting** — Add global `HashMap<String, (Arc<NamedSemaphore>, AtomicUsize)>` to reuse existing mappings. `sem_close` decrements refcount, munmaps only when zero.
|
||||
2. **Eventfd implementation** — Implement `eventfd()` via `/scheme/event/eventfd/` using the existing scheme mechanism. Remove libwayland's inline copy.
|
||||
|
||||
### Phase S2: Completeness (2-3 weeks)
|
||||
|
||||
3. **signalfd read path** — Implement read(2) → `signalfd_siginfo` struct population. The `/scheme/event` fd must deliver signal info formatted as `signalfd_siginfo`.
|
||||
4. **sem_open va_list** — Parse `mode_t` and `value` from varargs when O_CREAT. Requires `crate::header::stdarg` or manual stack walking.
|
||||
5. **sem_open name canonicalization** — Prefix names with `/scheme/shm/sem.` for namespace isolation.
|
||||
|
||||
### Phase S3: Robustness (3-4 weeks)
|
||||
|
||||
6. **EINTR handling** — Wrap futex waits to retry on `EINTR`.
|
||||
7. **sem_open cancellation safety** — Add `pthread_setcancelstate` around file ops (if pthread cancellation is supported).
|
||||
8. **eventfd semaphore mode** — Implement `EFD_SEMAPHORE` counting semantics (decrements on read, blocks at 0).
|
||||
|
||||
### Phase S4: POSIX Conformance (2-3 weeks)
|
||||
|
||||
9. **eventfd_read/eventfd_write** — Convenience wrappers.
|
||||
10. **sem_open existing-semaphore reopening** — Handle `(oflag & O_CREAT) == 0` path (open existing without O_EXCL).
|
||||
11. **Signalfd signal delivery verification** — Runtime tests proving signals arrive via signalfd.
|
||||
|
||||
---
|
||||
|
||||
## 6. Eventfd Kernel Requirement
|
||||
|
||||
Unlike signalfd and sem_open which can be implemented in userspace via existing schemes (`/scheme/event`, `shm_open`), eventfd currently relies on libwayland's inline implementation that opens `/scheme/event/eventfd/`. A canonical relibc implementation should use the same path.
|
||||
|
||||
The `/scheme/event` kernel scheme needs:
|
||||
- Support for `eventfd` sub-path
|
||||
- EFD_SEMAPHORE counting semantics in kernel
|
||||
- Non-blocking reads returning `u64` count
|
||||
|
||||
This is a **kernel change** and should be tracked separately from relibc.
|
||||
|
||||
---
|
||||
|
||||
## 7. Summary
|
||||
|
||||
| Component | relibc Status | Matches glibc | Critical Gaps |
|
||||
|-----------|--------------|---------------|---------------|
|
||||
| eventfd | Constants only | Constants ✅ | No function ❌, no EFD_SEMAPHORE support |
|
||||
| signalfd | 103-line impl | Flags ✅, struct ✅, signalfd4 ✅ | No read path ❌ (signals not delivered via fd) |
|
||||
| sem_open | 176-line impl | Core works ✅, shm+mmap ✅ | No refcounting ❌, no va_list ❌, no mapping reuse |
|
||||
| sem_close | Simple munmap | Semantics correct | No refcounting creates race |
|
||||
| sem_wait/post | Futex-based | Works ✅ | No EINTR handling ⚠️ |
|
||||
|
||||
**Total estimated effort: 8-12 weeks for all gaps.**
|
||||
**Critical path: eventfd kernal + signalfd read + sem_open refcounting (Phase S1).**
|
||||
Reference in New Issue
Block a user