Add support for "keyboard" input over the serial port.
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 1 Jun 2006 20:11:00 +0000 (20:11 +0000)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 1 Jun 2006 20:11:00 +0000 (20:11 +0000)
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.

23 files changed:
doc/intro.texi
doc/reference.texi
doc/threads.texi
doc/userprog.texi
solutions/p2.patch
solutions/p3.patch
solutions/p4.patch
src/Makefile.build
src/devices/input.c [new file with mode: 0644]
src/devices/input.h [new file with mode: 0644]
src/devices/kbd.c
src/devices/kbd.h
src/devices/serial.c
src/devices/serial.h
src/examples/shell.c
src/tests/Make.tests
src/tests/filesys/extended/Make.tests
src/threads/init.c
src/utils/.cvsignore
src/utils/Makefile
src/utils/README
src/utils/pintos
src/utils/squish-pty.c [new file with mode: 0644]

index f5e6fb3239ec8fd76d45c191ec967e5708a1ce97..6541cf1b903f5a46f141da68af72a7f68310da90 100644 (file)
@@ -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
index bc0e3ad156190a3ac7d680fa101d2db9d9d4365e..9b02d5a0b52027f2c6567a09c200159c5022fa24 100644 (file)
@@ -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}.
 
index eeb17eeac26298c6728c1349abbb5bc3a12f1222..706f76b7f035e5b7c0e17755dc5c7750aed4d34d 100644 (file)
@@ -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
index b3987d9a29748e547499746c0f50ea0ad96b1eb5..fa534d2382ef3007c36a3f809efbe3eaee29be55 100644 (file)
@@ -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
index 5d855c989e7194c53a6adb7f1acfe5aafa208ef1..03e45eece73ba6aa09897bae0cab517639f4f585 100644 (file)
@@ -503,7 +503,7 @@ diff -u src/userprog/syscall.c~ src/userprog/syscall.c
  #include <syscall-nr.h>
 +#include "userprog/process.h"
 +#include "userprog/pagedir.h"
-+#include "devices/kbd.h"
++#include "devices/input.h"
 +#include "filesys/filesys.h"
 +#include "filesys/file.h"
 +#include "threads/init.h"
@@ -823,7 +823,7 @@ diff -u src/userprog/syscall.c~ src/userprog/syscall.c
 +  if (handle == STDIN_FILENO) 
 +    {
 +      for (bytes_read = 0; (size_t) bytes_read < size; bytes_read++)
-+        if (udst >= (uint8_t *) PHYS_BASE || !put_user (udst++, kbd_getc ()))
++        if (udst >= (uint8_t *) PHYS_BASE || !put_user (udst++, input_getc ()))
 +          thread_exit ();
 +      return bytes_read;
 +    }
index 0ac49841dc1081b04fa98a2af3ffb977b456282d..f8d8591f6d3609cd037f8f6432cc372294a18277 100644 (file)
@@ -817,7 +817,7 @@ diff -u src/userprog/syscall.c~ src/userprog/syscall.c
  #include <syscall-nr.h>
 +#include "userprog/process.h"
 +#include "userprog/pagedir.h"
-+#include "devices/kbd.h"
++#include "devices/input.h"
 +#include "filesys/directory.h"
 +#include "filesys/filesys.h"
 +#include "filesys/file.h"
@@ -1159,7 +1159,7 @@ diff -u src/userprog/syscall.c~ src/userprog/syscall.c
 +          
 +          for (i = 0; i < read_amt; i++) 
 +            {
-+              char c = kbd_getc ();
++              char c = input_getc ();
 +              if (!page_lock (udst, true)) 
 +                thread_exit ();
 +              udst[i] = c;
index 33c0aa2a8d658887c8c4c3eb58322e3556e3b253..fff8ca1cba1625519d1611ec845392aa75f42a41 100644 (file)
@@ -627,22 +627,31 @@ Index: src/filesys/directory.c
 diff -u src/filesys/directory.c~ src/filesys/directory.c
 --- src/filesys/directory.c~
 +++ src/filesys/directory.c
-@@ -21,12 +21,36 @@ struct dir_entry 
+@@ -1,4 +1,5 @@
+ #include <string.h>
+ #include <list.h>
++#include "filesys/free-map.h"
+ #include "filesys/filesys.h"
+ #include "filesys/inode.h"
+@@ -21,12 +21,39 @@ struct dir_entry 
      bool in_use;                        /* In use or free? */
    };
  
 -/* Creates a directory with space for ENTRY_CNT entries in the
 -   given SECTOR.  Returns true if successful, false on failure. */
 +/* Creates a directory in the given SECTOR.
-+   The directory's parent is in PARENT_SECTOR. */
- bool
++   The directory's parent is in PARENT_SECTOR.
++   Returns inode of created directory if successful,
++   null pointer on faiilure.
++   On failure, SECTOR is released in the free map. */
+-bool
++struct inode *
 -dir_create (disk_sector_t sector, size_t entry_cnt) 
 +dir_create (disk_sector_t sector, disk_sector_t parent_sector) 
  {
 -  return inode_create (sector, entry_cnt * sizeof (struct dir_entry));
 +  struct inode *inode = inode_create (sector, DIR_INODE);
-+  bool success = inode != NULL;
-+  if (success) 
++  if (inode != NULL) 
 +    {
 +      struct dir_entry entries[2];
 +
@@ -658,13 +667,14 @@ diff -u src/filesys/directory.c~ src/filesys/directory.c
 +      strlcpy (entries[1].name, "..", sizeof entries[1].name);
 +      entries[1].in_use = true;
 +      
-+      success = (inode_write_at (inode, entries, sizeof entries, 0)
-+                 == sizeof entries);
-+      if (!success)
-+        inode_remove (inode);
++      if (inode_write_at (inode, entries, sizeof entries, 0) != sizeof entries)
++        {
++          inode_remove (inode);
++          inode_close (inode); 
++          inode = NULL;
++        } 
 +    }
-+  inode_close (inode); 
-+  return success;
++  return inode;
  }
  
  /* Opens and returns the directory for the given INODE, of which
@@ -806,7 +816,7 @@ diff -u src/filesys/directory.h~ src/filesys/directory.h
  
  /* Opening and closing directories. */
 -bool dir_create (disk_sector_t sector, size_t entry_cnt);
-+bool dir_create (disk_sector_t sector, disk_sector_t parent_sector);
++struct inode *dir_create (disk_sector_t sector, disk_sector_t parent_sector);
  struct dir *dir_open (struct inode *);
  struct dir *dir_open_root (void);
  struct dir *dir_reopen (struct dir *);
@@ -814,26 +824,32 @@ Index: src/filesys/file.c
 diff -u src/filesys/file.c~ src/filesys/file.c
 --- src/filesys/file.c~
 +++ src/filesys/file.c
+@@ -1,4 +1,5 @@
+ #include "filesys/file.h"
+ #include <debug.h>
++#include "filesys/free-map.h"
+ #include "filesys/inode.h"
+ #include "threads/malloc.h"
 @@ -11,6 +11,24 @@ struct file 
      bool deny_write;            /* Has file_deny_write() been called? */
    };
  
 +/* Creates a file in the given SECTOR,
-+   initially LENGTH bytes long. */
-+bool
++   initially LENGTH bytes long. 
++   Returns inode for the file on success, null pointer on failure.
++   On failure, SECTOR is released in the free map. */
++struct inode *
 +file_create (disk_sector_t sector, off_t length) 
 +{
 +  struct inode *inode = inode_create (sector, FILE_INODE);
-+  bool success = inode != NULL;
-+  if (success && length != 0) 
++  if (inode != NULL && length > 0
++      && inode_write_at (inode, "", 1, length - 1) != 1)
 +    {
-+      ASSERT (length >= 0);
-+      success = inode_write_at (inode, "", 1, length - 1) == 1;
-+      if (!success)
-+        inode_remove (inode); 
++      inode_remove (inode); 
++      inode_close (inode);
++      inode = NULL;
 +    }
-+  inode_close (inode);
-+  return success;
++  return inode;
 +}
 +
  /* Opens a file for the given INODE, of which it takes ownership,
@@ -863,7 +879,7 @@ diff -u src/filesys/file.h~ src/filesys/file.h
  struct inode;
  
  /* Opening and closing files. */
-+bool file_create (disk_sector_t sector, off_t length);
++struct inode *file_create (disk_sector_t sector, off_t length);
  struct file *file_open (struct inode *);
  struct file *file_reopen (struct file *);
  void file_close (struct file *);
@@ -1024,35 +1040,45 @@ diff -u src/filesys/filesys.c~ src/filesys/filesys.c
  }
  \f
  /* Creates a file named NAME with the given INITIAL_SIZE.
-@@ -44,16 +171,24 @@ filesys_done (void) 
+@@ -44,16 +171,32 @@ filesys_done (void) 
     Fails if a file named NAME already exists,
     or if internal memory allocation fails. */
  bool
 -filesys_create (const char *name, off_t initial_size) 
 +filesys_create (const char *name, off_t initial_size, enum inode_type type) 
  {
-+  struct dir *dir;
-+  char base_name[NAME_MAX + 1];
-   disk_sector_t inode_sector = 0;
+-  disk_sector_t inode_sector = 0;
 -  struct dir *dir = dir_open_root ();
 -  bool success = (dir != NULL
 -                  && free_map_allocate (1, &inode_sector)
 -                  && inode_create (inode_sector, initial_size)
 -                  && dir_add (dir, name, inode_sector));
+-  if (!success && inode_sector != 0) 
+-    free_map_release (inode_sector, 1);
++  struct dir *dir;
++  char base_name[NAME_MAX + 1];
++  disk_sector_t inode_sector;
++
 +  bool success = (resolve_name_to_entry (name, &dir, base_name)
 +                  && free_map_allocate (&inode_sector));
 +  if (success) 
 +    {
++      struct inode *inode;
 +      if (type == FILE_INODE)
-+        success = file_create (inode_sector, initial_size);
++        inode = file_create (inode_sector, initial_size);
++      else
++        inode = dir_create (inode_sector,
++                            inode_get_inumber (dir_get_inode (dir))); 
++      if (inode != NULL)
++        {
++          success = dir_add (dir, base_name, inode_sector);
++          if (!success)
++            inode_remove (inode);
++          inode_close (inode);
++        }
 +      else
-+        success = dir_create (inode_sector,
-+                              inode_get_inumber (dir_get_inode (dir))); 
++        success = false;
 +    }
-+  success = success && dir_add (dir, base_name, inode_sector);
-   if (!success && inode_sector != 0) 
--    free_map_release (inode_sector, 1);
-+    free_map_release (inode_sector);
    dir_close (dir);
  
    return success;
@@ -1115,9 +1141,10 @@ diff -u src/filesys/filesys.c~ src/filesys/filesys.c
  \f
  static void must_succeed_function (int, bool) NO_INLINE;
  #define MUST_SUCCEED(EXPR) must_succeed_function (__LINE__, EXPR)
-@@ -155,9 +306,15 @@ static void
+@@ -155,9 +306,18 @@ static void
  do_format (void)
  {
++  struct inode *inode;
    printf ("Formatting file system...");
 +
 +  /* Set up free map. */
@@ -1125,8 +1152,10 @@ diff -u src/filesys/filesys.c~ src/filesys/filesys.c
 -  if (!dir_create (ROOT_DIR_SECTOR, 16))
 +
 +  /* Set up root directory. */
-+  if (!dir_create (ROOT_DIR_SECTOR, ROOT_DIR_SECTOR))
++  inode = dir_create (ROOT_DIR_SECTOR, ROOT_DIR_SECTOR);
++  if (inode == NULL)
      PANIC ("root directory creation failed");
++  inode_close (inode);  
 +
    free_map_close ();
 +
@@ -1241,14 +1270,18 @@ diff -u src/filesys/free-map.c~ src/filesys/free-map.c
    file_close (free_map_file);
  }
  
-@@ -72,7 +76,7 @@ void
+@@ -72,5 +76,9 @@ void
  free_map_create (void) 
  {
++  struct inode *inode;
++
    /* Create inode. */
 -  if (!inode_create (FREE_MAP_SECTOR, bitmap_file_size (free_map)))
-+  if (!file_create (FREE_MAP_SECTOR, 0))
++  inode = file_create (FREE_MAP_SECTOR, 0);
++  if (inode == NULL)   
      PANIC ("free map creation failed");
++  inode_close (inode);
+
    /* Write bitmap to file. */
 Index: src/filesys/free-map.h
 diff -u src/filesys/free-map.h~ src/filesys/free-map.h
@@ -1343,7 +1376,7 @@ diff -u src/filesys/inode.c~ src/filesys/inode.c
    };
  
  /* Returns the number of sectors to allocate for an inode SIZE
-@@ -35,74 +50,54 @@ struct inode 
+@@ -35,74 +50,59 @@ struct inode 
      disk_sector_t sector;               /* Sector number of disk location. */
      int open_cnt;                       /* Number of openers. */
      bool removed;                       /* True if deleted, false otherwise. */
@@ -1397,7 +1430,8 @@ diff -u src/filesys/inode.c~ src/filesys/inode.c
 -inode_create (disk_sector_t sector, off_t length)
 +/* Initializes an inode of the given TYPE, writes the new inode
 +   to sector SECTOR on the file system disk, and returns the
-+   inode thus created.  Returns a null pointer if unsuccessful. */   
++   inode thus created.  Returns a null pointer if unsuccessful,
++   in which case SECTOR is released in the free map. */  
 +struct inode *
 +inode_create (disk_sector_t sector, enum inode_type type) 
  {
@@ -1405,6 +1439,7 @@ diff -u src/filesys/inode.c~ src/filesys/inode.c
 -  bool success = false;
 +  struct cache_block *block;
 +  struct inode_disk *disk_inode;
++  struct inode *inode;
  
 -  ASSERT (length >= 0);
 +  block = cache_lock (sector, EXCLUSIVE);
@@ -1441,7 +1476,10 @@ diff -u src/filesys/inode.c~ src/filesys/inode.c
 -      free (disk_inode);
 -    }
 -  return success;
-+  return inode_open (sector);
++  inode = inode_open (sector);
++  if (inode == NULL)
++    free_map_release (sector);
++  return inode;
  }
  
  /* Reads an inode from SECTOR
@@ -2738,7 +2776,7 @@ diff -u src/userprog/syscall.c~ src/userprog/syscall.c
  #include <syscall-nr.h>
 +#include "userprog/process.h"
 +#include "userprog/pagedir.h"
-+#include "devices/kbd.h"
++#include "devices/input.h"
 +#include "filesys/directory.h"
 +#include "filesys/filesys.h"
 +#include "filesys/file.h"
@@ -3141,7 +3179,7 @@ diff -u src/userprog/syscall.c~ src/userprog/syscall.c
 +          size_t i;
 +          
 +          for (i = 0; i < read_amt; i++) 
-+            udst[i] = kbd_getc ();
++            udst[i] = input_getc ();
 +          bytes_read = read_amt;
 +        }
 +
index 8fa804b0cb669d318e4ad82cbbc095061ef1e707..9a2728d356a1441bd4d13701022908a61b07870e 100644 (file)
@@ -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 (file)
index 0000000..4a12160
--- /dev/null
@@ -0,0 +1,52 @@
+#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);
+}
diff --git a/src/devices/input.h b/src/devices/input.h
new file mode 100644 (file)
index 0000000..a2f50e9
--- /dev/null
@@ -0,0 +1,12 @@
+#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 */
index e48d1b98291cb3571476d93eab5f958d1a00f6fe..4d7dfdf1a315df6cd3b92564c2719bc815d694de 100644 (file)
@@ -3,7 +3,7 @@
 #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"
 
@@ -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);
             }
         }
     }
index ecf477f566e35cc0d3c56aad2f079325b1724b4f..ed9c06bc90d2a03fd9f48c6d245bdcc22770d859 100644 (file)
@@ -4,7 +4,6 @@
 #include <stdint.h>
 
 void kbd_init (void);
-uint8_t kbd_getc (void);
 void kbd_print_stats (void);
 
 #endif /* devices/kbd.h */
index 1cb49a39ee25da6e35d4cd12356fb2a019cdd4b3..92b00ef06eaa67513be6858101f41358b54bb291 100644 (file)
@@ -1,5 +1,6 @@
 #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. */
@@ -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. */
 \f
 /* 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 ();
+}
 \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);
   
@@ -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 ();
 }
index 6d49c117ab9c5f682addf748e979f5d537562afc..b187a80240b1ca888d22d165a871cfa6240a7f4c 100644 (file)
@@ -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 */
index 916d25267ac758a198fb4d299af7fa54edf5bfd4..93641b45b8c3250ff3dd3582fc731668d34ebbe7 100644 (file)
@@ -59,7 +59,7 @@ read_line (char line[], size_t size)
 
       switch (c) 
         {
-        case '\n':
+        case '\r':
           *pos = '\0';
           putchar ('\n');
           return;
index 230e03af729c081789301318afaf3d5cbf4307d0..6595b468eeb7c04d90def8f781396b89a650f8ee 100644 (file)
@@ -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)
index cad67f962c4ce0242ada506dd3fcff817c0d0ed5..b8beeecdf7bca99a549b872819dfbd44d5f40fc0 100644 (file)
@@ -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
index 8af05ece005e629237e3cd459560369e4cab4ca7..c8889f10abbe6af8b1bca6c8874adadb045678f8 100644 (file)
@@ -9,6 +9,7 @@
 #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"
@@ -108,6 +109,7 @@ main (void)
   intr_init ();
   timer_init ();
   kbd_init ();
+  input_init ();
 #ifdef USERPROG
   exception_init ();
   syscall_init ();
index b0175f571ffe458b143edc7ec30d55c78571a23b..13d683392b62497d6c67f344067b5d0d7bbafd7d 100644 (file)
@@ -1 +1,2 @@
 setitimer-helper
+squish-pty
index 9eca3baa148404a344f88046e4ed9aa459166336..7215e905a8f17c61ef385fb5aa5934a1e33ee119 100644 (file)
@@ -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
index f27b6e25d7af7495d3666cf193828399e506fa7d..d99de82851976ccd2c13f00b8c70be875ed2ee45 100644 (file)
@@ -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
index ccf10377641baf04ee31ad679e4df626b8f16dcc..c30633c93de2b9b175fb898c709ffb287c61208d 100755 (executable)
@@ -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 <<EOF;
@@ -402,8 +411,10 @@ 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";
@@ -412,6 +423,7 @@ EOF
 
     # 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.
@@ -459,7 +471,7 @@ sub run_qemu {
     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';
diff --git a/src/utils/squish-pty.c b/src/utils/squish-pty.c
new file mode 100644 (file)
index 0000000..c8375a5
--- /dev/null
@@ -0,0 +1,355 @@
+#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");
+    }
+}