diff --git a/src/syscall.c b/src/syscall.c index eeb1175..d5db441 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -617,6 +617,179 @@ uint64 syscallDispatch(Registers* regs) int64 rawPid = (int64)regs->rdi; bool ok; if (rawPid > 0) + ok = threadKill((uint32)rawPid); + else + ok = threadKillGroup(rawPid == 0 ? currentThread()->pgid : (uint32)(-rawPid)); + regs->rax = ok ? 0 : (uint64)-1; + return (uint64)regs; + } + case sysSigSend: { + /* Same pid>0/pid==0/pid<0 convention as sysKill just above, this + time through sigRaiseGroup (signal.h) for the two group cases -- + see that function's own doc comment for why every per-target + special case (SIGKILL/SIGSTOP/SIGCONT bypasses, early-wake, + SigInfo) still applies exactly as it already does for a plain, + single-target send. */ + int64 rawPid = (int64)regs->rdi; + int signum = (int)regs->rsi; + uint32 senderPid = currentThreadId(); + bool isGroup = (rawPid <= 0); + uint32 targetPgid = isGroup ? (rawPid == 0 ? currentThread()->pgid : (uint32)(-rawPid)) : 0; + + /* siCodeUser/senderPid: a real userSigSend always has a genuine + sending process, unlike sched.c's own kernel-internal raises + (SIGALRM/SIGCHLD) or keyboard.c's terminal-generated ones -- + exactly real Unix's own SI_USER, si_pid the caller's own pid, + exactly like real kill(2). */ + bool ok = isGroup + ? sigRaiseGroup(targetPgid, signum, siCodeUser, (int)senderPid) + : sigRaiseInfo((uint32)rawPid, signum, siCodeUser, (int)senderPid, 0); + regs->rax = ok ? 0 : (uint64)-1; + + /* Every OTHER outcome of sigRaise/sigRaiseGroup either changes + nothing about the CALLING thread's own state (a signal aimed at + someone else) or, for an ordinary catchable signal aimed at + self, is already caught by isrDispatch's unconditional + deliverPendingSignals call right after this handler returns + (see isr.c) -- self-signaling sigUsr1 and having its handler + fire before ever reaching userspace again already works today + without any special case here. SIGSTOP against the caller's own + pid (or a group that includes it) is the one exception: + threadStop (called from sigRaise above, once per group member + for the group case) already flipped this very thread's state + away from Running, but sigRaise/sysSigSend never touch + sigPending for it (see signal.h), so deliverPendingSignals' + pending-bit scan would find nothing and simply hand `regs` + straight back -- returning to ring 3 and continuing to run a + thread the scheduler no longer considers Runnable, until the + next timer tick eventually notices. Explicit reschedule here + closes that gap, the same "flip state, then let schedule() do + the actual waiting" pattern sysSleep/sysExit already use for + their own self-directed state changes. The group case checks + targetPgid against the caller's OWN pgid rather than an exact id + match, since the caller itself -- not just some other member -- + is who this reschedule is actually about. */ + bool selfTargeted = isGroup ? (targetPgid == currentThread()->pgid) : ((uint32)rawPid == senderPid); + if (ok && signum == sigStop && selfTargeted) + return schedule((uint64)regs); + return (uint64)regs; + } + case sysSigAction: + regs->rax = sigSetHandler(currentThread(), (int)regs->rdi, regs->rsi, regs->rdx, regs->r10); + return (uint64)regs; + case sysSigProcMask: + regs->rax = sigProcMask(currentThread(), (int)regs->rdi, regs->rsi); + return (uint64)regs; + case sysSigReturn: + sigOnSigReturn(currentThread(), regs); + return (uint64)regs; + case sysAlarm: + regs->rax = threadSetAlarm(regs->rdi); + return (uint64)regs; + case sysSetpgid: { + /* Real setpgid(2)'s own "0 means self"/"0 means use pid itself as + the new group id" resolution -- threadSetPgid (sched.h) takes an + already-fully-resolved value, exactly like threadSetBrk/ + threadSetAlarm above do for THEIR raw syscall arguments. */ + uint32 pid = (regs->rdi == 0) ? currentThreadId() : (uint32)regs->rdi; + uint32 pgid = (regs->rsi == 0) ? pid : (uint32)regs->rsi; + regs->rax = threadSetPgid(pid, pgid) ? 0 : (uint64)-1; + return (uint64)regs; + } + case sysGetpgid: { + uint32 pid = (regs->rdi == 0) ? currentThreadId() : (uint32)regs->rdi; + uint32 pgid = threadPgid(pid); + regs->rax = (pgid == threadInvalidId) ? (uint64)-1 : (uint64)pgid; + return (uint64)regs; + } + case sysNice: { + /* Real setpriority(PRIO_PROCESS, ...)'s own "0 means the caller + itself" convention, same resolution shape as sysSetpgid's `pid` + argument just above -- rdi/pid, rsi/weight, straight through to + threadSetPriority (sched.h), which now does the SAME id==0/ + out-of-range/Dead-or-Unused rejection threadSetPgid already did + (see that function's own updated doc comment for why it had to + catch up once this syscall made it reachable from ring 3). */ + uint32 pid = (regs->rdi == 0) ? currentThreadId() : (uint32)regs->rdi; + uint32 weight = (uint32)regs->rsi; + regs->rax = threadSetPriority(pid, weight) ? 0 : (uint64)-1; + return (uint64)regs; + } + case sysGetPriority: { + uint32 pid = (regs->rdi == 0) ? currentThreadId() : (uint32)regs->rdi; + uint32 weight = threadPriority(pid); + regs->rax = (weight == 0) ? (uint64)-1 : (uint64)weight; + return (uint64)regs; + } + case sysTime: + /* Real time(2)'s job: seconds since the Unix epoch, off the real + CMOS RTC (rtc.c) -- the first syscall in this kernel answering + "what day is it" instead of "how long since boot" (sysSleep, + userAlarm). No arguments, nothing to validate. */ + regs->rax = (uint64)rtcNow(); + return (uint64)regs; + case sysSetFgPgrp: + /* Real tcsetpgrp(3)'s job -- names which process group keyboard.c's + real Ctrl-C/Ctrl-Z (SIGINT/SIGTSTP) is scoped to next. No + permission check at all (this kernel has no notion of "the + controlling terminal of MY session" to check ownership against, + the same honest, documented omission threadSetPgid's own doc + comment already gives for setpgid's session check) -- any + process can claim the foreground for any pgid, including one it + is not even a member of. */ + keyboardSetForegroundPgid((uint32)regs->rdi); + regs->rax = 0; + return (uint64)regs; + case sysGetFgPgrp: + regs->rax = (uint64)keyboardForegroundPgid(); + return (uint64)regs; + case sysPoll: { + /* Real poll(2): rdi/PollFd array, rsi/nfds, rdx/timeoutMs (signed, + -1 = wait forever, 0 = report current readiness without ever + blocking, like every real poll(2) implementation). Flat address + space (this kernel's honest fork()-less design, see doSpawn's own + comment) means the array can be read and written in place, the + same way sysStat writes straight into its caller's FbStat* + without a separate copy-out step. */ + uint64 nfds = regs->rsi; + int64 timeoutMs = (int64)regs->rdx; + if (nfds > maxPollFds || !userRangeOk(regs->rdi, nfds * sizeof(PollFd))) { + regs->rax = (uint64)-1; + return (uint64)regs; + } + PollFd* userFds = (PollFd*)regs->rdi; + Thread* self = currentThread(); + + /* First entry into this call (not a retry re-executing after a + park below) picks the deadline once and remembers it in + pollDeadline -- a later retry must NOT recompute it from the + still-unchanged rdx, or a poll that keeps almost-but-not-quite + finding a ready fd could have its effective timeout pushed out + indefinitely instead of counting down real elapsed time. */ + if (!self->pollActive && timeoutMs != 0) { + self->pollActive = true; + if (timeoutMs < 0) { + self->pollDeadline = 0; + } else { + uint64 ticks = ((uint64)timeoutMs * (uint64)pitFrequency()) / 1000; + if (ticks == 0) + ticks = 1; + self->pollDeadline = pitTicks() + ticks; + } + } + + int64 ready = 0; + for (uint64 i = 0; i < nfds; i++) { + int16 revents = 0; + if (vfsPollReady(userFds[i].fd, userFds[i].events, &revents)) + ready++; + userFds[i].revents = revents; + } + + bool expired = self->pollActive && self->pollDeadline != 0 && + pitTicks() >= self->pollDeadline; + if (ready > 0 || timeoutMs == 0 || expired) { + self->pollActive = false; default: regs->rax = (uint64)-1; return (uint64)regs;