diff --git a/configure.ac b/configure.ac
index f439f91..092be11 100644
--- a/configure.ac
+++ b/configure.ac
@@ -724,7 +724,7 @@ AC_CHECK_HEADERS(arpa/inet.h dl.h dlfcn.h elf.h fcntl.h floatingpoint.h \
   fpu_control.h glob.h grp.h limits.h locale.h \
   netdb.h netinet/in.h pwd.h stdbool.h strings.h \
   sys/param.h sys/select.h sys/socket.h sys/stat.h sys/resource.h \
-  sys/time.h sys/times.h sys/utsname.h time.h unistd.h)
+  sys/time.h sys/times.h sys/utsname.h time.h unistd.h termios.h)
 ## </NOTE>
 ## <NOTE>
 ## These are C99 headers but some C code (written to work also
diff --git a/src/include/Internal.h b/src/include/Internal.h
index c212409..d39d12a 100644
--- a/src/include/Internal.h
+++ b/src/include/Internal.h
@@ -549,6 +549,7 @@ SEXP do_isseekable(SEXP, SEXP, SEXP, SEXP);
 SEXP do_close(SEXP, SEXP, SEXP, SEXP);
 SEXP do_fifo(SEXP, SEXP, SEXP, SEXP);
 SEXP do_pipe(SEXP, SEXP, SEXP, SEXP);
+SEXP do_tty(SEXP, SEXP, SEXP, SEXP);
 SEXP do_url(SEXP, SEXP, SEXP, SEXP);
 SEXP do_gzfile(SEXP, SEXP, SEXP, SEXP);
 SEXP do_unz(SEXP, SEXP, SEXP, SEXP);
@@ -564,6 +565,7 @@ SEXP do_textconvalue(SEXP, SEXP, SEXP, SEXP);
 SEXP do_getconnection(SEXP, SEXP, SEXP, SEXP);
 SEXP do_getallconnections(SEXP, SEXP, SEXP, SEXP);
 SEXP do_sumconnection(SEXP, SEXP, SEXP, SEXP);
+SEXP do_sumtty(SEXP, SEXP, SEXP, SEXP);
 SEXP do_download(SEXP, SEXP, SEXP, SEXP);
 SEXP do_sockconn(SEXP, SEXP, SEXP, SEXP);
 SEXP do_sockselect(SEXP, SEXP, SEXP, SEXP);
diff --git a/src/library/base/R/connections.R b/src/library/base/R/connections.R
index 773219b..305509f 100644
--- a/src/library/base/R/connections.R
+++ b/src/library/base/R/connections.R
@@ -76,6 +76,12 @@ file <- function(description = "", open = "", blocking = TRUE,
 pipe <- function(description, open = "", encoding = getOption("encoding"))
     .Internal(pipe(description, open, encoding))
 
+tty <- function(description, open = "", blocking = TRUE, baudrate = NULL,
+                input = NULL, output = NULL, control = NULL, local = NULL,
+                chars = NULL)
+    .Internal(tty(description, open, blocking, baudrate, input,
+                  output, control, local, chars))
+
 fifo <- function(description, open = "", blocking = FALSE,
                  encoding = getOption("encoding"))
     .Internal(fifo(description, open, blocking, encoding))
diff --git a/src/library/base/man/connections.Rd b/src/library/base/man/connections.Rd
index 2ecdb45..2c7f08e 100644
--- a/src/library/base/man/connections.Rd
+++ b/src/library/base/man/connections.Rd
@@ -15,6 +15,7 @@
 \alias{bzfile}
 \alias{xzfile}
 \alias{url}
+\alias{tty}
 \alias{socketConnection}
 \alias{open}
 \alias{open.connection}
@@ -61,10 +62,15 @@ pipe(description, open = "", encoding = getOption("encoding"))
 fifo(description, open = "", blocking = FALSE,
      encoding = getOption("encoding"))
 
+tty(description, open = "", blocking = TRUE, baudrate = NULL, 
+    input = NULL, output = NULL, control = NULL, local = NULL,
+    chars = NULL)
+
 socketConnection(host = "localhost", port, server = FALSE,
                  blocking = FALSE, open = "a+",
                  encoding = getOption("encoding"))
 
+
 open(con, \dots)
 \method{open}{connection}(con, open = "r", blocking = TRUE, \dots)
 
@@ -95,6 +101,12 @@ isIncomplete(con)
     \code{xzfile} can also be negative: see the \sQuote{Compression}
     section.}
   \item{filename}{a filename within a zip file.}
+  \item{baudrate}{integer. Baud rate.}
+  \item{input}{list. input flags.}
+  \item{output}{list. output flags.}
+  \item{control}{list. control flags.}
+  \item{local}{list. local flags.}
+  \item{chars}{list. special characters.}
   \item{host}{character.  Host name for port.}
   \item{port}{integer.  The TCP port number.}
   \item{server}{logical.  Should the socket be a client or a server?}
@@ -105,7 +117,7 @@ isIncomplete(con)
   \item{\dots}{arguments passed to or from other methods.}
 }
 \details{
-  The first nine functions create connections.  By default the
+  The first ten functions create connections.  By default the
   connection is not opened (except for \code{socketConnection}), but may
   be opened by setting a non-empty value of argument \code{open}.
 
@@ -156,6 +168,9 @@ isIncomplete(con)
   For \code{fifo} the description is the path of the fifo.  (Windows
   does not have fifos, so attempts to use this function there are an
   error.)
+ 
+  For \code{tty} the description is the path or name of a tty
+  terminal interface (e.g. \code{"/dev/ttyS0"}).
 
   All platforms support \code{file}, \code{gzfile}, \code{bzfile},
   \code{xzfile} \code{unz} and \code{url("file://")} connections.  The
@@ -231,9 +246,9 @@ isIncomplete(con)
 
 \value{
   \code{file}, \code{pipe}, \code{fifo}, \code{url}, \code{gzfile},
-  \code{bzfile}, \code{xzfile}, \code{unz} and \code{socketConnection}
-  return a connection object which inherits from class
-  \code{"connection"} and has a first more specific class.
+  \code{bzfile}, \code{xzfile}, \code{unz}, \code{tty}, 
+  and \code{socketConnection} return a connection object which inherits
+  from class \code{"connection"} and has a first more specific class.
 
   \code{isOpen} returns a logical value, whether the connection is
   currently open.
@@ -283,12 +298,12 @@ isIncomplete(con)
   deferred if \code{open = ""} is given (the default for all but socket
   connections).  An explicit call to \code{open} can specify the mode,
   but otherwise the mode will be \code{"r"}.  (\code{gzfile},
-  \code{bzfile} and \code{xzfile} connections are exceptions, as the
-  compressed file always has to be opened in binary mode and no
-  conversion of line-endings is done even on Windows, so the default
-  mode is interpreted as \code{"rb"}.)  Most operations that need write
-  access or text-only or binary-only mode will override the default mode
-  of a non-yet-open connection.
+  \code{bzfile}, \code{xzfile}, and \code{tty} connections are 
+  exceptions. The compressed file always has to be opened in binary mode
+  and no conversion of line-endings is done even on Windows. The default
+  mode for these connection types is interpreted as \code{"rb"}.)  Most 
+  operations that need write access or text-only or binary-only mode 
+  will override the default mode of a non-yet-open connection.
 }
 
 \section{Compression}{
@@ -371,8 +386,8 @@ isIncomplete(con)
 }
 
 \section{Blocking}{
-  Whether or not the connection blocks can be specified for file, url
-  (default yes) fifo and socket connections (default not).
+  Whether or not the connection blocks can be specified for file, url,
+  and tty (default yes) fifo, and socket connections (default not).
 
   In blocking mode, functions using the connection do not return to the
   \R evaluator until the read/write is complete.  In non-blocking mode,
@@ -394,6 +409,8 @@ isIncomplete(con)
   the time the connection is opened (more precisely, when the last
   connection of that type -- \samp{http:}, \samp{ftp:} or socket -- was
   opened).
+
+  See TTY for blocking in \code{tty} connections.
 }
 
 \section{Fifos}{
@@ -407,6 +424,34 @@ isIncomplete(con)
   and provides an similar facility to \code{file()}.
 }
 
+\section{TTY}{
+  The input, output, control, local, and chars parameters of the
+  tty connection correspond directly to the flags and special
+  characters described in the Low-Level Terminal Interface
+  chapter (chapter 17) in the GNU libc manual
+  (http://www.gnu.org/s/libc/manual/)
+
+  When the tty represents a serial device, the baudrate argument
+  may be used to set the input and output baud rates for the 
+  device.
+
+  The input, output, control, and local arguments are each lists
+  of named logical values, for instance \samp{ICANON=FALSE}.
+  Each named logical value represents a terminal interface 
+  setting. The collection of settings is system dependent, but
+  most are standardized (see the Low-Level Terminal Interface
+  documentation).
+
+  The chars argument is a list of named _raw_ values, for instance
+  \samp{VMIN = as.raw(0)}. Special characters are used, for examle, 
+  to indicate which character should be considered the end of file
+  (EOF) character. In addition, the special characters VMIN and 
+  VTIME are used to control the blocking properties of the terminal.
+
+  \code{tty} connections are only supported on POSIX compliant 
+  systems (e.g. GNU Linux, BSD, Mac OS X) .
+}
+
 \section{Clipboard}{
   \code{file} can be used with \code{description = "clipboard"}
 #ifdef windows
@@ -447,7 +492,7 @@ isIncomplete(con)
 \note{
   \R's connections are modelled on those in S version 4 (see Chambers,
   1998).  However \R goes well beyond the S model, for example in output
-  text connections and URL, compressed and socket connections.
+  text connections and URL, compressed, tty, and socket connections.
 
   The default open mode in \R is \code{"r"} except for socket connections.
   This differs from S, where it is the equivalent of \code{"r+"},
diff --git a/src/main/connections.c b/src/main/connections.c
index 19f4cf5..c9e85d9 100644
--- a/src/main/connections.c
+++ b/src/main/connections.c
@@ -2033,6 +2033,842 @@ static Rconnection newclp(const char *url, const char *inmode)
     return new;
 }
 
+/* -------------------- tty connections --------------------- */
+
+#if defined(HAVE_TERMIOS_H) && defined(HAVE_FCNTL_H)\
+                            && defined(HAVE_FDOPEN)
+#define HAVE_TTY_POSIX 1
+#include <termios.h>
+
+/* strategy:
+   tty(description, open, blocking, baudrate, input, output, 
+       control, local, chars)
+  
+   description - name of tty device
+   open        - file read/write mode
+   blocking    - should read/writes block
+   baudrate    - integer baudrate
+   input       - list of named logical input flags
+   output      - list of named logical output flags
+   control     - list of named logical control flags
+   local       - list of named logical local flags
+   chars       - list of named raw special characters
+   
+   baudrate, input, output, control, local, and chars 
+   correspond to the flags and other items used to control
+   POSIX terminals. These are described in the header
+   termios.h and the Low-Level Terminal Interface chapter
+   (chapter 17) of the GNU libc manual.
+   http://www.gnu.org/s/libc/manual/
+
+   do_tty opens the tty, checks that the device is a tty, 
+   reads the terminal properties (struct termios), and closes
+   the device. The input, output, control, local, and chars 
+   arguments are parsed as _changes_ to the current tty 
+   settings.  
+*/
+
+#define SET_FLAG(flag, mask, valu)\
+    {if(*valu) flag |=  mask;\
+     else      flag &= ~mask;}
+
+/* The parse[iolc]flags series of functions convert a string (name)
+   into a bitmask, and sets the appropriate bit in an instance of 
+   struct termios (tio) according to a logical value (valu). Each
+   return 0 on success, -1 on failure. #ifdefs are applied liberally
+   in order to accommodate various, system dependent extensions to 
+   the termios interface.
+*/
+static int parseiflag(const char *name, int *valu, struct termios *tio)
+{
+    if(strcmp(name, "BRKINT") == 0) {
+        SET_FLAG(tio->c_iflag, BRKINT, valu);
+    } else if(strcmp(name, "ICRNL") == 0) {
+        SET_FLAG(tio->c_iflag, ICRNL, valu);
+    } else if(strcmp(name, "IGNBRK") == 0) {
+        SET_FLAG(tio->c_iflag, IGNBRK, valu);
+    } else if(strcmp(name, "IGNCR") == 0) {
+        SET_FLAG(tio->c_iflag, IGNCR, valu);
+    } else if(strcmp(name, "IGNPAR") == 0) {
+        SET_FLAG(tio->c_iflag, IGNPAR, valu);
+    } else if(strcmp(name, "INLCR") == 0) {
+        SET_FLAG(tio->c_iflag, INLCR, valu);
+    } else if(strcmp(name, "INPCK") == 0) {
+        SET_FLAG(tio->c_iflag, INPCK, valu);
+    } else if(strcmp(name, "ISTRIP") == 0) {
+        SET_FLAG(tio->c_iflag, ISTRIP, valu);
+#ifdef IUCLC
+    } else if(strcmp(name, "IUCLC") == 0) {
+        SET_FLAG(tio->c_iflag, IUCLC, valu);
+#endif
+    } else if(strcmp(name, "IXOFF") == 0) {
+        SET_FLAG(tio->c_iflag, IXOFF, valu);
+    } else if(strcmp(name, "IXON") == 0) {
+        SET_FLAG(tio->c_iflag, IXON, valu);
+    } else if(strcmp(name, "PARMRK") == 0) {
+        SET_FLAG(tio->c_iflag, PARMRK, valu);
+#ifdef IXANY /* BSD extension */
+    } else if(strcmp(name, "IXANY") == 0) {
+        SET_FLAG(tio->c_iflag, IXANY, valu);
+#endif
+#ifdef IMAXBEL /* BSD extension */
+    } else if(strcmp(name, "IMAXBEL") == 0) {
+        SET_FLAG(tio->c_iflag, IMAXBEL, valu);
+#endif
+#ifdef IUTF8
+    } else if(strcmp(name, "IUTF8") == 0) {
+        SET_FLAG(tio->c_iflag, IUTF8, valu);
+#endif
+    } else return -1;
+    return 0;
+}
+
+static int parseoflag(const char *name, int *valu, struct termios *tio)
+{
+    if(strcmp(name, "OPOST") == 0) {
+        SET_FLAG(tio->c_oflag, OPOST, valu);
+#ifdef OLCUC
+    } else if(strcmp(name, "OLCUC") == 0) {
+        SET_FLAG(tio->c_oflag, OLCUC, valu);
+#endif
+#ifdef ONLCR /* BSD extension */
+    } else if(strcmp(name, "ONLCR") == 0) {
+        SET_FLAG(tio->c_oflag, ONLCR, valu);
+#endif
+#ifdef OXTABS /* BSD extension */
+    } else if(strcmp(name, "OXTABS") == 0) {
+        SET_FLAG(tio->c_oflag, OXTABS, valu);
+#endif
+#ifdef ONOEOT /* BSD extension */
+    } else if(strcmp(name, "ONOEOT") == 0) {
+        SET_FLAG(tio->c_oflag, ONOEOT, valu);
+#endif
+#ifdef OCRNL
+    } else if(strcmp(name, "OCRNL") == 0) {
+        SET_FLAG(tio->c_oflag, OCRNL, valu);
+#endif
+#ifdef ONOCR
+    } else if(strcmp(name, "ONOCR") == 0) {
+        SET_FLAG(tio->c_oflag, ONOCR, valu);
+#endif
+#ifdef ONLRET
+    } else if(strcmp(name, "ONLRET") == 0) {
+        SET_FLAG(tio->c_oflag, ONLRET, valu);
+#endif
+#ifdef OFILL
+    } else if(strcmp(name, "OFILL") == 0) {
+        SET_FLAG(tio->c_oflag, OFILL, valu);
+#endif
+#ifdef OFDEL
+    } else if(strcmp(name, "OFDEL") == 0) {
+        SET_FLAG(tio->c_oflag, OFDEL, valu);
+#endif
+#ifdef NLDLY
+    } else if(strcmp(name, "NLDLY") == 0) {
+        warning(_("%s is a bitmask, use %s instead"),\
+                "NLDLY", "NL#");
+    } else if(strcmp(name, "NL0") == 0) {
+        tio->c_oflag &= ~NLDLY;
+        SET_FLAG(tio->c_oflag, NL0, valu);
+    } else if(strcmp(name, "NL1") == 0) {
+        tio->c_oflag &= ~NLDLY;
+        SET_FLAG(tio->c_oflag, NL1, valu);
+#endif
+#ifdef CRDLY
+    } else if(strcmp(name, "CRDLY") == 0) {
+        warning(_("%s is a bitmask, use %s instead"),\
+                "CRDLY", "CR[0-3]");
+    } else if(strcmp(name, "CR0") == 0) {
+        tio->c_oflag &= ~CRDLY;
+        SET_FLAG(tio->c_oflag, CR0, valu);
+    } else if(strcmp(name, "CR1") == 0) {
+        tio->c_oflag &= ~CRDLY;
+        SET_FLAG(tio->c_oflag, CR1, valu);
+    } else if(strcmp(name, "CR2") == 0) {
+        tio->c_oflag &= ~CRDLY;
+        SET_FLAG(tio->c_oflag, CR2, valu);
+    } else if(strcmp(name, "CR3") == 0) {
+        tio->c_oflag &= ~CRDLY;
+        SET_FLAG(tio->c_oflag, CR3, valu);
+#endif
+#ifdef TABDLY
+    } else if(strcmp(name, "TABDLY") == 0) {
+        warning(_("%s is a bitmask, use %s instead"),\
+                "TABDLY", "TAB[0-3]");
+    } else if(strcmp(name, "TAB0") == 0) {
+        tio->c_oflag &= ~TABDLY;
+        SET_FLAG(tio->c_oflag, TAB0, valu);
+    } else if(strcmp(name, "TAB1") == 0) {
+        tio->c_oflag &= ~TABDLY;
+        SET_FLAG(tio->c_oflag, TAB1, valu);
+    } else if(strcmp(name, "TAB2") == 0) {
+        tio->c_oflag &= ~TABDLY;
+        SET_FLAG(tio->c_oflag, TAB2, valu);
+    } else if(strcmp(name, "TAB3") == 0) {
+        tio->c_oflag &= ~TABDLY;
+        SET_FLAG(tio->c_oflag, TAB3, valu);
+#endif
+#ifdef BSDLY
+    } else if(strcmp(name, "BSDLY") == 0) {
+        warning(_("%s is a bitmask, use %s instead"),\
+                "BSDLY", "BS[0-1]");
+    } else if(strcmp(name, "BS0") == 0) {
+        tio->c_oflag &= ~BSDLY;
+        SET_FLAG(tio->c_oflag, BS0, valu);
+    } else if(strcmp(name, "BS1") == 0) {
+        tio->c_oflag &= ~BSDLY;
+        SET_FLAG(tio->c_oflag, BS1, valu);
+#endif
+#ifdef VTDLY
+    } else if(strcmp(name, "VTDLY") == 0) {
+        warning(_("%s is a bitmask, use %s instead"),\
+                "VTDLY", "VT[0-1]");
+    } else if(strcmp(name, "VT0") == 0) {
+        tio->c_oflag &= ~VTDLY;
+        SET_FLAG(tio->c_oflag, VT0, valu);
+    } else if(strcmp(name, "VT1") == 0) {
+        tio->c_oflag &= ~VTDLY;
+        SET_FLAG(tio->c_oflag, VT1, valu);
+#endif
+#ifdef FFDLY
+    } else if(strcmp(name, "FFDLY") == 0) {
+        warning(_("%s is a bitmask, use %s instead"),\
+                "FFDLY", "FF[0-1]");
+    } else if(strcmp(name, "FF0") == 0) {
+        tio->c_oflag &= ~FFDLY;
+        SET_FLAG(tio->c_oflag, FF0, valu);
+    } else if(strcmp(name, "FF1") == 0) {
+        tio->c_oflag &= ~FFDLY;
+        SET_FLAG(tio->c_oflag, FF1, valu);
+#endif
+    } else return -1;
+    return 0;
+}
+
+static int parsecflag(const char *name, int *valu, struct termios *tio)
+{
+    if(strcmp(name, "CSIZE") == 0) {
+        warning(_("%s is a bitmask, use %s instead"),\
+                "CSIZE", "CS[5-8]");
+    } else if(strcmp(name, "CS5") == 0) {
+        tio->c_cflag &= ~CSIZE;
+        SET_FLAG(tio->c_cflag, CS5, valu);
+    } else if(strcmp(name, "CS6") == 0) {
+        tio->c_cflag &= ~CSIZE;
+        SET_FLAG(tio->c_cflag, CS6, valu);
+    } else if(strcmp(name, "CS7") == 0) {
+        tio->c_cflag &= ~CSIZE;
+        SET_FLAG(tio->c_cflag, CS7, valu);
+    } else if(strcmp(name, "CS8") == 0) {
+        tio->c_cflag &= ~CSIZE;
+        SET_FLAG(tio->c_cflag, CS8, valu);
+    } else if(strcmp(name, "CSTOPB") == 0) {
+        SET_FLAG(tio->c_cflag, CSTOPB, valu);
+    } else if(strcmp(name, "CREAD") == 0) {
+        SET_FLAG(tio->c_cflag, CREAD, valu);
+    } else if(strcmp(name, "PARENB") == 0) {
+        SET_FLAG(tio->c_cflag, PARENB, valu);
+    } else if(strcmp(name, "PARODD") == 0) {
+        SET_FLAG(tio->c_cflag, PARODD, valu);
+    } else if(strcmp(name, "HUPCL") == 0) {
+        SET_FLAG(tio->c_cflag, HUPCL, valu);
+    } else if(strcmp(name, "CLOCAL") == 0) {
+        SET_FLAG(tio->c_cflag, CLOCAL, valu);
+#ifdef CCTS_OFLOW /* BSD extension */
+    } else if(strcmp(name, "CCTS_OFLOW") == 0) {
+        SET_FLAG(tio->c_cflag, CCTS_OFLOW, valu);
+#endif
+#ifdef CRTS_IFLOW /* BSD extension */
+    } else if(strcmp(name, "CRTS_IFLOW") == 0) {
+        SET_FLAG(tio->c_cflag, CRTS_IFLOW, valu);
+#endif
+#ifdef MDMBUF /* BSD estension */
+    } else if(strcmp(name, "MDMBUF") == 0) {
+        SET_FLAG(tio->c_cflag, MDMBUF, valu);
+#endif
+#ifdef CIGNORE /* BSD extension */
+    } else if(strcmp(name, "CIGNORE") == 0) {
+        SET_FLAG(tio->c_cflag, CIGNORE, valu);
+#endif
+#ifdef CMSPAR
+    } else if(strcmp(name, "CMSPAR") == 0) {
+        SET_FLAG(tio->c_cflag, CMSPAR, valu);
+#endif
+#ifdef CRTSCTS
+    } else if(strcmp(name, "CRTSCTS") == 0) {
+        SET_FLAG(tio->c_cflag, CRTSCTS, valu);
+#endif
+    } else return -1;
+    return 0;
+}
+
+static int parselflag(const char *name, int *valu, struct termios *tio)
+{
+    if(strcmp(name, "ECHO") == 0) {
+        SET_FLAG(tio->c_lflag, ECHO, valu);
+    } else if(strcmp(name, "ECHOE") == 0) {
+        SET_FLAG(tio->c_lflag, ECHOE, valu);
+    } else if(strcmp(name, "ECHOK") == 0) {
+        SET_FLAG(tio->c_lflag, ECHOK, valu);
+    } else if(strcmp(name, "ECHONL") == 0) {
+        SET_FLAG(tio->c_lflag, ECHONL, valu);
+#ifdef ECHOKE /* BSD extension */
+    } else if(strcmp(name, "ECHOKE") == 0) {
+        SET_FLAG(tio->c_lflag, ECHOKE, valu);
+#endif
+#ifdef ECHOPRT /* BSD extension */
+    } else if(strcmp(name, "ECHOPRT") == 0) {
+        SET_FLAG(tio->c_lflag, ECHOPRT, valu);
+#endif
+#ifdef ECHOCTL /* BSD extension */
+    } else if(strcmp(name, "ECHOCTL") == 0) {
+        SET_FLAG(tio->c_lflag, ECHOCTL, valu);
+#endif
+    } else if(strcmp(name, "ICANON") == 0) {
+        SET_FLAG(tio->c_lflag, ICANON, valu);
+    } else if(strcmp(name, "IEXTEN") == 0) {
+        SET_FLAG(tio->c_lflag, IEXTEN, valu);
+    } else if(strcmp(name, "ISIG") == 0) {
+        SET_FLAG(tio->c_lflag, ISIG, valu);
+    } else if(strcmp(name, "NOFLSH") == 0) {
+        SET_FLAG(tio->c_lflag, NOFLSH, valu);
+    } else if(strcmp(name, "TOSTOP") == 0) {
+        SET_FLAG(tio->c_lflag, TOSTOP, valu);
+#ifdef XCASE
+    } else if(strcmp(name, "XCASE") == 0) {
+        SET_FLAG(tio->c_lflag, XCASE, valu);
+#endif
+#ifdef ALTWERASE /* BSD extension */
+    } else if(strcmp(name, "ALTWERASE") == 0) {
+        SET_FLAG(tio->c_lflag, ALTWERASE, valu);
+#endif
+#ifdef FLUSHO /* BSD extension */
+    } else if(strcmp(name, "FLUSHO") == 0) {
+        SET_FLAG(tio->c_lflag, FLUSHO, valu);
+#endif
+#ifdef NOKERNINFO /* BSD extension */
+    } else if(strcmp(name, "NOKERNINFO") == 0) {
+        SET_FLAG(tio->c_lflag, NOKERNINFO, valu);
+#endif
+#ifdef PENDIN /* BSD extension */
+    } else if(strcmp(name, "PENDIN") == 0) {
+        SET_FLAG(tio->c_lflag, PENDIN, valu);
+#endif
+#ifdef EXTPROC
+    } else if(strcmp(name, "EXTPROC") == 0) {
+        SET_FLAG(tio->c_lflag, EXTPROC, valu);
+#endif
+    } else return -1;
+    return 0; 
+}
+
+/* The parsechars function converts a character string (name)
+   to an index in the c_cc array of an instance of
+   struct termios (tio), and sets the corresponding character
+   to a value (valu). parsechars returns 0 on success, -1 on failure
+*/
+int parsechars(const char *name, const char *valu, struct termios *tio)
+{
+    if(strcmp(name, "VEOF") == 0) {
+        tio->c_cc[VEOF] = *valu;
+    } else if(strcmp(name, "VEOL") == 0) {
+        tio->c_cc[VEOL] = *valu;
+    } else if(strcmp(name, "VERASE") == 0) {
+        tio->c_cc[VERASE] = *valu;
+    } else if(strcmp(name, "VINTR") == 0) {
+        tio->c_cc[VINTR] = *valu;
+    } else if(strcmp(name, "VKILL") == 0) {
+        tio->c_cc[VKILL] = *valu;
+    } else if(strcmp(name, "VMIN") == 0) {
+        tio->c_cc[VMIN] = *valu;
+    } else if(strcmp(name, "VQUIT") == 0) {
+        tio->c_cc[VQUIT] = *valu;
+    } else if(strcmp(name, "VSTART") == 0) {
+        tio->c_cc[VSTART] = *valu;
+    } else if(strcmp(name, "VSTOP") == 0) {
+        tio->c_cc[VSTOP] = *valu;
+    } else if(strcmp(name, "VSUSP") == 0) {
+        tio->c_cc[VSUSP] = *valu;
+    } else if(strcmp(name, "VTIME") == 0) {
+        tio->c_cc[VTIME] = *valu;
+#ifdef VLNEXT /* BSD extension */
+    } else if(strcmp(name, "VLNEXT") == 0) {
+        tio->c_cc[VLNEXT] = *valu;
+#endif
+#ifdef VDISCARD /* BSD extension */
+    } else if(strcmp(name, "VDISCARD") == 0) {
+        tio->c_cc[VDISCARD] = *valu;
+#endif
+#ifdef VSTATUS /* BSD extension */
+    } else if(strcmp(name, "VSTATUS") == 0) {
+        tio->c_cc[VSTATUS] = *valu;
+#endif
+#ifdef VSWTC
+    } else if(strcmp(name, "VSWTC") == 0) {
+        tio->c_cc[VSWTC] = *valu;
+#endif
+#ifdef VREPRINT
+    } else if(strcmp(name, "VREPRINT") == 0) {
+        tio->c_cc[VREPRINT] = *valu;
+#endif
+#ifdef VWERASE
+    } else if(strcmp(name, "VWERASE") == 0) {
+        tio->c_cc[VWERASE] = *valu;
+#endif
+#ifdef VEOL2
+    } else if(strcmp(name, "VEOL2") == 0) {
+        tio->c_cc[VEOL2] = *valu;
+#endif
+#ifdef VDSUSP
+    } else if(strcmp(name, "VDSUSP") == 0) {
+        tio->c_cc[VDSUSP] = *valu;
+#endif
+
+    } else return -1;
+    return 0;
+}
+
+/* The parsebaud function converts an integer (valu)
+   to a baudrate, and sets the input or output baudrate
+   of an instace of struct termios (tio). Whether parsebaud
+   sets the input or output baudrate is determined by an 
+   integer (io), where 0 indicates output, and nonzero
+   indicates input. parsebaud returns 0 on success, -1 on 
+   failure
+*/
+#define SET_BAUD(tio, io, baud)\
+    {if(io) cfsetispeed(tio, baud);\
+     else cfsetospeed(tio, baud);}
+int parsebaud(int io, int *valu, struct termios *tio)
+{
+    if(*valu==0) { SET_BAUD(tio, io, B0); }
+    else if(*valu==50) { SET_BAUD(tio, io, B50); }
+    else if(*valu==75) { SET_BAUD(tio, io, B75); }
+    else if(*valu==110) { SET_BAUD(tio, io, B110); }
+    else if(*valu==134) { SET_BAUD(tio, io, B134); }
+    else if(*valu==150) { SET_BAUD(tio, io, B150); }
+    else if(*valu==200) { SET_BAUD(tio, io, B200); }
+    else if(*valu==300) { SET_BAUD(tio, io, B300); }
+    else if(*valu==600) { SET_BAUD(tio, io, B600); }
+    else if(*valu==1200) { SET_BAUD(tio, io, B1200); }
+    else if(*valu==1800) { SET_BAUD(tio, io, B1800); }
+    else if(*valu==2400) { SET_BAUD(tio, io, B2400); }
+    else if(*valu==4800) { SET_BAUD(tio, io, B4800); }
+    else if(*valu==9600) { SET_BAUD(tio, io, B9600); }
+    else if(*valu==19200) { SET_BAUD(tio, io, B19200); }
+    else if(*valu==38400) { SET_BAUD(tio, io, B38400); }
+#ifdef B57600
+    else if(*valu==57600) { SET_BAUD(tio, io, B57600); }
+#endif
+#ifdef B115300
+    else if(*valu==115200) { SET_BAUD(tio, io, B115200); }
+#endif
+#ifdef B230400
+    else if(*valu==230400) { SET_BAUD(tio, io, B230400); }
+#endif
+#ifdef B460800
+    else if(*valu==460800) { SET_BAUD(tio, io, B460800); }
+#endif
+#ifdef B500000
+    else if(*valu==500000) { SET_BAUD(tio, io, B500000); }
+#endif
+#ifdef B576000
+    else if(*valu==576000) { SET_BAUD(tio, io, B576000); }
+#endif
+#ifdef B921600
+    else if(*valu==921600) { SET_BAUD(tio, io, B921600); }
+#endif
+#ifdef B1000000
+    else if(*valu==1000000) { SET_BAUD(tio, io, B1000000); }
+#endif
+#ifdef B1152000
+    else if(*valu==1152000) { SET_BAUD(tio, io, B1152000); }
+#endif
+#ifdef B1500000
+    else if(*valu==1500000) { SET_BAUD(tio, io, B1500000); }
+#endif
+#ifdef B2000000
+    else if(*valu==2000000) { SET_BAUD(tio, io, B2000000); }
+#endif
+#ifdef B2500000
+    else if(*valu==2500000) { SET_BAUD(tio, io, B2500000); }
+#endif
+#ifdef B3000000
+    else if(*valu==3000000) { SET_BAUD(tio, io, B3000000); }
+#endif
+#ifdef B3500000
+    else if(*valu==3500000) { SET_BAUD(tio, io, B3500000); }
+#endif
+#ifdef B4000000
+    else if(*valu==4000000) { SET_BAUD(tio, io, B4000000); }
+#endif
+    else return -1;
+    return 0;
+}
+
+typedef struct ttyconn {
+    FILE * fp;
+    struct termios initio; // initial settings 
+    struct termios curtio; // current settings
+} *Rttyconn;
+
+static void tty_close(Rconnection con)
+{
+    Rttyconn tty = (Rttyconn)con->private;
+    int fd = fileno(tty->fp);
+
+    /* restore initial tty attributes */
+    if(tcsetattr(fd, TCSANOW, &tty->initio) < 0) {
+        warning(_("failed to restore tty attributes %s: %s"),\
+          con->description, strerror(errno));
+    }
+    fclose(tty->fp);
+    con->isopen = FALSE;
+}
+
+static Rboolean tty_open(Rconnection con)
+{
+    Rttyconn tty = (Rttyconn)con->private;
+    int fd, omode;
+    FILE *fp;
+    char *ma, *mr, *mw, *mp; 
+    speed_t inbaud, outbaud;
+
+    /* open in binary mode only */
+    ma = strchr(con->mode, 'a');
+    mr = strchr(con->mode, 'r');
+    mw = strchr(con->mode, 'w');
+    mp = strchr(con->mode, '+');
+    omode = (mp || (mr && mw) || (mr && ma))? O_RDWR:\
+            (mw || ma)? O_WRONLY : O_RDONLY;
+    strcpy(con->mode, (mp || (mr && mw) || (mr && ma))? "r+b":\
+                      (mw || ma) ? "wb" : "rb" );
+
+    /* initially open with O_NONBLOCK, clear later */
+    fd = open(con->description, omode|O_NONBLOCK|O_NOCTTY);
+    if(fd < 0) {
+	warning(_("cannot open %s in mode %s: %s"),\
+            con->description, con->mode, strerror(errno));
+	return FALSE;
+    }
+    tty->fp = fdopen(fd, con->mode);
+    if(!tty->fp) {
+	warning(_("cannot open %s in mode %s: %s"),\
+            con->description, con->mode, strerror(errno));
+	return FALSE;
+    }
+
+    /* save initial tty attributes */
+    if(tcgetattr(fd, &tty->initio) < 0) {
+        warning(_("failed to get tty attributes %s: %s"),\
+            con->description, strerror(errno));
+        fclose(fp);
+        return FALSE;
+    }
+ 
+    /* set current tty attributes */
+    if(tcsetattr(fd, TCSANOW, &tty->curtio) < 0) {
+        warning(_("failed to set tty attributes %s: %s\n"),\
+            con->description, strerror(errno));
+        tty_close(con);
+        return FALSE;
+    }
+
+    /* clear O_NONBLOCK so that VMIN & VTIME take effect */
+    omode = fcntl(fd, F_GETFL);
+    if(omode < 0) {
+        warning(_("failed to get tty attributes %s: %s\n"),\
+            con->description, strerror(errno));
+        tty_close(con);
+        return FALSE;
+    }
+    omode &= ~O_NONBLOCK;
+    if(fcntl(fd, F_SETFL, omode) < 0) {
+        warning(_("failed to set tty attributes %s: %s\n"),\
+            con->description, strerror(errno));
+        tty_close(con);
+        return FALSE;
+    }
+
+    con->isopen = TRUE;
+    con->canwrite = mp || mw || ma;
+    con->canread = mp || mr;
+
+    /* set_iconv(con); // doesn't do anything (yet?) */
+    return TRUE;
+}
+
+static int tty_fflush(Rconnection con)
+{
+    Rttyconn tty = (Rttyconn)con->private;
+    return fflush(tty->fp);
+}
+
+static size_t 
+tty_read(void *ptr, size_t size, size_t nitems, Rconnection con)
+{
+    Rttyconn tty = (Rttyconn)con->private;
+    return fread(ptr, size, nitems, tty->fp);
+}
+
+static int tty_fgetc_internal(Rconnection con)
+{
+    char buf[1];
+    size_t size = tty_read(buf, 1, 1, con);
+    return (size < 1) ? R_EOF : (buf[0] % 256);
+}
+
+static int tty_fgetc(Rconnection con)
+{
+    return tty_fgetc_internal(con);
+}
+
+static size_t 
+tty_write(const void *ptr, size_t size, size_t nitems, Rconnection con)
+{
+    Rttyconn tty = (Rttyconn)con->private;
+    return fwrite(ptr, size, nitems, tty->fp);
+}
+
+static int tty_vfprintf(Rconnection con, const char *format, va_list ap)
+{
+    Rttyconn tty = con->private;
+    return vfprintf(tty->fp, format, ap);
+}
+
+static Rconnection
+newtty(const char *device, const char *open, struct termios *tio)
+{
+    Rconnection con;
+    Rttyconn tty;
+
+    con = (Rconnection) malloc(sizeof(struct Rconn));
+    if(!con) error(_("allocation of %s connection failed"), "tty");
+    con->class = (char *) malloc(strlen("tty") + 1);
+    if(!con->class) {
+	free(con);
+	error(_("allocation of %s connection failed"), "tty");
+    }
+    strcpy(con->class, "tty");
+
+    con->description = (char *) malloc(strlen(device) + 1);
+    if(!con->description) {
+	free(con->class); free(con);
+	error(_("allocation of tty connection failed"), "tty");
+    }
+    init_con(con, device, CE_NATIVE, open);
+
+    con->text = FALSE;
+    con->open = &tty_open;
+    con->close = &tty_close;
+    con->fflush = &tty_fflush;
+    con->read = &tty_read; 
+    con->write = &tty_write;
+    con->fgetc_internal = &tty_fgetc_internal; 
+    con->fgetc = &tty_fgetc;
+    con->vfprintf = &tty_vfprintf;
+
+    tty = (Rttyconn) malloc(sizeof(struct ttyconn));
+    if(!tty) {
+	free(con->description); free(con->class); free(con);
+	error(_("allocation of tty connection failed"));
+    }
+    con->private = (void *)tty;
+    memcpy(&tty->curtio, tio, sizeof(struct termios));
+
+    if(tty->curtio.c_lflag & ICANON) {
+        con->blocking = TRUE;
+    } else if(tty->curtio.c_cc[VMIN]  > 0 ||\
+              tty->curtio.c_cc[VTIME] > 0) {
+        con->blocking = TRUE;
+    } else con->blocking = FALSE;
+        
+    return con;
+}
+#endif /* HAVE_TTY_POSIX */
+
+SEXP attribute_hidden do_tty(SEXP call, SEXP op, SEXP args, SEXP env)
+{
+#ifdef HAVE_TTY_POSIX
+    SEXP elmt, name, names, sarg, down, class, ans;
+    struct termios tio;
+    const char *device, *mode;
+    int i, fd, ncon, block;
+    Rconnection con;
+
+    checkArity(op, args);
+
+    /* description */
+    down = args;
+    sarg = CAR(down);
+    if(!isString(sarg) || length(sarg) != 1)
+	error(_("invalid '%s' argument"), "description");
+    device = translateChar(STRING_ELT(sarg, 0));
+
+    /* mode */
+    down = CDR(down);
+    sarg = CAR(down);
+    if(!isString(sarg) || length(sarg) != 1)
+	error(_("invalid '%s' argument"), "open");
+    mode = CHAR(STRING_ELT(sarg, 0));
+
+    /* blocking */
+    down = CDR(down);
+    sarg = CAR(down);
+    if(!isLogical(sarg) || length(sarg) < 1)
+        error(_("invalid '%s' argument"), "blocking");
+    if(LOGICAL(sarg)[0] == NA_LOGICAL)
+        error(_("invalid '%a' argument"), "blocking");
+    block = LOGICAL(sarg)[0];
+
+
+    /* open, check isatty, get termios info, close */
+    fd = open(device, O_RDONLY|O_NONBLOCK|O_NOCTTY);
+    if(fd < 0) error(_("cannot open %s: %s"), device, strerror(errno));
+    if(!isatty(fd)) error("%s is not a tty", device);
+    if(tcgetattr(fd, &tio) < 0) {
+        close(fd);
+        error(_("failed to get serial attributes %s: %s"),\
+            con->description, strerror(errno));
+    }
+    close(fd);
+
+    /* if blocking FALSE, clear ICANON flag, set VMIN, VTIME */
+    if(!block) {
+        tio.c_lflag &= ~ICANON;
+        tio.c_cc[VMIN] = 0;
+        tio.c_cc[VTIME] = 0;
+    }
+
+    /* baudrate */
+    down = CDR(down);
+    sarg = CAR(down);
+    if(sarg != R_NilValue) {
+        if(!isInteger(sarg) || length(sarg) != 1)
+            error(_("invalid %s argument"), "baudrate");
+        elmt = sarg;
+        if(parsebaud(0, INTEGER(elmt), &tio))
+            error("invalid baudrate %d", INTEGER(elmt)[0]);
+        if(parsebaud(1, INTEGER(elmt), &tio))
+            error("invalid baudrate %d", INTEGER(elmt)[0]);
+    }
+     
+    /* input list */
+    down = CDR(down);
+    sarg = CAR(down);
+    if(sarg != R_NilValue) {
+        if(!isNewList(sarg) || length(sarg) < 1)
+            error(_("invalid %s argument"), "input");
+        names = getAttrib(sarg, R_NamesSymbol);
+        for(i = 0; i < length(sarg); i++) {
+            elmt = VECTOR_ELT(sarg, i);
+            name = STRING_ELT(names, i);
+            if(!isLogical(elmt) || length(elmt) != 1)
+                error(_("invalid %s flag %s"), "input", CHAR(name));
+            if(parseiflag(CHAR(name), LOGICAL(elmt), &tio))
+                error(_("invalid %s flag %s"), "input", CHAR(name));
+        }
+    }
+
+    /* output list */
+    down = CDR(down);
+    sarg = CAR(down);
+    if(sarg != R_NilValue) {
+        if(!isNewList(sarg) || length(sarg) < 1)
+            error(_("invalid %s argument"), "output");
+        names = getAttrib(sarg, R_NamesSymbol);
+        for(i = 0; i < length(sarg); i++) {
+            elmt = VECTOR_ELT(sarg, i);
+            name = STRING_ELT(names, i);
+            if(!isLogical(elmt) || length(elmt) != 1)
+                error(_("invalid %s flag %s"), "output", CHAR(name));
+            if(parseoflag(CHAR(name), LOGICAL(elmt), &tio))
+                error(_("invalid %s flag %s"), "output", CHAR(name));
+        }
+    }
+
+    /* control list */
+    down = CDR(down);
+    sarg = CAR(down);
+    if(sarg != R_NilValue) {
+        if(!isNewList(sarg) || length(sarg) < 1)
+            error(_("invalid %s argument"), "control");
+        names = getAttrib(sarg, R_NamesSymbol);
+        for(i = 0; i < length(sarg); i++) {
+            elmt = VECTOR_ELT(sarg, i);
+            name = STRING_ELT(names, i);
+            if(!isLogical(elmt) || length(elmt) != 1)
+                error(_("invalid %s flag %s"), "control", CHAR(name));
+            if(parsecflag(CHAR(name), LOGICAL(elmt), &tio))
+                error(_("invalid %s flag %s"), "control", CHAR(name));
+        }
+    }
+
+    /* local list */
+    down = CDR(down);
+    sarg = CAR(down);
+    if(sarg != R_NilValue) {
+        if(!isNewList(sarg) || length(sarg) < 1)
+            error(_("invalid %s argument"), "local");
+        names = getAttrib(sarg, R_NamesSymbol);
+        for(i = 0; i < length(sarg); i++) {
+            elmt = VECTOR_ELT(sarg, i);
+            name = STRING_ELT(names, i);
+            if(!isLogical(elmt) || length(elmt) != 1)
+                error(_("invalid %s flag %s"), "local", CHAR(name));
+            if(parselflag(CHAR(name), LOGICAL(elmt), &tio))
+                error(_("invalid %s flag %s"), "local", CHAR(name));
+        }
+    }
+
+    /* chars list */
+    down = CDR(down);
+    sarg = CAR(down);
+    if(sarg != R_NilValue) {
+        if(!isNewList(sarg) || length(sarg) < 1)
+            error(_("invalid %s argument"), "chars");
+        names = getAttrib(sarg, R_NamesSymbol);
+        for(i = 0; i < length(sarg); i++) {
+            elmt = VECTOR_ELT(sarg, i);
+            name = STRING_ELT(names, i);
+            if(TYPEOF(elmt) != RAWSXP || length(elmt) != 1)
+                error(_("invalid %s argument %s"), "chars", CHAR(name));
+            if(parsechars(CHAR(name), RAW(elmt), &tio))
+                error(_("invalid %s argument %s"), "chars", CHAR(name));
+        }
+    }
+
+    ncon = NextConnection();
+    con  = newtty(device, strlen(mode)?mode:"rb", &tio);
+    Connections[ncon] = con;
+    
+    /* open if requested */
+    if(strlen(mode)) {
+        if(!con->open(con)) {
+            con_destroy(ncon);
+            error(_("cannot open the connection"));
+        }
+    }
+
+    PROTECT(ans = ScalarInteger(ncon));
+    PROTECT(class = allocVector(STRSXP, 2));
+    SET_STRING_ELT(class, 0, mkChar("tty"));
+    SET_STRING_ELT(class, 1, mkChar("connection"));
+    classgets(ans, class);
+    con->ex_ptr = R_MakeExternalPtr(con->id, install("connection"),
+				    R_NilValue);
+    setAttrib(ans, install("conn_id"), con->ex_ptr);
+    R_RegisterCFinalizerEx(con->ex_ptr, conFinalizer, FALSE);
+    UNPROTECT(2);
+
+    return ans; 
+#else
+    error(_("tty connections are not supported on this platform"));
+    return R_NilValue;
+#endif 
+}
+
+
 /* ------------------- terminal connections --------------------- */
 
 static unsigned char  ConsoleBuf[CONSOLE_BUFFER_SIZE+1];
diff --git a/src/main/names.c b/src/main/names.c
index 48b6a1e..cf9aeed 100644
--- a/src/main/names.c
+++ b/src/main/names.c
@@ -948,6 +948,7 @@ attribute_hidden FUNTAB R_FunTab[] =
 {"bzfile",	do_gzfile,	1,      11,     4,      {PP_FUNCALL, PREC_FN,	0}},
 {"xzfile",	do_gzfile,	2,      11,     4,      {PP_FUNCALL, PREC_FN,	0}},
 {"unz",		do_unz,		0,      11,     3,      {PP_FUNCALL, PREC_FN,	0}},
+{"tty",		do_tty,		0,	11,	9,	{PP_FUNCALL, PREC_FN,	0}},
 {"seek",	do_seek,	0,      11,     4,      {PP_FUNCALL, PREC_FN,	0}},
 {"truncate",	do_truncate,	0,      11,     1,      {PP_FUNCALL, PREC_FN,	0}},
 {"pushBack",	do_pushback,	0,      11,     3,      {PP_FUNCALL, PREC_FN,	0}},
