Revise documentation accordingly.
Revise reference solution accordingly.
Change real Return key to produce \r, to match what is received on
serial port.
Update shell example program to expect \r at end of file.
Modify testing makefiles to supply /dev/null as input.
Add squish-pty help program to deal with Bochs,
and modify "pintos" to use it.
displayed in the terminal you used to run @command{pintos}. This is
because Pintos sends all output both to the VGA display and to the first
serial port, and by default the serial port is connected to Bochs's
-@code{stdout}. You can log this output to a file by redirecting at the
+@code{stdin} and @code{stdout}. You can log serial output to a file by
+redirecting at the
command line, e.g.@: @code{pintos run alarm-multiple > logfile}.
The @command{pintos} program offers several options for configuring the
the VM. Finally, you can select how you want VM output to be displayed:
use @option{-v} to turn off the VGA display, @option{-t} to use your
terminal window as the VGA display instead of opening a new window
-(Bochs only), or @option{-s} to suppress the serial output to
-@code{stdout}.
+(Bochs only), or @option{-s} to suppress serial input from @code{stdin}
+and output to @code{stdout}.
The Pintos kernel has commands and options other than @command{run}.
These are not very interesting for now, but you can see a list of them
@func{intr_init} sets up the CPU's @dfn{interrupt descriptor table}
(IDT) to ready it for interrupt handling (@pxref{Interrupt
Infrastructure}), then @func{timer_init} and @func{kbd_init} prepare for
-handling timer interrupts and keyboard interrupts, respectively. In
+handling timer interrupts and keyboard interrupts, respectively.
+@func{input_init} sets up to merge serial and keyboard input into one
+stream. In
projects 2 and later, we also prepare to handle interrupts caused by
user programs using @func{exception_init} and @func{syscall_init}.
@item serial.c
@itemx serial.h
Serial port driver. Again, @func{printf} calls this code for you,
-so you don't need to do so yourself. Feel free to look through it if
-you're curious.
+so you don't need to do so yourself.
+It handles serial input by passing it to the input layer (see below).
@item disk.c
@itemx disk.h
Supports reading and writing sectors on up to 4 IDE disks. This won't
actually be used until project 2.
+@item kbd.c
+@itemx kbd.h
+Keyboard driver. Handles keystrokes passing them to the input layer
+(see below).
+
+@item input.c
+@itemx input.h
+Input layer. Queues input characters passed along by the keyboard or
+serial drivers.
+
@item intq.c
@itemx intq.h
Interrupt queue, for managing a circular queue that both kernel
@var{buffer}. Returns the number of bytes actually read (0 at end of
file), or -1 if the file could not be read (due to a condition other
than end of file). Fd 0 reads from the keyboard using
-@func{kbd_getc}. (Keyboard input will not work if you pass the
-@option{-v} option to @command{pintos}.)
+@func{input_getc}.
@end deftypefn
@deftypefn {System Call} int write (int @var{fd}, const void *@var{buffer}, unsigned @var{size})
@code{pid_t}. By default, they're both @code{int}. You can make them
a one-to-one mapping, so that the same values in both identify the
same process, or you can use a more complex mapping. It's up to you.
-
-@item Keyboard input doesn't work.
-
-You are probably passing @option{-v} to @command{pintos}, but
-serial input isn't implemented. Don't use @option{-v} if you
-want to use the shell or otherwise need keyboard input.
@end table
@menu
devices_SRC += devices/vga.c # Video device.
devices_SRC += devices/serial.c # Serial port device.
devices_SRC += devices/disk.c # IDE disk device.
+devices_SRC += devices/input.c # Serial and keyboard input.
devices_SRC += devices/intq.c # Interrupt queue.
# Library code shared between kernel and user programs.
--- /dev/null
+#include "devices/input.h"
+#include <debug.h>
+#include "devices/intq.h"
+#include "devices/serial.h"
+
+/* Stores keys from the keyboard and serial port. */
+static struct intq buffer;
+
+/* Initializes the input buffer. */
+void
+input_init (void)
+{
+ intq_init (&buffer);
+}
+
+/* Adds a key to the input buffer.
+ Interrupts must be off and the buffer must not be full. */
+void
+input_putc (uint8_t key)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ ASSERT (!intq_full (&buffer));
+
+ intq_putc (&buffer, key);
+ serial_notify ();
+}
+
+/* Retrieves a key from the input buffer.
+ If the buffer is empty, waits for a key to be pressed. */
+uint8_t
+input_getc (void)
+{
+ enum intr_level old_level;
+ uint8_t key;
+
+ old_level = intr_disable ();
+ key = intq_getc (&buffer);
+ serial_notify ();
+ intr_set_level (old_level);
+
+ return key;
+}
+
+/* Returns true if the input buffer is full,
+ false otherwise.
+ Interrupts must be off. */
+bool
+input_full (void)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ return intq_full (&buffer);
+}
--- /dev/null
+#ifndef DEVICES_INPUT_H
+#define DEVICES_INPUT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+void input_init (void);
+void input_putc (uint8_t);
+uint8_t input_getc (void);
+bool input_full (void);
+
+#endif /* devices/input.h */
#include <debug.h>
#include <stdio.h>
#include <string.h>
-#include "devices/intq.h"
+#include "devices/input.h"
#include "threads/interrupt.h"
#include "threads/io.h"
True when on, false when off. */
static bool caps_lock;
-/* Keyboard buffer. */
-static struct intq buffer;
-
/* Number of keys pressed. */
static int64_t key_cnt;
void
kbd_init (void)
{
- intq_init (&buffer);
intr_register_ext (0x21, keyboard_interrupt, "8042 Keyboard");
}
-/* Retrieves a key from the keyboard buffer.
- If the buffer is empty, waits for a key to be pressed. */
-uint8_t
-kbd_getc (void)
-{
- enum intr_level old_level;
- uint8_t key;
-
- old_level = intr_disable ();
- key = intq_getc (&buffer);
- intr_set_level (old_level);
-
- return key;
-}
-
/* Prints keyboard statistics. */
void
kbd_print_stats (void)
{0x01, "\033"},
{0x0e, "\b"},
{0x0f, "\tQWERTYUIOP"},
- {0x1c, "\n"},
+ {0x1c, "\r"},
{0x1e, "ASDFGHJKL"},
{0x2c, "ZXCVBNM"},
{0x37, "*"},
c += 0x80;
/* Append to keyboard buffer. */
- if (!intq_full (&buffer))
+ if (!input_full ())
{
key_cnt++;
- intq_putc (&buffer, c);
+ input_putc (c);
}
}
}
#include <stdint.h>
void kbd_init (void);
-uint8_t kbd_getc (void);
void kbd_print_stats (void);
#endif /* devices/kbd.h */
#include "devices/serial.h"
#include <debug.h>
+#include "devices/input.h"
#include "devices/intq.h"
#include "devices/timer.h"
#include "threads/io.h"
/* DLAB=0 registers. */
#define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */
#define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */
-#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg. (read-only). */
-#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */
-#define LCR_REG (IO_BASE + 3) /* Line Control Register. */
-#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */
-#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */
+#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg.. */
/* DLAB=1 registers. */
#define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */
#define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */
+/* DLAB-insensitive registers. */
+#define IIR_REG (IO_BASE + 2) /* Interrupt Identification Reg. (read-only) */
+#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */
+#define LCR_REG (IO_BASE + 3) /* Line Control Register. */
+#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */
+#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */
+
/* Interrupt Enable Register bits. */
+#define IER_RECV 0x01 /* Interrupt when data received. */
#define IER_XMIT 0x02 /* Interrupt when transmit finishes. */
/* Line Control Register bits. */
#define MCR_OUT2 0x08 /* Output line 2. */
/* Line Status Register. */
+#define LSR_DR 0x01 /* Data Ready: received data byte is in RBR. */
#define LSR_THRE 0x20 /* THR Empty. */
\f
/* Transmission mode. */
void
serial_init_queue (void)
{
+ enum intr_level old_level;
+
ASSERT (mode == POLL);
+
intr_register_ext (0x20 + 4, serial_interrupt, "serial");
mode = QUEUE;
+ old_level = intr_disable ();
+ write_ier ();
+ intr_set_level (old_level);
}
/* Sends BYTE to the serial port. */
putc_poll (intq_getc (&txq));
intr_set_level (old_level);
}
+
+/* The fullness of the input buffer may have changed. Reassess
+ whether we should block receive interrupts.
+ Called by the input buffer routines when characters are added
+ to or removed from the buffer. */
+void
+serial_notify (void)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ if (mode == QUEUE)
+ write_ier ();
+}
\f
/* Configures the serial port for BPS bits per second. */
static void
set_serial (int bps)
{
- int baud_base = 1843200 / 16; /* Base rate of 16550A. */
- uint16_t divisor = baud_base / bps; /* Clock rate divisor. */
+ int base_rate = 1843200 / 16; /* Base rate of 16550A, in Hz. */
+ uint16_t divisor = base_rate / bps; /* Clock rate divisor. */
ASSERT (bps >= 300 && bps <= 115200);
/* Enable DLAB. */
outb (LCR_REG, LCR_N81 | LCR_DLAB);
- /* Set baud rate. */
+ /* Set data rate. */
outb (LS_REG, divisor & 0xff);
outb (MS_REG, divisor >> 8);
outb (LCR_REG, LCR_N81);
}
-/* Update interrupt enable register.
- If our transmit queue is empty, turn off transmit interrupt. */
+/* Update interrupt enable register. */
static void
write_ier (void)
{
- outb (IER_REG, intq_empty (&txq) ? 0 : IER_XMIT);
+ uint8_t ier = 0;
+
+ ASSERT (intr_get_level () == INTR_OFF);
+
+ /* Enable transmit interrupt if we have any characters to
+ transmit. */
+ if (!intq_empty (&txq))
+ ier |= IER_XMIT;
+
+ /* Enable receive interrupt if we have room to store any
+ characters we receive. */
+ if (!input_full ())
+ ier |= IER_RECV;
+
+ outb (IER_REG, ier);
}
/* Polls the serial port until it's ready,
outb (THR_REG, byte);
}
-/* Serial interrupt handler.
- As long as we have a byte to transmit,
- and the hardware is ready to accept a byte for transmission,
- transmit a byte.
- Then update interrupt enable register based on queue
- status. */
+/* Serial interrupt handler. */
static void
serial_interrupt (struct intr_frame *f UNUSED)
{
+ /* Inquire about interrupt in UART. Without this, we can
+ occasionally miss an interrupt running under qemu. */
+ inb (IIR_REG);
+
+ /* As long as we have room to receive a byte, and the hardware
+ has a byte for us, receive a byte. */
+ while (!input_full () && (inb (LSR_REG) & LSR_DR) != 0)
+ input_putc (inb (RBR_REG));
+
+ /* As long as we have a byte to transmit, and the hardware is
+ ready to accept a byte for transmission, transmit a byte. */
while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0)
outb (THR_REG, intq_getc (&txq));
+
+ /* Update interrupt enable register based on queue status. */
write_ier ();
}
void serial_init_queue (void);
void serial_putc (uint8_t);
void serial_flush (void);
+void serial_notify (void);
#endif /* devices/serial.h */
switch (c)
{
- case '\n':
+ case '\r':
*pos = '\0';
putchar ('\n');
return;
TESTCMD += -f
endif
TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F))
+TESTCMD += < /dev/null
TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).output
%.output: os.dsk
$(TESTCMD)
GETCMD += -- -q
GETCMD += $(KERNELFLAGS)
GETCMD += run 'tar fs.tar /'
-GETCMD += 2>> $(TEST).get-errors
-GETCMD += $(if $(VERBOSE),|tee -a,>>) $(TEST).get-output
+GETCMD += < /dev/null
+GETCMD += 2> $(TEST).get-errors $(if $(VERBOSE),|tee,>) $(TEST).get-output
tests/filesys/extended/%.output: os.dsk
rm -f tmp.dsk
#include <stdlib.h>
#include <string.h>
#include "devices/kbd.h"
+#include "devices/input.h"
#include "devices/serial.h"
#include "devices/timer.h"
#include "devices/vga.h"
intr_init ();
timer_init ();
kbd_init ();
+ input_init ();
#ifdef USERPROG
exception_init ();
syscall_init ();
setitimer-helper
+squish-pty
-all: setitimer-helper
+all: setitimer-helper squish-pty
+CC = gcc
+CFLAGS = -Wall -W
LDFLAGS = -lm
setitimer-helper: setitimer-helper.o
+squish-pty: squish-pty.o
clean:
- rm -f *.o setitimer-helper
+ rm -f *.o setitimer-helper squish-pty
+If you want to be able to give input to Pintos running in Bochs
+through a terminal (as opposed to a VGA console window), you will need
+to compile squish-pty (with `make') and install it in PATH.
+Otherwise, squish-pty is unneeded.
+
If your version of Perl predates 5.8.0 and you want the "pintos"
script's timeout support (with -T) to properly limit the child's CPU
time, then you will need to compile setitimer-helper (with `make') and
our ($sim); # Simulator: bochs, qemu, or gsx.
our ($debug) = "none"; # Debugger: none, monitor, or gdb.
our ($mem) = 4; # Physical RAM in MB.
-our ($serial_out) = 1; # Send output to serial port?
+our ($serial) = 1; # Use serial port for input and output?
our ($vga); # VGA output: window, terminal, or none.
our ($jitter); # Seed for random timer interrupts, if set.
our ($realtime); # Synchronize timer interrupts with real time?
"k|kill-on-failure" => \$kill_on_failure,
"v|no-vga" => sub { set_vga ('none'); },
- "s|no-serial" => sub { $serial_out = 0; },
+ "s|no-serial" => sub { $serial = 0; },
"t|terminal" => sub { set_vga ('terminal'); },
"p|put-file=s" => sub { add_file (\@puts, $_[1]); },
undef $timeout, print "warning: disabling timeout with --$debug\n"
if defined ($timeout) && $debug ne 'none';
- print "warning: enabling serial output for -k or --kill-on-failure\n"
- if $kill_on_failure && !$serial_out;
+ print "warning: enabling serial port for -k or --kill-on-failure\n"
+ if $kill_on_failure && !$serial;
}
# usage($exitcode).
--monitor Debug with simulator's monitor
--gdb Debug with gdb
Display options: (default is both VGA and serial)
- -v, --no-vga No VGA display
- -s, --no-serial No serial output
+ -v, --no-vga No VGA display or keyboard
+ -s, --no-serial No serial input or output
-t, --terminal Display VGA in terminal (Bochs only)
Timing options: (Bochs only)
-j SEED Randomize timer interrupts
# Select Bochs binary based on the chosen debugger.
my ($bin) = $debug eq 'monitor' ? 'bochs-dbg' : 'bochs';
+ my ($squish_pty);
+ if ($serial) {
+ for my $dir (split (':', $ENV{PATH})) {
+ $squish_pty = "$dir/squish-pty", last if -x "$dir/squish-pty";
+ }
+ print "warning: can't find squish-pty, so terminal input will fail\n"
+ if !defined $squish_pty;
+ }
+
# Write bochsrc.txt configuration file.
open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n";
print BOCHSRC <<EOF;
print_bochs_disk_line ("ata1-slave", 3);
}
if ($vga ne 'terminal') {
- print BOCHSRC "com1: enabled=1, mode=file, dev=/dev/stdout\n"
- if $serial_out;
+ if ($serial) {
+ my $mode = defined ($squish_pty) ? "term" : "file";
+ print BOCHSRC "com1: enabled=1, mode=$mode, dev=/dev/stdout\n";
+ }
print BOCHSRC "display_library: nogui\n" if $vga eq 'none';
} else {
print BOCHSRC "display_library: term\n";
# Compose Bochs command line.
my (@cmd) = ($bin, '-q');
+ unshift (@cmd, $squish_pty) if defined $squish_pty;
push (@cmd, '-j', $jitter) if defined $jitter;
# Run Bochs.
push (@cmd, '-m', $mem);
push (@cmd, '-net', 'none');
push (@cmd, '-nographic') if $vga eq 'none';
- push (@cmd, '-serial', 'stdio') if $serial_out && $vga ne 'none';
+ push (@cmd, '-serial', 'stdio') if $serial && $vga ne 'none';
push (@cmd, '-S') if $debug eq 'monitor';
push (@cmd, '-s', '-S') if $debug eq 'gdb';
push (@cmd, '-monitor', 'null') if $vga eq 'none' && $debug eq 'none';
--- /dev/null
+#define _GNU_SOURCE 1
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stropts.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+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 pty if FD_IS_PTY
+ 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_pty, const char *call)
+{
+ if (fd_is_pty)
+ {
+ if (retval < 0)
+ {
+ if (errno == EIO)
+ {
+ /* Slave side of pty has been closed. */
+ return false;
+ }
+ else
+ fail_io (call);
+ }
+ else
+ return true;
+ }
+ else
+ {
+ if (retval == 0)
+ {
+ close (*fd);
+ *fd = -1;
+ return true;
+ }
+ else
+ fail_io (call);
+ }
+}
+
+/* Copies data from stdin to PTY and from PTY to stdout until no
+ more data can be read or written. */
+static void
+relay (int pty, int dead_child_fd)
+{
+ struct pipe
+ {
+ int in, out;
+ char buf[BUFSIZ];
+ size_t size, ofs;
+ bool active;
+ };
+ struct pipe pipes[2];
+
+ /* Make PTY, stdin, and stdout non-blocking. */
+ make_nonblocking (pty, true);
+ make_nonblocking (STDIN_FILENO, true);
+ make_nonblocking (STDOUT_FILENO, true);
+
+ /* Configure noncanonical mode on PTY and stdin to avoid
+ waiting for end-of-line. We want to minimize context
+ switching on PTY (for efficiency) and minimize latency on
+ stdin to avoid a laggy user experience. */
+ make_noncanon (pty, 16, 1);
+ make_noncanon (STDIN_FILENO, 1, 0);
+
+ memset (pipes, 0, sizeof pipes);
+ pipes[0].in = STDIN_FILENO;
+ pipes[0].out = pty;
+ pipes[1].in = pty;
+ pipes[1].out = STDOUT_FILENO;
+
+ while (pipes[0].in != -1 || pipes[1].in != -1)
+ {
+ fd_set read_fds, write_fds;
+ 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->pty pipe until we
+ have some data for the pty->stdout pipe. If we get
+ too eager, Bochs 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);
+ }
+ FD_SET (dead_child_fd, &read_fds);
+
+ do
+ {
+ retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL);
+ }
+ while (retval < 0 && errno == EINTR);
+ if (retval < 0)
+ fail_io ("select");
+
+ if (FD_ISSET (dead_child_fd, &read_fds))
+ {
+ /* Child died. Do final relaying. */
+ struct pipe *p = &pipes[1];
+ if (p->out == -1)
+ return;
+ 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)
+ return;
+ }
+ }
+
+ 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 == pty, "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 == pty, "write"))
+ return;
+ }
+ }
+ }
+}
+
+static int dead_child_fd;
+
+static void
+sigchld_handler (int signo __attribute__ ((unused)))
+{
+ if (write (dead_child_fd, "", 1) < 0)
+ _exit (1);
+}
+
+int
+main (int argc __attribute__ ((unused)), char *argv[])
+{
+ int master, slave;
+ char *name;
+ pid_t pid;
+ struct sigaction sa;
+ int pipe_fds[2];
+ struct itimerval zero_itimerval, old_itimerval;
+
+ if (argc < 2)
+ {
+ fprintf (stderr,
+ "usage: squish-pty COMMAND [ARG]...\n"
+ "Squishes both stdin and stdout into a single pseudoterminal,\n"
+ "which is passed as stdout to run the specified COMMAND.\n");
+ return EXIT_FAILURE;
+ }
+
+ /* Open master side of pty and get ready to open slave. */
+ master = open ("/dev/ptmx", O_RDWR | O_NOCTTY);
+ if (master < 0)
+ fail_io ("open \"/dev/ptmx\"");
+ if (grantpt (master) < 0)
+ fail_io ("grantpt");
+ if (unlockpt (master) < 0)
+ fail_io ("unlockpt");
+
+ /* Open slave side of pty. */
+ name = ptsname (master);
+ if (name == NULL)
+ fail_io ("ptsname");
+ slave = open (name, O_RDWR);
+ if (slave < 0)
+ fail_io ("open \"%s\"", name);
+
+ /* System V implementations need STREAMS configuration for the
+ slave. */
+ if (isastream (slave))
+ {
+ if (ioctl (slave, I_PUSH, "ptem") < 0
+ || ioctl (slave, I_PUSH, "ldterm") < 0)
+ fail_io ("ioctl");
+ }
+
+ /* Arrange to get notified when a child dies, by writing a byte
+ to a pipe fd. We really want to use pselect() and
+ sigprocmask(), but Solaris 2.7 doesn't have it. */
+ if (pipe (pipe_fds) < 0)
+ fail_io ("pipe");
+ dead_child_fd = pipe_fds[1];
+
+ memset (&sa, 0, sizeof sa);
+ sa.sa_handler = sigchld_handler;
+ sigemptyset (&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ if (sigaction (SIGCHLD, &sa, NULL) < 0)
+ fail_io ("sigaction");
+
+ /* 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, &old_itimerval) < 0)
+ fail_io ("setitimer");
+
+ pid = fork ();
+ if (pid < 0)
+ fail_io ("fork");
+ else if (pid != 0)
+ {
+ /* Running in parent process. */
+ int status;
+ close (slave);
+ relay (master, pipe_fds[0]);
+
+ /* If the subprocess has died, die in the same fashion.
+ In particular, dying from SIGVTALRM tells the pintos
+ script that we ran out of CPU time. */
+ if (waitpid (pid, &status, WNOHANG) > 0)
+ {
+ if (WIFEXITED (status))
+ return WEXITSTATUS (status);
+ else if (WIFSIGNALED (status))
+ raise (WTERMSIG (status));
+ }
+ return 0;
+ }
+ else
+ {
+ /* Running in child process. */
+ if (setitimer (ITIMER_VIRTUAL, &old_itimerval, NULL) < 0)
+ fail_io ("setitimer");
+ if (dup2 (slave, STDOUT_FILENO) < 0)
+ fail_io ("dup2");
+ if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0
+ || close (slave) < 0 || close (master) < 0)
+ fail_io ("close");
+ execvp (argv[1], argv + 1);
+ fail_io ("exec");
+ }
+}