#include "inc/priv.h" #include "inc/cvalues.h" #include "inc/print.h" #include "inc/io.h" #include "inc/process.h" #include "inc/types.h" static const sl_type *processtype; static sl_v runningsym, exitedsym, stoppedsym, signaledsym; static sl_v iosym, insym, outsym, errsym, envsym, dirsym; static const int iosflags[] = { IOS_READ, IOS_CREATE|IOS_WRITE|IOS_TRUNC, IOS_CREATE|IOS_WRITE|IOS_TRUNC }; static int nullfd[3]; typedef struct sl_process sl_process; struct sl_pid { int pid; int code; bool dumped; sl_v state; char *msg; sl_pid *next; // FIXME(sigrid): binary tree }; struct sl_process { sl_pid *pid; sl_v stream[3]; }; static bool isprocess(sl_v v) { return iscvalue(v) && cvalue_class(v) == processtype; } static sl_process * toprocess(sl_v v) { if(sl_likely(isprocess(v))) return cvalue_data(v); cthrow(type_error("value", "process", v), v); } BUILTIN("find-program", find_program) { argcount(1); char *path = sys_find_program(tostr(args[0])); if(path == nil) return sl_nil; sl_v v = str_from_cstr(path); MEM_FREE(path); return v; } BUILTIN("process?", processp) { argcount(1); return isprocess(args[0]); } BUILTIN("process-alive?", process_alivep) { argcount(1); sl_pid *pid = toprocess(args[0])->pid; return pid->state != runningsym && pid->state != stoppedsym ? sl_nil : sl_t; } BUILTIN("process-core-dumped?", process_code_dumpedp) { argcount(1); sl_pid *pid = toprocess(args[0])->pid; return pid->dumped ? sl_t : sl_nil; } BUILTIN("process-state", process_state) { argcount(1); return toprocess(args[0])->pid->state; } BUILTIN("process-exit-code", process_exit_code) { argcount(1); sl_process *proc = toprocess(args[0]); if(proc->pid->code < 0) return sl_nil; return fixnum(proc->pid->code); } BUILTIN("process-exit-msg", process_exit_msg) { argcount(1); sl_process *proc = toprocess(args[0]); if(proc->pid == nil || proc->pid->code < 0) return sl_nil; if(proc->pid->code == 0 || proc->pid->msg == nil) return sl_emptystr; return str_from_cstr(proc->pid->msg); } static sl_v process_stream(sl_v p, int n) { sl_process *proc = toprocess(p); return proc->stream[n]; } BUILTIN("process-input", process_input) { argcount(1); return process_stream(args[0], 0); } BUILTIN("process-output", process_output) { argcount(1); return process_stream(args[0], 1); } BUILTIN("process-error", process_error) { argcount(1); return process_stream(args[0], 2); } BUILTIN("make-process", make_process) { const char *const fds[] = {"in", "out", "err"}; sl_v keys[] = {insym, outsym, errsym, envsym, dirsym}; sl_v values[nelem(keys)]; nargs = sl_process_args(args, 2, nargs, keys, nelem(keys), values); argcount(2); int argc = 0, fd[3]; int closefd[3], numclosefd = 0; int chclosefd[3], numchclosefd = 0; sl_v stream[3] = {sl_nil, sl_nil, sl_nil}; char *program = MEM_STRDUP(tostr(args[0])), **argv = nil, **env = nil; sl_pid *pid = nil; int dirfd = -1; for(int i = 0; i < nelem(values); i++) sl_gc_handle(&values[i]); sl_gc_handle(&stream[0]); sl_gc_handle(&stream[1]); sl_gc_handle(&stream[2]); sl_v v = sl_nil, err = sl_nil; SL_TRY{ // collect argv sl_v a = args[1]; (void)tocons(a); argc = llength(args[1]); if((argv = MEM_ALLOC((argc+1)*sizeof(char*))) == nil) bthrow(error(sl_errmem, "no memory for argv")); for(int i = 0; i < argc; i++, a = cdr_(a)){ argv[i] = nil; if(!iscons(a)) bthrow(type_error("argv", "cons", a)); sl_v s = car_(a); if(!isstr(s)) bthrow(type_error("arg", "str", s)); if((argv[i] = MEM_STRDUP(tostr(s))) == nil) bthrow(error(sl_errmem, "no memory for argv[%d]", i)); } argv[argc] = nil; // collect env sl_v e = values[3]; if(e != sl_unbound){ int ne = llength(e); if(e != sl_nil) (void)tocons(e); if((env = MEM_ALLOC((ne+1)*sizeof(char*))) == nil) bthrow(error(sl_errmem, "no memory for env")); for(int i = 0; i < ne; i++, e = cdr_(e)){ env[i] = nil; if(!iscons(e)) bthrow(type_error("env", "cons", e)); sl_v pair = car_(e); if(!iscons(pair)) bthrow(type_error("env", "assoc list", e)); const char *ek = tostr(car_(pair)), *ev = tostr(cdr_(pair)); int nk = strlen(ek), nv = strlen(ev), sz = nk+1+nv+1; if((env[i] = MEM_ALLOC(sz)) == nil) bthrow(error(sl_errmem, "no memory for env[%d]", i)); memcpy(env[i], ek, nk); env[i][nk] = '='; memcpy(env[i]+nk+1, ev, nv+1); } env[ne] = nil; } // get the streams ready for(int i = 0; i < nelem(fd); i++){ sl_v io = values[i]; if(io == sl_t) fd[i] = i; else if(io == sl_unbound || io == sl_nil) fd[i] = nullfd[i]; else if(io == outsym && i == 2) fd[i] = fd[1]; else if(io == iosym){ stream[i] = cvalue(sl_iotype, sizeof(ios)); ios *s = value2c(ios*, stream[i]); ios_pipe(s, iosflags[i] & IOS_RDWR, &fd[i]); if(fd[i] < 0) bthrow(error(sl_errio, "%s: pipe failed", fds[i])); closefd[numclosefd++] = fd[i]; chclosefd[numchclosefd++] = s->fd; }else if(isio(io)){ ios *s = toio( io, true, (iosflags[i] & IOS_READ) != 0, (iosflags[i] & IOS_WRITE) != 0 ); if(s->fd < 0) bthrow(error(sl_errio, "%s: memory buffers not supported yet", fds[i])); // FIXME(sigrid) fd[i] = s->fd; }else if(isstr(io)){ fd[i] = sys_open(tostr(io), iosflags[i], tofixnum(sym_value(sl_iodeffilemodesym))); if(fd[i] < 0) bthrow(error(sl_errio, "%s: could not open %s", fds[i], tostr(v))); closefd[numclosefd++] = fd[i]; }else bthrow(error(sl_errarg, "%s: invalid stream", fds[i])); } // init the pid if((pid = MEM_ALLOC(sizeof(sl_pid))) == nil) bthrow(error(sl_errmem, "no memory for pid")); pid->pid = -1; pid->code = -1; pid->msg = nil; pid->state = runningsym; pid->dumped = false; pid->next = slvm.pids; slvm.pids = pid; const sl_v dir = values[4]; // :dir const char *dirpath = nil; if(dir != sl_unbound && dir != sl_nil){ dirpath = tostr(dir); dirfd = sys_open(dirpath, IOS_DIR, 0); if(dirfd < 0) bthrow(error(sl_errarg, "dir: bad directory: %s", dirpath)); } // fork, close/deallocate parent's resources int chpid = sys_fork(env == nil ? 0 : FORK_CLEAR_ENV); if(chpid < 0) bthrow(error(sl_err, "fork failed")); if(chpid > 0){ // parent pid->pid = chpid; v = cvalue(processtype, sizeof(sl_process)); sl_process *process = value2c(sl_process*, v); process->pid = pid; for(int i = 0; i < nelem(fd); i++) process->stream[i] = stream[i]; }else{ // child for(int i = 0; i < numchclosefd; i++) sys_close(chclosefd[i]); if(dirfd >= 0){ if(sys_chdir(dirfd, dirpath) != 0) goto child_failed; sys_close(dirfd); } for(int i = 0; i < nelem(fd); i++){ if(sys_dup(fd[i], i) < 0) goto child_failed; } sys_exec(program, argv, env); child_failed: #if defined(__plan9__) sysfatal("make-process: %r"); #else // FIXME(sigrid): check errno? pass it on to the parent? sysfatal("make-process failed"); #endif } } SL_CATCH{ for(int i = 0; i < nelem(stream); i++){ if(isio(stream[i])) ios_close(value2c(ios*, stream[i])); } MEM_FREE(pid); err = sl.lasterror; } if(dirfd >= 0) sys_close(dirfd); for(int i = 0; i < numclosefd; i++) sys_close(closefd[i]); sl_free_gc_handles(nelem(values)+3); MEM_FREE(program); for(int i = 0; i < argc; i++) MEM_FREE(argv[i]); MEM_FREE(argv); if(env != nil){ for(int i = 0; env[i] != nil; i++) MEM_FREE(env[i]); MEM_FREE(env); } if(err != sl_nil) sl_raise(err); return v; } static void print_process(sl_v v) { sl_process *proc = toprocess(v); sl_print_str("#pid->pid)); sl_v state = proc->pid->state; if(state != sl_nil){ sl_print_chr(' '); sl_print(state); if(state != runningsym && state != stoppedsym && proc->pid->code != 0){ sl_print_chr(' '); sl_print(fixnum(proc->pid->code)); if(proc->pid->msg != nil){ sl_print_str(" \"", true); sl_print_str(proc->pid->msg, false); sl_print_chr('"'); if(proc->pid->dumped) sl_print_str(" dumped", true); } } } sl_print_chr('>'); } static void relocate_process(sl_v oldv, sl_v newv) { sl_process *p = toprocess(newv); for(int i = 0; i < nelem(p->stream); i++) p->stream[i] = sl_relocate(p->stream[i]); USED(oldv); } static void free_process(sl_v self) { sl_process *proc = value2c(sl_process*, self); sl_pid *p, *prev = nil; for(p = slvm.pids; p != nil; prev = p, p = p->next){ if(p == proc->pid){ if(prev == nil) slvm.pids = p->next; else prev->next = p->next; break; } } if(p == nil) abort(); MEM_FREE(p->msg); MEM_FREE(p); proc->pid = nil; } BUILTIN("process-close", process_close) { argcount(1); sl_process *proc = toprocess(args[0]); for(int i = 0; i < nelem(proc->stream); i++){ if(isio(proc->stream[i])) ios_close(value2c(ios*, proc->stream[i])); } return sl_void; } BUILTIN("process-post", process_post) { argcount(3); sl_process *proc = toprocess(args[0]); const char *note = tostr(args[1]); const char *sig = tosym(args[2])->name; return sys_postnote(proc->pid->pid, note, sig) == 0 ? sl_t : sl_nil; } static sl_cvtable process_vtable = { print_process, relocate_process, free_process, nil, nil, }; void process_update(int pid, process_state_t state, int code, const char *msg) { for(sl_pid *p = slvm.pids; p != nil; p = p->next){ if(p->pid >= 0 && p->pid != pid) continue; switch(state){ case PSTATE_EXITED: p->state = exitedsym; break; case PSTATE_STOPPED: p->state = stoppedsym; break; case PSTATE_DUMPED: p->dumped = true; sl_fallthrough; case PSTATE_SIGNALED: p->state = signaledsym; break; case PSTATE_RUNNING: p->state = runningsym; break; } p->code = code; p->msg = msg == nil ? nil : MEM_STRDUP(msg); slvm.pidupdates++; // FIXME(sigrid): atomic break; } // GC'ed - nobody cares anymore } void process_init(void) { if((nullfd[0] = sys_open(SYS_DEV_NULL, IOS_READ, 0)) < 0) sysfatal("%s", SYS_DEV_NULL); if((nullfd[1] = nullfd[2] = sys_open(SYS_DEV_NULL, IOS_WRITE, 0)) < 0) sysfatal("%s", SYS_DEV_NULL); processtype = define_opaque_type( mk_sym("process", false), sizeof(sl_process), &process_vtable, nil ); runningsym = mk_csym(":running"); exitedsym = mk_csym(":exited"); stoppedsym = mk_csym(":stopped"); signaledsym = mk_csym(":signaled"); insym = mk_csym(":in"); outsym = mk_csym(":out"); errsym = mk_csym(":err"); envsym = mk_csym(":env"); iosym = mk_csym(":io"); dirsym = mk_csym(":dir"); }