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:
parent
906318f2b6
commit
8351e0323b
15 changed files with 434 additions and 71 deletions
|
|
@ -534,6 +534,11 @@ int inet_sendto(void *h, char *buf, int n,
|
|||
uint addr, ushort port);
|
||||
int inet_readable(void *h);
|
||||
int inet_hup(void *h);
|
||||
int inet_shutdown(void *h, int how);
|
||||
int file_describe(struct file *f, char *buf, int cap);
|
||||
int proc_fd_mask(int pid, uint64 mask[2]);
|
||||
int proc_fd_link(int pid, int fd, char *buf, int cap);
|
||||
const char *ev_kind_name(struct evobj *o);
|
||||
void inet_close(void *h);
|
||||
|
||||
// proc.c
|
||||
|
|
@ -552,6 +557,8 @@ int sigsend(int pid, int sig);
|
|||
void alarm_arm(struct proc *p, uint after, uint every);
|
||||
uint alarm_left(struct proc *p);
|
||||
void alarm_thread_start(void);
|
||||
void poll_kick(void);
|
||||
int poll_wait_tick(void);
|
||||
int sig_default_kills(int sig);
|
||||
int proc_exists(int);
|
||||
int proc_num(void);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ struct file {
|
|||
int memfd_seg;
|
||||
struct evobj *ev;
|
||||
uint64 off;
|
||||
// What /proc/<pid>/fd shows. `ino` names the open thing (one per socket,
|
||||
// one per pipe); `path` is the absolute path an inode was opened by, as
|
||||
// written -- symlinks along it are not resolved. kmalloc'd, or 0.
|
||||
uint64 ino;
|
||||
char *path;
|
||||
struct file *fnext; // file.c's free list, while ref is 0
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -330,6 +330,9 @@ struct socket {
|
|||
// bidirectional connection.
|
||||
struct socket *peer;
|
||||
int peer_closed;
|
||||
// shutdown(SHUT_WR): this end has said all it will. The peer reads EOF but
|
||||
// can still answer, which is what a request/response helper depends on.
|
||||
int wr_shut;
|
||||
|
||||
char data[SOCKBUF];
|
||||
uint nread, nwrite;
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ input_sync(int dev)
|
|||
return;
|
||||
input_report(dev, EV_SYN, SYN_REPORT, 0);
|
||||
wakeup(&idev[dev]);
|
||||
poll_kick(); // Xorg polls its devices; a mouse moves at the next tick otherwise
|
||||
}
|
||||
|
||||
int
|
||||
|
|
|
|||
|
|
@ -574,11 +574,15 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
// first delivered signal jump to a stale address in the new image.
|
||||
// mmap_next likewise belongs to the old address space; left alone it
|
||||
// could hand out mmap ranges overlapping the new image's layout.
|
||||
// Ignored signals stay ignored, and the mask is inherited (execve(2)):
|
||||
// neither points into the old image. `nohup` and every program that ignores
|
||||
// SIGPIPE before running a helper depend on it.
|
||||
memset(myproc()->vma, 0, sizeof(myproc()->vma));
|
||||
memset(myproc()->sig_handler, 0, sizeof(myproc()->sig_handler));
|
||||
for (int i = 0; i < 32; i++)
|
||||
if (myproc()->sig_handler[i] != 1)
|
||||
myproc()->sig_handler[i] = 0;
|
||||
memset(myproc()->sig_restorer, 0, sizeof(myproc()->sig_restorer));
|
||||
myproc()->fs_base = 0;
|
||||
myproc()->signal_mask = 0;
|
||||
myproc()->pending_signals = 0;
|
||||
myproc()->in_signal = 0;
|
||||
myproc()->sigsusp_active = 0;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ struct {
|
|||
struct file *free;
|
||||
struct kmem_cache *more;
|
||||
uint n;
|
||||
uint64 serial; // the last ino handed out
|
||||
} ftable;
|
||||
|
||||
void
|
||||
|
|
@ -63,6 +64,8 @@ filealloc(void)
|
|||
ftable.n++;
|
||||
}
|
||||
f->ref = 1;
|
||||
f->ino = ++ftable.serial;
|
||||
f->path = 0;
|
||||
FSSTAT_TAKE(fsstat.file_used, fsstat.file_peak); // 0 -> 1
|
||||
// Cleared here rather than left to the caller: every creator sets
|
||||
// readable/writable/off, but only open() sets nonblock, so a struct file
|
||||
|
|
@ -101,12 +104,16 @@ fileclose(struct file *f)
|
|||
lock_close_file(f);
|
||||
ff = *f;
|
||||
f->ref = 0;
|
||||
f->path = 0;
|
||||
f->type = FD_NONE;
|
||||
f->fnext = ftable.free;
|
||||
ftable.free = f;
|
||||
FSSTAT_DROP(fsstat.file_used); // 1 -> 0
|
||||
release(&ftable.lock);
|
||||
|
||||
if(ff.path)
|
||||
kmfree(ff.path);
|
||||
|
||||
if(ff.type == FD_MEMFD)
|
||||
shm_detach(ff.memfd_seg); // the last mapper frees the pages
|
||||
else if(ff.type == FD_EVENT)
|
||||
|
|
@ -129,6 +136,42 @@ fileclose(struct file *f)
|
|||
}
|
||||
}
|
||||
|
||||
// What /proc/<pid>/fd/N points at, in Linux's spelling: the path, or
|
||||
// "socket:[ino]", "pipe:[ino]", "anon_inode:[eventpoll]". Returns the length.
|
||||
// ftable.lock is what keeps `path` alive: fileclose() takes it from the file
|
||||
// under the lock and frees it after.
|
||||
int
|
||||
file_describe(struct file *f, char *buf, int cap)
|
||||
{
|
||||
const char *pre = 0, *s = 0;
|
||||
int n = 0;
|
||||
|
||||
acquire(&ftable.lock);
|
||||
if(f->ref > 0){
|
||||
switch(f->type){
|
||||
case FD_INODE: s = f->path; if(!s) pre = "inode:["; break;
|
||||
case FD_SOCKET: pre = "socket:["; break;
|
||||
case FD_PIPE: pre = "pipe:["; break;
|
||||
case FD_MEMFD: s = "/memfd: (deleted)"; break;
|
||||
case FD_EVENT: s = ev_kind_name(f->ev); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if(pre){
|
||||
char num[24];
|
||||
int k = 0;
|
||||
uint64 v = f->type == FD_INODE && f->ip ? f->ip->inum : f->ino;
|
||||
do { num[k++] = '0' + v % 10; v /= 10; } while(v);
|
||||
while(*pre && n < cap) buf[n++] = *pre++;
|
||||
while(k && n < cap) buf[n++] = num[--k];
|
||||
if(n < cap) buf[n++] = ']';
|
||||
} else if(s){
|
||||
while(*s && n < cap) buf[n++] = *s++;
|
||||
}
|
||||
release(&ftable.lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
filestat(struct file *f, struct stat *st)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,24 +11,40 @@
|
|||
#include "uapi/posix.h"
|
||||
#include "uapi/errno.h"
|
||||
|
||||
#define PIPESIZE 512
|
||||
// 64 KB, as on Linux, in pages taken the first time each is written.
|
||||
//
|
||||
// It was 512 bytes in the struct. That is xv6's size, and it made every pipe a
|
||||
// handoff per half kilobyte: busybox's wget does TLS in a helper process that
|
||||
// talks to it over pipes, a TLS record is 16 KB, and an HTTPS download that
|
||||
// should have taken nine seconds was still going after a minute and a half. A
|
||||
// shell pipeline that only ever moves a line still costs one page.
|
||||
#define PIPESIZE 65536
|
||||
#define PIPE_PAGES (PIPESIZE / PGSIZE)
|
||||
#define PIPE_BUF 4096 // writes up to this size are not interleaved
|
||||
|
||||
struct pipe {
|
||||
struct spinlock lock;
|
||||
char data[PIPESIZE];
|
||||
char *page[PIPE_PAGES];
|
||||
uint nread; // number of bytes read
|
||||
uint nwrite; // number of bytes written
|
||||
int readopen; // read fd is still open
|
||||
int writeopen; // write fd is still open
|
||||
};
|
||||
|
||||
// A pipe is about 560 bytes and used to come from kalloc(), which is a whole
|
||||
// 4096-byte page -- so every `|` in a shell command burned three and a half
|
||||
// kilobytes it never touched. This is what kmem_cache exists for, and the
|
||||
// cache is created on first use rather than at boot so a kernel that never
|
||||
// opens a pipe holds no page for one.
|
||||
// The struct is small and comes from a cache; the buffer is pages. The cache
|
||||
// is created on first use so a kernel that never opens a pipe holds no page
|
||||
// for one.
|
||||
static struct kmem_cache *pipe_cache;
|
||||
|
||||
static void
|
||||
pipe_free(struct pipe *p)
|
||||
{
|
||||
for(int i = 0; i < PIPE_PAGES; i++)
|
||||
if(p->page[i])
|
||||
kfree(p->page[i]);
|
||||
kmem_cache_free(pipe_cache, p);
|
||||
}
|
||||
|
||||
int
|
||||
pipealloc(struct file **f0, struct file **f1)
|
||||
{
|
||||
|
|
@ -42,6 +58,7 @@ pipealloc(struct file **f0, struct file **f1)
|
|||
goto bad;
|
||||
if((p = kmem_cache_alloc(pipe_cache)) == 0)
|
||||
goto bad;
|
||||
memset(p->page, 0, sizeof(p->page));
|
||||
p->readopen = 1;
|
||||
p->writeopen = 1;
|
||||
p->nwrite = 0;
|
||||
|
|
@ -55,6 +72,7 @@ pipealloc(struct file **f0, struct file **f1)
|
|||
(*f1)->readable = 0;
|
||||
(*f1)->writable = 1;
|
||||
(*f1)->pipe = p;
|
||||
(*f1)->ino = (*f0)->ino; // one pipe, as /proc/<pid>/fd names it
|
||||
return 0;
|
||||
|
||||
//PAGEBREAK: 20
|
||||
|
|
@ -81,9 +99,10 @@ pipeclose(struct pipe *p, int writable)
|
|||
}
|
||||
if(p->readopen == 0 && p->writeopen == 0){
|
||||
release(&p->lock);
|
||||
kmem_cache_free(pipe_cache, p);
|
||||
pipe_free(p);
|
||||
} else
|
||||
release(&p->lock);
|
||||
poll_kick(); // a poller sees the hangup now, not at the tick
|
||||
}
|
||||
|
||||
//PAGEBREAK: 40
|
||||
|
|
@ -108,8 +127,12 @@ pipewrite(struct pipe *p, char *addr, int n)
|
|||
return -EPIPE;
|
||||
}
|
||||
|
||||
for(i = 0; i < n; i++){
|
||||
while(p->nwrite == p->nread + PIPESIZE){ //DOC: pipewrite-full
|
||||
// A write of PIPE_BUF or less goes in whole or waits for room, so two
|
||||
// writers' records never interleave; a larger one goes in as room appears.
|
||||
uint want_room = (n <= PIPE_BUF) ? (uint)n : 1;
|
||||
i = 0;
|
||||
while(i < n){
|
||||
while(PIPESIZE - (p->nwrite - p->nread) < want_room){ //DOC: pipewrite-full
|
||||
if(p->readopen == 0){
|
||||
release(&p->lock);
|
||||
sigsend(myproc()->pid, SIGPIPE);
|
||||
|
|
@ -120,12 +143,32 @@ pipewrite(struct pipe *p, char *addr, int n)
|
|||
return -1;
|
||||
}
|
||||
wakeup(&p->nread);
|
||||
release(&p->lock);
|
||||
poll_kick();
|
||||
acquire(&p->lock);
|
||||
if(PIPESIZE - (p->nwrite - p->nread) >= want_room)
|
||||
break;
|
||||
sleep(&p->nwrite, &p->lock); //DOC: pipewrite-sleep
|
||||
}
|
||||
p->data[p->nwrite++ % PIPESIZE] = addr[i];
|
||||
// One run: up to the end of this page, of what is left, of the room.
|
||||
uint off = p->nwrite % PIPESIZE;
|
||||
uint run = PGSIZE - off % PGSIZE;
|
||||
uint room = PIPESIZE - (p->nwrite - p->nread);
|
||||
if(run > (uint)(n - i)) run = (uint)(n - i);
|
||||
if(run > room) run = room;
|
||||
char **pg = &p->page[off / PGSIZE];
|
||||
if(*pg == 0 && (*pg = kalloc()) == 0){
|
||||
release(&p->lock);
|
||||
return i > 0 ? i : -ENOMEM;
|
||||
}
|
||||
memmove(*pg + off % PGSIZE, addr + i, run);
|
||||
p->nwrite += run;
|
||||
i += run;
|
||||
want_room = 1;
|
||||
}
|
||||
wakeup(&p->nread); //DOC: pipewrite-wakeup1
|
||||
release(&p->lock);
|
||||
poll_kick();
|
||||
return n;
|
||||
}
|
||||
|
||||
|
|
@ -142,13 +185,19 @@ piperead(struct pipe *p, char *addr, int n)
|
|||
}
|
||||
sleep(&p->nread, &p->lock); //DOC: piperead-sleep
|
||||
}
|
||||
for(i = 0; i < n; i++){ //DOC: piperead-copy
|
||||
if(p->nread == p->nwrite)
|
||||
break;
|
||||
addr[i] = p->data[p->nread++ % PIPESIZE];
|
||||
i = 0;
|
||||
while(i < n && p->nread != p->nwrite){ //DOC: piperead-copy
|
||||
uint off = p->nread % PIPESIZE;
|
||||
uint run = PGSIZE - off % PGSIZE;
|
||||
if(run > (uint)(n - i)) run = (uint)(n - i);
|
||||
if(run > p->nwrite - p->nread) run = p->nwrite - p->nread;
|
||||
memmove(addr + i, p->page[off / PGSIZE] + off % PGSIZE, run);
|
||||
p->nread += run;
|
||||
i += run;
|
||||
}
|
||||
wakeup(&p->nwrite); //DOC: piperead-wakeup
|
||||
release(&p->lock);
|
||||
poll_kick(); // a writer polling for room
|
||||
return i;
|
||||
}
|
||||
|
||||
|
|
@ -168,8 +217,8 @@ pipe_poll(struct pipe *p, int readable, int writable)
|
|||
if(writable){
|
||||
if(p->readopen == 0)
|
||||
rev |= POLLERR;
|
||||
else if(p->nwrite < p->nread + PIPESIZE)
|
||||
rev |= POLLOUT;
|
||||
else if(PIPESIZE - (p->nwrite - p->nread) >= PIPE_BUF)
|
||||
rev |= POLLOUT; // room for a whole PIPE_BUF write, as on Linux
|
||||
}
|
||||
release(&p->lock);
|
||||
return rev;
|
||||
|
|
|
|||
|
|
@ -43,18 +43,22 @@
|
|||
// identity of the object.
|
||||
//
|
||||
// 1 the root, /proc
|
||||
// 2 /proc/self
|
||||
// PROCFS_GLOBAL | id a global file, id being the PROC_* minor that
|
||||
// kernel/dev/procfs.c already uses
|
||||
// pid << 8 the directory /proc/<pid>
|
||||
// (pid << 8) | fileid a file inside it
|
||||
// (pid << 8) | 0x80 | fd /proc/<pid>/fd/<fd>
|
||||
//
|
||||
// pid is bounded by NPROC and file ids by PF_MAX, so the ranges cannot
|
||||
// collide. Shifting by 8 rather than packing tightly keeps the numbers
|
||||
// readable in a debugger, which matters more than density here.
|
||||
// File ids stay below 0x80 and fds below 128 (NOFILE). pids are not bounded
|
||||
// by NPROC -- they count up -- so the global bit sits far above any pid << 8;
|
||||
// at 0x10000 it was /proc/256. Shifting by 8 rather than packing tightly keeps
|
||||
// the numbers readable in a debugger, which matters more than density here.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define PROCFS_ROOT 1ULL
|
||||
#define PROCFS_GLOBAL 0x10000ULL
|
||||
#define PROCFS_SELF 2ULL
|
||||
#define PROCFS_GLOBAL (1ULL << 40)
|
||||
|
||||
#define PF_DIR 0 // the directory itself
|
||||
#define PF_STAT 1
|
||||
|
|
@ -63,10 +67,14 @@
|
|||
#define PF_STATM 4
|
||||
#define PF_EXE 5
|
||||
#define PF_SCHEDSTAT 6
|
||||
#define PF_MAX 6
|
||||
#define PF_FD 7 // the directory /proc/<pid>/fd
|
||||
#define PF_MAX 7
|
||||
#define PF_FDENT 0x80 // | fd: one link in it
|
||||
|
||||
#define INO_PID(ino) ((int)((ino) >> 8))
|
||||
#define INO_FILE(ino) ((int)((ino) & 0xFF))
|
||||
#define INO_IS_FDENT(ino) (!INO_IS_GLOBAL(ino) && (INO_FILE(ino) & PF_FDENT))
|
||||
#define INO_FD(ino) (INO_FILE(ino) & 0x7F)
|
||||
#define INO_IS_GLOBAL(ino) (((ino) & PROCFS_GLOBAL) != 0)
|
||||
|
||||
// The global files, by the name they appear under and the PROC_* id that
|
||||
|
|
@ -121,6 +129,9 @@ static const struct global_file pidfiles[] = {
|
|||
// inventing a wait time that is not measured would be worse than saying
|
||||
// something true in its place.
|
||||
{ "schedstat", PF_SCHEDSTAT },
|
||||
// One symlink per open descriptor, named by its number and pointing at the
|
||||
// path it was opened by, or at "socket:[ino]" and the like.
|
||||
{ "fd", PF_FD },
|
||||
};
|
||||
#define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0])))
|
||||
|
||||
|
|
@ -267,6 +278,15 @@ gen_file(uint64 ino, char *buf, int cap)
|
|||
|
||||
if (INO_IS_GLOBAL(ino))
|
||||
return proc_gen_global((int)(ino & 0xFF), buf, cap);
|
||||
// Whoever is asking: the one file whose content depends on the reader.
|
||||
if (ino == PROCFS_SELF) {
|
||||
sbputi(&s, myproc()->pid);
|
||||
return s.n;
|
||||
}
|
||||
if (INO_IS_FDENT(ino)) {
|
||||
int n = proc_fd_link(INO_PID(ino), INO_FD(ino), buf, cap);
|
||||
return n > 0 ? n : 0;
|
||||
}
|
||||
|
||||
if (proc_snapshot_pid(INO_PID(ino), &pi) < 0)
|
||||
return 0; // it exited between lookup and read
|
||||
|
|
@ -310,7 +330,8 @@ procfs_iload(struct inode *ip)
|
|||
ip->showmode = 1;
|
||||
ip->atime = ip->mtime = ip->ctime = 0;
|
||||
|
||||
if (ino == PROCFS_ROOT || (!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_DIR)) {
|
||||
if (ino == PROCFS_ROOT ||
|
||||
(!INO_IS_GLOBAL(ino) && (INO_FILE(ino) == PF_DIR || INO_FILE(ino) == PF_FD))) {
|
||||
ip->type = T_DIR;
|
||||
ip->size = 0;
|
||||
ip->nlink = 2;
|
||||
|
|
@ -329,8 +350,12 @@ procfs_iload(struct inode *ip)
|
|||
// symlink whose size is 0 resolves to the empty string -- which is worse
|
||||
// than the regular file was, because it looks like it worked. Generating it
|
||||
// here costs one small render per stat() of one file.
|
||||
if (!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_EXE) {
|
||||
char path[64];
|
||||
// /proc/self and /proc/<pid>/fd/N are links too. Their targets can change
|
||||
// under a cached inode, which is why readlink() does not trust this size
|
||||
// for procfs.
|
||||
if (ino == PROCFS_SELF || INO_IS_FDENT(ino) ||
|
||||
(!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_EXE)) {
|
||||
char path[256];
|
||||
int len = gen_file(ino, path, sizeof(path));
|
||||
ip->type = T_SYMLINK;
|
||||
ip->size = len > 0 ? (uint64)len : 0;
|
||||
|
|
@ -469,8 +494,19 @@ procfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
|||
|
||||
if (namecmp(name, ".") == 0)
|
||||
return iget(dp->dev, dp->inum);
|
||||
if (namecmp(name, "..") == 0)
|
||||
return iget(dp->dev, PROCFS_ROOT); // /proc/<pid>/.. is /proc
|
||||
if (namecmp(name, "..") == 0) // /proc/<pid>/.. is /proc
|
||||
return iget(dp->dev, INO_FILE(dp->inum) == PF_FD ? (dp->inum & ~0xFFULL)
|
||||
: PROCFS_ROOT);
|
||||
|
||||
// Inside /proc/<pid>/fd/: a number that is an open descriptor.
|
||||
if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_FD) {
|
||||
char tmp[4];
|
||||
int fd = name_to_pid(name);
|
||||
if (fd < 0 || fd >= NOFILE || fd >= 128 ||
|
||||
proc_fd_link(INO_PID(dp->inum), fd, tmp, sizeof(tmp)) < 0)
|
||||
return 0;
|
||||
return iget(dp->dev, (dp->inum & ~0xFFULL) | PF_FDENT | fd);
|
||||
}
|
||||
|
||||
// Inside /proc/<pid>/
|
||||
if (dp->inum != PROCFS_ROOT) {
|
||||
|
|
@ -480,6 +516,9 @@ procfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (namecmp(name, "self") == 0)
|
||||
return iget(dp->dev, PROCFS_SELF);
|
||||
|
||||
// In /proc itself: a number is a process, a word is a global file.
|
||||
pid = name_to_pid(name);
|
||||
if (pid > 0) {
|
||||
|
|
@ -516,6 +555,7 @@ struct linux_dirent64 {
|
|||
|
||||
#define DT_DIR_ 4
|
||||
#define DT_REG_ 8
|
||||
#define DT_LNK_ 10
|
||||
|
||||
static int
|
||||
emit(char *out, int cap, int used, uint64 ino, int type, const char *name)
|
||||
|
|
@ -574,17 +614,30 @@ procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off)
|
|||
if (dp->inum == PROCFS_ROOT) {
|
||||
EMIT(PROCFS_ROOT, DT_DIR_, ".");
|
||||
EMIT(PROCFS_ROOT, DT_DIR_, "..");
|
||||
EMIT(PROCFS_SELF, DT_LNK_, "self");
|
||||
for (i = 0; i < NGLOBALS; i++)
|
||||
EMIT(PROCFS_GLOBAL | globals[i].id, DT_REG_, globals[i].name);
|
||||
for (i = 0; proc_snapshot(i, &pi) == 0; i++) {
|
||||
utoa((uint)pi.pid, name);
|
||||
EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name);
|
||||
}
|
||||
} else if (INO_FILE(dp->inum) == PF_FD) {
|
||||
uint64 mask[2];
|
||||
EMIT(dp->inum, DT_DIR_, ".");
|
||||
EMIT(dp->inum & ~0xFFULL, DT_DIR_, "..");
|
||||
if (proc_fd_mask(INO_PID(dp->inum), mask) == 0)
|
||||
for (i = 0; i < 128; i++)
|
||||
if (mask[i / 64] & (1ULL << (i % 64))) {
|
||||
utoa((uint)i, name);
|
||||
EMIT((dp->inum & ~0xFFULL) | PF_FDENT | i, DT_LNK_, name);
|
||||
}
|
||||
} else {
|
||||
EMIT(dp->inum, DT_DIR_, ".");
|
||||
EMIT(PROCFS_ROOT, DT_DIR_, "..");
|
||||
for (i = 0; i < NPIDFILES; i++)
|
||||
EMIT((dp->inum & ~0xFFULL) | pidfiles[i].id, DT_REG_, pidfiles[i].name);
|
||||
EMIT((dp->inum & ~0xFFULL) | pidfiles[i].id,
|
||||
pidfiles[i].id == PF_FD ? DT_DIR_ : pidfiles[i].id == PF_EXE ? DT_LNK_ : DT_REG_,
|
||||
pidfiles[i].name);
|
||||
}
|
||||
|
||||
full:
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ struct inet_sock {
|
|||
int connected;
|
||||
int conn_failed;
|
||||
int peer_closed; // FIN seen, or the connection died
|
||||
int wr_shut; // shutdown(SHUT_WR): our FIN is sent
|
||||
int listening;
|
||||
|
||||
// Connections lwIP has completed but nobody has accept()ed yet.
|
||||
|
|
@ -194,6 +195,7 @@ net_wake(void)
|
|||
netwait_seq++;
|
||||
release(&netwait_lock);
|
||||
wakeup(&netwait_seq);
|
||||
poll_kick(); // and poll/select/epoll waiters, not at the tick
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -958,7 +960,7 @@ inet_sendto(void *h, char *buf, int n, uint addr, ushort port)
|
|||
uint seen;
|
||||
|
||||
lwip_sys_lock();
|
||||
if (is->peer_closed || is->tpcb == 0) {
|
||||
if (is->peer_closed || is->wr_shut || is->tpcb == 0) {
|
||||
lwip_sys_unlock();
|
||||
// Same contract as a pipe with no reader, which is what the AF_UNIX side
|
||||
// does: the signal first, then the error.
|
||||
|
|
@ -1005,6 +1007,32 @@ inet_write(void *h, char *buf, int n)
|
|||
return inet_sendto(h, buf, n, 0, 0);
|
||||
}
|
||||
|
||||
// shutdown(2) on a TCP socket. SHUT_WR sends our FIN and the peer can keep
|
||||
// sending; SHUT_RD is local, reads see EOF. The pcb is never freed here --
|
||||
// tcp_shutdown(pcb, 1, 1) would free it -- so close() still owns it.
|
||||
int
|
||||
inet_shutdown(void *h, int how)
|
||||
{
|
||||
struct inet_sock *is = h;
|
||||
|
||||
if (is->type != SOCK_STREAM)
|
||||
return 0;
|
||||
lwip_sys_lock();
|
||||
if (is->tpcb == 0 || is->listening || !is->connected) {
|
||||
lwip_sys_unlock();
|
||||
return -ENOTCONN;
|
||||
}
|
||||
if ((how == SHUT_WR || how == SHUT_RDWR) && !is->wr_shut) {
|
||||
is->wr_shut = 1;
|
||||
tcp_shutdown(is->tpcb, 0, 1);
|
||||
}
|
||||
if (how == SHUT_RD || how == SHUT_RDWR)
|
||||
is->peer_closed = 1;
|
||||
lwip_sys_unlock();
|
||||
net_wake();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
inet_readable(void *h)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@ sockclose(struct socket *s)
|
|||
s->ref = 0;
|
||||
wakeup(&s->nqueue); // an accept() blocked on this listener
|
||||
release(&stab.lock);
|
||||
poll_kick(); // the peer's poll() sees the hangup now
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -459,6 +460,7 @@ sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout
|
|||
// on a full buffer that this call has just made room in.
|
||||
wakeup(&s->nwrite);
|
||||
release(&stab.lock);
|
||||
poll_kick(); // a writer polling for room
|
||||
|
||||
// Outside the lock, because fileclose can sleep.
|
||||
if(fout){
|
||||
|
|
@ -520,7 +522,7 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
|
|||
// look for it. Same contract as a pipe with no reader: a signal, then an
|
||||
// error. A process that writes to a dead connection and is not told about
|
||||
// it spins forever believing it is making progress.
|
||||
if(s->peer == 0){
|
||||
if(s->peer == 0 || s->wr_shut){
|
||||
release(&stab.lock);
|
||||
sigsend(myproc()->pid, SIGPIPE);
|
||||
return -EPIPE;
|
||||
|
|
@ -569,6 +571,9 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
|
|||
|
||||
wakeup(&s->peer->nread);
|
||||
release(&stab.lock);
|
||||
// poll() and epoll waiters, now rather than at the next tick: an X client
|
||||
// and its server talk over these, a request and a reply at a time.
|
||||
poll_kick();
|
||||
return i;
|
||||
}
|
||||
|
||||
|
|
@ -854,6 +859,7 @@ sockconnect(struct socket *s, uint dev, uint64 ino)
|
|||
wakeup(&srv->nqueue); // an accept() may be blocked on this
|
||||
|
||||
release(&stab.lock);
|
||||
poll_kick(); // or a listener in poll(), as Xorg's is
|
||||
|
||||
// Deliberately does *not* wait for accept(). On Unix a queued connection is
|
||||
// already established: the client may write, and the bytes sit in the
|
||||
|
|
@ -872,6 +878,9 @@ sockconnect(struct socket *s, uint dev, uint64 ino)
|
|||
int
|
||||
sock_shutdown(struct socket *s, int how)
|
||||
{
|
||||
if(s->domain == AF_INET)
|
||||
return inet_shutdown(s->inet, how);
|
||||
|
||||
acquire(&stab.lock);
|
||||
if(s->state != SS_CONNECTED){
|
||||
release(&stab.lock);
|
||||
|
|
@ -882,9 +891,20 @@ sock_shutdown(struct socket *s, int how)
|
|||
s->peer_closed = 1;
|
||||
wakeup(&s->nread);
|
||||
}
|
||||
if(how == SHUT_WR || how == SHUT_RDWR)
|
||||
if(how == SHUT_RDWR)
|
||||
orphan_peer(s);
|
||||
else if(how == SHUT_WR){
|
||||
// Half-close: the peer sees EOF once it has read what is there, and the
|
||||
// link stays, so its reply still reaches us. Cutting the link here made
|
||||
// the reply a SIGPIPE -- busybox wget does exactly this to ssl_client.
|
||||
s->wr_shut = 1;
|
||||
if(s->peer){
|
||||
s->peer->peer_closed = 1;
|
||||
wakeup(&s->peer->nread);
|
||||
}
|
||||
}
|
||||
release(&stab.lock);
|
||||
poll_kick();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1907,6 +1907,50 @@ proc_snapshot_pid(int pid, struct procinfo *out)
|
|||
return -1;
|
||||
}
|
||||
|
||||
// /proc/<pid>/fd: which descriptors are open, and what one points at. The
|
||||
// ptable lock keeps the process from being reaped -- exit() closes its files
|
||||
// first -- and file_describe() holds the file steady itself.
|
||||
int
|
||||
proc_fd_mask(int pid, uint64 mask[2])
|
||||
{
|
||||
struct proc *p;
|
||||
int r = -1;
|
||||
|
||||
mask[0] = mask[1] = 0;
|
||||
acquire(&ptable.lock);
|
||||
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
|
||||
if (p->state == UNUSED || p->pid != pid)
|
||||
continue;
|
||||
for (int fd = 0; fd < NOFILE && fd < 128; fd++)
|
||||
if (p->ofile[fd])
|
||||
mask[fd / 64] |= 1ULL << (fd % 64);
|
||||
r = 0;
|
||||
break;
|
||||
}
|
||||
release(&ptable.lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
proc_fd_link(int pid, int fd, char *buf, int cap)
|
||||
{
|
||||
struct proc *p;
|
||||
int r = -1;
|
||||
|
||||
if (fd < 0 || fd >= NOFILE)
|
||||
return -1;
|
||||
acquire(&ptable.lock);
|
||||
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
|
||||
if (p->state == UNUSED || p->pid != pid)
|
||||
continue;
|
||||
if (p->ofile[fd])
|
||||
r = file_describe(p->ofile[fd], buf, cap);
|
||||
break;
|
||||
}
|
||||
release(&ptable.lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
int proc_num(void)
|
||||
{
|
||||
struct proc *p;
|
||||
|
|
|
|||
|
|
@ -113,19 +113,13 @@ clock_ns(int clock)
|
|||
return sec * 1000000000ULL + usec * 1000ULL;
|
||||
}
|
||||
|
||||
// One tick, or until a signal or kill makes waiting pointless.
|
||||
// One tick, a poll_kick, or a signal or kill that makes waiting pointless:
|
||||
// poll's wait, so data arriving wakes an epoll_wait as it wakes a poll.
|
||||
// Returns 1 for a tick, 0 for a kick, -EINTR.
|
||||
static int
|
||||
ev_block(void)
|
||||
{
|
||||
struct proc *p = myproc();
|
||||
if (p->killed || (p->pending_signals & ~p->signal_mask))
|
||||
return -EINTR;
|
||||
acquire(&tickslock);
|
||||
uint t0 = ticks;
|
||||
while (ticks == t0 && !p->killed && !(p->pending_signals & ~p->signal_mask))
|
||||
sleep(&ticks, &tickslock);
|
||||
release(&tickslock);
|
||||
return 0;
|
||||
return poll_wait_tick();
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
@ -178,6 +172,19 @@ ev_arg(int n, int kind, struct file **pf)
|
|||
return f->ev;
|
||||
}
|
||||
|
||||
// The name Linux gives each kind in /proc/<pid>/fd.
|
||||
const char *
|
||||
ev_kind_name(struct evobj *o)
|
||||
{
|
||||
switch (o ? o->kind : 0) {
|
||||
case EV_EVENTFD: return "anon_inode:[eventfd]";
|
||||
case EV_TIMERFD: return "anon_inode:[timerfd]";
|
||||
case EV_SIGNALFD: return "anon_inode:[signalfd]";
|
||||
case EV_EPOLL: return "anon_inode:[eventpoll]";
|
||||
}
|
||||
return "anon_inode:[event]";
|
||||
}
|
||||
|
||||
// Called by fileclose() for the last reference.
|
||||
void
|
||||
ev_close(struct evobj *o)
|
||||
|
|
@ -714,11 +721,12 @@ epoll_wait_common(int timeout_ms)
|
|||
got = ep_ready(o, buf, max);
|
||||
if (got > 0 || ticks_left == 0)
|
||||
break;
|
||||
if (ev_block() < 0) {
|
||||
int w = ev_block();
|
||||
if (w < 0) {
|
||||
got = -EINTR;
|
||||
break;
|
||||
}
|
||||
if (ticks_left > 0)
|
||||
if (w > 0 && ticks_left > 0)
|
||||
ticks_left--;
|
||||
}
|
||||
if (got > 0 && copy_to_user(uevents, buf, got * sizeof(*buf)) < 0)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "uapi/posix.h"
|
||||
#include "core/syscall.h"
|
||||
#include "core/uaccess.h"
|
||||
#include "core/kmem.h"
|
||||
#include "uapi/errno.h"
|
||||
#include "fs/schfs.h" /* SCHFS_BLKSIZE, SCHFS_NAME_MAX — statfs reports them */
|
||||
#include "gfx/gfx.h" /* gfx_cols/gfx_rows — TIOCGWINSZ reports them */
|
||||
|
|
@ -139,6 +140,53 @@ fdalloc(struct file *f)
|
|||
return -1;
|
||||
}
|
||||
|
||||
// Remember the absolute path `f` was opened by, for /proc/<pid>/fd. `path` is
|
||||
// joined to the directory it was resolved from -- the cwd, or dirfd's own
|
||||
// remembered path -- and `.`, `..` and repeated slashes are folded out. This
|
||||
// is lexical: a symlink along the way stays in the name. Nothing depends on
|
||||
// it being there, so running out of memory just leaves it unset.
|
||||
static void
|
||||
file_setpath(struct file *f, int dirfd, const char *path)
|
||||
{
|
||||
const char *base = "";
|
||||
if (path[0] != '/') {
|
||||
struct file *d;
|
||||
if (dirfd == AT_FDCWD)
|
||||
base = myproc()->cwd_path;
|
||||
else if (dirfd >= 0 && dirfd < NOFILE && (d = myproc()->ofile[dirfd]) && d->path)
|
||||
base = d->path;
|
||||
else
|
||||
return;
|
||||
}
|
||||
char *buf = kalloc();
|
||||
if (!buf) return;
|
||||
int n = 0; // buf is "/a/b", no trailing slash
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
const char *s = pass ? path : base;
|
||||
for (;;) {
|
||||
while (*s == '/') s++;
|
||||
const char *e = s;
|
||||
while (*e && *e != '/') e++;
|
||||
int len = (int)(e - s);
|
||||
if (len == 0) break;
|
||||
if (len == 2 && s[0] == '.' && s[1] == '.') {
|
||||
while (n > 0 && buf[--n] != '/') ;
|
||||
} else if (!(len == 1 && s[0] == '.')) {
|
||||
if (n + 1 + len >= PGSIZE) { kfree(buf); return; }
|
||||
buf[n++] = '/';
|
||||
memmove(buf + n, s, len);
|
||||
n += len;
|
||||
}
|
||||
s = e;
|
||||
}
|
||||
}
|
||||
if (n == 0) buf[n++] = '/';
|
||||
buf[n] = 0;
|
||||
if ((f->path = kmalloc(n + 1)) != 0)
|
||||
memmove(f->path, buf, n + 1);
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
// Resolves a dirfd (AT_FDCWD or a real fd) to the inode a relative path
|
||||
// should be resolved from. 0 on an invalid dirfd.
|
||||
static struct inode *
|
||||
|
|
@ -602,6 +650,7 @@ addr_t sys_open(void)
|
|||
}
|
||||
iunlock(ip); end_op();
|
||||
f->type = FD_INODE; f->ip = ip;
|
||||
file_setpath(f, AT_FDCWD, path);
|
||||
f->off = (omode & O_APPEND) ? ip->size : 0;
|
||||
f->readable = !(omode & O_WRONLY);
|
||||
f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
|
||||
|
|
@ -659,6 +708,7 @@ addr_t sys_openat(void)
|
|||
uint64 start = (omode & O_APPEND) ? ip->size : 0;
|
||||
iunlock(ip); end_op();
|
||||
f->type = FD_INODE; f->ip = ip; f->off = start;
|
||||
file_setpath(f, dirfd, path);
|
||||
f->readable = !(omode & O_WRONLY);
|
||||
f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
|
||||
f->nonblock = (omode & O_NONBLOCK) != 0;
|
||||
|
|
@ -697,6 +747,19 @@ addr_t sys_fstat(void)
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
// A socket, a pipe or an anonymous inode: Linux answers with its type and
|
||||
// an inode number, which is what `ls -l /proc/<pid>/fd` and every program
|
||||
// asking "is my stdin a socket?" look at. ino matches /proc/<pid>/fd.
|
||||
if (f->type != FD_INODE && f->type != FD_MEMFD && myproc()->syscall_map) {
|
||||
struct linux_stat *ls = (struct linux_stat *)st;
|
||||
memset(ls, 0, sizeof(*ls));
|
||||
ls->st_mode = f->type == FD_SOCKET ? LINUX_S_IFSOCK | 0777
|
||||
: f->type == FD_PIPE ? LINUX_S_IFIFO | 0600 : 0600;
|
||||
ls->st_ino = f->ino;
|
||||
ls->st_nlink = 1;
|
||||
ls->st_blksize = PGSIZE;
|
||||
return 0;
|
||||
}
|
||||
if (f->type != FD_INODE) return -EINVAL;
|
||||
ilock(f->ip);
|
||||
if (myproc()->syscall_map) stati_linux(f->ip, (struct linux_stat*)st);
|
||||
|
|
@ -1229,10 +1292,14 @@ addr_t sys_readlink(void)
|
|||
if (!(ip = namei(path))) return -ENOENT;
|
||||
ilock(ip);
|
||||
if (ip->type != T_SYMLINK) { iunlockput(ip); return -EINVAL; }
|
||||
int n = (int)ip->size < bufsiz ? (int)ip->size : bufsiz;
|
||||
if (readi(ip, 0, (uint64)buf, 0, n) != n) { iunlockput(ip); return -EIO; }
|
||||
// A /proc link can change after its inode was loaded -- an fd dup2()ed
|
||||
// over -- so its size is not trusted: the read says how long it is.
|
||||
int proc = ip->dev == PROCFS_DEV;
|
||||
int n = (proc || (int)ip->size > bufsiz) ? bufsiz : (int)ip->size;
|
||||
int got = readi(ip, 0, (uint64)buf, 0, n);
|
||||
if (got < 0 || (!proc && got != n)) { iunlockput(ip); return -EIO; }
|
||||
iunlockput(ip);
|
||||
return n;
|
||||
return got;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -2090,10 +2157,14 @@ addr_t sys_readlinkat(void)
|
|||
if (!(ip = at_namei(dirfd, path))) return -ENOENT;
|
||||
ilock(ip);
|
||||
if (ip->type != T_SYMLINK) { iunlockput(ip); return -EINVAL; }
|
||||
int n = (int)ip->size < bufsiz ? (int)ip->size : bufsiz;
|
||||
if (readi(ip, 0, (uint64)buf, 0, n) != n) { iunlockput(ip); return -EIO; }
|
||||
// A /proc link can change after its inode was loaded -- an fd dup2()ed
|
||||
// over -- so its size is not trusted: the read says how long it is.
|
||||
int proc = ip->dev == PROCFS_DEV;
|
||||
int n = (proc || (int)ip->size > bufsiz) ? bufsiz : (int)ip->size;
|
||||
int got = readi(ip, 0, (uint64)buf, 0, n);
|
||||
if (got < 0 || (!proc && got != n)) { iunlockput(ip); return -EIO; }
|
||||
iunlockput(ip);
|
||||
return n;
|
||||
return got;
|
||||
}
|
||||
|
||||
addr_t sys_fchmodat(void) { return 0; } // stub
|
||||
|
|
|
|||
|
|
@ -656,9 +656,8 @@ sock_name(int peer)
|
|||
SYSCALL_DEFINE(getsockname) { return sock_name(0); }
|
||||
SYSCALL_DEFINE(getpeername) { return sock_name(1); }
|
||||
|
||||
// shutdown(2). Half-close is not modelled -- there is one buffer per
|
||||
// direction and no per-direction flag -- so SHUT_WR and SHUT_RDWR both mean
|
||||
// "the peer sees end-of-file", which is the behaviour programs depend on.
|
||||
// shutdown(2). SHUT_WR is a half-close: the peer reads EOF and can still
|
||||
// write back (see sock_shutdown).
|
||||
SYSCALL_DEFINE(sockshutdown)
|
||||
{
|
||||
struct socket *s;
|
||||
|
|
|
|||
|
|
@ -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--;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue