From 837e5b7fb902bd749106309ef76a5276c73ca34c Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 1 Jun 2006 20:11:00 +0000 Subject: [PATCH] Add support for "keyboard" input over the serial port. 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. --- doc/intro.texi | 7 +- doc/reference.texi | 4 +- doc/threads.texi | 14 +- doc/userprog.texi | 9 +- src/Makefile.build | 1 + src/devices/input.c | 52 ++++ src/devices/input.h | 12 + src/devices/kbd.c | 27 +- src/devices/kbd.h | 1 - src/devices/serial.c | 79 ++++-- src/devices/serial.h | 1 + src/examples/shell.c | 2 +- src/tests/Make.tests | 1 + src/tests/filesys/extended/Make.tests | 4 +- src/threads/init.c | 2 + src/utils/.cvsignore | 1 + src/utils/Makefile | 7 +- src/utils/README | 5 + src/utils/pintos | 30 ++- src/utils/squish-pty.c | 355 ++++++++++++++++++++++++++ 20 files changed, 545 insertions(+), 69 deletions(-) create mode 100644 src/devices/input.c create mode 100644 src/devices/input.h create mode 100644 src/utils/squish-pty.c diff --git a/doc/intro.texi b/doc/intro.texi index f5e6fb3..6541cf1 100644 --- a/doc/intro.texi +++ b/doc/intro.texi @@ -239,7 +239,8 @@ read. However, you've probably noticed by now that the same text was 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 @@ -254,8 +255,8 @@ with a debugger (@pxref{GDB}). You can set the amount of memory to give 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 diff --git a/doc/reference.texi b/doc/reference.texi index bc0e3ad..9b02d5a 100644 --- a/doc/reference.texi +++ b/doc/reference.texi @@ -177,7 +177,9 @@ The next set of calls initializes the interrupt system. @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}. diff --git a/doc/threads.texi b/doc/threads.texi index eeb17ee..706f76b 100644 --- a/doc/threads.texi +++ b/doc/threads.texi @@ -217,14 +217,24 @@ call this code yourself. @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 diff --git a/doc/userprog.texi b/doc/userprog.texi index b3987d9..fa534d2 100644 --- a/doc/userprog.texi +++ b/doc/userprog.texi @@ -690,8 +690,7 @@ Reads @var{size} bytes from the file open as @var{fd} into @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}) @@ -910,12 +909,6 @@ You can choose whatever suitable types you like for @code{tid_t} and @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 diff --git a/src/Makefile.build b/src/Makefile.build index 8fa804b..9a2728d 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -28,6 +28,7 @@ devices_SRC += devices/kbd.c # Keyboard device. 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. diff --git a/src/devices/input.c b/src/devices/input.c new file mode 100644 index 0000000..4a12160 --- /dev/null +++ b/src/devices/input.c @@ -0,0 +1,52 @@ +#include "devices/input.h" +#include +#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); +} diff --git a/src/devices/input.h b/src/devices/input.h new file mode 100644 index 0000000..a2f50e9 --- /dev/null +++ b/src/devices/input.h @@ -0,0 +1,12 @@ +#ifndef DEVICES_INPUT_H +#define DEVICES_INPUT_H + +#include +#include + +void input_init (void); +void input_putc (uint8_t); +uint8_t input_getc (void); +bool input_full (void); + +#endif /* devices/input.h */ diff --git a/src/devices/kbd.c b/src/devices/kbd.c index e48d1b9..4d7dfdf 100644 --- a/src/devices/kbd.c +++ b/src/devices/kbd.c @@ -3,7 +3,7 @@ #include #include #include -#include "devices/intq.h" +#include "devices/input.h" #include "threads/interrupt.h" #include "threads/io.h" @@ -20,9 +20,6 @@ static bool left_ctrl, right_ctrl; /* Left and right Ctl keys. */ 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; @@ -32,25 +29,9 @@ static intr_handler_func keyboard_interrupt; 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) @@ -75,7 +56,7 @@ static const struct keymap invariant_keymap[] = {0x01, "\033"}, {0x0e, "\b"}, {0x0f, "\tQWERTYUIOP"}, - {0x1c, "\n"}, + {0x1c, "\r"}, {0x1e, "ASDFGHJKL"}, {0x2c, "ZXCVBNM"}, {0x37, "*"}, @@ -167,10 +148,10 @@ keyboard_interrupt (struct intr_frame *args UNUSED) c += 0x80; /* Append to keyboard buffer. */ - if (!intq_full (&buffer)) + if (!input_full ()) { key_cnt++; - intq_putc (&buffer, c); + input_putc (c); } } } diff --git a/src/devices/kbd.h b/src/devices/kbd.h index ecf477f..ed9c06b 100644 --- a/src/devices/kbd.h +++ b/src/devices/kbd.h @@ -4,7 +4,6 @@ #include void kbd_init (void); -uint8_t kbd_getc (void); void kbd_print_stats (void); #endif /* devices/kbd.h */ diff --git a/src/devices/serial.c b/src/devices/serial.c index 1cb49a3..92b00ef 100644 --- a/src/devices/serial.c +++ b/src/devices/serial.c @@ -1,5 +1,6 @@ #include "devices/serial.h" #include +#include "devices/input.h" #include "devices/intq.h" #include "devices/timer.h" #include "threads/io.h" @@ -19,17 +20,21 @@ /* 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. */ @@ -40,6 +45,7 @@ #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. */ /* Transmission mode. */ @@ -75,9 +81,15 @@ serial_init_poll (void) 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. */ @@ -123,20 +135,32 @@ serial_flush (void) 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 (); +} /* 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); @@ -144,12 +168,25 @@ set_serial (int bps) 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, @@ -164,16 +201,24 @@ putc_poll (uint8_t byte) 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 (); } diff --git a/src/devices/serial.h b/src/devices/serial.h index 6d49c11..b187a80 100644 --- a/src/devices/serial.h +++ b/src/devices/serial.h @@ -7,5 +7,6 @@ void serial_init_poll (void); void serial_init_queue (void); void serial_putc (uint8_t); void serial_flush (void); +void serial_notify (void); #endif /* devices/serial.h */ diff --git a/src/examples/shell.c b/src/examples/shell.c index 916d252..93641b4 100644 --- a/src/examples/shell.c +++ b/src/examples/shell.c @@ -59,7 +59,7 @@ read_line (char line[], size_t size) switch (c) { - case '\n': + case '\r': *pos = '\0'; putchar ('\n'); return; diff --git a/src/tests/Make.tests b/src/tests/Make.tests index 230e03a..6595b46 100644 --- a/src/tests/Make.tests +++ b/src/tests/Make.tests @@ -63,6 +63,7 @@ ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) 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) diff --git a/src/tests/filesys/extended/Make.tests b/src/tests/filesys/extended/Make.tests index cad67f9..b8beeec 100644 --- a/src/tests/filesys/extended/Make.tests +++ b/src/tests/filesys/extended/Make.tests @@ -39,8 +39,8 @@ endif 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 diff --git a/src/threads/init.c b/src/threads/init.c index 8af05ec..c8889f1 100644 --- a/src/threads/init.c +++ b/src/threads/init.c @@ -9,6 +9,7 @@ #include #include #include "devices/kbd.h" +#include "devices/input.h" #include "devices/serial.h" #include "devices/timer.h" #include "devices/vga.h" @@ -108,6 +109,7 @@ main (void) intr_init (); timer_init (); kbd_init (); + input_init (); #ifdef USERPROG exception_init (); syscall_init (); diff --git a/src/utils/.cvsignore b/src/utils/.cvsignore index b0175f5..13d6833 100644 --- a/src/utils/.cvsignore +++ b/src/utils/.cvsignore @@ -1 +1,2 @@ setitimer-helper +squish-pty diff --git a/src/utils/Makefile b/src/utils/Makefile index 9eca3ba..7215e90 100644 --- a/src/utils/Makefile +++ b/src/utils/Makefile @@ -1,7 +1,10 @@ -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 diff --git a/src/utils/README b/src/utils/README index f27b6e2..d99de82 100644 --- a/src/utils/README +++ b/src/utils/README @@ -1,3 +1,8 @@ +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 diff --git a/src/utils/pintos b/src/utils/pintos index ccf1037..c30633c 100755 --- a/src/utils/pintos +++ b/src/utils/pintos @@ -11,7 +11,7 @@ our ($start_time) = time (); 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? @@ -64,7 +64,7 @@ sub parse_command_line { "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]); }, @@ -92,8 +92,8 @@ sub parse_command_line { 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). @@ -115,8 +115,8 @@ Debugger selection: --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 @@ -378,6 +378,15 @@ sub run_bochs { # 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 < +#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 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"); + } +} -- 2.30.2