diff options
| -rw-r--r-- | sys/man/1/os | 97 | ||||
| -rw-r--r-- | sys/man/3/cmd | 223 | ||||
| -rw-r--r-- | sys/src/cmd/os.c | 261 |
3 files changed, 581 insertions, 0 deletions
diff --git a/sys/man/1/os b/sys/man/1/os new file mode 100644 index 000000000..708db1c67 --- /dev/null +++ b/sys/man/1/os @@ -0,0 +1,97 @@ +.TH OS 1 +.SH NAME +os \- interface to host OS commands (drawterm only) +.SH SYNOPSIS +.B os +[ +.B -b +] [ +.B -m +.I mountpoint +] [ +.BI -d " dir" +] [ +.B -n +] [ +.BI -N " level" +] +.I cmd +[ +.IR arg ... +] +.SH DESCRIPTION +.I Os +uses a +.IR cmd (3) +device to execute a command, +.IR cmd , +on a host system. +If the +.B -m +option is given, +.I os +uses the device at +.IR mountpoint , +otherwise it is asssumed to be at +.BR /mnt/term/cmd . +.PP +The +.B -d +option causes the command to run in directory +.IR dir ; +an error results and the command will not run if +.I dir +does not exist or is inaccessible. +The standard output and standard error of the command appear on the standard output +and standard error streams of the +.I os +command itself. +.I Os +copies the standard input to the remote command's standard input; redirect +.IR os 's +input to +.B /dev/null +if there is no input to the command. +.I Os +terminates when +.I cmd +does, and its exit status reflects the status of +.I cmd +(if available). +.PP +If the +.I os +command is killed or exits (eg, for lack of input and output), +the host's own process control operations are used to (attempt to) kill +.IR cmd , +if it is still running. +The +.B -b +(background) option suppresses that behaviour. +.PP +The +.B -n +option causes +.I cmd +to run with less than normal priority (`nice'). +The +.B -N +option sets low priority to a particular +.I level +from 1 to 3. +.SH FILES +.B /mnt/term/cmd/clone +.SH SOURCE +.B /sys/src/cmd/os.c +.SH "SEE ALSO" +.IR rcpu (1), +.IR cmd (3) +.SH DIAGNOSTICS +The exit status of +.I os +reflects any error that occurs when starting +.I cmd +and, if it starts successfully, the status of +.I os +is the exit status of +.IR cmd . diff --git a/sys/man/3/cmd b/sys/man/3/cmd new file mode 100644 index 000000000..c3a0c745d --- /dev/null +++ b/sys/man/3/cmd @@ -0,0 +1,223 @@ +.TH CMD 3 +.SH NAME +cmd \- interface to host operating system commands +.SH SYNOPSIS +.B bind -a '#C' / +.PP +.B /cmd/clone +.br +.BI /cmd/ n /ctl +.br +.BI /cmd/ n /data +.br +.BI /cmd/ n /stderr +.br +.BI /cmd/ n /status +.br +.BI /cmd/ n /wait +.SH DESCRIPTION +.I Cmd +provides a way to run commands in the underlying operating system's +command interpreter of drawterm or when Inferno is running hosted. +It serves a three-level directory that is conventionally bound +behind the root directory. +The top of the hierarchy is a directory +.BR cmd , +that contains a +.B clone +file and zero or more numbered directories. +Each directory represents a distinct connection to the host's command interpreter. +The directory contains five files: +.BR ctl , +.BR data , +.BR stderr , +.B status +and +.BR wait , +used as described below. +Opening the +.B clone +file reserves a connection: it is equivalent to opening the +.B ctl +file of an unused connection directory, creating a new one if necessary. +.PP +The file +.B ctl +controls a connection. +When read, it returns the decimal number +.I n +of its connection directory. +Thus, opening and reading +.B clone +allocates a connection directory and reveals the number of the allocated directory, +allowing the other files to be named (eg, +.BI /cmd/ n /data\fR). +.PP +.B Ctl +accepts the following textual commands, allowing quoting as interpreted by +.IR parsecmd (2): +.TP +.BI "dir " wdir +Run the host command in directory +.IR wdir , +which is a directory +.I "on the host system" . +Issue this request before starting the command. +By default, commands are run in the Inferno root directory on the host system. +.TP +.BI "exec " "command args ..." +Spawn a host process to run the +.I command +with arguments as given. +The write returns with an error, setting the error string, if anything prevents +starting the command. +If write returns successfully, the command has started, and its standard input and +output may be accessed through +.BR data , +and its error output accessed through +.B stderr +(see below). +If arguments containing white space are quoted (following the conventions of +.IR rc (1) +or +.IR parsecmd (2)), +they are requoted by +.I cmd +using the host command interpreter's conventions so that +.I command +sees exactly the same arguments as were written to +.BR ctl . +.TP +.B kill +Kill the host command immediately. +.TP +.B killonclose +Set the device to kill the host command when the +.B ctl +file is closed (normally all files must be closed, see below). +.TP +.BI nice " \fR[\fPn\fR]\fP" +Run the host command at less than normal scheduling priority. +Issue this request before starting the command. +The optional value +.IR n , +in the range 1 to 3, +indicates the degree of `niceness' (default: 1). +.PP +The +.B data +file provides a connection to the input and output of a previously-started +host command. +It must be opened separately for reading and for writing. +When opened for reading, it returns data that the command writes to its standard output; when closed, further writes by the command will receive the host +equivalent of `write to closed pipe'. +When opened for writing, data written to the file +can be read by the command on its standard input; when closed, further reads by +the command will see the host equivalent of `end of file'. +(Unfortunately there is no way to know when the command needs input.) +.PP +The +.B stderr +file provides a similar read-only connection to the error output from the command. +If the +.B stderr +file is not opened, the error output will be discarded. +.PP +Once started, a host command runs until it terminates or until it is killed, +by using the +.B kill +or +.B killonclose +requests above, or by closing all +.BR ctl , +.B data +and +.B wait +files for a connection. +.PP +The read-only +.B status +file provides a single line giving the status of the connection (not the command), of the form: +.IP +.BI cmd/ "n opens state wdir arg0" +.PP +where the fields are separated by white space. The meaning of each field is: +.TP +.I n +The +.B cmd +directory number. +.TP +.I opens +The decimal number of open file descriptors for +.BR ctl , +.B data +and +.BR wait . +.TP +.I state +The status of the interface in directory +.IR n : +.RS +.TF Execute +.TP +.B Open +Allocated for use but not yet running a command. +.TP +.B Execute +Running a command. +.TP +.B Done +Command terminated: status available in the +.B status +file (or via +.BR wait ). +.TP +.B Close +Command completed. Available for reallocation via +.BR clone . +.RE +.PD +.TP +.I wdir +The command's initial working directory on the host. +.TP +.I arg0 +The host command name (without arguments). +.PP +The read-only +.B wait +file must be opened before starting a command via +.BR ctl . +When read, it blocks until the command terminates. +The read then returns with a single status line, to be +parsed using +.B tokenize +(see +.IR getfields (2)). +There are five fields: +host process ID (or 0 if unknown); +time the command spent in user code in milliseconds (or 0); +time spent in system code in milliseconds (or 0); +real time in milliseconds (or 0); +and a string giving the exit status of the command. +The exit status is host-dependent, except that an empty string +means success, and a non-empty string contains a diagnostic. +.PP +.SS "Command execution" +In all cases, the command runs in the host operating system's +own file name space. +All file names will be interpreted in that space, not Plan9's. +For example, on Unix +.B / +refers to the host's file system root, not Plan9's; +the effects of mounts and binds will not be visible. +.SH "SEE ALSO" +.IR os (1) +.SH DIAGNOSTICS +A +.B write +to +.B ctl +returns with an error and sets the error string if +a command cannot be started or killed successfully. diff --git a/sys/src/cmd/os.c b/sys/src/cmd/os.c new file mode 100644 index 000000000..54b873897 --- /dev/null +++ b/sys/src/cmd/os.c @@ -0,0 +1,261 @@ +#include <u.h> +#include <libc.h> + +enum { + Fstdin, + Fstdout, + Fstderr, + Fwait, + Fctl, + Nfd, +}; + +enum { + Pcopyin, + Pcopyout, + Pcopyerr, + Preadwait, + Npid, +}; + +char *mnt = "/mnt/term/cmd"; +char *dir = nil; +char buf[8192]; +int fd[Nfd] = {-1}; +int pid[Npid]; +int nice, foreground = 1; + +void +killstdin(void) +{ + if(pid[Pcopyin] != 0) + postnote(PNPROC, pid[Pcopyin], "kill"); +} + +void +killproc(void) +{ + if(fd[Fctl] >= 0) + write(fd[Fctl], "kill", 4); +} + +int +catch(void*, char *msg) +{ + if(strcmp(msg, "interrupt") == 0 + || strcmp(msg, "hangup") == 0 + || strcmp(msg, "kill") == 0){ + killproc(); + return 1; + } + return 0; +} + +void +fd01(int fd0, int fd1) +{ + int i; + + if(fd0 >= 0 && fd0 != 0){ + if(dup(fd0, 0) < 0) + sysfatal("dup: %r"); + } + if(fd1 >= 0 && fd1 != 1){ + if(dup(fd1, 1) < 0) + sysfatal("dup: %r"); + } + for(i = 0; i<Nfd; i++){ + if(fd[i] > 2) + close(fd[i]); + if(fd[i] == fd0) + fd[i] = 0; + else if(fd[i] == fd1) + fd[i] = 1; + else + fd[i] = -1; + } +} + +void +copy(void) +{ + int n; + + while((n = read(0, buf, sizeof(buf))) > 0) + if(write(1, buf, n) != n) + break; +} + +void +usage(void) +{ + fprint(2, "%s: [ -b ] [ -m mountpoint ] [ -d dir ] [ -n ] [ -N level ] cmd [ arg... ]\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + Waitmsg *w; + char *s; + int n; + + quotefmtinstall(); + + ARGBEGIN { + case 'b': + foreground = 0; + break; + case 'm': + mnt = cleanname(EARGF(usage())); + break; + case 'd': + dir = EARGF(usage()); + break; + case 'n': + nice = 1; + break; + case 'N': + nice = atoi(EARGF(usage())); + break; + default: + usage(); + } ARGEND; + + if(argc < 1) + usage(); + + seprint(buf, &buf[sizeof(buf)], "%s/clone", mnt); + if((fd[Fctl] = open(buf, ORDWR)) < 0) + sysfatal("open: %r"); + s = &buf[strlen(mnt)+1]; + if((n = read(fd[Fctl], s, &buf[sizeof(buf)-1]-s)) < 0) + sysfatal("read clone: %r"); + while(n > 0 && buf[n-1] == '\n') + n--; + s += n; + + seprint(s, &buf[sizeof(buf)], "/wait"); + if((fd[Fwait] = open(buf, OREAD)) < 0) + sysfatal("open: %r"); + + if(foreground){ + seprint(s, &buf[sizeof(buf)], "/data"); + if((fd[Fstdin] = open(buf, OWRITE)) < 0) + sysfatal("open: %r"); + + seprint(s, &buf[sizeof(buf)], "/data"); + if((fd[Fstdout] = open(buf, OREAD)) < 0) + sysfatal("open: %r"); + + seprint(s, &buf[sizeof(buf)], "/stderr"); + if((fd[Fstderr] = open(buf, OREAD)) < 0) + sysfatal("open: %r"); + } + + if(dir != nil){ + if(fprint(fd[Fctl], "dir %q", dir) < 0) + sysfatal("cannot change directory: %r"); + } else { + /* + * try to automatically change directory if we are in + * /mnt/term or /mnt/term/root, but unlike -d flag, + * do not error when the dir ctl command fails. + */ + if((s = strrchr(mnt, '/')) != nil){ + n = s - mnt; + dir = getwd(buf, sizeof(buf)); + if(strncmp(dir, mnt, n) == 0 && (dir[n] == 0 || dir[n] == '/')){ + dir += n; + if(strncmp(dir, "/root", 5) == 0 && (dir[5] == 0 || dir[5] == '/')) + dir += 5; + if(*dir == 0) + dir = "/"; + /* hack for win32: /C:... -> C:/... */ + if(dir[1] >= 'A' && dir[1] <= 'Z' && dir[2] == ':') + dir[0] = dir[1], dir[1] = ':', dir[2] = '/'; + fprint(fd[Fctl], "dir %q", dir); + } + } + } + + if(nice != 0) + fprint(fd[Fctl], "nice %d", nice); + + if(foreground) + fprint(fd[Fctl], "killonclose"); + + s = seprint(buf, &buf[sizeof(buf)], "exec"); + while(*argv != nil){ + s = seprint(s, &buf[sizeof(buf)], " %q", *argv++); + if(s >= &buf[sizeof(buf)-1]) + sysfatal("too many arguments"); + } + + if(write(fd[Fctl], buf, s - buf) < 0) + sysfatal("write: %r"); + + if((pid[Preadwait] = fork()) == -1) + sysfatal("fork: %r"); + if(pid[Preadwait] == 0){ + fd01(fd[Fwait], 2); + if((n = read(0, buf, sizeof(buf)-1)) < 0) + rerrstr(buf, sizeof(buf)); + else { + char *f[5]; + + while(n > 0 && buf[n-1] == '\n') + n--; + buf[n] = 0; + if(tokenize(buf, f, 5) == 5) + exits(f[4]); + } + exits(buf); + } + + if(foreground){ + if((pid[Pcopyerr] = fork()) == -1) + sysfatal("fork: %r"); + if(pid[Pcopyerr] == 0){ + fd01(fd[Fstderr], 2); + copy(); + rerrstr(buf, sizeof(buf)); + exits(buf); + } + if((pid[Pcopyout] = fork()) == -1) + sysfatal("fork: %r"); + if(pid[Pcopyout] == 0){ + fd01(fd[Fstdout], 1); + copy(); + rerrstr(buf, sizeof(buf)); + exits(buf); + } + if((pid[Pcopyin] = fork()) == -1) + sysfatal("fork: %r"); + if(pid[Pcopyin] == 0){ + fd01(0, fd[Fstdin]); + copy(); + rerrstr(buf, sizeof(buf)); + exits(buf); + } + } + fd01(fd[Fctl], 2); + atexit(killstdin); + atnotify(catch, 1); + + while((w = wait()) != nil){ + if((s = strstr(w->msg, ": ")) == nil) + s = w->msg; + else + s += 2; + for(n = 0; n < Npid; n++){ + if(pid[n] == w->pid){ + pid[n] = 0; + break; + } + } + if(n == Preadwait) + exits(s); + free(w); + } +} |
