summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/man/1/os97
-rw-r--r--sys/man/3/cmd223
-rw-r--r--sys/src/cmd/os.c261
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);
+ }
+}