| /* |
| * linux/kernel/signal.c |
| * |
| * Copyright (C) 1991, 1992 Linus Torvalds |
| */ |
| |
| #include <linux/sched.h> |
| #include <linux/kernel.h> |
| #include <linux/signal.h> |
| #include <linux/errno.h> |
| #include <linux/wait.h> |
| #include <linux/ptrace.h> |
| #include <linux/unistd.h> |
| |
| #include <asm/segment.h> |
| |
| #define _S(nr) (1<<((nr)-1)) |
| |
| #define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP))) |
| |
| extern int core_dump(long signr,struct pt_regs * regs); |
| |
| asmlinkage int do_signal(unsigned long oldmask, struct pt_regs * regs); |
| |
| struct sigcontext_struct { |
| unsigned short gs, __gsh; |
| unsigned short fs, __fsh; |
| unsigned short es, __esh; |
| unsigned short ds, __dsh; |
| unsigned long edi; |
| unsigned long esi; |
| unsigned long ebp; |
| unsigned long esp; |
| unsigned long ebx; |
| unsigned long edx; |
| unsigned long ecx; |
| unsigned long eax; |
| unsigned long trapno; |
| unsigned long err; |
| unsigned long eip; |
| unsigned short cs, __csh; |
| unsigned long eflags; |
| unsigned long esp_at_signal; |
| unsigned short ss, __ssh; |
| unsigned long i387; |
| unsigned long oldmask; |
| unsigned long cr2; |
| }; |
| |
| asmlinkage int sys_sigprocmask(int how, sigset_t *set, sigset_t *oset) |
| { |
| sigset_t new_set, old_set = current->blocked; |
| int error; |
| |
| if (set) { |
| error = verify_area(VERIFY_READ, set, sizeof(sigset_t)); |
| if (error) |
| return error; |
| new_set = get_fs_long((unsigned long *) set) & _BLOCKABLE; |
| switch (how) { |
| case SIG_BLOCK: |
| current->blocked |= new_set; |
| break; |
| case SIG_UNBLOCK: |
| current->blocked &= ~new_set; |
| break; |
| case SIG_SETMASK: |
| current->blocked = new_set; |
| break; |
| default: |
| return -EINVAL; |
| } |
| } |
| if (oset) { |
| error = verify_area(VERIFY_WRITE, oset, sizeof(sigset_t)); |
| if (error) |
| return error; |
| put_fs_long(old_set, (unsigned long *) oset); |
| } |
| return 0; |
| } |
| |
| asmlinkage int sys_sgetmask(void) |
| { |
| return current->blocked; |
| } |
| |
| asmlinkage int sys_ssetmask(int newmask) |
| { |
| int old=current->blocked; |
| |
| current->blocked = newmask & _BLOCKABLE; |
| return old; |
| } |
| |
| asmlinkage int sys_sigpending(sigset_t *set) |
| { |
| int error; |
| /* fill in "set" with signals pending but blocked. */ |
| error = verify_area(VERIFY_WRITE, set, 4); |
| if (!error) |
| put_fs_long(current->blocked & current->signal, (unsigned long *)set); |
| return error; |
| } |
| |
| /* |
| * atomically swap in the new signal mask, and wait for a signal. |
| */ |
| asmlinkage int sys_sigsuspend(int restart, unsigned long oldmask, unsigned long set) |
| { |
| unsigned long mask; |
| struct pt_regs * regs = (struct pt_regs *) &restart; |
| |
| mask = current->blocked; |
| current->blocked = set & _BLOCKABLE; |
| regs->eax = -EINTR; |
| while (1) { |
| current->state = TASK_INTERRUPTIBLE; |
| schedule(); |
| if (do_signal(mask,regs)) |
| return -EINTR; |
| } |
| } |
| |
| /* |
| * POSIX 3.3.1.3: |
| * "Setting a signal action to SIG_IGN for a signal that is pending |
| * shall cause the pending signal to be discarded, whether or not |
| * it is blocked" (but SIGCHLD is unspecified: linux leaves it alone). |
| * |
| * "Setting a signal action to SIG_DFL for a signal that is pending |
| * and whose default action is to ignore the signal (for example, |
| * SIGCHLD), shall cause the pending signal to be discarded, whether |
| * or not it is blocked" |
| * |
| * Note the silly behaviour of SIGCHLD: SIG_IGN means that the signal |
| * isn't actually ignored, but does automatic child reaping, while |
| * SIG_DFL is explicitly said by POSIX to force the signal to be ignored.. |
| */ |
| static void check_pending(int signum) |
| { |
| struct sigaction *p; |
| |
| p = signum - 1 + current->sigaction; |
| if (p->sa_handler == SIG_IGN) { |
| if (signum == SIGCHLD) |
| return; |
| current->signal &= ~_S(signum); |
| return; |
| } |
| if (p->sa_handler == SIG_DFL) { |
| if (signum != SIGCONT && signum != SIGCHLD && signum != SIGWINCH) |
| return; |
| current->signal &= ~_S(signum); |
| return; |
| } |
| } |
| |
| asmlinkage int sys_signal(int signum, unsigned long handler) |
| { |
| struct sigaction tmp; |
| |
| if (signum<1 || signum>32 || signum==SIGKILL || signum==SIGSTOP) |
| return -EINVAL; |
| if (handler >= TASK_SIZE) |
| return -EFAULT; |
| tmp.sa_handler = (void (*)(int)) handler; |
| tmp.sa_mask = 0; |
| tmp.sa_flags = SA_ONESHOT | SA_NOMASK; |
| tmp.sa_restorer = NULL; |
| handler = (long) current->sigaction[signum-1].sa_handler; |
| current->sigaction[signum-1] = tmp; |
| check_pending(signum); |
| return handler; |
| } |
| |
| asmlinkage int sys_sigaction(int signum, const struct sigaction * action, |
| struct sigaction * oldaction) |
| { |
| struct sigaction new_sa, *p; |
| |
| if (signum<1 || signum>32 || signum==SIGKILL || signum==SIGSTOP) |
| return -EINVAL; |
| p = signum - 1 + current->sigaction; |
| if (action) { |
| memcpy_fromfs(&new_sa, action, sizeof(struct sigaction)); |
| if (new_sa.sa_flags & SA_NOMASK) |
| new_sa.sa_mask = 0; |
| else { |
| new_sa.sa_mask |= _S(signum); |
| new_sa.sa_mask &= _BLOCKABLE; |
| } |
| if (TASK_SIZE <= (unsigned long) new_sa.sa_handler) |
| return -EFAULT; |
| } |
| if (oldaction) { |
| if (!verify_area(VERIFY_WRITE,oldaction, sizeof(struct sigaction))) |
| memcpy_tofs(oldaction, p, sizeof(struct sigaction)); |
| } |
| if (action) { |
| *p = new_sa; |
| check_pending(signum); |
| } |
| return 0; |
| } |
| |
| asmlinkage int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options); |
| |
| /* |
| * This sets regs->esp even though we don't actually use sigstacks yet.. |
| */ |
| asmlinkage int sys_sigreturn(unsigned long __unused) |
| { |
| #define CHECK_SEG(x) if (x) x |= 3 |
| #define COPY(x) regs->x = context.x |
| struct sigcontext_struct context; |
| struct pt_regs * regs; |
| |
| regs = (struct pt_regs *) &__unused; |
| memcpy_fromfs(&context,(void *) regs->esp, sizeof(context)); |
| current->blocked = context.oldmask & _BLOCKABLE; |
| CHECK_SEG(context.ss); |
| CHECK_SEG(context.cs); |
| CHECK_SEG(context.ds); |
| CHECK_SEG(context.es); |
| CHECK_SEG(context.fs); |
| CHECK_SEG(context.gs); |
| COPY(eip); COPY(eflags); |
| COPY(ecx); COPY(edx); |
| COPY(ebx); |
| COPY(esp); COPY(ebp); |
| COPY(edi); COPY(esi); |
| COPY(cs); COPY(ss); |
| COPY(ds); COPY(es); |
| COPY(fs); COPY(gs); |
| regs->orig_eax = -1; /* disable syscall checks */ |
| return context.eax; |
| } |
| |
| /* |
| * Set up a signal frame... Make the stack look the way iBCS2 expects |
| * it to look. |
| */ |
| static void setup_frame(struct sigaction * sa, unsigned long ** fp, unsigned long eip, |
| struct pt_regs * regs, int signr, unsigned long oldmask) |
| { |
| unsigned long * frame; |
| |
| #define __CODE ((unsigned long)(frame+24)) |
| #define CODE(x) ((unsigned long *) ((x)+__CODE)) |
| frame = *fp; |
| if (regs->ss != USER_DS) |
| frame = (unsigned long *) sa->sa_restorer; |
| frame -= 32; |
| verify_area(VERIFY_WRITE,frame,32*4); |
| /* set up the "normal" stack seen by the signal handler (iBCS2) */ |
| put_fs_long(__CODE,frame); |
| put_fs_long(signr, frame+1); |
| put_fs_long(regs->gs, frame+2); |
| put_fs_long(regs->fs, frame+3); |
| put_fs_long(regs->es, frame+4); |
| put_fs_long(regs->ds, frame+5); |
| put_fs_long(regs->edi, frame+6); |
| put_fs_long(regs->esi, frame+7); |
| put_fs_long(regs->ebp, frame+8); |
| put_fs_long((long)*fp, frame+9); |
| put_fs_long(regs->ebx, frame+10); |
| put_fs_long(regs->edx, frame+11); |
| put_fs_long(regs->ecx, frame+12); |
| put_fs_long(regs->eax, frame+13); |
| put_fs_long(0, frame+14); /* trapno - not implemented */ |
| put_fs_long(0, frame+15); /* err - not implemented */ |
| put_fs_long(eip, frame+16); |
| put_fs_long(regs->cs, frame+17); |
| put_fs_long(regs->eflags, frame+18); |
| put_fs_long(regs->esp, frame+19); |
| put_fs_long(regs->ss, frame+20); |
| put_fs_long(0,frame+21); /* 387 state pointer - not implemented*/ |
| /* non-iBCS2 extensions.. */ |
| put_fs_long(oldmask, frame+22); |
| put_fs_long(current->tss.cr2, frame+23); |
| /* set up the return code... */ |
| put_fs_long(0x0000b858, CODE(0)); /* popl %eax ; movl $,%eax */ |
| put_fs_long(0x80cd0000, CODE(4)); /* int $0x80 */ |
| put_fs_long(__NR_sigreturn, CODE(2)); |
| *fp = frame; |
| #undef __CODE |
| #undef CODE |
| } |
| |
| /* |
| * Note that 'init' is a special process: it doesn't get signals it doesn't |
| * want to handle. Thus you cannot kill init even with a SIGKILL even by |
| * mistake. |
| * |
| * Note that we go through the signals twice: once to check the signals that |
| * the kernel can handle, and then we build all the user-level signal handling |
| * stack-frames in one go after that. |
| */ |
| asmlinkage int do_signal(unsigned long oldmask, struct pt_regs * regs) |
| { |
| unsigned long mask = ~current->blocked; |
| unsigned long handler_signal = 0; |
| unsigned long *frame = NULL; |
| unsigned long eip = 0; |
| unsigned long signr; |
| struct sigaction * sa; |
| |
| while ((signr = current->signal & mask)) { |
| __asm__("bsf %2,%1\n\t" |
| "btrl %1,%0" |
| :"=m" (current->signal),"=r" (signr) |
| :"1" (signr)); |
| sa = current->sigaction + signr; |
| signr++; |
| if ((current->flags & PF_PTRACED) && signr != SIGKILL) { |
| current->exit_code = signr; |
| current->state = TASK_STOPPED; |
| notify_parent(current); |
| schedule(); |
| if (!(signr = current->exit_code)) |
| continue; |
| current->exit_code = 0; |
| if (signr == SIGSTOP) |
| continue; |
| if (_S(signr) & current->blocked) { |
| current->signal |= _S(signr); |
| continue; |
| } |
| sa = current->sigaction + signr - 1; |
| } |
| if (sa->sa_handler == SIG_IGN) { |
| if (signr != SIGCHLD) |
| continue; |
| /* check for SIGCHLD: it's special */ |
| while (sys_waitpid(-1,NULL,WNOHANG) > 0) |
| /* nothing */; |
| continue; |
| } |
| if (sa->sa_handler == SIG_DFL) { |
| if (current->pid == 1) |
| continue; |
| switch (signr) { |
| case SIGCONT: case SIGCHLD: case SIGWINCH: |
| continue; |
| |
| case SIGSTOP: case SIGTSTP: case SIGTTIN: case SIGTTOU: |
| if (current->flags & PF_PTRACED) |
| continue; |
| current->state = TASK_STOPPED; |
| current->exit_code = signr; |
| if (!(current->p_pptr->sigaction[SIGCHLD-1].sa_flags & |
| SA_NOCLDSTOP)) |
| notify_parent(current); |
| schedule(); |
| continue; |
| |
| case SIGQUIT: case SIGILL: case SIGTRAP: |
| case SIGIOT: case SIGFPE: case SIGSEGV: |
| if (core_dump(signr,regs)) |
| signr |= 0x80; |
| /* fall through */ |
| default: |
| current->signal |= _S(signr & 0x7f); |
| do_exit(signr); |
| } |
| } |
| /* |
| * OK, we're invoking a handler |
| */ |
| if (regs->orig_eax >= 0) { |
| if (regs->eax == -ERESTARTNOHAND || |
| (regs->eax == -ERESTARTSYS && !(sa->sa_flags & SA_RESTART))) |
| regs->eax = -EINTR; |
| } |
| handler_signal |= 1 << (signr-1); |
| mask &= ~sa->sa_mask; |
| } |
| if (regs->orig_eax >= 0 && |
| (regs->eax == -ERESTARTNOHAND || |
| regs->eax == -ERESTARTSYS || |
| regs->eax == -ERESTARTNOINTR)) { |
| regs->eax = regs->orig_eax; |
| regs->eip -= 2; |
| } |
| if (!handler_signal) /* no handler will be called - return 0 */ |
| return 0; |
| eip = regs->eip; |
| frame = (unsigned long *) regs->esp; |
| signr = 1; |
| sa = current->sigaction; |
| for (mask = 1 ; mask ; sa++,signr++,mask += mask) { |
| if (mask > handler_signal) |
| break; |
| if (!(mask & handler_signal)) |
| continue; |
| setup_frame(sa,&frame,eip,regs,signr,oldmask); |
| eip = (unsigned long) sa->sa_handler; |
| if (sa->sa_flags & SA_ONESHOT) |
| sa->sa_handler = NULL; |
| /* force a supervisor-mode page-in of the signal handler to reduce races */ |
| __asm__("testb $0,%%fs:%0": :"m" (*(char *) eip)); |
| regs->cs = USER_CS; regs->ss = USER_DS; |
| regs->ds = USER_DS; regs->es = USER_DS; |
| regs->gs = USER_DS; regs->fs = USER_DS; |
| current->blocked |= sa->sa_mask; |
| oldmask |= sa->sa_mask; |
| } |
| regs->esp = (unsigned long) frame; |
| regs->eip = eip; /* "return" to the first handler */ |
| return 1; |
| } |