kernel: HTTPS through wget, half-close, and /proc/<pid>/fd

busybox wget hands TLS to ssl_client over a socketpair and then does
shutdown(SHUT_WR). That cut the AF_UNIX link both ways, so ssl_client's
reply was a SIGPIPE and every https:// download hung. SHUT_WR is now a
half-close: the peer reads EOF and can still answer. TCP sockets get
shutdown() at all (tcp_shutdown for the FIN); it used to do nothing.

- exec keeps ignored signals and the signal mask, as execve(2) says.
- /proc/<pid>/fd: a link per descriptor -- the path it was opened by,
  socket:[n], pipe:[n], anon_inode:[eventpoll]. /proc/self too. The
  global inode bit moves above pid << 8; at 0x10000 it was /proc/256.
- fstat on a socket or pipe answers S_IFSOCK / S_IFIFO instead of EINVAL.
- poll, select and epoll wake as soon as a pipe, socket or input device
  changes, not on the next tick.
- Pipes are 64 KB of lazily allocated pages, copied in blocks, with
  PIPE_BUF atomicity: 300 MB through `yes | head | wc` in 7 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-24 20:28:03 +02:00
commit 8351e0323b
15 changed files with 434 additions and 71 deletions

View file

@ -24,22 +24,47 @@
#define POLL_MAX_FDS 64
// Sleep one timer tick, or until killed/signaled. Returns 0 to keep
// waiting, -EINTR if the wait must be abandoned.
static int
poll_block_one_tick(void)
// Bumped, under tickslock, by anything that can make a descriptor ready
// sooner than the next tick: see poll_kick().
static uint poll_seq;
// Something a poller may be waiting for has happened. The wait below sleeps
// on the tick channel, so this wakes it now rather than at the tick.
//
// Without it every poll() that found nothing slept out the rest of a 10 ms
// tick whatever arrived in the meantime. busybox's wget polls before each
// read, and downloaded at 85 KB/s -- a segment per tick -- where apk, which
// blocks in read() and is woken by the data, managed 5.8 MB/s over the same
// connection. Called from the network stack's wakeup; anything else that
// makes a file readable can call it too, and until it does it is no worse
// than it was.
void
poll_kick(void)
{
if (myproc()->killed)
return -EINTR;
if (myproc()->pending_signals & ~myproc()->signal_mask)
acquire(&tickslock);
poll_seq++;
release(&tickslock);
wakeup(&ticks);
}
// Sleep one timer tick, or until a poll_kick, or until killed/signaled.
// Returns 1 if a tick went by, 0 if it was a kick -- which must not count
// against a timeout, or a timed poll on a busy network would end early --
// and -EINTR if the wait must be abandoned. epoll waits with it too.
int
poll_wait_tick(void)
{
struct proc *p = myproc();
if (p->killed || (p->pending_signals & ~p->signal_mask))
return -EINTR;
acquire(&tickslock);
uint t0 = ticks;
while (ticks == t0 && !myproc()->killed &&
!(myproc()->pending_signals & ~myproc()->signal_mask))
uint t0 = ticks, s0 = poll_seq;
while (ticks == t0 && poll_seq == s0 && !p->killed &&
!(p->pending_signals & ~p->signal_mask))
sleep(&ticks, &tickslock);
int ticked = ticks != t0;
release(&tickslock);
return 0;
return ticked;
}
// Shared core: fds[] already in kernel memory. timeout_ticks < 0 means
@ -67,9 +92,10 @@ do_poll(struct pollfd *fds, int nfds, int timeout_ticks)
}
if (nready > 0 || timeout_ticks == 0)
return nready;
if (poll_block_one_tick() < 0)
int w = poll_wait_tick();
if (w < 0)
return -EINTR;
if (timeout_ticks > 0)
if (w > 0 && timeout_ticks > 0)
timeout_ticks--;
}
}
@ -215,9 +241,10 @@ sys_select(void)
}
return nready;
}
if (poll_block_one_tick() < 0)
int w = poll_wait_tick();
if (w < 0)
return -EINTR;
if (timeout_ticks > 0)
if (w > 0 && timeout_ticks > 0)
timeout_ticks--;
}
}
@ -287,9 +314,10 @@ sys_pselect6(void)
}
return nready;
}
if (poll_block_one_tick() < 0)
int w = poll_wait_tick();
if (w < 0)
return -EINTR;
if (timeout_ticks > 0)
if (w > 0 && timeout_ticks > 0)
timeout_ticks--;
}
}