From 2a7028b32e0b34edc8d3d570af1d0d810bea36b4 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 2 Jun 2006 22:06:51 +0000 Subject: [PATCH] Replace GSX Server support with VMware Player support. Fully support serial terminal with Player. --- doc/installation.texi | 3 +- doc/intro.texi | 3 +- src/utils/.cvsignore | 1 + src/utils/Makefile | 5 +- src/utils/pintos | 119 +++++++++----- src/utils/squish-unix.c | 338 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 423 insertions(+), 46 deletions(-) create mode 100644 src/utils/squish-unix.c diff --git a/doc/installation.texi b/doc/installation.texi index bdcdfc3..3bab217 100644 --- a/doc/installation.texi +++ b/doc/installation.texi @@ -65,7 +65,7 @@ Optional: @uref{http://www.tug.org/, @TeX{}}. Also required to build the PDF version of the documentation. @item -Optional: @uref{http://www.vmware.com/, VMware GSX Server}. This is a +Optional: @uref{http://www.vmware.com/, VMware Player}. This is a third platform that can also be used to test Pintos. @end itemize @@ -93,6 +93,7 @@ about missing @file{gdb-macros}, it is installed correctly. @item Compile the remaining Pintos utilities by typing @command{make} in @file{src/utils}. Install @file{squish-pty} somewhere in @env{PATH}. +To support VMware Player, install @file{squish-unix}. If your Perl is older than version 5.8.0, also install @file{setitimer-helper}; otherwise, it is unneeded. diff --git a/doc/intro.texi b/doc/intro.texi index 0a5f85b..a74ce82 100644 --- a/doc/intro.texi +++ b/doc/intro.texi @@ -17,8 +17,7 @@ systems and software can run under it. In class we will use the @uref{http://bochs.sourceforge.net, , Bochs} and @uref{http://fabrice.bellard.free.fr/qemu/, , qemu} simulators. Pintos has also been tested with -@uref{http://www.vmware.com/products/server/gsx_features.html, , -VMware GSX Server}. +@uref{http://www.vmware.com/, , VMware Player}. These projects are hard. CS 140 has a reputation of taking a lot of time, and deservedly so. We will do what we can to reduce the workload, such diff --git a/src/utils/.cvsignore b/src/utils/.cvsignore index 13d6833..b96f278 100644 --- a/src/utils/.cvsignore +++ b/src/utils/.cvsignore @@ -1,2 +1,3 @@ setitimer-helper squish-pty +squish-unix diff --git a/src/utils/Makefile b/src/utils/Makefile index 7215e90..46a9124 100644 --- a/src/utils/Makefile +++ b/src/utils/Makefile @@ -1,10 +1,11 @@ -all: setitimer-helper squish-pty +all: setitimer-helper squish-pty squish-unix CC = gcc CFLAGS = -Wall -W LDFLAGS = -lm setitimer-helper: setitimer-helper.o squish-pty: squish-pty.o +squish-unix: squish-unix.o clean: - rm -f *.o setitimer-helper squish-pty + rm -f *.o setitimer-helper squish-pty squish-unix diff --git a/src/utils/pintos b/src/utils/pintos index c30633c..9de9f9a 100755 --- a/src/utils/pintos +++ b/src/utils/pintos @@ -8,7 +8,7 @@ use Getopt::Long qw(:config bundling); # Command-line options. our ($start_time) = time (); -our ($sim); # Simulator: bochs, qemu, or gsx. +our ($sim); # Simulator: bochs, qemu, or player. our ($debug) = "none"; # Debugger: none, monitor, or gdb. our ($mem) = 4; # Physical RAM in MB. our ($serial) = 1; # Use serial port for input and output? @@ -49,7 +49,7 @@ sub parse_command_line { GetOptions ("sim=s" => sub { set_sim (@_) }, "bochs" => sub { set_sim ("bochs") }, "qemu" => sub { set_sim ("qemu") }, - "gsx" => sub { set_sim ("gsx") }, + "player" => sub { set_sim ("player") }, "debug=s" => sub { set_debug (@_) }, "no-debug" => sub { set_debug ("none") }, @@ -109,7 +109,7 @@ where each OPTION is one of the following options Simulator selection: --bochs (default) Use Bochs as simulator --qemu Use qemu as simulator - --gsx Use VMware GSX Server 3.x as simulator + --player Use VMware Player as simulator Debugger selection: --no-debug (default) No debugger --monitor Debug with simulator's monitor @@ -366,8 +366,8 @@ sub run_vm { run_bochs (); } elsif ($sim eq 'qemu') { run_qemu (); - } elsif ($sim eq 'gsx') { - run_gsx (); + } elsif ($sim eq 'player') { + run_player (); } else { die "unknown simulator `$sim'\n"; } @@ -380,9 +380,7 @@ sub run_bochs { my ($squish_pty); if ($serial) { - for my $dir (split (':', $ENV{PATH})) { - $squish_pty = "$dir/squish-pty", last if -x "$dir/squish-pty"; - } + $squish_pty = find_in_path ("squish-pty"); print "warning: can't find squish-pty, so terminal input will fail\n" if !defined $squish_pty; } @@ -478,38 +476,50 @@ sub run_qemu { run_command (@cmd); } -# gsx_unsup($flag) +# player_unsup($flag) # -# Prints a message that $flag is unsupported by GSX Server. -sub gsx_unsup { +# Prints a message that $flag is unsupported by VMware Player. +sub player_unsup { my ($flag) = @_; - print "warning: no support for $flag with VMware GSX Server\n"; + print "warning: no support for $flag with VMware Player\n"; } -# Runs VMware GSX Server. -sub run_gsx { - gsx_unsup ("--$debug") if $debug ne 'none'; - gsx_unsup ("--no-vga") if $vga eq 'none'; - gsx_unsup ("--terminal") if $vga eq 'terminal'; - gsx_unsup ("--jitter") if defined $jitter; - gsx_unsup ("--kill-on-failure") if defined $kill_on_failure; +# Runs VMware Player. +sub run_player { + player_unsup ("--$debug") if $debug ne 'none'; + player_unsup ("--no-vga") if $vga eq 'none'; + player_unsup ("--terminal") if $vga eq 'terminal'; + player_unsup ("--jitter") if defined $jitter; + player_unsup ("--timeout"), undef $timeout if defined $timeout; + player_unsup ("--kill-on-failure"), undef $kill_on_failure + if defined $kill_on_failure; - unlink ("pintos.out"); + # Memory size must be multiple of 4. + $mem = int (($mem + 3) / 4) * 4; open (VMX, ">", "pintos.vmx") or die "pintos.vmx: create: $!\n"; chmod 0777 & ~umask, "pintos.vmx"; print VMX <{FILE_NAME}; next if !defined $dsk; - my ($pln) = $dsk; - $pln =~ s/\.dsk//; - $pln .= ".pln"; - my ($device) = "ide" . int ($i / 2) . ":" . ($i % 2); + my ($pln) = "$device.pln"; print VMX <", $pln) or die "$pln: create: $!\n"; print PLN <&/dev/null"); - system ("vmware-cmd $vmx stop hard >&/dev/null"); - system ("vmware -l -G -x -q $vmx"); - system ("vmware-cmd $vmx stop hard >&/dev/null"); - system ("vmware-cmd -s unregister $vmx >&/dev/null"); + my (@cmd) = ("vmplayer", $vmx); + unshift (@cmd, $squish_unix, "pintos.socket") if $squish_unix; + print join (' ', @cmd), "\n"; + xsystem (@cmd); } # Disk utilities. @@ -831,3 +858,13 @@ sub SIGVTALRM { } return 0; } + +# find_in_path ($program) +# +# Searches for $program in $ENV{PATH}. +# Returns $program if found, otherwise undef. +sub find_in_path { + my ($program) = @_; + -x "$_/$program" and return $program foreach split (':', $ENV{PATH}); + return; +} diff --git a/src/utils/squish-unix.c b/src/utils/squish-unix.c new file mode 100644 index 0000000..805205b --- /dev/null +++ b/src/utils/squish-unix.c @@ -0,0 +1,338 @@ +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void +fail_io (const char *msg, ...) + __attribute__ ((noreturn)) + __attribute__ ((format (printf, 1, 2))); + +/* Prints MSG, formatting as with printf(), + plus an error message based on errno, + and exits. */ +static void +fail_io (const char *msg, ...) +{ + va_list args; + + va_start (args, msg); + vfprintf (stderr, msg, args); + va_end (args); + + if (errno != 0) + fprintf (stderr, ": %s", strerror (errno)); + putc ('\n', stderr); + exit (EXIT_FAILURE); +} + +/* If FD is a terminal, configures it for noncanonical input mode + with VMIN and VTIME set as indicated. + If FD is not a terminal, has no effect. */ +static void +make_noncanon (int fd, int vmin, int vtime) +{ + if (isatty (fd)) + { + struct termios termios; + if (tcgetattr (fd, &termios) < 0) + fail_io ("tcgetattr"); + termios.c_lflag &= ~(ICANON | ECHO); + termios.c_cc[VMIN] = vmin; + termios.c_cc[VTIME] = vtime; + if (tcsetattr (fd, TCSANOW, &termios) < 0) + fail_io ("tcsetattr"); + } +} + +/* Make FD non-blocking if NONBLOCKING is true, + or blocking if NONBLOCKING is false. */ +static void +make_nonblocking (int fd, bool nonblocking) +{ + int flags = fcntl (fd, F_GETFL); + if (flags < 0) + fail_io ("fcntl"); + if (nonblocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + if (fcntl (fd, F_SETFL, flags) < 0) + fail_io ("fcntl"); +} + +/* Handle a read or write on *FD, which is the socket if + FD_IS_SOCK is true, that returned end-of-file or error + indication RETVAL. The system call is named CALL, for use in + error messages. Returns true if processing may continue, + false if we're all done. */ +static bool +handle_error (ssize_t retval, int *fd, bool fd_is_sock, const char *call) +{ + if (retval == 0) + { + if (fd_is_sock) + return false; + else + { + *fd = -1; + return true; + } + } + else + fail_io (call); +} + +/* Copies data from stdin to SOCK and from SOCK to stdout until no + more data can be read or written. */ +static void +relay (int sock) +{ + struct pipe + { + int in, out; + char buf[BUFSIZ]; + size_t size, ofs; + bool active; + }; + struct pipe pipes[2]; + + /* In case stdin is a file, go back to the beginning. + This allows replaying the input on reset. */ + lseek (STDIN_FILENO, 0, SEEK_SET); + + /* Make SOCK, stdin, and stdout non-blocking. */ + make_nonblocking (sock, true); + make_nonblocking (STDIN_FILENO, true); + make_nonblocking (STDOUT_FILENO, true); + + /* Configure noncanonical mode on stdin to avoid waiting for + end-of-line. */ + make_noncanon (STDIN_FILENO, 1, 0); + + memset (pipes, 0, sizeof pipes); + pipes[0].in = STDIN_FILENO; + pipes[0].out = sock; + pipes[1].in = sock; + pipes[1].out = STDOUT_FILENO; + + while (pipes[0].in != -1 || pipes[1].in != -1 + || (pipes[1].size && pipes[1].out != -1)) + { + fd_set read_fds, write_fds; + sigset_t empty_set; + int retval; + int i; + + FD_ZERO (&read_fds); + FD_ZERO (&write_fds); + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + + /* Don't do anything with the stdin->sock pipe until we + have some data for the sock->stdout pipe. If we get + too eager, vmplayer will throw away our input. */ + if (i == 0 && !pipes[1].active) + continue; + + if (p->in != -1 && p->size + p->ofs < sizeof p->buf) + FD_SET (p->in, &read_fds); + if (p->out != -1 && p->size > 0) + FD_SET (p->out, &write_fds); + } + sigemptyset (&empty_set); + retval = pselect (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL, + &empty_set); + if (retval < 0) + { + if (errno == EINTR) + { + /* Child died. Do final relaying. */ + struct pipe *p = &pipes[1]; + if (p->out == -1) + exit (0); + make_nonblocking (STDOUT_FILENO, false); + for (;;) + { + ssize_t n; + + /* Write buffer. */ + while (p->size > 0) + { + n = write (p->out, p->buf + p->ofs, p->size); + if (n < 0) + fail_io ("write"); + else if (n == 0) + fail_io ("zero-length write"); + p->ofs += n; + p->size -= n; + } + p->ofs = 0; + + p->size = n = read (p->in, p->buf, sizeof p->buf); + if (n <= 0) + exit (0); + } + } + fail_io ("select"); + } + + for (i = 0; i < 2; i++) + { + struct pipe *p = &pipes[i]; + if (p->in != -1 && FD_ISSET (p->in, &read_fds)) + { + ssize_t n = read (p->in, p->buf + p->ofs + p->size, + sizeof p->buf - p->ofs - p->size); + if (n > 0) + { + p->active = true; + p->size += n; + if (p->size == BUFSIZ && p->ofs != 0) + { + memmove (p->buf, p->buf + p->ofs, p->size); + p->ofs = 0; + } + } + else if (!handle_error (n, &p->in, p->in == sock, "read")) + return; + } + if (p->out != -1 && FD_ISSET (p->out, &write_fds)) + { + ssize_t n = write (p->out, p->buf + p->ofs, p->size); + if (n > 0) + { + p->ofs += n; + p->size -= n; + if (p->size == 0) + p->ofs = 0; + } + else if (!handle_error (n, &p->out, p->out == sock, "write")) + return; + } + } + } +} + +static void +sigchld_handler (int signo __attribute__ ((unused))) +{ + /* Nothing to do. */ +} + +int +main (int argc __attribute__ ((unused)), char *argv[]) +{ + pid_t pid; + struct itimerval zero_itimerval; + struct sockaddr_un sun; + sigset_t sigchld_set; + int sock; + + if (argc < 3) + { + fprintf (stderr, + "usage: squish-unix SOCKET COMMAND [ARG]...\n" + "Squishes both stdin and stdout into a single Unix domain\n" + "socket named SOCKET, and runs COMMAND as a subprocess.\n"); + return EXIT_FAILURE; + } + + /* Create socket. */ + sock = socket (PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) + fail_io ("socket"); + + /* Configure socket. */ + sun.sun_family = AF_LOCAL; + strncpy (sun.sun_path, argv[1], sizeof sun.sun_path); + sun.sun_path[sizeof sun.sun_path - 1] = '\0'; + if (unlink (sun.sun_path) < 0 && errno != ENOENT) + fail_io ("unlink"); + if (bind (sock, (struct sockaddr *) &sun, + (offsetof (struct sockaddr_un, sun_path) + + strlen (sun.sun_path) + 1)) < 0) + fail_io ("bind"); + + /* Listen on socket. */ + if (listen (sock, 1) < 0) + fail_io ("listen"); + + /* Block SIGCHLD and set up a handler for it. */ + sigemptyset (&sigchld_set); + sigaddset (&sigchld_set, SIGCHLD); + if (sigprocmask (SIG_BLOCK, &sigchld_set, NULL) < 0) + fail_io ("sigprocmask"); + if (signal (SIGCHLD, sigchld_handler) == SIG_ERR) + fail_io ("signal"); + + /* Save the virtual interval timer, which might have been set + by the process that ran us. It really should be applied to + our child process. */ + memset (&zero_itimerval, 0, sizeof zero_itimerval); + if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, NULL) < 0) + fail_io ("setitimer"); + + pid = fork (); + if (pid < 0) + fail_io ("fork"); + else if (pid != 0) + { + /* Running in parent process. */ + make_nonblocking (sock, true); + for (;;) + { + fd_set read_fds; + sigset_t empty_set; + int retval; + int conn; + + /* Wait for connection. */ + FD_ZERO (&read_fds); + FD_SET (sock, &read_fds); + sigemptyset (&empty_set); + retval = pselect (sock + 1, &read_fds, NULL, NULL, NULL, &empty_set); + if (retval < 0) + { + if (errno == EINTR) + break; + fail_io ("select"); + } + + /* Accept connection. */ + conn = accept (sock, NULL, NULL); + if (conn < 0) + fail_io ("accept"); + + /* Relay connection. */ + relay (conn); + close (conn); + } + return 0; + } + else + { + /* Running in child process. */ + if (close (sock) < 0) + fail_io ("close"); + execvp (argv[2], argv + 2); + fail_io ("exec"); + } +} -- 2.30.2