am a6a36c0b: Update the value for the NFC shared UID.

* commit 'a6a36c0b799b84d06b695027d205f21d13a3275b':
  Update the value for the NFC shared UID.
diff --git a/adb/adb.c b/adb/adb.c
index d9f96df..f5e6e0c 100644
--- a/adb/adb.c
+++ b/adb/adb.c
@@ -302,8 +302,10 @@
 {
     asocket *s;
 
-    D("handle_packet() %d\n", p->msg.command);
-
+    D("handle_packet() %c%c%c%c\n", ((char*) (&(p->msg.command)))[0],
+            ((char*) (&(p->msg.command)))[1],
+            ((char*) (&(p->msg.command)))[2],
+            ((char*) (&(p->msg.command)))[3]);
     print_packet("recv", p);
 
     switch(p->msg.command){
@@ -680,9 +682,11 @@
     dup2(fd, 1);
     dup2(fd, 2);
     fprintf(stderr,"--- adb starting (pid %d) ---\n", getpid());
+    adb_close(fd);
 
     fd = unix_open("/dev/null", O_RDONLY);
     dup2(fd, 0);
+    adb_close(fd);
 }
 #endif
 
@@ -873,7 +877,7 @@
             // don't run as root if ro.secure is set...
             secure = 1;
 
-            // ... except we allow running as root in userdebug builds if the 
+            // ... except we allow running as root in userdebug builds if the
             // service.adb.root property has been set by the "adb root" command
             property_get("ro.debuggable", value, "");
             if (strcmp(value, "1") == 0) {
@@ -1264,8 +1268,8 @@
 
 int main(int argc, char **argv)
 {
-    adb_trace_init();
 #if ADB_HOST
+    adb_trace_init();
     adb_sysdeps_init();
     return adb_commandline(argc - 1, argv + 1);
 #else
diff --git a/adb/commandline.c b/adb/commandline.c
index dcba83b..5ed1b52 100644
--- a/adb/commandline.c
+++ b/adb/commandline.c
@@ -683,6 +683,7 @@
     char buf[4096];
     int no_daemon = 0;
     int is_daemon = 0;
+    int is_server = 0;
     int persist = 0;
     int r;
     int quote;
@@ -719,7 +720,9 @@
 
     /* modifiers and flags */
     while(argc > 0) {
-        if(!strcmp(argv[0],"nodaemon")) {
+        if(!strcmp(argv[0],"server")) {
+            is_server = 1;
+        } else if(!strcmp(argv[0],"nodaemon")) {
             no_daemon = 1;
         } else if (!strcmp(argv[0], "fork-server")) {
             /* this is a special flag used only when the ADB client launches the ADB Server */
@@ -766,7 +769,7 @@
     adb_set_transport(ttype, serial);
     adb_set_tcp_specifics(server_port);
 
-    if ((argc > 0) && (!strcmp(argv[0],"server"))) {
+    if (is_server) {
         if (no_daemon || is_daemon) {
             r = adb_main(is_daemon, server_port);
         } else {
@@ -838,12 +841,24 @@
         return adb_send_emulator_command(argc, argv);
     }
 
-    if(!strcmp(argv[0], "shell")) {
+    if(!strcmp(argv[0], "shell") || !strcmp(argv[0], "hell")) {
         int r;
         int fd;
 
+        char h = (argv[0][0] == 'h');
+
+        if (h) {
+            printf("\x1b[41;33m");
+            fflush(stdout);
+        }
+
         if(argc < 2) {
-            return interactive_shell();
+            r = interactive_shell();
+            if (h) {
+                printf("\x1b[0m");
+                fflush(stdout);
+            }
+            return r;
         }
 
         snprintf(buf, sizeof buf, "shell:%s", argv[1]);
@@ -877,6 +892,10 @@
                 adb_sleep_ms(1000);
                 do_cmd(ttype, serial, "wait-for-device", 0);
             } else {
+                if (h) {
+                    printf("\x1b[0m");
+                    fflush(stdout);
+                }
                 return r;
             }
         }
diff --git a/adb/file_sync_client.c b/adb/file_sync_client.c
index da25ae8..5c7a26f 100644
--- a/adb/file_sync_client.c
+++ b/adb/file_sync_client.c
@@ -57,9 +57,9 @@
     if (t == 0)  /* prevent division by 0 :-) */
         t = 1000000;
 
-    fprintf(stderr,"%lld KB/s (%d bytes in %lld.%03llds)\n",
+    fprintf(stderr,"%lld KB/s (%lld bytes in %lld.%03llds)\n",
             ((((long long) total_bytes) * 1000000LL) / t) / 1024LL,
-            total_bytes, (t / 1000000LL), (t % 1000000LL) / 1000LL);
+            (long long) total_bytes, (t / 1000000LL), (t % 1000000LL) / 1000LL);
 }
 
 void sync_quit(int fd)
diff --git a/adb/file_sync_service.h b/adb/file_sync_service.h
index 11ea06b..e402e06 100644
--- a/adb/file_sync_service.h
+++ b/adb/file_sync_service.h
@@ -17,7 +17,7 @@
 #ifndef _FILE_SYNC_SERVICE_H_
 #define _FILE_SYNC_SERVICE_H_
 
-#ifdef __ppc__
+#ifdef HAVE_BIG_ENDIAN
 static inline unsigned __swap_uint32(unsigned x) 
 {
     return (((x) & 0xFF000000) >> 24)
diff --git a/adb/jdwp_service.c b/adb/jdwp_service.c
index 0c26f7b..cd62b55 100644
--- a/adb/jdwp_service.c
+++ b/adb/jdwp_service.c
@@ -5,6 +5,7 @@
 #include <errno.h>
 #include <stdio.h>
 #include <string.h>
+#include <unistd.h>
 
 /* here's how these things work.
 
@@ -320,6 +321,7 @@
             struct iovec     iov;
             char             dummy = '!';
             char             buffer[sizeof(struct cmsghdr) + sizeof(int)];
+            int flags;
 
             iov.iov_base       = &dummy;
             iov.iov_len        = 1;
@@ -337,10 +339,27 @@
             cmsg->cmsg_type  = SCM_RIGHTS;
             ((int*)CMSG_DATA(cmsg))[0] = fd;
 
+            flags = fcntl(proc->socket,F_GETFL,0);
+
+            if (flags == -1) {
+                D("failed to get cntl flags for socket %d: %s\n",
+                  proc->pid, strerror(errno));
+                goto CloseProcess;
+
+            }
+
+            if (fcntl(proc->socket, F_SETFL, flags & ~O_NONBLOCK) == -1) {
+                D("failed to remove O_NONBLOCK flag for socket %d: %s\n",
+                  proc->pid, strerror(errno));
+                goto CloseProcess;
+            }
+
             for (;;) {
                 ret = sendmsg(proc->socket, &msg, 0);
-                if (ret >= 0)
+                if (ret >= 0) {
+                    adb_close(fd);
                     break;
+                }
                 if (errno == EINTR)
                     continue;
                 D("sending new file descriptor to JDWP %d failed: %s\n",
@@ -354,6 +373,12 @@
             for (n = 1; n < proc->out_count; n++)
                 proc->out_fds[n-1] = proc->out_fds[n];
 
+            if (fcntl(proc->socket, F_SETFL, flags) == -1) {
+                D("failed to set O_NONBLOCK flag for socket %d: %s\n",
+                  proc->pid, strerror(errno));
+                goto CloseProcess;
+            }
+
             if (--proc->out_count == 0)
                 fdevent_del( proc->fde, FDE_WRITE );
         }
@@ -474,6 +499,7 @@
 
     /* only wait for incoming connections */
     fdevent_add(control->fde, FDE_READ);
+    close_on_exec(s);
 
     D("jdwp control socket started (%d)\n", control->listen_socket);
     return 0;
diff --git a/adb/services.c b/adb/services.c
index 487c7d3..c22ce17 100644
--- a/adb/services.c
+++ b/adb/services.c
@@ -309,6 +309,7 @@
         dup2(pts, 1);
         dup2(pts, 2);
 
+        adb_close(pts);
         adb_close(ptm);
 
         execl(cmd, cmd, arg0, arg1, NULL);
diff --git a/adb/sockets.c b/adb/sockets.c
index 9f1b598..43925e4 100644
--- a/adb/sockets.c
+++ b/adb/sockets.c
@@ -65,8 +65,11 @@
     asocket *result = NULL;
 
     adb_mutex_lock(&socket_list_lock);
-    for(s = local_socket_list.next; s != &local_socket_list && !result; s = s->next) {
-        if(s->id == id) result = s;
+    for (s = local_socket_list.next; s != &local_socket_list; s = s->next) {
+        if (s->id == id) {
+            result = s;
+            break;
+        }
     }
     adb_mutex_unlock(&socket_list_lock);
 
@@ -366,7 +369,7 @@
 asocket *create_local_socket(int fd)
 {
     asocket *s = calloc(1, sizeof(asocket));
-    if(s == 0) fatal("cannot allocate socket");
+    if (s == NULL) fatal("cannot allocate socket");
     install_local_socket(s);
     s->fd = fd;
     s->enqueue = local_socket_enqueue;
@@ -482,7 +485,7 @@
     asocket *s = calloc(1, sizeof(aremotesocket));
     adisconnect*  dis = &((aremotesocket*)s)->disconnect;
 
-    if(s == 0) fatal("cannot allocate socket");
+    if (s == NULL) fatal("cannot allocate socket");
     s->id = id;
     s->enqueue = remote_socket_enqueue;
     s->ready = remote_socket_ready;
@@ -761,8 +764,7 @@
 {
     D("Creating smart socket \n");
     asocket *s = calloc(1, sizeof(asocket));
-    if(s == 0) fatal("cannot allocate socket");
-    s->id = 0;
+    if (s == NULL) fatal("cannot allocate socket");
     s->enqueue = smart_socket_enqueue;
     s->ready = smart_socket_ready;
     s->close = smart_socket_close;
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index 6372649..74f4ed1 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -387,7 +387,13 @@
 
 static __inline__ int  adb_socket_accept(int  serverfd, struct sockaddr*  addr, socklen_t  *addrlen)
 {
-    return  accept( serverfd, addr, addrlen );
+    int fd;
+
+    fd = accept(serverfd, addr, addrlen);
+    if (fd >= 0)
+        close_on_exec(fd);
+
+    return fd;
 }
 
 #undef   accept
diff --git a/adb/transport.c b/adb/transport.c
index 62bdfdb..2baf340 100644
--- a/adb/transport.c
+++ b/adb/transport.c
@@ -79,7 +79,7 @@
 {
     adisconnect*  dis = t->disconnects.next;
 
-    D("run_transport_disconnects: %p (%s)\n", t, t->serial ? t->serial : "unknown" );
+    D("%s: run_transport_disconnects\n", t->serial);
     while (dis != &t->disconnects) {
         adisconnect*  next = dis->next;
         dis->func( dis->opaque, t );
@@ -87,75 +87,91 @@
     }
 }
 
+#if ADB_TRACE
+static void
+dump_packet(const char* name, const char* func, apacket* p)
+{
+    unsigned  command = p->msg.command;
+    int       len     = p->msg.data_length;
+    char      cmd[9];
+    char      arg0[12], arg1[12];
+    int       n;
+
+    for (n = 0; n < 4; n++) {
+        int  b = (command >> (n*8)) & 255;
+        if (b < 32 || b >= 127)
+            break;
+        cmd[n] = (char)b;
+    }
+    if (n == 4) {
+        cmd[4] = 0;
+    } else {
+        /* There is some non-ASCII name in the command, so dump
+            * the hexadecimal value instead */
+        snprintf(cmd, sizeof cmd, "%08x", command);
+    }
+
+    if (p->msg.arg0 < 256U)
+        snprintf(arg0, sizeof arg0, "%d", p->msg.arg0);
+    else
+        snprintf(arg0, sizeof arg0, "0x%x", p->msg.arg0);
+
+    if (p->msg.arg1 < 256U)
+        snprintf(arg1, sizeof arg1, "%d", p->msg.arg1);
+    else
+        snprintf(arg1, sizeof arg1, "0x%x", p->msg.arg1);
+
+    D("%s: %s: [%s] arg0=%s arg1=%s (len=%d) ",
+        name, func, cmd, arg0, arg1, len);
+    dump_hex(p->data, len);
+}
+#endif /* ADB_TRACE */
+
 static int
-read_packet(int  fd, apacket** ppacket)
+read_packet(int  fd, const char* name, apacket** ppacket)
 {
     char *p = (char*)ppacket;  /* really read a packet address */
     int   r;
     int   len = sizeof(*ppacket);
+    char  buff[8];
+    if (!name) {
+        snprintf(buff, sizeof buff, "fd=%d", fd);
+        name = buff;
+    }
     while(len > 0) {
         r = adb_read(fd, p, len);
         if(r > 0) {
             len -= r;
             p   += r;
         } else {
-            D("read_packet: %d error %d %d\n", fd, r, errno);
+            D("%s: read_packet (fd=%d), error ret=%d errno=%d: %s\n", name, fd, r, errno, strerror(errno));
             if((r < 0) && (errno == EINTR)) continue;
             return -1;
         }
     }
 
 #if ADB_TRACE
-    if (ADB_TRACING)
-    {
-        unsigned  command = (*ppacket)->msg.command;
-        int       len     = (*ppacket)->msg.data_length;
-        char      cmd[5];
-        int       n;
-
-        for (n = 0; n < 4; n++) {
-            int  b = (command >> (n*8)) & 255;
-            if (b >= 32 && b < 127)
-                cmd[n] = (char)b;
-            else
-                cmd[n] = '.';
-        }
-        cmd[4] = 0;
-
-        D("read_packet: %d ok: [%08x %s] %08x %08x (%d) ",
-          fd, command, cmd, (*ppacket)->msg.arg0, (*ppacket)->msg.arg1, len);
-        dump_hex((*ppacket)->data, len);
+    if (ADB_TRACING) {
+        dump_packet(name, "from remote", *ppacket);
     }
 #endif
     return 0;
 }
 
 static int
-write_packet(int  fd, apacket** ppacket)
+write_packet(int  fd, const char* name, apacket** ppacket)
 {
     char *p = (char*) ppacket;  /* we really write the packet address */
     int r, len = sizeof(ppacket);
+    char buff[8];
+    if (!name) {
+        snprintf(buff, sizeof buff, "fd=%d", fd);
+        name = buff;
+    }
 
 #if ADB_TRACE
-    if (ADB_TRACING)
-    {
-        unsigned  command = (*ppacket)->msg.command;
-        int       len     = (*ppacket)->msg.data_length;
-        char      cmd[5];
-        int       n;
-
-        for (n = 0; n < 4; n++) {
-            int  b = (command >> (n*8)) & 255;
-            if (b >= 32 && b < 127)
-                cmd[n] = (char)b;
-            else
-                cmd[n] = '.';
-        }
-        cmd[4] = 0;
-
-        D("write_packet: %d [%08x %s] %08x %08x (%d) ",
-          fd, command, cmd, (*ppacket)->msg.arg0, (*ppacket)->msg.arg1, len);
-        dump_hex((*ppacket)->data, len);
+    if (ADB_TRACING) {
+        dump_packet(name, "to remote", *ppacket);
     }
 #endif
     len = sizeof(ppacket);
@@ -165,7 +181,7 @@
             len -= r;
             p += r;
         } else {
-            D("write_packet: %d error %d %d\n", fd, r, errno);
+            D("%s: write_packet (fd=%d) error ret=%d errno=%d: %s\n", name, fd, r, errno, strerror(errno));
             if((r < 0) && (errno == EINTR)) continue;
             return -1;
         }
@@ -175,10 +191,11 @@
 
 static void transport_socket_events(int fd, unsigned events, void *_t)
 {
+    atransport *t = _t;
     if(events & FDE_READ){
         apacket *p = 0;
-        if(read_packet(fd, &p)){
-            D("failed to read packet from transport socket on fd %d\n", fd);
+        if(read_packet(fd, t->serial, &p)){
+            D("%s: failed to read packet from transport socket on fd %d\n", t->serial, fd);
         } else {
             handle_packet(p, (atransport *) _t);
         }
@@ -208,7 +225,7 @@
         D("Transport is null \n");
     }
 
-    if(write_packet(t->transport_socket, &p)){
+    if(write_packet(t->transport_socket, t->serial, &p)){
         fatal_errno("cannot enqueue packet on transport socket");
     }
 }
@@ -231,52 +248,51 @@
     atransport *t = _t;
     apacket *p;
 
-    D("from_remote: starting thread for transport %p, on fd %d\n", t, t->fd );
-
-    D("from_remote: transport %p SYNC online (%d)\n", t, t->sync_token + 1);
+    D("%s: starting transport output thread on fd %d, SYNC online (%d)\n",
+       t->serial, t->fd, t->sync_token + 1);
     p = get_apacket();
     p->msg.command = A_SYNC;
     p->msg.arg0 = 1;
     p->msg.arg1 = ++(t->sync_token);
     p->msg.magic = A_SYNC ^ 0xffffffff;
-    if(write_packet(t->fd, &p)) {
+    if(write_packet(t->fd, t->serial, &p)) {
         put_apacket(p);
-        D("from_remote: failed to write SYNC apacket to transport %p", t);
+        D("%s: failed to write SYNC packet\n", t->serial);
         goto oops;
     }
 
-    D("from_remote: data pump  for transport %p\n", t);
+    D("%s: data pump started\n", t->serial);
     for(;;) {
         p = get_apacket();
 
         if(t->read_from_remote(p, t) == 0){
-            D("from_remote: received remote packet, sending to transport %p\n",
-              t);
-            if(write_packet(t->fd, &p)){
+            D("%s: received remote packet, sending to transport\n",
+              t->serial);
+            if(write_packet(t->fd, t->serial, &p)){
                 put_apacket(p);
-                D("from_remote: failed to write apacket to transport %p", t);
+                D("%s: failed to write apacket to transport\n", t->serial);
                 goto oops;
             }
         } else {
-            D("from_remote: remote read failed for transport %p\n", p);
+            D("%s: remote read failed for transport\n", t->serial);
             put_apacket(p);
             break;
         }
     }
 
-    D("from_remote: SYNC offline for transport %p\n", t);
+    D("%s: SYNC offline for transport\n", t->serial);
     p = get_apacket();
     p->msg.command = A_SYNC;
     p->msg.arg0 = 0;
     p->msg.arg1 = 0;
     p->msg.magic = A_SYNC ^ 0xffffffff;
-    if(write_packet(t->fd, &p)) {
+    if(write_packet(t->fd, t->serial, &p)) {
         put_apacket(p);
-        D("from_remote: failed to write SYNC apacket to transport %p", t);
+        D("%s: failed to write SYNC apacket to transport", t->serial);
     }
 
 oops:
-    D("from_remote: thread is exiting for transport %p\n", t);
+    D("%s: transport output thread is exiting\n", t->serial);
     kick_transport(t);
     transport_unref(t);
     return 0;
@@ -288,35 +304,35 @@
     apacket *p;
     int active = 0;
 
-    D("to_remote: starting input_thread for %p, reading from fd %d\n",
-       t, t->fd);
+    D("%s: starting transport input thread, reading from fd %d\n",
+       t->serial, t->fd);
 
     for(;;){
-        if(read_packet(t->fd, &p)) {
-            D("to_remote: failed to read apacket from transport %p on fd %d\n", 
-               t, t->fd );
+        if(read_packet(t->fd, t->serial, &p)) {
+            D("%s: failed to read apacket from transport on fd %d\n",
+               t->serial, t->fd );
             break;
         }
         if(p->msg.command == A_SYNC){
             if(p->msg.arg0 == 0) {
-                D("to_remote: transport %p SYNC offline\n", t);
+                D("%s: transport SYNC offline\n", t->serial);
                 put_apacket(p);
                 break;
             } else {
                 if(p->msg.arg1 == t->sync_token) {
-                    D("to_remote: transport %p SYNC online\n", t);
+                    D("%s: transport SYNC online\n", t->serial);
                     active = 1;
                 } else {
-                    D("to_remote: trandport %p ignoring SYNC %d != %d\n",
-                      t, p->msg.arg1, t->sync_token);
+                    D("%s: transport ignoring SYNC %d != %d\n",
+                      t->serial, p->msg.arg1, t->sync_token);
                 }
             }
         } else {
             if(active) {
-                D("to_remote: transport %p got packet, sending to remote\n", t);
+                D("%s: transport got packet, sending to remote\n", t->serial);
                 t->write_to_remote(p, t);
             } else {
-                D("to_remote: transport %p ignoring packet while offline\n", t);
+                D("%s: transport ignoring packet while offline\n", t->serial);
             }
         }
 
@@ -327,7 +343,7 @@
     // while a client socket is still active.
     close_all_sockets(t);
 
-    D("to_remote: thread is exiting for transport %p, fd %d\n", t, t->fd);
+    D("%s: transport input thread is exiting, fd %d\n", t->serial, t->fd);
     kick_transport(t);
     transport_unref(t);
     return 0;
@@ -508,7 +524,7 @@
             p   += r;
         } else {
             if((r < 0) && (errno == EINTR)) continue;
-            D("transport_read_action: on fd %d, error %d: %s\n", 
+            D("transport_read_action: on fd %d, error %d: %s\n",
               fd, errno, strerror(errno));
             return -1;
         }
@@ -530,7 +546,7 @@
             p   += r;
         } else {
             if((r < 0) && (errno == EINTR)) continue;
-            D("transport_write_action: on fd %d, error %d: %s\n", 
+            D("transport_write_action: on fd %d, error %d: %s\n",
               fd, errno, strerror(errno));
             return -1;
         }
@@ -557,7 +573,7 @@
     t = m.transport;
 
     if(m.action == 0){
-        D("transport: %p removing and free'ing %d\n", t, t->transport_socket);
+        D("transport: %s removing and free'ing %d\n", t->serial, t->transport_socket);
 
             /* IMPORTANT: the remove closes one half of the
             ** socket pair.  The close closes the other half.
@@ -593,12 +609,11 @@
             fatal_errno("cannot open transport socketpair");
         }
 
-        D("transport: %p (%d,%d) starting\n", t, s[0], s[1]);
+        D("transport: %s (%d,%d) starting\n", t->serial, s[0], s[1]);
 
         t->transport_socket = s[0];
         t->fd = s[1];
 
-        D("transport: %p install %d\n", t, t->transport_socket );
         fdevent_install(&(t->transport_fde),
                         t->transport_socket,
                         transport_socket_events,
@@ -653,7 +668,7 @@
     tmsg m;
     m.transport = transport;
     m.action = 1;
-    D("transport: %p registered\n", transport);
+    D("transport: %s registered\n", transport->serial);
     if(transport_write_action(transport_registration_send, &m)) {
         fatal_errno("cannot write transport registration socket\n");
     }
@@ -664,7 +679,7 @@
     tmsg m;
     m.transport = transport;
     m.action = 0;
-    D("transport: %p removed\n", transport);
+    D("transport: %s removed\n", transport->serial);
     if(transport_write_action(transport_registration_send, &m)) {
         fatal_errno("cannot write transport registration socket\n");
     }
@@ -674,15 +689,16 @@
 static void transport_unref_locked(atransport *t)
 {
     t->ref_count--;
-    D("transport: %p R- (ref=%d)\n", t, t->ref_count);
     if (t->ref_count == 0) {
-        D("transport: %p kicking and closing\n", t);
+        D("transport: %s unref (kicking and closing)\n", t->serial);
         if (!t->kicked) {
             t->kicked = 1;
             t->kick(t);
         }
         t->close(t);
         remove_transport(t);
+    } else {
+        D("transport: %s unref (count=%d)\n", t->serial, t->ref_count);
     }
 }
 
@@ -857,7 +873,13 @@
 void register_socket_transport(int s, const char *serial, int port, int local)
 {
     atransport *t = calloc(1, sizeof(atransport));
-    D("transport: %p init'ing for socket %d, on port %d\n", t, s, port);
+    char buff[32];
+
+    if (!serial) {
+        snprintf(buff, sizeof buff, "T-%p", t);
+        serial = buff;
+    }
+    D("transport: %s init'ing for socket %d, on port %d\n", serial, s, port);
     if ( init_socket_transport(t, s, port, local) < 0 ) {
         adb_close(s);
         free(t);
@@ -961,21 +983,26 @@
 #if ADB_TRACE
     int  len0 = len;
 #endif
-    D("readx: %d %p %d\n", fd, ptr, (int)len);
+    D("readx: fd=%d wanted=%d\n", fd, (int)len);
     while(len > 0) {
         r = adb_read(fd, p, len);
         if(r > 0) {
             len -= r;
             p += r;
         } else {
-            D("readx: %d %d %s\n", fd, r, strerror(errno));
-            if((r < 0) && (errno == EINTR)) continue;
+            if (r < 0) {
+                D("readx: fd=%d error %d: %s\n", fd, errno, strerror(errno));
+                if (errno == EINTR)
+                    continue;
+            } else {
+                D("readx: fd=%d disconnected\n", fd);
+            }
             return -1;
         }
     }
 
 #if ADB_TRACE
-    D("readx: %d ok: ", fd);
+    D("readx: fd=%d wanted=%d got=%d\n", fd, len0, len0 - len);
     dump_hex( ptr, len0 );
 #endif
     return 0;
@@ -987,7 +1014,7 @@
     int r;
 
 #if ADB_TRACE
-    D("writex: %d %p %d: ", fd, ptr, (int)len);
+    D("writex: fd=%d len=%d: ", fd, (int)len);
     dump_hex( ptr, len );
 #endif
     while(len > 0) {
@@ -996,13 +1023,16 @@
             len -= r;
             p += r;
         } else {
-            D("writex: %d %d %s\n", fd, r, strerror(errno));
-            if((r < 0) && (errno == EINTR)) continue;
+            if (r < 0) {
+                D("writex: fd=%d error %d: %s\n", fd, errno, strerror(errno));
+                if (errno == EINTR)
+                    continue;
+            } else {
+                D("writex: fd=%d disconnected\n", fd);
+            }
             return -1;
         }
     }
-
-    D("writex: %d ok\n", fd);
     return 0;
 }
 
diff --git a/adb/transport_local.c b/adb/transport_local.c
index 8dfc98d..4431ba7 100644
--- a/adb/transport_local.c
+++ b/adb/transport_local.c
@@ -25,7 +25,7 @@
 #define  TRACE_TAG  TRACE_TRANSPORT
 #include "adb.h"
 
-#ifdef __ppc__
+#ifdef HAVE_BIG_ENDIAN
 #define H4(x)	(((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24)
 static inline void fix_endians(apacket *p)
 {
@@ -61,7 +61,7 @@
 
     fix_endians(p);
 
-#if 0 && defined __ppc__
+#if 0 && defined HAVE_BIG_ENDIAN
     D("read remote packet: %04x arg0=%0x arg1=%0x data_length=%0x data_check=%0x magic=%0x\n",
       p->msg.command, p->msg.arg0, p->msg.arg1, p->msg.data_length, p->msg.data_check, p->msg.magic);
 #endif
@@ -89,7 +89,7 @@
 
     fix_endians(p);
 
-#if 0 && defined __ppc__
+#if 0 && defined HAVE_BIG_ENDIAN
     D("write remote packet: %04x arg0=%0x arg1=%0x data_length=%0x data_check=%0x magic=%0x\n",
       p->msg.command, p->msg.arg0, p->msg.arg1, p->msg.data_length, p->msg.data_check, p->msg.magic);
 #endif
diff --git a/adb/transport_usb.c b/adb/transport_usb.c
index 2584163..ee6b637 100644
--- a/adb/transport_usb.c
+++ b/adb/transport_usb.c
@@ -27,8 +27,7 @@
 #include "usb_vendors.h"
 #endif
 
-/* XXX better define? */
-#ifdef __ppc__
+#ifdef HAVE_BIG_ENDIAN
 #define H4(x)	(((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24)
 static inline void fix_endians(apacket *p)
 {
diff --git a/adb/usb_linux.c b/adb/usb_linux.c
index bb86813..cd61083 100644
--- a/adb/usb_linux.c
+++ b/adb/usb_linux.c
@@ -149,7 +149,7 @@
 
 //        DBGX("[ scanning %s ]\n", busname);
         while((de = readdir(devdir))) {
-            unsigned char devdesc[256];
+            unsigned char devdesc[4096];
             unsigned char* bufptr = devdesc;
             unsigned char* bufend;
             struct usb_device_descriptor* device;
@@ -191,9 +191,8 @@
                 continue;
             }
 
-            vid = __le16_to_cpu(device->idVendor);
-            pid = __le16_to_cpu(device->idProduct);
-            pid = devdesc[10] | (devdesc[11] << 8);
+            vid = device->idVendor;
+            pid = device->idProduct;
             DBGX("[ %s is V:%04x P:%04x ]\n", devname, vid, pid);
 
                 // should have config descriptor next
@@ -617,7 +616,7 @@
             ctrl.bRequestType = USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE;
             ctrl.bRequest = USB_REQ_GET_DESCRIPTOR;
             ctrl.wValue = (USB_DT_STRING << 8) | serial_index;
-            ctrl.wIndex = languages[i];
+            ctrl.wIndex = __le16_to_cpu(languages[i]);
             ctrl.wLength = sizeof(buffer);
             ctrl.data = buffer;
 
@@ -627,7 +626,7 @@
                 // skip first word, and copy the rest to the serial string, changing shorts to bytes.
                 result /= 2;
                 for (i = 1; i < result; i++)
-                    serial[i - 1] = buffer[i];
+                    serial[i - 1] = __le16_to_cpu(buffer[i]);
                 serial[i - 1] = 0;
                 break;
             }
diff --git a/adb/usb_vendors.c b/adb/usb_vendors.c
index cbc2464..d642566 100644
--- a/adb/usb_vendors.c
+++ b/adb/usb_vendors.c
@@ -69,6 +69,8 @@
 #define VENDOR_ID_PANTECH       0x10A9
 // Qualcomm's USB Vendor ID
 #define VENDOR_ID_QUALCOMM      0x05c6
+// On-The-Go-Video's USB Vendor ID
+#define VENDOR_ID_OTGV          0x2257
 // NEC's USB Vendor ID
 #define VENDOR_ID_NEC           0x0409
 // Panasonic Mobile Communication's USB Vendor ID
@@ -83,6 +85,8 @@
 #define VENDOR_ID_ASUS          0x0b05
 // Philips's USB Vendor ID
 #define VENDOR_ID_PHILIPS       0x0471
+// Texas Instruments's USB Vendor ID
+#define VENDOR_ID_TI            0x0451
 
 
 /** built-in vendor list */
@@ -104,6 +108,7 @@
     VENDOR_ID_KYOCERA,
     VENDOR_ID_PANTECH,
     VENDOR_ID_QUALCOMM,
+    VENDOR_ID_OTGV,
     VENDOR_ID_NEC,
     VENDOR_ID_PMC,
     VENDOR_ID_TOSHIBA,
@@ -111,6 +116,7 @@
     VENDOR_ID_KT_TECH,
     VENDOR_ID_ASUS,
     VENDOR_ID_PHILIPS,
+    VENDOR_ID_TI,
 };
 
 #define BUILT_IN_VENDOR_COUNT    (sizeof(builtInVendorIds)/sizeof(builtInVendorIds[0]))
@@ -172,7 +178,7 @@
 /* builds the path to the adb vendor id file. returns 0 if success */
 int build_path(char* buff, size_t len, const char* format, const char* home)
 {
-    if (snprintf(buff, len, format, home, ANDROID_PATH, ANDROID_ADB_INI) >= len) {
+    if (snprintf(buff, len, format, home, ANDROID_PATH, ANDROID_ADB_INI) >= (signed)len) {
         return 1;
     }
 
diff --git a/debuggerd/Android.mk b/debuggerd/Android.mk
index 3c1cf02..752c953 100644
--- a/debuggerd/Android.mk
+++ b/debuggerd/Android.mk
@@ -1,11 +1,15 @@
 # Copyright 2005 The Android Open Source Project
 
-ifeq ($(TARGET_ARCH),arm)
+ifneq ($(filter arm x86,$(TARGET_ARCH)),)
 
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES:= debuggerd.c getevent.c unwind-arm.c pr-support.c utility.c
+LOCAL_SRC_FILES:= debuggerd.c utility.c getevent.c $(TARGET_ARCH)/machine.c $(TARGET_ARCH)/unwind.c symbol_table.c
+ifeq ($(TARGET_ARCH),arm)
+LOCAL_SRC_FILES += $(TARGET_ARCH)/pr-support.c
+endif
+
 LOCAL_CFLAGS := -Wall
 LOCAL_MODULE := debuggerd
 
@@ -22,7 +26,7 @@
 
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := crasher.c
-LOCAL_SRC_FILES += crashglue.S
+LOCAL_SRC_FILES += $(TARGET_ARCH)/crashglue.S
 LOCAL_MODULE := crasher
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 LOCAL_MODULE_TAGS := eng
@@ -46,4 +50,4 @@
 include $(BUILD_EXECUTABLE)
 endif # ARCH_ARM_HAVE_VFP == true
 
-endif # TARGET_ARCH == arm
+endif # arm or x86 in TARGET_ARCH
diff --git a/debuggerd/crashglue.S b/debuggerd/arm/crashglue.S
similarity index 99%
rename from debuggerd/crashglue.S
rename to debuggerd/arm/crashglue.S
index 0c1fd9b..eb9d0e3 100644
--- a/debuggerd/crashglue.S
+++ b/debuggerd/arm/crashglue.S
@@ -2,7 +2,7 @@
 .type crash1, %function
 .globl crashnostack
 .type crashnostack, %function
-		
+
 crash1:
 	ldr r0, =0xa5a50000
 	ldr r1, =0xa5a50001
diff --git a/debuggerd/arm/machine.c b/debuggerd/arm/machine.c
new file mode 100644
index 0000000..ccd0baf
--- /dev/null
+++ b/debuggerd/arm/machine.c
@@ -0,0 +1,229 @@
+/* system/debuggerd/debuggerd.c
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <sys/exec_elf.h>
+#include <sys/stat.h>
+
+#include <cutils/sockets.h>
+#include <cutils/properties.h>
+
+#include <linux/input.h>
+
+#include "utility.h"
+
+#ifdef WITH_VFP
+#ifdef WITH_VFP_D32
+#define NUM_VFP_REGS 32
+#else
+#define NUM_VFP_REGS 16
+#endif
+#endif
+
+/* Main entry point to get the backtrace from the crashing process */
+extern int unwind_backtrace_with_ptrace(int tfd, pid_t pid, mapinfo *map,
+                                        unsigned int sp_list[],
+                                        int *frame0_pc_sane,
+                                        bool at_fault);
+
+void dump_stack_and_code(int tfd, int pid, mapinfo *map,
+                         int unwind_depth, unsigned int sp_list[],
+                         bool at_fault)
+{
+    unsigned int sp, pc, p, end, data;
+    struct pt_regs r;
+    int sp_depth;
+    bool only_in_tombstone = !at_fault;
+    char code_buffer[80];
+
+    if(ptrace(PTRACE_GETREGS, pid, 0, &r)) return;
+    sp = r.ARM_sp;
+    pc = r.ARM_pc;
+
+    _LOG(tfd, only_in_tombstone, "\ncode around pc:\n");
+
+    end = p = pc & ~3;
+    p -= 32;
+    end += 32;
+
+    /* Dump the code around PC as:
+     *  addr       contents
+     *  00008d34   fffffcd0 4c0eb530 b0934a0e 1c05447c
+     *  00008d44   f7ff18a0 490ced94 68035860 d0012b00
+     */
+    while (p <= end) {
+        int i;
+
+        sprintf(code_buffer, "%08x ", p);
+        for (i = 0; i < 4; i++) {
+            data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL);
+            sprintf(code_buffer + strlen(code_buffer), "%08x ", data);
+            p += 4;
+        }
+        _LOG(tfd, only_in_tombstone, "%s\n", code_buffer);
+    }
+
+    if ((unsigned) r.ARM_lr != pc) {
+        _LOG(tfd, only_in_tombstone, "\ncode around lr:\n");
+
+        end = p = r.ARM_lr & ~3;
+        p -= 32;
+        end += 32;
+
+        /* Dump the code around LR as:
+         *  addr       contents
+         *  00008d34   fffffcd0 4c0eb530 b0934a0e 1c05447c
+         *  00008d44   f7ff18a0 490ced94 68035860 d0012b00
+         */
+        while (p <= end) {
+            int i;
+
+            sprintf(code_buffer, "%08x ", p);
+            for (i = 0; i < 4; i++) {
+                data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL);
+                sprintf(code_buffer + strlen(code_buffer), "%08x ", data);
+                p += 4;
+            }
+            _LOG(tfd, only_in_tombstone, "%s\n", code_buffer);
+        }
+    }
+
+    p = sp - 64;
+    p &= ~3;
+    if (unwind_depth != 0) {
+        if (unwind_depth < STACK_CONTENT_DEPTH) {
+            end = sp_list[unwind_depth-1];
+        }
+        else {
+            end = sp_list[STACK_CONTENT_DEPTH-1];
+        }
+    }
+    else {
+        end = sp | 0x000000ff;
+        end += 0xff;
+    }
+
+    _LOG(tfd, only_in_tombstone, "\nstack:\n");
+
+    /* If the crash is due to PC == 0, there will be two frames that
+     * have identical SP value.
+     */
+    if (sp_list[0] == sp_list[1]) {
+        sp_depth = 1;
+    }
+    else {
+        sp_depth = 0;
+    }
+
+    while (p <= end) {
+         char *prompt;
+         char level[16];
+         data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL);
+         if (p == sp_list[sp_depth]) {
+             sprintf(level, "#%02d", sp_depth++);
+             prompt = level;
+         }
+         else {
+             prompt = "   ";
+         }
+
+         /* Print the stack content in the log for the first 3 frames. For the
+          * rest only print them in the tombstone file.
+          */
+         _LOG(tfd, (sp_depth > 2) || only_in_tombstone,
+              "%s %08x  %08x  %s\n", prompt, p, data,
+              map_to_name(map, data, ""));
+         p += 4;
+    }
+    /* print another 64-byte of stack data after the last frame */
+
+    end = p+64;
+    while (p <= end) {
+         data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL);
+         _LOG(tfd, (sp_depth > 2) || only_in_tombstone,
+              "    %08x  %08x  %s\n", p, data,
+              map_to_name(map, data, ""));
+         p += 4;
+    }
+}
+
+void dump_pc_and_lr(int tfd, int pid, mapinfo *map, int unwound_level,
+                    bool at_fault)
+{
+    struct pt_regs r;
+
+    if(ptrace(PTRACE_GETREGS, pid, 0, &r)) {
+        _LOG(tfd, !at_fault, "tid %d not responding!\n", pid);
+        return;
+    }
+
+    if (unwound_level == 0) {
+        _LOG(tfd, !at_fault, "         #%02d  pc %08x  %s\n", 0, r.ARM_pc,
+             map_to_name(map, r.ARM_pc, "<unknown>"));
+    }
+    _LOG(tfd, !at_fault, "         #%02d  lr %08x  %s\n", 1, r.ARM_lr,
+            map_to_name(map, r.ARM_lr, "<unknown>"));
+}
+
+void dump_registers(int tfd, int pid, bool at_fault)
+{
+    struct pt_regs r;
+    bool only_in_tombstone = !at_fault;
+
+    if(ptrace(PTRACE_GETREGS, pid, 0, &r)) {
+        _LOG(tfd, only_in_tombstone,
+             "cannot get registers: %s\n", strerror(errno));
+        return;
+    }
+
+    _LOG(tfd, only_in_tombstone, " r0 %08x  r1 %08x  r2 %08x  r3 %08x\n",
+         r.ARM_r0, r.ARM_r1, r.ARM_r2, r.ARM_r3);
+    _LOG(tfd, only_in_tombstone, " r4 %08x  r5 %08x  r6 %08x  r7 %08x\n",
+         r.ARM_r4, r.ARM_r5, r.ARM_r6, r.ARM_r7);
+    _LOG(tfd, only_in_tombstone, " r8 %08x  r9 %08x  10 %08x  fp %08x\n",
+         r.ARM_r8, r.ARM_r9, r.ARM_r10, r.ARM_fp);
+    _LOG(tfd, only_in_tombstone,
+         " ip %08x  sp %08x  lr %08x  pc %08x  cpsr %08x\n",
+         r.ARM_ip, r.ARM_sp, r.ARM_lr, r.ARM_pc, r.ARM_cpsr);
+
+#ifdef WITH_VFP
+    struct user_vfp vfp_regs;
+    int i;
+
+    if(ptrace(PTRACE_GETVFPREGS, pid, 0, &vfp_regs)) {
+        _LOG(tfd, only_in_tombstone,
+             "cannot get registers: %s\n", strerror(errno));
+        return;
+    }
+
+    for (i = 0; i < NUM_VFP_REGS; i += 2) {
+        _LOG(tfd, only_in_tombstone,
+             " d%-2d %016llx  d%-2d %016llx\n",
+              i, vfp_regs.fpregs[i], i+1, vfp_regs.fpregs[i+1]);
+    }
+    _LOG(tfd, only_in_tombstone, " scr %08lx\n\n", vfp_regs.fpscr);
+#endif
+}
diff --git a/debuggerd/pr-support.c b/debuggerd/arm/pr-support.c
similarity index 100%
rename from debuggerd/pr-support.c
rename to debuggerd/arm/pr-support.c
diff --git a/debuggerd/unwind-arm.c b/debuggerd/arm/unwind.c
similarity index 96%
rename from debuggerd/unwind-arm.c
rename to debuggerd/arm/unwind.c
index 9642d2e..d9600b7 100644
--- a/debuggerd/unwind-arm.c
+++ b/debuggerd/arm/unwind.c
@@ -37,6 +37,8 @@
 #include <unwind.h>
 #include "utility.h"
 
+#include "symbol_table.h"
+
 typedef struct _ZSt9type_info type_info; /* This names C++ type_info type */
 
 void __attribute__((weak)) __cxa_call_unexpected(_Unwind_Control_Block *ucbp);
@@ -393,6 +395,7 @@
     phase2_vrs *vrs = (phase2_vrs*) context;
     const mapinfo *mi;
     bool only_in_tombstone = !at_fault;
+    const struct symbol* sym = 0;
 
     if (stack_level < STACK_CONTENT_DEPTH) {
         sp_list[stack_level] = vrs->core.r[R_SP];
@@ -427,7 +430,7 @@
         if (pc & 1) {
             _uw prev_word;
             pc = (pc & ~1);
-            prev_word = get_remote_word(pid, (void *) pc-4);
+            prev_word = get_remote_word(pid, (char *) pc-4);
             // Long offset 
             if ((prev_word & 0xf0000000) == 0xf0000000 && 
                 (prev_word & 0x0000e000) == 0x0000e000) {
@@ -448,12 +451,22 @@
      * 1MB boundaries, and the library may be larger than 1MB. So for .so 
      * addresses we print the relative offset in back trace.
      */
-    rel_pc = pc;
     mi = pc_to_mapinfo(map, pc, &rel_pc);
 
-    _LOG(tfd, only_in_tombstone, 
-         "         #%02d  pc %08x  %s\n", stack_level, rel_pc, 
-         mi ? mi->name : "");
+    /* See if we can determine what symbol this stack frame resides in */
+    if (mi != 0 && mi->symbols != 0) {
+        sym = symbol_table_lookup(mi->symbols, rel_pc);
+    }
+
+    if (sym) {
+        _LOG(tfd, only_in_tombstone,
+            "         #%02d  pc %08x  %s (%s)\n", stack_level, rel_pc,
+            mi ? mi->name : "", sym->name);
+    } else {
+        _LOG(tfd, only_in_tombstone,
+            "         #%02d  pc %08x  %s\n", stack_level, rel_pc,
+            mi ? mi->name : "");
+    }
 
     return _URC_NO_REASON;
 }
diff --git a/debuggerd/crasher.c b/debuggerd/crasher.c
index f4a5a62..00652e9 100644
--- a/debuggerd/crasher.c
+++ b/debuggerd/crasher.c
@@ -19,6 +19,7 @@
 
 void crash1(void);
 void crashnostack(void);
+void maybeabort(void);
 
 static void debuggerd_connect()
 {
diff --git a/debuggerd/debuggerd.c b/debuggerd/debuggerd.c
index b557cea..ecfe01f 100644
--- a/debuggerd/debuggerd.c
+++ b/debuggerd/debuggerd.c
@@ -16,8 +16,6 @@
 */
 
 #include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
 #include <errno.h>
 #include <signal.h>
 #include <pthread.h>
@@ -33,37 +31,21 @@
 
 #include <cutils/sockets.h>
 #include <cutils/logd.h>
-#include <cutils/sockets.h>
 #include <cutils/properties.h>
 
 #include <linux/input.h>
 
 #include <private/android_filesystem_config.h>
 
+#include "debuggerd.h"
 #include "utility.h"
 
-#ifdef WITH_VFP
-#ifdef WITH_VFP_D32
-#define NUM_VFP_REGS 32
-#else
-#define NUM_VFP_REGS 16
-#endif
-#endif
-
-/* Main entry point to get the backtrace from the crashing process */
-extern int unwind_backtrace_with_ptrace(int tfd, pid_t pid, mapinfo *map,
-                                        unsigned int sp_list[],
-                                        int *frame0_pc_sane,
-                                        bool at_fault);
-
-static int logsocket = -1;
-
 #define ANDROID_LOG_INFO 4
 
 /* Log information onto the tombstone */
 void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...)
 {
-    char buf[128];
+    char buf[512];
 
     va_list ap;
     va_start(ap, fmt);
@@ -79,13 +61,6 @@
         __android_log_vprint(ANDROID_LOG_INFO, "DEBUG", fmt, ap);
 }
 
-#define LOG(fmt...) _LOG(-1, 0, fmt)
-#if 0
-#define XLOG(fmt...) _LOG(-1, 0, fmt)
-#else
-#define XLOG(fmt...) do {} while(0)
-#endif
-
 // 6f000000-6f01e000 rwxp 00000000 00:0c 16389419   /system/lib/libcomposer.so
 // 012345678901234567890123456789012345678901234567890123456789
 // 0         1         2         3         4         5
@@ -106,10 +81,11 @@
 
     mi->start = strtoul(line, 0, 16);
     mi->end = strtoul(line + 9, 0, 16);
-    /* To be filled in parse_exidx_info if the mapped section starts with
+    /* To be filled in parse_elf_info if the mapped section starts with
      * elf_header
      */
     mi->exidx_start = mi->exidx_end = 0;
+    mi->symbols = 0;
     mi->next = 0;
     strcpy(mi->name, line + 49);
 
@@ -125,186 +101,6 @@
     _LOG(tfd, false, "Build fingerprint: '%s'\n", fingerprint);
 }
 
-
-void dump_stack_and_code(int tfd, int pid, mapinfo *map,
-                         int unwind_depth, unsigned int sp_list[],
-                         bool at_fault)
-{
-    unsigned int sp, pc, p, end, data;
-    struct pt_regs r;
-    int sp_depth;
-    bool only_in_tombstone = !at_fault;
-    char code_buffer[80];
-
-    if(ptrace(PTRACE_GETREGS, pid, 0, &r)) return;
-    sp = r.ARM_sp;
-    pc = r.ARM_pc;
-
-    _LOG(tfd, only_in_tombstone, "\ncode around pc:\n");
-
-    end = p = pc & ~3;
-    p -= 32;
-    end += 32;
-
-    /* Dump the code around PC as:
-     *  addr       contents
-     *  00008d34   fffffcd0 4c0eb530 b0934a0e 1c05447c
-     *  00008d44   f7ff18a0 490ced94 68035860 d0012b00
-     */
-    while (p <= end) {
-        int i;
-
-        sprintf(code_buffer, "%08x ", p);
-        for (i = 0; i < 4; i++) {
-            data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL);
-            sprintf(code_buffer + strlen(code_buffer), "%08x ", data);
-            p += 4;
-        }
-        _LOG(tfd, only_in_tombstone, "%s\n", code_buffer);
-    }
-
-    if ((unsigned) r.ARM_lr != pc) {
-        _LOG(tfd, only_in_tombstone, "\ncode around lr:\n");
-
-        end = p = r.ARM_lr & ~3;
-        p -= 32;
-        end += 32;
-
-        /* Dump the code around LR as:
-         *  addr       contents
-         *  00008d34   fffffcd0 4c0eb530 b0934a0e 1c05447c
-         *  00008d44   f7ff18a0 490ced94 68035860 d0012b00
-         */
-        while (p <= end) {
-            int i;
-
-            sprintf(code_buffer, "%08x ", p);
-            for (i = 0; i < 4; i++) {
-                data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL);
-                sprintf(code_buffer + strlen(code_buffer), "%08x ", data);
-                p += 4;
-            }
-            _LOG(tfd, only_in_tombstone, "%s\n", code_buffer);
-        }
-    }
-
-    p = sp - 64;
-    p &= ~3;
-    if (unwind_depth != 0) {
-        if (unwind_depth < STACK_CONTENT_DEPTH) {
-            end = sp_list[unwind_depth-1];
-        }
-        else {
-            end = sp_list[STACK_CONTENT_DEPTH-1];
-        }
-    }
-    else {
-        end = sp | 0x000000ff;
-        end += 0xff;
-    }
-
-    _LOG(tfd, only_in_tombstone, "\nstack:\n");
-
-    /* If the crash is due to PC == 0, there will be two frames that
-     * have identical SP value.
-     */
-    if (sp_list[0] == sp_list[1]) {
-        sp_depth = 1;
-    }
-    else {
-        sp_depth = 0;
-    }
-
-    while (p <= end) {
-         char *prompt;
-         char level[16];
-         data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL);
-         if (p == sp_list[sp_depth]) {
-             sprintf(level, "#%02d", sp_depth++);
-             prompt = level;
-         }
-         else {
-             prompt = "   ";
-         }
-
-         /* Print the stack content in the log for the first 3 frames. For the
-          * rest only print them in the tombstone file.
-          */
-         _LOG(tfd, (sp_depth > 2) || only_in_tombstone,
-              "%s %08x  %08x  %s\n", prompt, p, data,
-              map_to_name(map, data, ""));
-         p += 4;
-    }
-    /* print another 64-byte of stack data after the last frame */
-
-    end = p+64;
-    while (p <= end) {
-         data = ptrace(PTRACE_PEEKTEXT, pid, (void*)p, NULL);
-         _LOG(tfd, (sp_depth > 2) || only_in_tombstone,
-              "    %08x  %08x  %s\n", p, data,
-              map_to_name(map, data, ""));
-         p += 4;
-    }
-}
-
-void dump_pc_and_lr(int tfd, int pid, mapinfo *map, int unwound_level,
-                    bool at_fault)
-{
-    struct pt_regs r;
-
-    if(ptrace(PTRACE_GETREGS, pid, 0, &r)) {
-        _LOG(tfd, !at_fault, "tid %d not responding!\n", pid);
-        return;
-    }
-
-    if (unwound_level == 0) {
-        _LOG(tfd, !at_fault, "         #%02d  pc %08x  %s\n", 0, r.ARM_pc,
-             map_to_name(map, r.ARM_pc, "<unknown>"));
-    }
-    _LOG(tfd, !at_fault, "         #%02d  lr %08x  %s\n", 1, r.ARM_lr,
-            map_to_name(map, r.ARM_lr, "<unknown>"));
-}
-
-void dump_registers(int tfd, int pid, bool at_fault)
-{
-    struct pt_regs r;
-    bool only_in_tombstone = !at_fault;
-
-    if(ptrace(PTRACE_GETREGS, pid, 0, &r)) {
-        _LOG(tfd, only_in_tombstone,
-             "cannot get registers: %s\n", strerror(errno));
-        return;
-    }
-
-    _LOG(tfd, only_in_tombstone, " r0 %08x  r1 %08x  r2 %08x  r3 %08x\n",
-         r.ARM_r0, r.ARM_r1, r.ARM_r2, r.ARM_r3);
-    _LOG(tfd, only_in_tombstone, " r4 %08x  r5 %08x  r6 %08x  r7 %08x\n",
-         r.ARM_r4, r.ARM_r5, r.ARM_r6, r.ARM_r7);
-    _LOG(tfd, only_in_tombstone, " r8 %08x  r9 %08x  10 %08x  fp %08x\n",
-         r.ARM_r8, r.ARM_r9, r.ARM_r10, r.ARM_fp);
-    _LOG(tfd, only_in_tombstone,
-         " ip %08x  sp %08x  lr %08x  pc %08x  cpsr %08x\n",
-         r.ARM_ip, r.ARM_sp, r.ARM_lr, r.ARM_pc, r.ARM_cpsr);
-
-#ifdef WITH_VFP
-    struct user_vfp vfp_regs;
-    int i;
-
-    if(ptrace(PTRACE_GETVFPREGS, pid, 0, &vfp_regs)) {
-        _LOG(tfd, only_in_tombstone,
-             "cannot get registers: %s\n", strerror(errno));
-        return;
-    }
-
-    for (i = 0; i < NUM_VFP_REGS; i += 2) {
-        _LOG(tfd, only_in_tombstone,
-             " d%-2d %016llx  d%-2d %016llx\n",
-              i, vfp_regs.fpregs[i], i+1, vfp_regs.fpregs[i+1]);
-    }
-    _LOG(tfd, only_in_tombstone, " scr %08lx\n\n", vfp_regs.fpscr);
-#endif
-}
-
 const char *get_signame(int sig)
 {
     switch(sig) {
@@ -399,7 +195,7 @@
     if(sig) dump_fault_addr(tfd, tid, sig);
 }
 
-static void parse_exidx_info(mapinfo *milist, pid_t pid)
+static void parse_elf_info(mapinfo *milist, pid_t pid)
 {
     mapinfo *mi;
     for (mi = milist; mi != NULL; mi = mi->next) {
@@ -422,13 +218,18 @@
                 /* Parse the program header */
                 get_remote_struct(pid, (char *) (ptr+i), &phdr,
                                   sizeof(Elf32_Phdr));
+#ifdef __arm__
                 /* Found a EXIDX segment? */
                 if (phdr.p_type == PT_ARM_EXIDX) {
                     mi->exidx_start = mi->start + phdr.p_offset;
                     mi->exidx_end = mi->exidx_start + phdr.p_filesz;
                     break;
                 }
+#endif
             }
+
+            /* Try to load symbols from this file */
+            mi->symbols = symbol_table_create(mi->name);
         }
     }
 }
@@ -440,7 +241,9 @@
     mapinfo *milist = 0;
     unsigned int sp_list[STACK_CONTENT_DEPTH];
     int stack_depth;
+#ifdef __arm__
     int frame0_pc_sane = 1;
+#endif
 
     if (!at_fault) {
         _LOG(tfd, true,
@@ -466,8 +269,9 @@
         fclose(fp);
     }
 
-    parse_exidx_info(milist, tid);
+    parse_elf_info(milist, tid);
 
+#if __arm__
     /* If stack unwinder fails, use the default solution to dump the stack
      * content.
      */
@@ -482,9 +286,18 @@
     }
 
     dump_stack_and_code(tfd, tid, milist, stack_depth, sp_list, at_fault);
+#elif __i386__
+    /* If stack unwinder fails, use the default solution to dump the stack
+    * content.
+    */
+    stack_depth = unwind_backtrace_with_ptrace_x86(tfd, tid, milist,at_fault);
+#else
+#error "Unsupported architecture"
+#endif
 
     while(milist) {
         mapinfo *next = milist->next;
+        symbol_table_free(milist->symbols);
         free(milist);
         milist = next;
     }
@@ -863,10 +676,12 @@
     if(fd != -1) close(fd);
 }
 
+
 int main()
 {
     int s;
     struct sigaction act;
+    int logsocket = -1;
 
     logsocket = socket_local_client("logd",
             ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_DGRAM);
diff --git a/debuggerd/debuggerd.h b/debuggerd/debuggerd.h
new file mode 100644
index 0000000..e3cdc7c
--- /dev/null
+++ b/debuggerd/debuggerd.h
@@ -0,0 +1,39 @@
+/* system/debuggerd/debuggerd.h
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <cutils/logd.h>
+#include <sys/ptrace.h>
+#include <unwind.h>
+#include "utility.h"
+#include "symbol_table.h"
+
+
+/* Main entry point to get the backtrace from the crashing process */
+extern int unwind_backtrace_with_ptrace(int tfd, pid_t pid, mapinfo *map,
+                                        unsigned int sp_list[],
+                                        int *frame0_pc_sane,
+                                        bool at_fault);
+
+extern void dump_registers(int tfd, int pid, bool at_fault);
+
+extern int unwind_backtrace_with_ptrace_x86(int tfd, pid_t pid, mapinfo *map, bool at_fault);
+
+void dump_pc_and_lr(int tfd, int pid, mapinfo *map, int unwound_level, bool at_fault);
+
+void dump_stack_and_code(int tfd, int pid, mapinfo *map,
+                         int unwind_depth, unsigned int sp_list[],
+                         bool at_fault);
diff --git a/debuggerd/symbol_table.c b/debuggerd/symbol_table.c
new file mode 100644
index 0000000..fd008fe
--- /dev/null
+++ b/debuggerd/symbol_table.c
@@ -0,0 +1,240 @@
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "symbol_table.h"
+#include "utility.h"
+
+#include <linux/elf.h>
+
+// Compare func for qsort
+static int qcompar(const void *a, const void *b)
+{
+    return ((struct symbol*)a)->addr - ((struct symbol*)b)->addr;
+}
+
+// Compare func for bsearch
+static int bcompar(const void *addr, const void *element)
+{
+    struct symbol *symbol = (struct symbol*)element;
+
+    if((unsigned int)addr < symbol->addr) {
+        return -1;
+    }
+
+    if((unsigned int)addr - symbol->addr >= symbol->size) {
+        return 1;
+    }
+
+    return 0;
+}
+
+/*
+ *  Create a symbol table from a given file
+ *
+ *  Parameters:
+ *      filename - Filename to process
+ *
+ *  Returns:
+ *      A newly-allocated SymbolTable structure, or NULL if error.
+ *      Free symbol table with symbol_table_free()
+ */
+struct symbol_table *symbol_table_create(const char *filename)
+{
+    struct symbol_table *table = NULL;
+
+    // Open the file, and map it into memory
+    struct stat sb;
+    int length;
+    char *base;
+
+    XLOG("Creating symbol table for %s\n", filename);
+    int fd = open(filename, O_RDONLY);
+
+    if(fd < 0) {
+        goto out;
+    }
+
+    fstat(fd, &sb);
+    length = sb.st_size;
+
+    base = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
+
+    if(!base) {
+        goto out_close;
+    }
+
+    // Parse the file header
+    Elf32_Ehdr *hdr = (Elf32_Ehdr*)base;
+    Elf32_Shdr *shdr = (Elf32_Shdr*)(base + hdr->e_shoff);
+
+    // Search for the dynamic symbols section
+    int sym_idx = -1;
+    int dynsym_idx = -1;
+    int i;
+
+    for(i = 0; i < hdr->e_shnum; i++) {
+        if(shdr[i].sh_type == SHT_SYMTAB ) {
+            sym_idx = i;
+        }
+        if(shdr[i].sh_type == SHT_DYNSYM ) {
+            dynsym_idx = i;
+        }
+    }
+    if ((dynsym_idx == -1) && (sym_idx == -1)) {
+        goto out_unmap;
+    }
+
+    table = malloc(sizeof(struct symbol_table));
+    if(!table) {
+        goto out_unmap;
+    }
+    table->name = strdup(filename);
+    table->num_symbols = 0;
+
+    Elf32_Sym *dynsyms = NULL;
+    Elf32_Sym *syms = NULL;
+    int dynnumsyms = 0;
+    int numsyms = 0;
+    char *dynstr = NULL;
+    char *str = NULL;
+
+    if (dynsym_idx != -1) {
+        dynsyms = (Elf32_Sym*)(base + shdr[dynsym_idx].sh_offset);
+        dynnumsyms = shdr[dynsym_idx].sh_size / shdr[dynsym_idx].sh_entsize;
+        int dynstr_idx = shdr[dynsym_idx].sh_link;
+        dynstr = base + shdr[dynstr_idx].sh_offset;
+    }
+
+    if (sym_idx != -1) {
+        syms = (Elf32_Sym*)(base + shdr[sym_idx].sh_offset);
+        numsyms = shdr[sym_idx].sh_size / shdr[sym_idx].sh_entsize;
+        int str_idx = shdr[sym_idx].sh_link;
+        str = base + shdr[str_idx].sh_offset;
+    }
+
+    int symbol_count = 0;
+    int dynsymbol_count = 0;
+
+    if (dynsym_idx != -1) {
+        // Iterate through the dynamic symbol table, and count how many symbols
+        // are actually defined
+        for(i = 0; i < dynnumsyms; i++) {
+            if(dynsyms[i].st_shndx != SHN_UNDEF) {
+                dynsymbol_count++;
+            }
+        }
+        XLOG("Dynamic Symbol count: %d\n", dynsymbol_count);
+    }
+
+    if (sym_idx != -1) {
+        // Iterate through the symbol table, and count how many symbols
+        // are actually defined
+        for(i = 0; i < numsyms; i++) {
+            if((syms[i].st_shndx != SHN_UNDEF) &&
+                (strlen(str+syms[i].st_name)) &&
+                (syms[i].st_value != 0) && (syms[i].st_size != 0)) {
+                symbol_count++;
+            }
+        }
+        XLOG("Symbol count: %d\n", symbol_count);
+    }
+
+    // Now, create an entry in our symbol table structure for each symbol...
+    table->num_symbols += symbol_count + dynsymbol_count;
+    table->symbols = malloc(table->num_symbols * sizeof(struct symbol));
+    if(!table->symbols) {
+        free(table);
+        table = NULL;
+        goto out_unmap;
+    }
+
+
+    int j = 0;
+    if (dynsym_idx != -1) {
+        // ...and populate them
+        for(i = 0; i < dynnumsyms; i++) {
+            if(dynsyms[i].st_shndx != SHN_UNDEF) {
+                table->symbols[j].name = strdup(dynstr + dynsyms[i].st_name);
+                table->symbols[j].addr = dynsyms[i].st_value;
+                table->symbols[j].size = dynsyms[i].st_size;
+                XLOG("name: %s, addr: %x, size: %x\n",
+                    table->symbols[j].name, table->symbols[j].addr, table->symbols[j].size);
+                j++;
+            }
+        }
+    }
+
+    if (sym_idx != -1) {
+        // ...and populate them
+        for(i = 0; i < numsyms; i++) {
+            if((syms[i].st_shndx != SHN_UNDEF) &&
+                (strlen(str+syms[i].st_name)) &&
+                (syms[i].st_value != 0) && (syms[i].st_size != 0)) {
+                table->symbols[j].name = strdup(str + syms[i].st_name);
+                table->symbols[j].addr = syms[i].st_value;
+                table->symbols[j].size = syms[i].st_size;
+                XLOG("name: %s, addr: %x, size: %x\n",
+                    table->symbols[j].name, table->symbols[j].addr, table->symbols[j].size);
+                j++;
+            }
+        }
+    }
+
+    // Sort the symbol table entries, so they can be bsearched later
+    qsort(table->symbols, table->num_symbols, sizeof(struct symbol), qcompar);
+
+out_unmap:
+    munmap(base, length);
+
+out_close:
+    close(fd);
+
+out:
+    return table;
+}
+
+/*
+ * Free a symbol table
+ *
+ * Parameters:
+ *     table - Table to free
+ */
+void symbol_table_free(struct symbol_table *table)
+{
+    int i;
+
+    if(!table) {
+        return;
+    }
+
+    for(i=0; i<table->num_symbols; i++) {
+        free(table->symbols[i].name);
+    }
+
+    free(table->symbols);
+    free(table);
+}
+
+/*
+ * Search for an address in the symbol table
+ *
+ * Parameters:
+ *      table - Table to search in
+ *      addr - Address to search for.
+ *
+ * Returns:
+ *      A pointer to the Symbol structure corresponding to the
+ *      symbol which contains this address, or NULL if no symbol
+ *      contains it.
+ */
+const struct symbol *symbol_table_lookup(struct symbol_table *table, unsigned int addr)
+{
+    if(!table) {
+        return NULL;
+    }
+
+    return bsearch((void*)addr, table->symbols, table->num_symbols, sizeof(struct symbol), bcompar);
+}
diff --git a/debuggerd/symbol_table.h b/debuggerd/symbol_table.h
new file mode 100644
index 0000000..7f41f91
--- /dev/null
+++ b/debuggerd/symbol_table.h
@@ -0,0 +1,20 @@
+#ifndef SYMBOL_TABLE_H
+#define SYMBOL_TABLE_H
+
+struct symbol {
+    unsigned int addr;
+    unsigned int size;
+    char *name;
+};
+
+struct symbol_table {
+    struct symbol *symbols;
+    int num_symbols;
+    char *name;
+};
+
+struct symbol_table *symbol_table_create(const char *filename);
+void symbol_table_free(struct symbol_table *table);
+const struct symbol *symbol_table_lookup(struct symbol_table *table, unsigned int addr);
+
+#endif
diff --git a/debuggerd/utility.c b/debuggerd/utility.c
index 8f3931c..2afdb46 100644
--- a/debuggerd/utility.c
+++ b/debuggerd/utility.c
@@ -38,14 +38,14 @@
     unsigned int i;
 
     for (i = 0; i+4 <= size; i+=4) {
-        *(int *)(dst+i) = ptrace(PTRACE_PEEKTEXT, pid, src+i, NULL);
+        *(int *)((char *)dst+i) = ptrace(PTRACE_PEEKTEXT, pid, (char *)src+i, NULL);
     }
 
     if (i < size) {
         int val;
 
         assert((size - i) < 4);
-        val = ptrace(PTRACE_PEEKTEXT, pid, src+i, NULL);
+        val = ptrace(PTRACE_PEEKTEXT, pid, (char *)src+i, NULL);
         while (i < size) {
             ((unsigned char *)dst)[i] = val & 0xff;
             i++;
@@ -69,11 +69,12 @@
 /* Find the containing map info for the pc */
 const mapinfo *pc_to_mapinfo(mapinfo *mi, unsigned pc, unsigned *rel_pc)
 {
+    *rel_pc = pc;
     while(mi) {
         if((pc >= mi->start) && (pc < mi->end)){
             // Only calculate the relative offset for shared libraries
             if (strstr(mi->name, ".so")) {
-                *rel_pc = pc - mi->start;
+                *rel_pc -= mi->start;
             }
             return mi;
         }
diff --git a/debuggerd/utility.h b/debuggerd/utility.h
index 49f5951..0682b85 100644
--- a/debuggerd/utility.h
+++ b/debuggerd/utility.h
@@ -21,6 +21,8 @@
 #include <stddef.h>
 #include <stdbool.h>
 
+#include "symbol_table.h"
+
 #ifndef PT_ARM_EXIDX
 #define PT_ARM_EXIDX    0x70000001      /* .ARM.exidx segment */
 #endif
@@ -33,6 +35,7 @@
     unsigned end;
     unsigned exidx_start;
     unsigned exidx_end;
+    struct symbol_table *symbols;
     char name[];
 } mapinfo;
 
@@ -53,4 +56,11 @@
 /* Log information onto the tombstone */
 extern void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...);
 
+#define LOG(fmt...) _LOG(-1, 0, fmt)
+#if 0
+#define XLOG(fmt...) _LOG(-1, 0, fmt)
+#else
+#define XLOG(fmt...) do {} while(0)
+#endif
+
 #endif
diff --git a/debuggerd/x86/crashglue.S b/debuggerd/x86/crashglue.S
new file mode 100644
index 0000000..59df432
--- /dev/null
+++ b/debuggerd/x86/crashglue.S
@@ -0,0 +1,15 @@
+.globl crash1
+.globl crashnostack
+
+crash1:
+	movl $0xa5a50000, %eax
+	movl $0xa5a50001, %ebx
+	movl $0xa5a50002, %ecx
+
+	movl $0, %edx
+	jmp *%edx
+
+
+crashnostack:
+	movl $0, %ebp
+	jmp *%ebp
diff --git a/debuggerd/x86/machine.c b/debuggerd/x86/machine.c
new file mode 100644
index 0000000..9d418cf
--- /dev/null
+++ b/debuggerd/x86/machine.c
@@ -0,0 +1,61 @@
+/* system/debuggerd/debuggerd.c
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <sys/exec_elf.h>
+#include <sys/stat.h>
+
+#include <cutils/sockets.h>
+#include <cutils/properties.h>
+
+#include <linux/input.h>
+
+#include "../utility.h"
+#include "x86_utility.h"
+
+void dump_registers(int tfd, int pid, bool at_fault)
+{
+    struct pt_regs_x86 r;
+    bool only_in_tombstone = !at_fault;
+
+    if(ptrace(PTRACE_GETREGS, pid, 0, &r)) {
+        _LOG(tfd, only_in_tombstone,
+             "cannot get registers: %s\n", strerror(errno));
+        return;
+    }
+//if there is no stack, no print just like arm
+    if(!r.ebp)
+        return;
+    _LOG(tfd, only_in_tombstone, " eax %08x  ebx %08x  ecx %08x  edx %08x\n",
+         r.eax, r.ebx, r.ecx, r.edx);
+    _LOG(tfd, only_in_tombstone, " esi %08x  edi %08x\n",
+         r.esi, r.edi);
+    _LOG(tfd, only_in_tombstone, " xcs %08x  xds %08x  xes %08x  xfs %08x xss %08x\n",
+         r.xcs, r.xds, r.xes, r.xfs, r.xss);
+    _LOG(tfd, only_in_tombstone,
+         " eip %08x  ebp %08x  esp %08x  flags %08x\n",
+         r.eip, r.ebp, r.esp, r.eflags);
+}
diff --git a/debuggerd/x86/unwind.c b/debuggerd/x86/unwind.c
new file mode 100644
index 0000000..8f84e01
--- /dev/null
+++ b/debuggerd/x86/unwind.c
@@ -0,0 +1,85 @@
+#include <cutils/logd.h>
+#include <sys/ptrace.h>
+#include "../utility.h"
+#include "x86_utility.h"
+
+
+int unwind_backtrace_with_ptrace_x86(int tfd, pid_t pid, mapinfo *map,
+                                 bool at_fault)
+{
+    struct pt_regs_x86 r;
+    unsigned int stack_level = 0;
+    unsigned int stack_depth = 0;
+    unsigned int rel_pc;
+    unsigned int stack_ptr;
+    unsigned int stack_content;
+
+    if(ptrace(PTRACE_GETREGS, pid, 0, &r)) return 0;
+    unsigned int eip = (unsigned int)r.eip;
+    unsigned int ebp = (unsigned int)r.ebp;
+    unsigned int cur_sp = (unsigned int)r.esp;
+    const mapinfo *mi;
+    const struct symbol* sym = 0;
+
+
+//ebp==0, it indicates that the stack is poped to the bottom or there is no stack at all.
+    while (ebp) {
+        _LOG(tfd, !at_fault, "#0%d ",stack_level);
+        mi = pc_to_mapinfo(map, eip, &rel_pc);
+
+        /* See if we can determine what symbol this stack frame resides in */
+        if (mi != 0 && mi->symbols != 0) {
+            sym = symbol_table_lookup(mi->symbols, rel_pc);
+        }
+        if (sym) {
+            _LOG(tfd, !at_fault, "    eip: %08x  %s (%s)\n", eip, mi ? mi->name : "", sym->name);
+        } else {
+            _LOG(tfd, !at_fault, "    eip: %08x  %s\n", eip, mi ? mi->name : "");
+        }
+
+        stack_level++;
+        if (stack_level >= STACK_DEPTH || eip == 0)
+            break;
+        eip = ptrace(PTRACE_PEEKTEXT, pid, (void*)(ebp + 4), NULL);
+        ebp = ptrace(PTRACE_PEEKTEXT, pid, (void*)ebp, NULL);
+    }
+    ebp = (unsigned int)r.ebp;
+    stack_depth = stack_level;
+    stack_level = 0;
+    if (ebp)
+        _LOG(tfd, !at_fault, "stack: \n");
+    while (ebp) {
+        _LOG(tfd, !at_fault, "#0%d \n",stack_level);
+        stack_ptr = cur_sp;
+        while((int)(ebp - stack_ptr) >= 0) {
+            stack_content = ptrace(PTRACE_PEEKTEXT, pid, (void*)stack_ptr, NULL);
+            mi = pc_to_mapinfo(map, stack_content, &rel_pc);
+
+            /* See if we can determine what symbol this stack frame resides in */
+            if (mi != 0 && mi->symbols != 0) {
+                sym = symbol_table_lookup(mi->symbols, rel_pc);
+            }
+            if (sym) {
+                _LOG(tfd, !at_fault, "    %08x  %08x  %s (%s)\n",
+                    stack_ptr, stack_content, mi ? mi->name : "", sym->name);
+            } else {
+                _LOG(tfd, !at_fault, "    %08x  %08x  %s\n", stack_ptr, stack_content, mi ? mi->name : "");
+            }
+
+            stack_ptr = stack_ptr + 4;
+            //the stack frame may be very deep.
+            if((int)(stack_ptr - cur_sp) >= STACK_FRAME_DEPTH) {
+                _LOG(tfd, !at_fault, "    ......  ......  \n");
+                break;
+            }
+        }
+        cur_sp = ebp + 4;
+        stack_level++;
+        if (stack_level >= STACK_DEPTH || stack_level >= stack_depth)
+            break;
+        ebp = ptrace(PTRACE_PEEKTEXT, pid, (void*)ebp, NULL);
+    }
+
+    return stack_depth;
+}
+
diff --git a/debuggerd/x86/x86_utility.h b/debuggerd/x86/x86_utility.h
new file mode 100644
index 0000000..ac6a885
--- /dev/null
+++ b/debuggerd/x86/x86_utility.h
@@ -0,0 +1,40 @@
+/*
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define STACK_DEPTH 8
+#define STACK_FRAME_DEPTH 64
+
+typedef struct pt_regs_x86 {
+    long ebx;
+    long ecx;
+    long edx;
+    long esi;
+    long edi;
+    long ebp;
+    long eax;
+    int  xds;
+    int  xes;
+    int  xfs;
+    int  xgs;
+    long orig_eax;
+    long eip;
+    int  xcs;
+    long eflags;
+    long esp;
+    int  xss;
+}pt_regs_x86;
+
diff --git a/fastboot/engine.c b/fastboot/engine.c
index 8ba202c..48073ee 100644
--- a/fastboot/engine.c
+++ b/fastboot/engine.c
@@ -97,14 +97,20 @@
 {
     Action *a;
     va_list ap;
+    size_t cmdsize;
 
     a = calloc(1, sizeof(Action));
     if (a == 0) die("out of memory");
 
     va_start(ap, fmt);
-    vsprintf(a->cmd, fmt, ap);
+    cmdsize = vsnprintf(a->cmd, sizeof(a->cmd), fmt, ap);
     va_end(ap);
 
+    if (cmdsize >= sizeof(a->cmd)) {
+        free(a);
+        die("Command length (%d) exceeds maximum size (%d)", cmdsize, sizeof(a->cmd));
+    }
+
     if (action_last) {
         action_last->next = a;
     } else {
diff --git a/fastboot/fastboot.c b/fastboot/fastboot.c
index f3bfbeba..14048a1 100644
--- a/fastboot/fastboot.c
+++ b/fastboot/fastboot.c
@@ -223,6 +223,7 @@
             "  boot <kernel> [ <ramdisk> ]              download and boot kernel\n"
             "  flash:raw boot <kernel> [ <ramdisk> ]    create bootimage and flash it\n"
             "  devices                                  list all connected devices\n"
+            "  continue                                 continue with autoboot\n"
             "  reboot                                   reboot device normally\n"
             "  reboot-bootloader                        reboot device into bootloader\n"
             "\n"
diff --git a/include/arch/darwin-x86/AndroidConfig.h b/include/arch/darwin-x86/AndroidConfig.h
index 2bb44c6..9914bd5 100644
--- a/include/arch/darwin-x86/AndroidConfig.h
+++ b/include/arch/darwin-x86/AndroidConfig.h
@@ -300,4 +300,12 @@
  */
 #define HAVE_PRINTF_ZD 1
 
+/*
+ * We need to open binary files using O_BINARY on Windows.
+ * Most systems lack (and actually don't need) this flag.
+ */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
 #endif /*_ANDROID_CONFIG_H*/
diff --git a/include/arch/freebsd-x86/AndroidConfig.h b/include/arch/freebsd-x86/AndroidConfig.h
index 992b689..ed5af59 100644
--- a/include/arch/freebsd-x86/AndroidConfig.h
+++ b/include/arch/freebsd-x86/AndroidConfig.h
@@ -358,4 +358,12 @@
  */
 #define HAVE_PRINTF_ZD 1
 
+/*
+ * We need to open binary files using O_BINARY on Windows.
+ * Most systems lack (and actually don't need) this flag.
+ */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
 #endif /*_ANDROID_CONFIG_H*/
diff --git a/include/arch/linux-arm/AndroidConfig.h b/include/arch/linux-arm/AndroidConfig.h
index 5b7ccee..c70618d 100644
--- a/include/arch/linux-arm/AndroidConfig.h
+++ b/include/arch/linux-arm/AndroidConfig.h
@@ -354,4 +354,12 @@
  */
 #define HAVE_PRINTF_ZD 1
 
+/*
+ * We need to open binary files using O_BINARY on Windows.
+ * Most systems lack (and actually don't need) this flag.
+ */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
 #endif /* _ANDROID_CONFIG_H */
diff --git a/include/arch/linux-ppc/AndroidConfig.h b/include/arch/linux-ppc/AndroidConfig.h
new file mode 100644
index 0000000..91af629
--- /dev/null
+++ b/include/arch/linux-ppc/AndroidConfig.h
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Android config -- "Linux".  Used for desktop ppc Linux.
+ */
+#ifndef _ANDROID_CONFIG_H
+#define _ANDROID_CONFIG_H
+
+/*
+ * ===========================================================================
+ *                              !!! IMPORTANT !!!
+ * ===========================================================================
+ *
+ * This file is included by ALL C/C++ source files.  Don't put anything in
+ * here unless you are absolutely certain it can't go anywhere else.
+ *
+ * Any C++ stuff must be wrapped with "#ifdef __cplusplus".  Do not use "//"
+ * comments.
+ */
+
+/*
+ * Threading model.  Choose one:
+ *
+ * HAVE_PTHREADS - use the pthreads library.
+ * HAVE_WIN32_THREADS - use Win32 thread primitives.
+ *  -- combine HAVE_CREATETHREAD, HAVE_CREATEMUTEX, and HAVE__BEGINTHREADEX
+ */
+#define HAVE_PTHREADS
+
+/*
+ * Do we have the futex syscall?
+ */
+
+#define HAVE_FUTEX
+
+/*
+ * Process creation model.  Choose one:
+ *
+ * HAVE_FORKEXEC - use fork() and exec()
+ * HAVE_WIN32_PROC - use CreateProcess()
+ */
+#define HAVE_FORKEXEC
+
+/*
+ * Process out-of-memory adjustment.  Set if running on Linux,
+ * where we can write to /proc/<pid>/oom_adj to modify the out-of-memory
+ * badness adjustment.
+ */
+#define HAVE_OOM_ADJ
+
+/*
+ * IPC model.  Choose one:
+ *
+ * HAVE_SYSV_IPC - use the classic SysV IPC mechanisms (semget, shmget).
+ * HAVE_MACOSX_IPC - use Macintosh IPC mechanisms (sem_open, mmap).
+ * HAVE_WIN32_IPC - use Win32 IPC (CreateSemaphore, CreateFileMapping).
+ * HAVE_ANDROID_IPC - use Android versions (?, mmap).
+ */
+#define HAVE_SYSV_IPC
+
+/*
+ * Memory-mapping model. Choose one:
+ *
+ * HAVE_POSIX_FILEMAP - use the Posix sys/mmap.h
+ * HAVE_WIN32_FILEMAP - use Win32 filemaps
+ */
+#define  HAVE_POSIX_FILEMAP
+
+/*
+ * Define this if you have <termio.h>
+ */
+#define  HAVE_TERMIO_H
+
+/*
+ * Define this if you have <sys/sendfile.h>
+ */
+#define  HAVE_SYS_SENDFILE_H 1
+
+/*
+ * Define this if you build against MSVCRT.DLL
+ */
+/* #define HAVE_MS_C_RUNTIME */
+
+/*
+ * Define this if you have sys/uio.h
+ */
+#define  HAVE_SYS_UIO_H
+
+/*
+ * Define this if your platforms implements symbolic links
+ * in its filesystems
+ */
+#define HAVE_SYMLINKS
+
+/*
+ * Define this if we have localtime_r().
+ */
+#define HAVE_LOCALTIME_R
+
+/*
+ * Define this if we have gethostbyname_r().
+ */
+#define HAVE_GETHOSTBYNAME_R
+
+/*
+ * Define this if we have ioctl().
+ */
+#define HAVE_IOCTL
+
+/*
+ * Define this if we want to use WinSock.
+ */
+/* #define HAVE_WINSOCK */
+
+/*
+ * Define this if have clock_gettime() and friends
+ *
+ * Desktop Linux has this in librt, but it's broken in goobuntu, yielding
+ * mildly or wildly inaccurate results.
+ */
+/*#define HAVE_POSIX_CLOCKS*/
+
+/*
+ * Define this if we have pthread_cond_timedwait_monotonic() and
+ * clock_gettime(CLOCK_MONOTONIC).
+ */
+/* #define HAVE_TIMEDWAIT_MONOTONIC */
+
+/*
+ * Define this if we have linux style epoll()
+ */
+#define HAVE_EPOLL
+
+/*
+ * Endianness of the target machine.  Choose one:
+ *
+ * HAVE_ENDIAN_H -- have endian.h header we can include.
+ * HAVE_LITTLE_ENDIAN -- we are little endian.
+ * HAVE_BIG_ENDIAN -- we are big endian.
+ */
+#define HAVE_ENDIAN_H
+#define HAVE_BIG_ENDIAN
+
+/*
+ * We need to choose between 32-bit and 64-bit off_t.  All of our code should
+ * agree on the same size.  For desktop systems, use 64-bit values,
+ * because some of our libraries (e.g. wxWidgets) expect to be built that way.
+ */
+#define _FILE_OFFSET_BITS 64
+#define _LARGEFILE_SOURCE 1
+
+/*
+ * Defined if we have the backtrace() call for retrieving a stack trace.
+ * Needed for CallStack to operate; if not defined, CallStack is
+ * non-functional.
+ */
+#define HAVE_BACKTRACE 1
+
+/*
+ * Defined if we have the dladdr() call for retrieving the symbol associated
+ * with a memory address.  If not defined, stack crawls will not have symbolic
+ * information.
+ */
+#define HAVE_DLADDR 1
+
+/*
+ * Defined if we have the cxxabi.h header for demangling C++ symbols.  If
+ * not defined, stack crawls will be displayed with raw mangled symbols
+ */
+#define HAVE_CXXABI 0
+
+/*
+ * Defined if we have the gettid() system call.
+ */
+/* #define HAVE_GETTID */
+
+/*
+ * Defined if we have the sched_setscheduler() call
+ */
+#define HAVE_SCHED_SETSCHEDULER
+
+/*
+ * Add any extra platform-specific defines here.
+ */
+
+/*
+ * Define if we have <malloc.h> header
+ */
+#define HAVE_MALLOC_H
+
+/*
+ * Define if we have Linux-style non-filesystem Unix Domain Sockets
+ */
+
+/*
+ * What CPU architecture does this platform use?
+ */
+#define ARCH_PPC
+
+
+/*
+ * Define if we have Linux's inotify in <sys/inotify.h>.
+ */
+/*#define HAVE_INOTIFY 1*/
+
+/*
+ * Define if we have madvise() in <sys/mman.h>
+ */
+#define HAVE_MADVISE 1
+
+/*
+ * Define if tm struct has tm_gmtoff field
+ */
+#define HAVE_TM_GMTOFF 1
+
+/*
+ * Define if dirent struct has d_type field
+ */
+#define HAVE_DIRENT_D_TYPE 1
+
+/*
+ * Define if libc includes Android system properties implementation.
+ */
+/* #define HAVE_LIBC_SYSTEM_PROPERTIES */
+
+/*
+ * Define if system provides a system property server (should be
+ * mutually exclusive with HAVE_LIBC_SYSTEM_PROPERTIES).
+ */
+#define HAVE_SYSTEM_PROPERTY_SERVER
+
+/*
+ * sprintf() format string for shared library naming.
+ */
+#define OS_SHARED_LIB_FORMAT_STR    "lib%s.so"
+
+/*
+ * type for the third argument to mincore().
+ */
+#define MINCORE_POINTER_TYPE unsigned char *
+
+/*
+ * Do we have the sigaction flag SA_NOCLDWAIT?
+ */
+#define HAVE_SA_NOCLDWAIT
+
+/*
+ * The default path separator for the platform
+ */
+#define OS_PATH_SEPARATOR '/'
+
+/*
+ * Is the filesystem case sensitive?
+ */
+#define OS_CASE_SENSITIVE
+
+/*
+ * Define if <sys/socket.h> exists.
+ */
+#define HAVE_SYS_SOCKET_H 1
+
+/*
+ * Define if the strlcpy() function exists on the system.
+ */
+/* #define HAVE_STRLCPY 1 */
+
+/*
+ * Define if the open_memstream() function exists on the system.
+ */
+#define HAVE_OPEN_MEMSTREAM 1
+
+/*
+ * Define if the BSD funopen() function exists on the system.
+ */
+/* #define HAVE_FUNOPEN 1 */
+
+/*
+ * Define if prctl() exists
+ */
+#define HAVE_PRCTL 1
+
+/*
+ * Define if writev() exists
+ */
+#define HAVE_WRITEV 1
+
+/*
+ * Define if <stdint.h> exists.
+ */
+#define HAVE_STDINT_H 1
+
+/*
+ * Define if <stdbool.h> exists.
+ */
+#define HAVE_STDBOOL_H 1
+
+/*
+ * Define if <sched.h> exists.
+ */
+#define HAVE_SCHED_H 1
+
+/*
+ * Define if pread() exists
+ */
+#define HAVE_PREAD 1
+
+/*
+ * We need to open binary files using O_BINARY on Windows.
+ * Most systems lack (and actually don't need) this flag.
+ */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+#endif /*_ANDROID_CONFIG_H*/
diff --git a/include/arch/linux-sh/AndroidConfig.h b/include/arch/linux-sh/AndroidConfig.h
index 0dd7b3b..622ca50 100644
--- a/include/arch/linux-sh/AndroidConfig.h
+++ b/include/arch/linux-sh/AndroidConfig.h
@@ -361,4 +361,12 @@
  */
 #define HAVE_PRINTF_ZD 1
 
+/*
+ * We need to open binary files using O_BINARY on Windows.
+ * Most systems lack (and actually don't need) this flag.
+ */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
 #endif /* _ANDROID_CONFIG_H */
diff --git a/include/arch/linux-x86/AndroidConfig.h b/include/arch/linux-x86/AndroidConfig.h
index 24f6ccc..26dcf1f 100644
--- a/include/arch/linux-x86/AndroidConfig.h
+++ b/include/arch/linux-x86/AndroidConfig.h
@@ -328,4 +328,12 @@
  */
 #define HAVE_PRINTF_ZD 1
 
+/*
+ * We need to open binary files using O_BINARY on Windows.
+ * Most systems lack (and actually don't need) this flag.
+ */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
 #endif /*_ANDROID_CONFIG_H*/
diff --git a/include/arch/target_linux-x86/AndroidConfig.h b/include/arch/target_linux-x86/AndroidConfig.h
index 617e7fa..9b357d9 100644
--- a/include/arch/target_linux-x86/AndroidConfig.h
+++ b/include/arch/target_linux-x86/AndroidConfig.h
@@ -227,7 +227,7 @@
 /*
  * Define if we have Linux's dbus 
  */
-#define HAVE_DBUS 1
+/* #define HAVE_DBUS 1 */
 
 /*
  * Define if tm struct has tm_gmtoff field
@@ -345,4 +345,12 @@
  */
 #define HAVE_PRINTF_ZD 1
 
+/*
+ * We need to open binary files using O_BINARY on Windows.
+ * Most systems lack (and actually don't need) this flag.
+ */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
 #endif /* _ANDROID_CONFIG_H */
diff --git a/include/arch/windows/AndroidConfig.h b/include/arch/windows/AndroidConfig.h
index f0bf56f..17c84c7 100644
--- a/include/arch/windows/AndroidConfig.h
+++ b/include/arch/windows/AndroidConfig.h
@@ -333,4 +333,10 @@
  */
 /* #define HAVE_PRINTF_ZD 1 */
 
+/*
+ * We need to open binary files using O_BINARY on Windows.
+ * We don't define it on Windows since it is part of the io headers.
+ */
+/* #define O_BINARY 0 */
+
 #endif /*_ANDROID_CONFIG_H*/
diff --git a/include/cutils/log.h b/include/cutils/log.h
index dd47c35..f602017 100644
--- a/include/cutils/log.h
+++ b/include/cutils/log.h
@@ -291,11 +291,11 @@
  */
 #define LOG_ALWAYS_FATAL_IF(cond, ...) \
     ( (CONDITION(cond)) \
-    ? ((void)android_printAssert(#cond, LOG_TAG, __VA_ARGS__)) \
+    ? ((void)android_printAssert(#cond, LOG_TAG, ## __VA_ARGS__)) \
     : (void)0 )
 
 #define LOG_ALWAYS_FATAL(...) \
-    ( ((void)android_printAssert(NULL, LOG_TAG, __VA_ARGS__)) )
+    ( ((void)android_printAssert(NULL, LOG_TAG, ## __VA_ARGS__)) )
 
 /*
  * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
@@ -308,7 +308,7 @@
 
 #else
 
-#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, __VA_ARGS__)
+#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ## __VA_ARGS__)
 #define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
 
 #endif
@@ -317,7 +317,7 @@
  * Assertion that generates a log message when the assertion fails.
  * Stripped out of release builds.  Uses the current LOG_TAG.
  */
-#define LOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), __VA_ARGS__)
+#define LOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__)
 //#define LOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond)
 
 // ---------------------------------------------------------------------
@@ -403,8 +403,24 @@
 #define android_vprintLog(prio, cond, tag, fmt...) \
     __android_log_vprint(prio, tag, fmt)
 
+/* XXX Macros to work around syntax errors in places where format string
+ * arg is not passed to LOG_ASSERT, LOG_ALWAYS_FATAL or LOG_ALWAYS_FATAL_IF
+ * (happens only in debug builds).
+ */
+
+/* Returns 2nd arg.  Used to substitute default value if caller's vararg list
+ * is empty.
+ */
+#define __android_second(dummy, second, ...)     second
+
+/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
+ * returns nothing.
+ */
+#define __android_rest(first, ...)               , ## __VA_ARGS__
+
 #define android_printAssert(cond, tag, fmt...) \
-    __android_log_assert(cond, tag, fmt)
+    __android_log_assert(cond, tag, \
+        __android_second(0, ## fmt, NULL) __android_rest(fmt))
 
 #define android_writeLog(prio, tag, text) \
     __android_log_write(prio, tag, text)
@@ -413,7 +429,7 @@
     __android_log_bwrite(tag, payload, len)
 #define android_btWriteLog(tag, type, payload, len) \
     __android_log_btwrite(tag, type, payload, len)
-	
+
 // TODO: remove these prototypes and their users
 #define android_testLog(prio, tag) (1)
 #define android_writevLog(vec,num) do{}while(0)
diff --git a/include/netutils/dhcp.h b/include/netutils/dhcp.h
new file mode 100644
index 0000000..96798c5
--- /dev/null
+++ b/include/netutils/dhcp.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+#ifndef _NETUTILS_DHCP_H_
+#define _NETUTILS_DHCP_H_
+
+#include <sys/cdefs.h>
+#include <arpa/inet.h>
+
+__BEGIN_DECLS
+
+extern int do_dhcp(char *iname);
+extern int dhcp_do_request(const char *ifname,
+                          in_addr_t *ipaddr,
+                          in_addr_t *gateway,
+                          in_addr_t *mask,
+                          in_addr_t *dns1,
+                          in_addr_t *dns2,
+                          in_addr_t *server,
+                          uint32_t  *lease);
+extern int dhcp_stop(const char *ifname);
+extern int dhcp_release_lease(const char *ifname);
+extern char *dhcp_get_errmsg();
+
+__END_DECLS
+
+#endif /* _NETUTILS_DHCP_H_ */
diff --git a/include/netutils/ifc.h b/include/netutils/ifc.h
new file mode 100644
index 0000000..e245262
--- /dev/null
+++ b/include/netutils/ifc.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2008, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+#ifndef _NETUTILS_IFC_H_
+#define _NETUTILS_IFC_H_
+
+#include <sys/cdefs.h>
+#include <arpa/inet.h>
+
+__BEGIN_DECLS
+
+extern int ifc_init(void);
+extern void ifc_close(void);
+
+extern int ifc_get_ifindex(const char *name, int *if_indexp);
+extern int ifc_get_hwaddr(const char *name, void *ptr);
+
+extern int ifc_up(const char *name);
+extern int ifc_down(const char *name);
+
+extern int ifc_enable(const char *ifname);
+extern int ifc_disable(const char *ifname);
+
+extern int ifc_reset_connections(const char *ifname);
+
+extern int ifc_set_addr(const char *name, in_addr_t addr);
+extern int ifc_set_mask(const char *name, in_addr_t mask);
+extern int ifc_set_hwaddr(const char *name, const void *ptr);
+
+/* This function is deprecated. Use ifc_add_route instead. */
+extern int ifc_add_host_route(const char *name, in_addr_t addr);
+extern int ifc_remove_host_routes(const char *name);
+extern int ifc_get_default_route(const char *ifname);
+/* This function is deprecated. Use ifc_add_route instead */
+extern int ifc_set_default_route(const char *ifname, in_addr_t gateway);
+/* This function is deprecated. Use ifc_add_route instead */
+extern int ifc_create_default_route(const char *name, in_addr_t addr);
+extern int ifc_remove_default_route(const char *ifname);
+extern int ifc_add_route(const char *name, const char *addr, int prefix_length,
+                         const char *gw);
+
+extern int ifc_get_info(const char *name, in_addr_t *addr, in_addr_t *mask,
+                        in_addr_t *flags);
+
+extern int ifc_configure(const char *ifname, in_addr_t address,
+                         in_addr_t netmask, in_addr_t gateway,
+                         in_addr_t dns1, in_addr_t dns2);
+
+__END_DECLS
+
+#endif /* _NETUTILS_IFC_H_ */
diff --git a/include/pixelflinger/pixelflinger.h b/include/pixelflinger/pixelflinger.h
index dca0b90..8a2b442 100644
--- a/include/pixelflinger/pixelflinger.h
+++ b/include/pixelflinger/pixelflinger.h
@@ -315,7 +315,7 @@
 ssize_t gglInit(GGLContext** context);
 ssize_t gglUninit(GGLContext* context);
 
-GGLint gglBitBlti(
+GGLint gglBitBlit(
         GGLContext* c,
         int tmu,
         GGLint crop[4],
diff --git a/include/private/android_filesystem_config.h b/include/private/android_filesystem_config.h
index 9bf6cd3..f1b1ae0 100644
--- a/include/private/android_filesystem_config.h
+++ b/include/private/android_filesystem_config.h
@@ -52,6 +52,8 @@
 #define AID_VPN           1016  /* vpn system */
 #define AID_KEYSTORE      1017  /* keystore subsystem */
 #define AID_USB           1018  /* USB devices */
+#define AID_DRM           1019  /* DRM server */
+#define AID_DRMIO         1020  /* DRM IO server */
 #define AID_GPS           1021  /* GPS daemon */
 #define AID_UNUSED1       1022  /* deprecated, DO NOT USE */
 #define AID_NFC           1023  /* nfc subsystem */
@@ -96,6 +98,8 @@
     { "adb",       AID_ADB, },
     { "install",   AID_INSTALL, },
     { "media",     AID_MEDIA, },
+    { "drm",       AID_DRM, },
+    { "drmio",     AID_DRMIO, },
     { "nfc",       AID_NFC, },
     { "shell",     AID_SHELL, },
     { "cache",     AID_CACHE, },
@@ -171,6 +175,7 @@
     { 00640, AID_SYSTEM,    AID_SYSTEM,    "system/etc/bluetooth/auto_pairing.conf" },
     { 00444, AID_RADIO,     AID_AUDIO,     "system/etc/AudioPara4.csv" },
     { 00555, AID_ROOT,      AID_ROOT,      "system/etc/ppp/*" },
+    { 00555, AID_ROOT,      AID_ROOT,      "system/etc/rc.*" },
     { 00644, AID_SYSTEM,    AID_SYSTEM,    "data/app/*" },
     { 00644, AID_SYSTEM,    AID_SYSTEM,    "data/app-private/*" },
     { 00644, AID_APP,       AID_APP,       "data/data/*" },
diff --git a/include/sysutils/SocketClient.h b/include/sysutils/SocketClient.h
index e7fb177..d6bb7d5 100644
--- a/include/sysutils/SocketClient.h
+++ b/include/sysutils/SocketClient.h
@@ -19,6 +19,10 @@
     /* Peer group ID */
     gid_t mGid;
 
+    /* Reference count (starts at 1) */
+    pthread_mutex_t mRefCountMutex;
+    int mRefCount;
+
 public:
     SocketClient(int sock);
     virtual ~SocketClient() {}
@@ -28,8 +32,19 @@
     uid_t getUid() const { return mUid; }
     gid_t getGid() const { return mGid; }
 
+    // Send null-terminated C strings:
     int sendMsg(int code, const char *msg, bool addErrno);
     int sendMsg(const char *msg);
+
+    // Sending binary data:
+    int sendData(const void *data, int len);
+
+    // Optional reference counting.  Reference count starts at 1.  If
+    // it's decremented to 0, it deletes itself.
+    // SocketListener creates a SocketClient (at refcount 1) and calls
+    // decRef() when it's done with the client.
+    void incRef();
+    bool decRef(); // returns true at 0 (but note: SocketClient already deleted)
 };
 
 typedef android::List<SocketClient *> SocketClientCollection;
diff --git a/include/sysutils/SocketListener.h b/include/sysutils/SocketListener.h
index c7edfeb..6592b01 100644
--- a/include/sysutils/SocketListener.h
+++ b/include/sysutils/SocketListener.h
@@ -30,7 +30,7 @@
     pthread_t               mThread;
 
 public:
-    SocketListener(const char *socketNames, bool listen);
+    SocketListener(const char *socketName, bool listen);
     SocketListener(int socketFd, bool listen);
 
     virtual ~SocketListener();
diff --git a/init/init_parser.c b/init/init_parser.c
index d136c28..fafd732 100644
--- a/init/init_parser.c
+++ b/init/init_parser.c
@@ -187,7 +187,7 @@
 
     nargs = 0;
     state.filename = fn;
-    state.line = 1;
+    state.line = 0;
     state.ptr = s;
     state.nexttoken = 0;
     state.parse_line = parse_line_no_op;
@@ -197,6 +197,7 @@
             state.parse_line(&state, 0, 0);
             return;
         case T_NEWLINE:
+            state.line++;
             if (nargs) {
                 int kw = lookup_keyword(args[0]);
                 if (kw_is(kw, SECTION)) {
diff --git a/init/parser.c b/init/parser.c
index 2f36ac7..3c2ec00 100644
--- a/init/parser.c
+++ b/init/parser.c
@@ -83,7 +83,6 @@
             state->ptr = x;
             return T_EOF;
         case '\n':
-            state->line++;
             x++;
             state->ptr = x;
             return T_NEWLINE;
@@ -94,9 +93,13 @@
             continue;
         case '#':
             while (*x && (*x != '\n')) x++;
-            state->line++;
-            state->ptr = x;
-            return T_NEWLINE;
+            if (*x == '\n') {
+                state->ptr = x+1;
+                return T_NEWLINE;
+            } else {
+                state->ptr = x;
+                return T_EOF;
+            }
         default:
             goto text;
         }
diff --git a/init/util.c b/init/util.c
old mode 100644
new mode 100755
index 377754b..d8ec88e
--- a/init/util.c
+++ b/init/util.c
@@ -439,8 +439,9 @@
         if (x) {
             x += 2;
             n = 0;
-            while (*x && !isspace(*x)) {
-                hardware[n++] = tolower(*x);
+            while (*x && *x != '\n') {
+                if (!isspace(*x))
+                    hardware[n++] = tolower(*x);
                 x++;
                 if (n == 31) break;
             }
diff --git a/libcutils/Android.mk b/libcutils/Android.mk
index e8c7775..3dc3d69 100644
--- a/libcutils/Android.mk
+++ b/libcutils/Android.mk
@@ -112,12 +112,17 @@
 LOCAL_SRC_FILES := $(commonSources) ashmem-dev.c mq.c
 
 ifeq ($(TARGET_ARCH),arm)
-LOCAL_SRC_FILES += memset32.S
+LOCAL_SRC_FILES += arch-arm/memset32.S
 else  # !arm
 ifeq ($(TARGET_ARCH),sh)
 LOCAL_SRC_FILES += memory.c atomic-android-sh.c
 else  # !sh
+ifeq ($(TARGET_ARCH_VARIANT),x86-atom)
+LOCAL_CFLAGS += -DHAVE_MEMSET16 -DHAVE_MEMSET32
+LOCAL_SRC_FILES += arch-x86/android_memset16.S arch-x86/android_memset32.S memory.c
+else # !x86-atom
 LOCAL_SRC_FILES += memory.c
+endif # !x86-atom
 endif # !sh
 endif # !arm
 
diff --git a/libcutils/memset32.S b/libcutils/arch-arm/memset32.S
similarity index 100%
rename from libcutils/memset32.S
rename to libcutils/arch-arm/memset32.S
diff --git a/libcutils/arch-x86/android_memset16.S b/libcutils/arch-x86/android_memset16.S
new file mode 100644
index 0000000..b1f09cb
--- /dev/null
+++ b/libcutils/arch-x86/android_memset16.S
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Contributed by: Intel Corporation
+ */
+
+#if defined(USE_SSE2)
+
+# include "cache_wrapper.S"
+# undef __i686
+# define USE_AS_ANDROID
+# define sse2_memset16_atom android_memset16
+# include "sse2-memset16-atom.S"
+
+#else
+
+# include "memset16.S"
+
+#endif
diff --git a/libcutils/arch-x86/android_memset32.S b/libcutils/arch-x86/android_memset32.S
new file mode 100644
index 0000000..1fb2ffe
--- /dev/null
+++ b/libcutils/arch-x86/android_memset32.S
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Contributed by: Intel Corporation
+ */
+
+#if defined(USE_SSE2)
+
+# include "cache_wrapper.S"
+# undef __i686
+# define USE_AS_ANDROID
+# define sse2_memset32_atom android_memset32
+# include "sse2-memset32-atom.S"
+
+#else
+
+# include "memset32.S"
+
+#endif
+
diff --git a/libcutils/arch-x86/cache_wrapper.S b/libcutils/arch-x86/cache_wrapper.S
new file mode 100644
index 0000000..508fdd3
--- /dev/null
+++ b/libcutils/arch-x86/cache_wrapper.S
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Contributed by: Intel Corporation
+ */
+
+/* Values are optimized for Atom */
+#define SHARED_CACHE_SIZE       (512*1024)            /* Atom L2 Cache */
+#define DATA_CACHE_SIZE         (24*1024)             /* Atom L1 Data Cache */
+#define SHARED_CACHE_SIZE_HALF  (SHARED_CACHE_SIZE / 2)
+#define DATA_CACHE_SIZE_HALF    (DATA_CACHE_SIZE / 2)
diff --git a/libcutils/arch-x86/sse2-memset16-atom.S b/libcutils/arch-x86/sse2-memset16-atom.S
new file mode 100644
index 0000000..cafec82
--- /dev/null
+++ b/libcutils/arch-x86/sse2-memset16-atom.S
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Contributed by: Intel Corporation
+ */
+
+#ifndef L
+# define L(label)	.L##label
+#endif
+
+#ifndef ALIGN
+# define ALIGN(n)	.p2align n
+#endif
+
+#ifndef cfi_startproc
+# define cfi_startproc			.cfi_startproc
+#endif
+
+#ifndef cfi_endproc
+# define cfi_endproc			.cfi_endproc
+#endif
+
+#ifndef cfi_rel_offset
+# define cfi_rel_offset(reg, off)	.cfi_rel_offset reg, off
+#endif
+
+#ifndef cfi_restore
+# define cfi_restore(reg)		.cfi_restore reg
+#endif
+
+#ifndef cfi_adjust_cfa_offset
+# define cfi_adjust_cfa_offset(off)	.cfi_adjust_cfa_offset off
+#endif
+
+#ifndef ENTRY
+# define ENTRY(name)			\
+	.type name,  @function; 	\
+	.globl name;			\
+	.p2align 4;			\
+name:					\
+	cfi_startproc
+#endif
+
+#ifndef END
+# define END(name)			\
+	cfi_endproc;			\
+	.size name, .-name
+#endif
+
+#define CFI_PUSH(REG)						\
+  cfi_adjust_cfa_offset (4);					\
+  cfi_rel_offset (REG, 0)
+
+#define CFI_POP(REG)						\
+  cfi_adjust_cfa_offset (-4);					\
+  cfi_restore (REG)
+
+#define PUSH(REG)	pushl REG; CFI_PUSH (REG)
+#define POP(REG)	popl REG; CFI_POP (REG)
+
+#ifdef USE_AS_BZERO16
+# define DEST		PARMS
+# define LEN		DEST+4
+#else
+# define DEST		PARMS
+# define CHR		DEST+4
+# define LEN		CHR+4
+#endif
+
+#if 1
+# define SETRTNVAL
+#else
+# define SETRTNVAL	movl DEST(%esp), %eax
+#endif
+
+#ifdef SHARED
+# define ENTRANCE	PUSH (%ebx);
+# define RETURN_END	POP (%ebx); ret
+# define RETURN		RETURN_END; CFI_PUSH (%ebx)
+# define PARMS		8		/* Preserve EBX.  */
+# define JMPTBL(I, B)	I - B
+
+/* Load an entry in a jump table into EBX and branch to it.  TABLE is a
+   jump table with relative offsets.   */
+# define BRANCH_TO_JMPTBL_ENTRY(TABLE)				\
+    /* We first load PC into EBX.  */				\
+    call	__i686.get_pc_thunk.bx;				\
+    /* Get the address of the jump table.  */			\
+    add		$(TABLE - .), %ebx;				\
+    /* Get the entry and convert the relative offset to the	\
+       absolute address.  */					\
+    add		(%ebx,%ecx,4), %ebx;				\
+    /* We loaded the jump table and adjuested EDX. Go.  */	\
+    jmp		*%ebx
+
+	.section	.gnu.linkonce.t.__i686.get_pc_thunk.bx,"ax",@progbits
+	.globl	__i686.get_pc_thunk.bx
+	.hidden	__i686.get_pc_thunk.bx
+	ALIGN (4)
+	.type	__i686.get_pc_thunk.bx,@function
+__i686.get_pc_thunk.bx:
+	movl	(%esp), %ebx
+	ret
+#else
+# define ENTRANCE
+# define RETURN_END	ret
+# define RETURN		RETURN_END
+# define PARMS		4
+# define JMPTBL(I, B)	I
+
+/* Branch to an entry in a jump table.  TABLE is a jump table with
+   absolute offsets.  */
+# define BRANCH_TO_JMPTBL_ENTRY(TABLE)				\
+    jmp		*TABLE(,%ecx,4)
+#endif
+
+	.section .text.sse2,"ax",@progbits
+	ALIGN (4)
+ENTRY (sse2_memset16_atom)
+	ENTRANCE
+
+	movl	LEN(%esp), %ecx
+#ifdef USE_AS_ANDROID
+	shr	$1, %ecx
+#endif
+#ifdef USE_AS_BZERO16
+	xor	%eax, %eax
+#else
+	movzwl	CHR(%esp), %eax
+	mov	%eax, %edx
+	shl	$16, %eax
+	or	%edx, %eax
+#endif
+	movl	DEST(%esp), %edx
+	cmp	$32, %ecx
+	jae	L(32wordsormore)
+
+L(write_less32words):
+	lea	(%edx, %ecx, 2), %edx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_less32words))
+
+
+	.pushsection .rodata.sse2,"a",@progbits
+	ALIGN (2)
+L(table_less32words):
+	.int	JMPTBL (L(write_0words), L(table_less32words))
+	.int	JMPTBL (L(write_1words), L(table_less32words))
+	.int	JMPTBL (L(write_2words), L(table_less32words))
+	.int	JMPTBL (L(write_3words), L(table_less32words))
+	.int	JMPTBL (L(write_4words), L(table_less32words))
+	.int	JMPTBL (L(write_5words), L(table_less32words))
+	.int	JMPTBL (L(write_6words), L(table_less32words))
+	.int	JMPTBL (L(write_7words), L(table_less32words))
+	.int	JMPTBL (L(write_8words), L(table_less32words))
+	.int	JMPTBL (L(write_9words), L(table_less32words))
+	.int	JMPTBL (L(write_10words), L(table_less32words))
+	.int	JMPTBL (L(write_11words), L(table_less32words))
+	.int	JMPTBL (L(write_12words), L(table_less32words))
+	.int	JMPTBL (L(write_13words), L(table_less32words))
+	.int	JMPTBL (L(write_14words), L(table_less32words))
+	.int	JMPTBL (L(write_15words), L(table_less32words))
+	.int	JMPTBL (L(write_16words), L(table_less32words))
+	.int	JMPTBL (L(write_17words), L(table_less32words))
+	.int	JMPTBL (L(write_18words), L(table_less32words))
+	.int	JMPTBL (L(write_19words), L(table_less32words))
+	.int	JMPTBL (L(write_20words), L(table_less32words))
+	.int	JMPTBL (L(write_21words), L(table_less32words))
+	.int	JMPTBL (L(write_22words), L(table_less32words))
+	.int	JMPTBL (L(write_23words), L(table_less32words))
+	.int	JMPTBL (L(write_24words), L(table_less32words))
+	.int	JMPTBL (L(write_25words), L(table_less32words))
+	.int	JMPTBL (L(write_26words), L(table_less32words))
+	.int	JMPTBL (L(write_27words), L(table_less32words))
+	.int	JMPTBL (L(write_28words), L(table_less32words))
+	.int	JMPTBL (L(write_29words), L(table_less32words))
+	.int	JMPTBL (L(write_30words), L(table_less32words))
+	.int	JMPTBL (L(write_31words), L(table_less32words))
+	.popsection
+
+	ALIGN (4)
+L(write_28words):
+	movl	%eax, -56(%edx)
+	movl	%eax, -52(%edx)
+L(write_24words):
+	movl	%eax, -48(%edx)
+	movl	%eax, -44(%edx)
+L(write_20words):
+	movl	%eax, -40(%edx)
+	movl	%eax, -36(%edx)
+L(write_16words):
+	movl	%eax, -32(%edx)
+	movl	%eax, -28(%edx)
+L(write_12words):
+	movl	%eax, -24(%edx)
+	movl	%eax, -20(%edx)
+L(write_8words):
+	movl	%eax, -16(%edx)
+	movl	%eax, -12(%edx)
+L(write_4words):
+	movl	%eax, -8(%edx)
+	movl	%eax, -4(%edx)
+L(write_0words):
+	SETRTNVAL
+	RETURN
+
+	ALIGN (4)
+L(write_29words):
+	movl	%eax, -58(%edx)
+	movl	%eax, -54(%edx)
+L(write_25words):
+	movl	%eax, -50(%edx)
+	movl	%eax, -46(%edx)
+L(write_21words):
+	movl	%eax, -42(%edx)
+	movl	%eax, -38(%edx)
+L(write_17words):
+	movl	%eax, -34(%edx)
+	movl	%eax, -30(%edx)
+L(write_13words):
+	movl	%eax, -26(%edx)
+	movl	%eax, -22(%edx)
+L(write_9words):
+	movl	%eax, -18(%edx)
+	movl	%eax, -14(%edx)
+L(write_5words):
+	movl	%eax, -10(%edx)
+	movl	%eax, -6(%edx)
+L(write_1words):
+	mov	%ax, -2(%edx)
+	SETRTNVAL
+	RETURN
+
+	ALIGN (4)
+L(write_30words):
+	movl	%eax, -60(%edx)
+	movl	%eax, -56(%edx)
+L(write_26words):
+	movl	%eax, -52(%edx)
+	movl	%eax, -48(%edx)
+L(write_22words):
+	movl	%eax, -44(%edx)
+	movl	%eax, -40(%edx)
+L(write_18words):
+	movl	%eax, -36(%edx)
+	movl	%eax, -32(%edx)
+L(write_14words):
+	movl	%eax, -28(%edx)
+	movl	%eax, -24(%edx)
+L(write_10words):
+	movl	%eax, -20(%edx)
+	movl	%eax, -16(%edx)
+L(write_6words):
+	movl	%eax, -12(%edx)
+	movl	%eax, -8(%edx)
+L(write_2words):
+	movl	%eax, -4(%edx)
+	SETRTNVAL
+	RETURN
+
+	ALIGN (4)
+L(write_31words):
+	movl	%eax, -62(%edx)
+	movl	%eax, -58(%edx)
+L(write_27words):
+	movl	%eax, -54(%edx)
+	movl	%eax, -50(%edx)
+L(write_23words):
+	movl	%eax, -46(%edx)
+	movl	%eax, -42(%edx)
+L(write_19words):
+	movl	%eax, -38(%edx)
+	movl	%eax, -34(%edx)
+L(write_15words):
+	movl	%eax, -30(%edx)
+	movl	%eax, -26(%edx)
+L(write_11words):
+	movl	%eax, -22(%edx)
+	movl	%eax, -18(%edx)
+L(write_7words):
+	movl	%eax, -14(%edx)
+	movl	%eax, -10(%edx)
+L(write_3words):
+	movl	%eax, -6(%edx)
+	movw	%ax, -2(%edx)
+	SETRTNVAL
+	RETURN
+
+	ALIGN (4)
+
+L(32wordsormore):
+	shl	$1, %ecx
+	test	$0x01, %edx
+	jz	L(aligned2bytes)
+	mov	%eax, (%edx)
+	mov	%eax, -4(%edx, %ecx)
+	sub	$2, %ecx
+	add	$1, %edx
+	rol	$8, %eax
+L(aligned2bytes):
+#ifdef USE_AS_BZERO16
+	pxor	%xmm0, %xmm0
+#else
+	movd	%eax, %xmm0
+	pshufd	$0, %xmm0, %xmm0
+#endif
+	testl	$0xf, %edx
+	jz	L(aligned_16)
+/* ECX > 32 and EDX is not 16 byte aligned.  */
+L(not_aligned_16):
+	movdqu	%xmm0, (%edx)
+	movl	%edx, %eax
+	and	$-16, %edx
+	add	$16, %edx
+	sub	%edx, %eax
+	add	%eax, %ecx
+	movd	%xmm0, %eax
+
+	ALIGN (4)
+L(aligned_16):
+	cmp	$128, %ecx
+	jae	L(128bytesormore)
+
+L(aligned_16_less128bytes):
+	add	%ecx, %edx
+	shr	$1, %ecx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes))
+
+	ALIGN (4)
+L(128bytesormore):
+#ifdef SHARED_CACHE_SIZE
+	PUSH (%ebx)
+	mov	$SHARED_CACHE_SIZE, %ebx
+#else
+# ifdef SHARED
+	call	__i686.get_pc_thunk.bx
+	add	$_GLOBAL_OFFSET_TABLE_, %ebx
+	mov	__x86_shared_cache_size@GOTOFF(%ebx), %ebx
+# else
+	PUSH (%ebx)
+	mov	__x86_shared_cache_size, %ebx
+# endif
+#endif
+	cmp	%ebx, %ecx
+	jae	L(128bytesormore_nt_start)
+
+	
+#ifdef DATA_CACHE_SIZE
+	POP (%ebx)
+# define RESTORE_EBX_STATE CFI_PUSH (%ebx)
+	cmp	$DATA_CACHE_SIZE, %ecx
+#else
+# ifdef SHARED
+#  define RESTORE_EBX_STATE
+	call	__i686.get_pc_thunk.bx
+	add	$_GLOBAL_OFFSET_TABLE_, %ebx
+	cmp	__x86_data_cache_size@GOTOFF(%ebx), %ecx
+# else
+	POP (%ebx)
+#  define RESTORE_EBX_STATE CFI_PUSH (%ebx)
+	cmp	__x86_data_cache_size, %ecx
+# endif
+#endif
+
+	jae	L(128bytes_L2_normal)
+	subl	$128, %ecx
+L(128bytesormore_normal):
+	sub	$128, %ecx
+	movdqa	%xmm0, (%edx)
+	movdqa	%xmm0, 0x10(%edx)
+	movdqa	%xmm0, 0x20(%edx)
+	movdqa	%xmm0, 0x30(%edx)
+	movdqa	%xmm0, 0x40(%edx)
+	movdqa	%xmm0, 0x50(%edx)
+	movdqa	%xmm0, 0x60(%edx)
+	movdqa	%xmm0, 0x70(%edx)
+	lea	128(%edx), %edx
+	jb	L(128bytesless_normal)
+
+
+	sub	$128, %ecx
+	movdqa	%xmm0, (%edx)
+	movdqa	%xmm0, 0x10(%edx)
+	movdqa	%xmm0, 0x20(%edx)
+	movdqa	%xmm0, 0x30(%edx)
+	movdqa	%xmm0, 0x40(%edx)
+	movdqa	%xmm0, 0x50(%edx)
+	movdqa	%xmm0, 0x60(%edx)
+	movdqa	%xmm0, 0x70(%edx)
+	lea	128(%edx), %edx
+	jae	L(128bytesormore_normal)
+
+L(128bytesless_normal):
+	lea	128(%ecx), %ecx
+	add	%ecx, %edx
+	shr	$1, %ecx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes))
+
+	ALIGN (4)
+L(128bytes_L2_normal):
+	prefetcht0	0x380(%edx)
+	prefetcht0	0x3c0(%edx)
+	sub	$128, %ecx
+	movdqa	%xmm0, (%edx)
+	movaps	%xmm0, 0x10(%edx)
+	movaps	%xmm0, 0x20(%edx)
+	movaps	%xmm0, 0x30(%edx)
+	movaps	%xmm0, 0x40(%edx)
+	movaps	%xmm0, 0x50(%edx)
+	movaps	%xmm0, 0x60(%edx)
+	movaps	%xmm0, 0x70(%edx)
+	add	$128, %edx
+	cmp	$128, %ecx 	
+	jae	L(128bytes_L2_normal)
+
+L(128bytesless_L2_normal):
+	add	%ecx, %edx
+	shr	$1, %ecx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes))
+
+	RESTORE_EBX_STATE
+L(128bytesormore_nt_start):
+	sub	%ebx, %ecx
+	mov	%ebx, %eax
+	and	$0x7f, %eax
+	add	%eax, %ecx
+	movd	%xmm0, %eax
+	ALIGN (4)
+L(128bytesormore_shared_cache_loop):
+	prefetcht0	0x3c0(%edx)
+	prefetcht0	0x380(%edx)
+	sub	$0x80, %ebx
+	movdqa	%xmm0, (%edx)
+	movdqa	%xmm0, 0x10(%edx)
+	movdqa	%xmm0, 0x20(%edx)
+	movdqa	%xmm0, 0x30(%edx)
+	movdqa	%xmm0, 0x40(%edx)
+	movdqa	%xmm0, 0x50(%edx)
+	movdqa	%xmm0, 0x60(%edx)
+	movdqa	%xmm0, 0x70(%edx)
+	add	$0x80, %edx
+	cmp	$0x80, %ebx
+	jae	L(128bytesormore_shared_cache_loop)
+	cmp	$0x80, %ecx
+	jb	L(shared_cache_loop_end)
+	ALIGN (4)
+L(128bytesormore_nt):
+	sub	$0x80, %ecx
+	movntdq	%xmm0, (%edx)
+	movntdq	%xmm0, 0x10(%edx)
+	movntdq	%xmm0, 0x20(%edx)
+	movntdq	%xmm0, 0x30(%edx)
+	movntdq	%xmm0, 0x40(%edx)
+	movntdq	%xmm0, 0x50(%edx)
+	movntdq	%xmm0, 0x60(%edx)
+	movntdq	%xmm0, 0x70(%edx)
+	add	$0x80, %edx
+	cmp	$0x80, %ecx
+	jae	L(128bytesormore_nt)
+	sfence
+L(shared_cache_loop_end):
+#if defined DATA_CACHE_SIZE || !defined SHARED
+	POP (%ebx)
+#endif
+	add	%ecx, %edx
+	shr	$1, %ecx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes))
+
+
+	.pushsection .rodata.sse2,"a",@progbits
+	ALIGN (2)
+L(table_16_128bytes):
+	.int	JMPTBL (L(aligned_16_0bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_2bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_4bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_6bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_8bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_10bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_12bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_14bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_16bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_18bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_20bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_22bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_24bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_26bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_28bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_30bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_32bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_34bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_36bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_38bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_40bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_42bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_44bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_46bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_48bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_50bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_52bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_54bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_56bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_58bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_60bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_62bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_64bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_66bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_68bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_70bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_72bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_74bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_76bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_78bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_80bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_82bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_84bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_86bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_88bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_90bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_92bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_94bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_96bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_98bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_100bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_102bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_104bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_106bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_108bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_110bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_112bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_114bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_116bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_118bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_120bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_122bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_124bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_126bytes), L(table_16_128bytes))
+	.popsection
+
+
+	ALIGN (4)
+L(aligned_16_112bytes):
+	movdqa	%xmm0, -112(%edx)
+L(aligned_16_96bytes):
+	movdqa	%xmm0, -96(%edx)
+L(aligned_16_80bytes):
+	movdqa	%xmm0, -80(%edx)
+L(aligned_16_64bytes):
+	movdqa	%xmm0, -64(%edx)
+L(aligned_16_48bytes):
+	movdqa	%xmm0, -48(%edx)
+L(aligned_16_32bytes):
+	movdqa	%xmm0, -32(%edx)
+L(aligned_16_16bytes):
+	movdqa	%xmm0, -16(%edx)
+L(aligned_16_0bytes):
+	SETRTNVAL
+	RETURN
+
+
+	ALIGN (4)
+L(aligned_16_114bytes):
+	movdqa	%xmm0, -114(%edx)
+L(aligned_16_98bytes):
+	movdqa	%xmm0, -98(%edx)
+L(aligned_16_82bytes):
+	movdqa	%xmm0, -82(%edx)
+L(aligned_16_66bytes):
+	movdqa	%xmm0, -66(%edx)
+L(aligned_16_50bytes):
+	movdqa	%xmm0, -50(%edx)
+L(aligned_16_34bytes):
+	movdqa	%xmm0, -34(%edx)
+L(aligned_16_18bytes):
+	movdqa	%xmm0, -18(%edx)
+L(aligned_16_2bytes):
+	movw	%ax, -2(%edx)
+	SETRTNVAL
+	RETURN
+
+	ALIGN (4)
+L(aligned_16_116bytes):
+	movdqa	%xmm0, -116(%edx)
+L(aligned_16_100bytes):
+	movdqa	%xmm0, -100(%edx)
+L(aligned_16_84bytes):
+	movdqa	%xmm0, -84(%edx)
+L(aligned_16_68bytes):
+	movdqa	%xmm0, -68(%edx)
+L(aligned_16_52bytes):
+	movdqa	%xmm0, -52(%edx)
+L(aligned_16_36bytes):
+	movdqa	%xmm0, -36(%edx)
+L(aligned_16_20bytes):
+	movdqa	%xmm0, -20(%edx)
+L(aligned_16_4bytes):
+	movl	%eax, -4(%edx)
+	SETRTNVAL
+	RETURN
+
+
+	ALIGN (4)
+L(aligned_16_118bytes):
+	movdqa	%xmm0, -118(%edx)
+L(aligned_16_102bytes):
+	movdqa	%xmm0, -102(%edx)
+L(aligned_16_86bytes):
+	movdqa	%xmm0, -86(%edx)
+L(aligned_16_70bytes):
+	movdqa	%xmm0, -70(%edx)
+L(aligned_16_54bytes):
+	movdqa	%xmm0, -54(%edx)
+L(aligned_16_38bytes):
+	movdqa	%xmm0, -38(%edx)
+L(aligned_16_22bytes):
+	movdqa	%xmm0, -22(%edx)
+L(aligned_16_6bytes):
+	movl	%eax, -6(%edx)
+	movw	%ax, -2(%edx)
+	SETRTNVAL
+	RETURN
+
+
+	ALIGN (4)
+L(aligned_16_120bytes):
+	movdqa	%xmm0, -120(%edx)
+L(aligned_16_104bytes):
+	movdqa	%xmm0, -104(%edx)
+L(aligned_16_88bytes):
+	movdqa	%xmm0, -88(%edx)
+L(aligned_16_72bytes):
+	movdqa	%xmm0, -72(%edx)
+L(aligned_16_56bytes):
+	movdqa	%xmm0, -56(%edx)
+L(aligned_16_40bytes):
+	movdqa	%xmm0, -40(%edx)
+L(aligned_16_24bytes):
+	movdqa	%xmm0, -24(%edx)
+L(aligned_16_8bytes):
+	movq	%xmm0, -8(%edx)
+	SETRTNVAL
+	RETURN
+
+
+	ALIGN (4)
+L(aligned_16_122bytes):
+	movdqa	%xmm0, -122(%edx)
+L(aligned_16_106bytes):
+	movdqa	%xmm0, -106(%edx)
+L(aligned_16_90bytes):
+	movdqa	%xmm0, -90(%edx)
+L(aligned_16_74bytes):
+	movdqa	%xmm0, -74(%edx)
+L(aligned_16_58bytes):
+	movdqa	%xmm0, -58(%edx)
+L(aligned_16_42bytes):
+	movdqa	%xmm0, -42(%edx)
+L(aligned_16_26bytes):
+	movdqa	%xmm0, -26(%edx)
+L(aligned_16_10bytes):
+	movq	%xmm0, -10(%edx)
+	movw	%ax, -2(%edx)
+	SETRTNVAL
+	RETURN
+
+
+	ALIGN (4)
+L(aligned_16_124bytes):
+	movdqa	%xmm0, -124(%edx)
+L(aligned_16_108bytes):
+	movdqa	%xmm0, -108(%edx)
+L(aligned_16_92bytes):
+	movdqa	%xmm0, -92(%edx)
+L(aligned_16_76bytes):
+	movdqa	%xmm0, -76(%edx)
+L(aligned_16_60bytes):
+	movdqa	%xmm0, -60(%edx)
+L(aligned_16_44bytes):
+	movdqa	%xmm0, -44(%edx)
+L(aligned_16_28bytes):
+	movdqa	%xmm0, -28(%edx)
+L(aligned_16_12bytes):
+	movq	%xmm0, -12(%edx)
+	movl	%eax, -4(%edx)
+	SETRTNVAL
+	RETURN
+
+
+	ALIGN (4)
+L(aligned_16_126bytes):
+	movdqa	%xmm0, -126(%edx)
+L(aligned_16_110bytes):
+	movdqa	%xmm0, -110(%edx)
+L(aligned_16_94bytes):
+	movdqa	%xmm0, -94(%edx)
+L(aligned_16_78bytes):
+	movdqa	%xmm0, -78(%edx)
+L(aligned_16_62bytes):
+	movdqa	%xmm0, -62(%edx)
+L(aligned_16_46bytes):
+	movdqa	%xmm0, -46(%edx)
+L(aligned_16_30bytes):
+	movdqa	%xmm0, -30(%edx)
+L(aligned_16_14bytes):
+	movq	%xmm0, -14(%edx)
+	movl	%eax, -6(%edx)
+	movw	%ax, -2(%edx)
+	SETRTNVAL
+	RETURN
+
+END (sse2_memset16_atom)
diff --git a/libcutils/arch-x86/sse2-memset32-atom.S b/libcutils/arch-x86/sse2-memset32-atom.S
new file mode 100644
index 0000000..4a52484
--- /dev/null
+++ b/libcutils/arch-x86/sse2-memset32-atom.S
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Contributed by: Intel Corporation
+ */
+
+#ifndef L
+# define L(label)	.L##label
+#endif
+
+#ifndef ALIGN
+# define ALIGN(n)	.p2align n
+#endif
+
+#ifndef cfi_startproc
+# define cfi_startproc			.cfi_startproc
+#endif
+
+#ifndef cfi_endproc
+# define cfi_endproc			.cfi_endproc
+#endif
+
+#ifndef cfi_rel_offset
+# define cfi_rel_offset(reg, off)	.cfi_rel_offset reg, off
+#endif
+
+#ifndef cfi_restore
+# define cfi_restore(reg)		.cfi_restore reg
+#endif
+
+#ifndef cfi_adjust_cfa_offset
+# define cfi_adjust_cfa_offset(off)	.cfi_adjust_cfa_offset off
+#endif
+
+#ifndef ENTRY
+# define ENTRY(name)			\
+	.type name,  @function; 	\
+	.globl name;			\
+	.p2align 4;			\
+name:					\
+	cfi_startproc
+#endif
+
+#ifndef END
+# define END(name)			\
+	cfi_endproc;			\
+	.size name, .-name
+#endif
+
+#define CFI_PUSH(REG)						\
+  cfi_adjust_cfa_offset (4);					\
+  cfi_rel_offset (REG, 0)
+
+#define CFI_POP(REG)						\
+  cfi_adjust_cfa_offset (-4);					\
+  cfi_restore (REG)
+
+#define PUSH(REG)	pushl REG; CFI_PUSH (REG)
+#define POP(REG)	popl REG; CFI_POP (REG)
+
+#ifdef USE_AS_BZERO32
+# define DEST		PARMS
+# define LEN		DEST+4
+#else
+# define DEST		PARMS
+# define DWDS		DEST+4
+# define LEN		DWDS+4
+#endif
+
+#ifdef USE_AS_WMEMSET32
+# define SETRTNVAL	movl DEST(%esp), %eax
+#else
+# define SETRTNVAL
+#endif
+
+#ifdef SHARED
+# define ENTRANCE	PUSH (%ebx);
+# define RETURN_END	POP (%ebx); ret
+# define RETURN		RETURN_END; CFI_PUSH (%ebx)
+# define PARMS		8		/* Preserve EBX.  */
+# define JMPTBL(I, B)	I - B
+
+/* Load an entry in a jump table into EBX and branch to it.  TABLE is a
+   jump table with relative offsets.   */
+# define BRANCH_TO_JMPTBL_ENTRY(TABLE)				\
+    /* We first load PC into EBX.  */				\
+    call	__i686.get_pc_thunk.bx;				\
+    /* Get the address of the jump table.  */			\
+    add		$(TABLE - .), %ebx;				\
+    /* Get the entry and convert the relative offset to the	\
+       absolute address.  */					\
+    add		(%ebx,%ecx,4), %ebx;				\
+    /* We loaded the jump table and adjuested EDX. Go.  */	\
+    jmp		*%ebx
+
+	.section	.gnu.linkonce.t.__i686.get_pc_thunk.bx,"ax",@progbits
+	.globl	__i686.get_pc_thunk.bx
+	.hidden	__i686.get_pc_thunk.bx
+	ALIGN (4)
+	.type	__i686.get_pc_thunk.bx,@function
+__i686.get_pc_thunk.bx:
+	movl	(%esp), %ebx
+	ret
+#else
+# define ENTRANCE
+# define RETURN_END	ret
+# define RETURN		RETURN_END
+# define PARMS		4
+# define JMPTBL(I, B)	I
+
+/* Branch to an entry in a jump table.  TABLE is a jump table with
+   absolute offsets.  */
+# define BRANCH_TO_JMPTBL_ENTRY(TABLE)				\
+    jmp		*TABLE(,%ecx,4)
+#endif
+
+	.section .text.sse2,"ax",@progbits
+	ALIGN (4)
+ENTRY (sse2_memset32_atom)
+	ENTRANCE
+
+	movl	LEN(%esp), %ecx
+#ifdef USE_AS_ANDROID
+	shr     $2, %ecx
+#endif
+#ifdef USE_AS_BZERO32
+	xor	%eax, %eax
+#else
+	mov	DWDS(%esp), %eax
+	mov	%eax, %edx
+#endif
+	movl	DEST(%esp), %edx
+	cmp	$16, %ecx
+	jae	L(16dbwordsormore)
+
+L(write_less16dbwords):
+	lea	(%edx, %ecx, 4), %edx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_less16dbwords))
+
+	.pushsection .rodata.sse2,"a",@progbits
+	ALIGN (2)
+L(table_less16dbwords):
+	.int	JMPTBL (L(write_0dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_1dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_2dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_3dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_4dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_5dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_6dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_7dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_8dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_9dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_10dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_11dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_12dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_13dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_14dbwords), L(table_less16dbwords))
+	.int	JMPTBL (L(write_15dbwords), L(table_less16dbwords))
+	.popsection
+
+	ALIGN (4)
+L(write_15dbwords):
+	movl	%eax, -60(%edx)
+L(write_14dbwords):
+	movl	%eax, -56(%edx)
+L(write_13dbwords):
+	movl	%eax, -52(%edx)
+L(write_12dbwords):
+	movl	%eax, -48(%edx)
+L(write_11dbwords):
+	movl	%eax, -44(%edx)
+L(write_10dbwords):
+	movl	%eax, -40(%edx)
+L(write_9dbwords):
+	movl	%eax, -36(%edx)
+L(write_8dbwords):
+	movl	%eax, -32(%edx)
+L(write_7dbwords):
+	movl	%eax, -28(%edx)
+L(write_6dbwords):
+	movl	%eax, -24(%edx)
+L(write_5dbwords):
+	movl	%eax, -20(%edx)
+L(write_4dbwords):
+	movl	%eax, -16(%edx)
+L(write_3dbwords):
+	movl	%eax, -12(%edx)
+L(write_2dbwords):
+	movl	%eax, -8(%edx)
+L(write_1dbwords):
+	movl	%eax, -4(%edx)
+L(write_0dbwords):
+	SETRTNVAL
+	RETURN
+
+	ALIGN (4)
+L(16dbwordsormore):
+	test	$3, %edx
+	jz	L(aligned4bytes)
+	mov	%eax, (%edx)
+	mov	%eax, -4(%edx, %ecx, 4)
+	sub	$1, %ecx
+	rol	$24, %eax
+	add	$1, %edx
+	test	$3, %edx
+	jz	L(aligned4bytes)
+	ror	$8, %eax
+	add	$1, %edx
+	test	$3, %edx
+	jz	L(aligned4bytes)
+	ror	$8, %eax
+	add	$1, %edx
+L(aligned4bytes):
+	shl	$2, %ecx
+
+#ifdef USE_AS_BZERO32
+	pxor	%xmm0, %xmm0
+#else
+	movd	%eax, %xmm0
+	pshufd	$0, %xmm0, %xmm0
+#endif
+	testl	$0xf, %edx
+	jz	L(aligned_16)
+/* ECX > 32 and EDX is not 16 byte aligned.  */
+L(not_aligned_16):
+	movdqu	%xmm0, (%edx)
+	movl	%edx, %eax
+	and	$-16, %edx
+	add	$16, %edx
+	sub	%edx, %eax
+	add	%eax, %ecx
+	movd	%xmm0, %eax
+	ALIGN (4)
+L(aligned_16):
+	cmp	$128, %ecx
+	jae	L(128bytesormore)
+
+L(aligned_16_less128bytes):
+	add	%ecx, %edx
+	shr	$2, %ecx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes))
+
+	ALIGN (4)
+L(128bytesormore):
+#ifdef SHARED_CACHE_SIZE
+	PUSH (%ebx)
+	mov	$SHARED_CACHE_SIZE, %ebx
+#else
+# ifdef SHARED
+	call	__i686.get_pc_thunk.bx
+	add	$_GLOBAL_OFFSET_TABLE_, %ebx
+	mov	__x86_shared_cache_size@GOTOFF(%ebx), %ebx
+# else
+	PUSH (%ebx)
+	mov	__x86_shared_cache_size, %ebx
+# endif
+#endif
+	cmp	%ebx, %ecx
+	jae	L(128bytesormore_nt_start)
+	
+#ifdef DATA_CACHE_SIZE
+	POP (%ebx)
+# define RESTORE_EBX_STATE CFI_PUSH (%ebx)
+	cmp	$DATA_CACHE_SIZE, %ecx
+#else
+# ifdef SHARED
+#  define RESTORE_EBX_STATE
+	call	__i686.get_pc_thunk.bx
+	add	$_GLOBAL_OFFSET_TABLE_, %ebx
+	cmp	__x86_data_cache_size@GOTOFF(%ebx), %ecx
+# else
+	POP (%ebx)
+#  define RESTORE_EBX_STATE CFI_PUSH (%ebx)
+	cmp	__x86_data_cache_size, %ecx
+# endif
+#endif
+
+	jae	L(128bytes_L2_normal)
+	subl	$128, %ecx
+L(128bytesormore_normal):
+	sub	$128, %ecx
+	movdqa	%xmm0, (%edx)
+	movdqa	%xmm0, 0x10(%edx)
+	movdqa	%xmm0, 0x20(%edx)
+	movdqa	%xmm0, 0x30(%edx)
+	movdqa	%xmm0, 0x40(%edx)
+	movdqa	%xmm0, 0x50(%edx)
+	movdqa	%xmm0, 0x60(%edx)
+	movdqa	%xmm0, 0x70(%edx)
+	lea	128(%edx), %edx
+	jb	L(128bytesless_normal)
+
+
+	sub	$128, %ecx
+	movdqa	%xmm0, (%edx)
+	movdqa	%xmm0, 0x10(%edx)
+	movdqa	%xmm0, 0x20(%edx)
+	movdqa	%xmm0, 0x30(%edx)
+	movdqa	%xmm0, 0x40(%edx)
+	movdqa	%xmm0, 0x50(%edx)
+	movdqa	%xmm0, 0x60(%edx)
+	movdqa	%xmm0, 0x70(%edx)
+	lea	128(%edx), %edx
+	jae	L(128bytesormore_normal)
+
+L(128bytesless_normal):
+	lea	128(%ecx), %ecx
+	add	%ecx, %edx
+	shr	$2, %ecx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes))
+
+	ALIGN (4)
+L(128bytes_L2_normal):
+	prefetcht0	0x380(%edx)
+	prefetcht0	0x3c0(%edx)
+	sub	$128, %ecx
+	movdqa	%xmm0, (%edx)
+	movaps	%xmm0, 0x10(%edx)
+	movaps	%xmm0, 0x20(%edx)
+	movaps	%xmm0, 0x30(%edx)
+	movaps	%xmm0, 0x40(%edx)
+	movaps	%xmm0, 0x50(%edx)
+	movaps	%xmm0, 0x60(%edx)
+	movaps	%xmm0, 0x70(%edx)
+	add	$128, %edx
+	cmp	$128, %ecx 	
+	jae	L(128bytes_L2_normal)
+
+L(128bytesless_L2_normal):
+	add	%ecx, %edx
+	shr	$2, %ecx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes))
+
+	RESTORE_EBX_STATE
+L(128bytesormore_nt_start):
+	sub	%ebx, %ecx
+	mov	%ebx, %eax
+	and	$0x7f, %eax
+	add	%eax, %ecx
+	movd	%xmm0, %eax
+	ALIGN (4)
+L(128bytesormore_shared_cache_loop):
+	prefetcht0	0x3c0(%edx)
+	prefetcht0	0x380(%edx)
+	sub	$0x80, %ebx
+	movdqa	%xmm0, (%edx)
+	movdqa	%xmm0, 0x10(%edx)
+	movdqa	%xmm0, 0x20(%edx)
+	movdqa	%xmm0, 0x30(%edx)
+	movdqa	%xmm0, 0x40(%edx)
+	movdqa	%xmm0, 0x50(%edx)
+	movdqa	%xmm0, 0x60(%edx)
+	movdqa	%xmm0, 0x70(%edx)
+	add	$0x80, %edx
+	cmp	$0x80, %ebx
+	jae	L(128bytesormore_shared_cache_loop)
+	cmp	$0x80, %ecx
+	jb	L(shared_cache_loop_end)
+
+	ALIGN (4)
+L(128bytesormore_nt):
+	sub	$0x80, %ecx
+	movntdq	%xmm0, (%edx)
+	movntdq	%xmm0, 0x10(%edx)
+	movntdq	%xmm0, 0x20(%edx)
+	movntdq	%xmm0, 0x30(%edx)
+	movntdq	%xmm0, 0x40(%edx)
+	movntdq	%xmm0, 0x50(%edx)
+	movntdq	%xmm0, 0x60(%edx)
+	movntdq	%xmm0, 0x70(%edx)
+	add	$0x80, %edx
+	cmp	$0x80, %ecx
+	jae	L(128bytesormore_nt)
+	sfence
+L(shared_cache_loop_end):
+#if defined DATA_CACHE_SIZE || !defined SHARED
+	POP (%ebx)
+#endif
+	add	%ecx, %edx
+	shr	$2, %ecx
+	BRANCH_TO_JMPTBL_ENTRY (L(table_16_128bytes))
+
+	.pushsection .rodata.sse2,"a",@progbits
+	ALIGN (2)
+L(table_16_128bytes):
+	.int	JMPTBL (L(aligned_16_0bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_4bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_8bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_12bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_16bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_20bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_24bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_28bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_32bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_36bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_40bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_44bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_48bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_52bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_56bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_60bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_64bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_68bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_72bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_76bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_80bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_84bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_88bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_92bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_96bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_100bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_104bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_108bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_112bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_116bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_120bytes), L(table_16_128bytes))
+	.int	JMPTBL (L(aligned_16_124bytes), L(table_16_128bytes))
+	.popsection
+
+	ALIGN (4)
+L(aligned_16_112bytes):
+	movdqa	%xmm0, -112(%edx)
+L(aligned_16_96bytes):
+	movdqa	%xmm0, -96(%edx)
+L(aligned_16_80bytes):
+	movdqa	%xmm0, -80(%edx)
+L(aligned_16_64bytes):
+	movdqa	%xmm0, -64(%edx)
+L(aligned_16_48bytes):
+	movdqa	%xmm0, -48(%edx)
+L(aligned_16_32bytes):
+	movdqa	%xmm0, -32(%edx)
+L(aligned_16_16bytes):
+	movdqa	%xmm0, -16(%edx)
+L(aligned_16_0bytes):
+	SETRTNVAL
+	RETURN
+
+	ALIGN (4)
+L(aligned_16_116bytes):
+	movdqa	%xmm0, -116(%edx)
+L(aligned_16_100bytes):
+	movdqa	%xmm0, -100(%edx)
+L(aligned_16_84bytes):
+	movdqa	%xmm0, -84(%edx)
+L(aligned_16_68bytes):
+	movdqa	%xmm0, -68(%edx)
+L(aligned_16_52bytes):
+	movdqa	%xmm0, -52(%edx)
+L(aligned_16_36bytes):
+	movdqa	%xmm0, -36(%edx)
+L(aligned_16_20bytes):
+	movdqa	%xmm0, -20(%edx)
+L(aligned_16_4bytes):
+	movl	%eax, -4(%edx)
+	SETRTNVAL
+	RETURN
+
+	ALIGN (4)
+L(aligned_16_120bytes):
+	movdqa	%xmm0, -120(%edx)
+L(aligned_16_104bytes):
+	movdqa	%xmm0, -104(%edx)
+L(aligned_16_88bytes):
+	movdqa	%xmm0, -88(%edx)
+L(aligned_16_72bytes):
+	movdqa	%xmm0, -72(%edx)
+L(aligned_16_56bytes):
+	movdqa	%xmm0, -56(%edx)
+L(aligned_16_40bytes):
+	movdqa	%xmm0, -40(%edx)
+L(aligned_16_24bytes):
+	movdqa	%xmm0, -24(%edx)
+L(aligned_16_8bytes):
+	movq	%xmm0, -8(%edx)
+	SETRTNVAL
+	RETURN
+
+	ALIGN (4)
+L(aligned_16_124bytes):
+	movdqa	%xmm0, -124(%edx)
+L(aligned_16_108bytes):
+	movdqa	%xmm0, -108(%edx)
+L(aligned_16_92bytes):
+	movdqa	%xmm0, -92(%edx)
+L(aligned_16_76bytes):
+	movdqa	%xmm0, -76(%edx)
+L(aligned_16_60bytes):
+	movdqa	%xmm0, -60(%edx)
+L(aligned_16_44bytes):
+	movdqa	%xmm0, -44(%edx)
+L(aligned_16_28bytes):
+	movdqa	%xmm0, -28(%edx)
+L(aligned_16_12bytes):
+	movq	%xmm0, -12(%edx)
+	movl	%eax, -4(%edx)
+	SETRTNVAL
+	RETURN
+
+END (sse2_memset32_atom)
diff --git a/libcutils/memory.c b/libcutils/memory.c
index ef6c7e6..6486b45 100644
--- a/libcutils/memory.c
+++ b/libcutils/memory.c
@@ -16,6 +16,7 @@
 
 #include <cutils/memory.h>
 
+#if !HAVE_MEMSET16
 void android_memset16(uint16_t* dst, uint16_t value, size_t size)
 {
     size >>= 1;
@@ -23,7 +24,9 @@
         *dst++ = value;
     }
 }
+#endif
 
+#if !HAVE_MEMSET32
 void android_memset32(uint32_t* dst, uint32_t value, size_t size)
 {
     size >>= 2;
@@ -31,6 +34,7 @@
         *dst++ = value;
     }
 }
+#endif
 
 #if !HAVE_STRLCPY
 /*
diff --git a/libdiskconfig/Android.mk b/libdiskconfig/Android.mk
index 1d0ecb4..c887955 100644
--- a/libdiskconfig/Android.mk
+++ b/libdiskconfig/Android.mk
@@ -3,17 +3,25 @@
 
 ifneq ($(TARGET_SIMULATOR),true)
 
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
+commonSources := \
 	diskconfig.c \
 	diskutils.c \
 	write_lst.c \
 	config_mbr.c
 
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(commonSources)
 LOCAL_MODULE := libdiskconfig
 LOCAL_SYSTEM_SHARED_LIBRARIES := libcutils liblog libc
-
 include $(BUILD_SHARED_LIBRARY)
 
+ifeq ($(HOST_OS),linux)
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(commonSources)
+LOCAL_MODULE := libdiskconfig_host
+LOCAL_SYSTEM_SHARED_LIBRARIES := libcutils
+LOCAL_CFLAGS := -O2 -g -W -Wall -Werror -D_LARGEFILE64_SOURCE
+include $(BUILD_HOST_STATIC_LIBRARY)
+endif # HOST_OS == linux
+
 endif  # ! TARGET_SIMULATOR
diff --git a/liblog/logd_write.c b/liblog/logd_write.c
index 9923bba..a0a753b 100644
--- a/liblog/logd_write.c
+++ b/liblog/logd_write.c
@@ -56,7 +56,7 @@
  * the simulator rather than a desktop tool and want to use the device.
  */
 static enum {
-    kLogUninitialized, kLogNotAvailable, kLogAvailable 
+    kLogUninitialized, kLogNotAvailable, kLogAvailable
 } g_log_status = kLogUninitialized;
 int __android_log_dev_available(void)
 {
@@ -189,7 +189,7 @@
 
 int __android_log_vprint(int prio, const char *tag, const char *fmt, va_list ap)
 {
-    char buf[LOG_BUF_SIZE];    
+    char buf[LOG_BUF_SIZE];
 
     vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
 
@@ -223,12 +223,23 @@
 void __android_log_assert(const char *cond, const char *tag,
 			  const char *fmt, ...)
 {
-    va_list ap;
-    char buf[LOG_BUF_SIZE];    
+    char buf[LOG_BUF_SIZE];
 
-    va_start(ap, fmt);
-    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
-    va_end(ap);
+    if (fmt) {
+        va_list ap;
+        va_start(ap, fmt);
+        vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
+        va_end(ap);
+    } else {
+        /* Msg not provided, log condition.  N.B. Do not use cond directly as
+         * format string as it could contain spurious '%' syntax (e.g.
+         * "%d" in "blocks%devs == 0").
+         */
+        if (cond)
+            snprintf(buf, LOG_BUF_SIZE, "Assertion failed: %s", cond);
+        else
+            strcpy(buf, "Unspecified assertion failed");
+    }
 
     __android_log_write(ANDROID_LOG_FATAL, tag, buf);
 
diff --git a/libnetutils/dhcp_utils.c b/libnetutils/dhcp_utils.c
index dc71bab..064eb0c 100644
--- a/libnetutils/dhcp_utils.c
+++ b/libnetutils/dhcp_utils.c
@@ -116,6 +116,14 @@
     }
 }
 
+static const char *ipaddr_to_string(in_addr_t addr)
+{
+    struct in_addr in_addr;
+
+    in_addr.s_addr = addr;
+    return inet_ntoa(in_addr);
+}
+
 /*
  * Start the dhcp client daemon, and wait for it to finish
  * configuring the interface.
@@ -172,7 +180,13 @@
         return -1;
     }
     if (strcmp(prop_value, "ok") == 0) {
+        char dns_prop_name[PROPERTY_KEY_MAX];
         fill_ip_info(interface, ipaddr, gateway, mask, dns1, dns2, server, lease);
+        /* copy the dhcp.XXX.dns properties to net.XXX.dns */
+        snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns1", interface);
+        property_set(dns_prop_name, *dns1 ? ipaddr_to_string(*dns1) : "");
+        snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns2", interface);
+        property_set(dns_prop_name, *dns2 ? ipaddr_to_string(*dns2) : "");
         return 0;
     } else {
         snprintf(errmsg, sizeof(errmsg), "DHCP result was %s", prop_value);
@@ -286,4 +300,4 @@
         snprintf(errmsg, sizeof(errmsg), "DHCP Renew result was %s", prop_value);
         return -1;
     }
-}
\ No newline at end of file
+}
diff --git a/libnetutils/dhcpclient.c b/libnetutils/dhcpclient.c
index 6755ba1..ff00432 100644
--- a/libnetutils/dhcpclient.c
+++ b/libnetutils/dhcpclient.c
@@ -36,8 +36,8 @@
 
 #include <dirent.h>
 
+#include <netutils/ifc.h>
 #include "dhcpmsg.h"
-#include "ifc_utils.h"
 #include "packet.h"
 
 #define VERBOSE 2
@@ -85,16 +85,12 @@
 //    exit(1);
 }
 
-const char *ipaddr(uint32_t addr)
+const char *ipaddr(in_addr_t addr)
 {
-    static char buf[32];
+    struct in_addr in_addr;
 
-    sprintf(buf,"%d.%d.%d.%d",
-            addr & 255,
-            ((addr >> 8) & 255),
-            ((addr >> 16) & 255),
-            (addr >> 24));
-    return buf;
+    in_addr.s_addr = addr;
+    return inet_ntoa(in_addr);
 }
 
 typedef struct dhcp_info dhcp_info;
@@ -128,31 +124,11 @@
     *lease = last_good_info.lease;
 }
 
-static int ifc_configure(const char *ifname, dhcp_info *info)
+static int dhcp_configure(const char *ifname, dhcp_info *info)
 {
-    char dns_prop_name[PROPERTY_KEY_MAX];
-
-    if (ifc_set_addr(ifname, info->ipaddr)) {
-        printerr("failed to set ipaddr %s: %s\n", ipaddr(info->ipaddr), strerror(errno));
-        return -1;
-    }
-    if (ifc_set_mask(ifname, info->netmask)) {
-        printerr("failed to set netmask %s: %s\n", ipaddr(info->netmask), strerror(errno));
-        return -1;
-    }
-    if (ifc_create_default_route(ifname, info->gateway)) {
-        printerr("failed to set default route %s: %s\n", ipaddr(info->gateway), strerror(errno));
-        return -1;
-    }
-
-    snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns1", ifname);
-    property_set(dns_prop_name, info->dns1 ? ipaddr(info->dns1) : "");
-    snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns2", ifname);
-    property_set(dns_prop_name, info->dns2 ? ipaddr(info->dns2) : "");
-
     last_good_info = *info;
-
-    return 0;
+    return ifc_configure(ifname, info->ipaddr, info->netmask, info->gateway,
+                         info->dns1, info->dns2);
 }
 
 static const char *dhcp_type_to_name(uint32_t type)
@@ -449,7 +425,7 @@
                 printerr("timed out\n");
                 if ( info.type == DHCPOFFER ) {
                     printerr("no acknowledgement from DHCP server\nconfiguring %s with offered parameters\n", ifname);
-                    return ifc_configure(ifname, &info);
+                    return dhcp_configure(ifname, &info);
                 }
                 errno = ETIME;
                 close(s);
@@ -530,7 +506,7 @@
             if (info.type == DHCPACK) {
                 printerr("configuring %s\n", ifname);
                 close(s);
-                return ifc_configure(ifname, &info);
+                return dhcp_configure(ifname, &info);
             } else if (info.type == DHCPNAK) {
                 printerr("configuration request denied\n");
                 close(s);
diff --git a/libnetutils/ifc_utils.c b/libnetutils/ifc_utils.c
index bde336f..0ca5fe6 100644
--- a/libnetutils/ifc_utils.c
+++ b/libnetutils/ifc_utils.c
@@ -27,8 +27,12 @@
 #include <arpa/inet.h>
 
 #include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
 #include <linux/sockios.h>
 #include <linux/route.h>
+#include <linux/ipv6_route.h>
+#include <netdb.h>
 #include <linux/wireless.h>
 
 #ifdef ANDROID
@@ -43,9 +47,10 @@
 #endif
 
 static int ifc_ctl_sock = -1;
+static int ifc_ctl_sock6 = -1;
 void printerr(char *fmt, ...);
 
-static const char *ipaddr_to_string(uint32_t addr)
+static const char *ipaddr_to_string(in_addr_t addr)
 {
     struct in_addr in_addr;
 
@@ -64,6 +69,17 @@
     return ifc_ctl_sock < 0 ? -1 : 0;
 }
 
+int ifc_init6(void)
+{
+    if (ifc_ctl_sock6 == -1) {
+        ifc_ctl_sock6 = socket(AF_INET6, SOCK_DGRAM, 0);
+        if (ifc_ctl_sock6 < 0) {
+            printerr("socket() failed: %s\n", strerror(errno));
+        }
+    }
+    return ifc_ctl_sock6 < 0 ? -1 : 0;
+}
+
 void ifc_close(void)
 {
     if (ifc_ctl_sock != -1) {
@@ -72,6 +88,14 @@
     }
 }
 
+void ifc_close6(void)
+{
+    if (ifc_ctl_sock6 != -1) {
+        (void)close(ifc_ctl_sock6);
+        ifc_ctl_sock6 = -1;
+    }
+}
+
 static void ifc_init_ifr(const char *name, struct ifreq *ifr)
 {
     memset(ifr, 0, sizeof(struct ifreq));
@@ -88,7 +112,7 @@
     r = ioctl(ifc_ctl_sock, SIOCGIFHWADDR, &ifr);
     if(r < 0) return -1;
 
-    memcpy(ptr, &ifr.ifr_hwaddr.sa_data, 6);
+    memcpy(ptr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN);
     return 0;    
 }
 
@@ -143,6 +167,17 @@
     return ioctl(ifc_ctl_sock, SIOCSIFADDR, &ifr);
 }
 
+int ifc_set_hwaddr(const char *name, const void *ptr)
+{
+    int r;
+    struct ifreq ifr;
+    ifc_init_ifr(name, &ifr);
+
+    ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
+    memcpy(&ifr.ifr_hwaddr.sa_data, ptr, ETH_ALEN);
+    return ioctl(ifc_ctl_sock, SIOCSIFHWADDR, &ifr);
+}
+
 int ifc_set_mask(const char *name, in_addr_t mask)
 {
     struct ifreq ifr;
@@ -185,45 +220,80 @@
     return 0;
 }
 
-
-int ifc_create_default_route(const char *name, in_addr_t addr)
+in_addr_t get_ipv4_netmask(int prefix_length)
 {
-    struct rtentry rt;
+    in_addr_t mask = 0;
 
-    memset(&rt, 0, sizeof(rt));
-    
-    rt.rt_dst.sa_family = AF_INET;
-    rt.rt_flags = RTF_UP | RTF_GATEWAY;
-    rt.rt_dev = (void*) name;
-    init_sockaddr_in(&rt.rt_genmask, 0);
-    init_sockaddr_in(&rt.rt_gateway, addr);
-    
-    return ioctl(ifc_ctl_sock, SIOCADDRT, &rt);
+    mask = ~mask << (32 - prefix_length);
+    mask = htonl(mask);
+
+    return mask;
 }
 
-int ifc_add_host_route(const char *name, in_addr_t addr)
+int ifc_add_ipv4_route(const char *ifname, struct in_addr dst, int prefix_length,
+      struct in_addr gw)
 {
     struct rtentry rt;
     int result;
+    in_addr_t netmask;
 
     memset(&rt, 0, sizeof(rt));
-    
+
     rt.rt_dst.sa_family = AF_INET;
-    rt.rt_flags = RTF_UP | RTF_HOST;
-    rt.rt_dev = (void*) name;
-    init_sockaddr_in(&rt.rt_dst, addr);
-    init_sockaddr_in(&rt.rt_genmask, 0);
-    init_sockaddr_in(&rt.rt_gateway, 0);
-    
+    rt.rt_dev = (void*) ifname;
+
+    netmask = get_ipv4_netmask(prefix_length);
+    init_sockaddr_in(&rt.rt_genmask, netmask);
+    init_sockaddr_in(&rt.rt_dst, dst.s_addr);
+    rt.rt_flags = RTF_UP;
+
+    if (prefix_length == 32) {
+        rt.rt_flags |= RTF_HOST;
+    }
+
+    if (gw.s_addr != 0) {
+        rt.rt_flags |= RTF_GATEWAY;
+        init_sockaddr_in(&rt.rt_gateway, gw.s_addr);
+    }
+
     ifc_init();
+
+    if (ifc_ctl_sock < 0) {
+        return -errno;
+    }
+
     result = ioctl(ifc_ctl_sock, SIOCADDRT, &rt);
-    if (result < 0 && errno == EEXIST) {
-        result = 0;
+    if (result < 0) {
+        if (errno == EEXIST) {
+            result = 0;
+        } else {
+            result = -errno;
+        }
     }
     ifc_close();
     return result;
 }
 
+int ifc_create_default_route(const char *name, in_addr_t gw)
+{
+    struct in_addr in_dst, in_gw;
+
+    in_dst.s_addr = 0;
+    in_gw.s_addr = gw;
+
+    return ifc_add_ipv4_route(name, in_dst, 0, in_gw);
+}
+
+int ifc_add_host_route(const char *name, in_addr_t dst)
+{
+    struct in_addr in_dst, in_gw;
+
+    in_dst.s_addr = dst;
+    in_gw.s_addr = 0;
+
+    return ifc_add_ipv4_route(name, in_dst, 32, in_gw);
+}
+
 int ifc_enable(const char *ifname)
 {
     int result;
@@ -429,10 +499,119 @@
 
     ifc_close();
 
-    snprintf(dns_prop_name, sizeof(dns_prop_name), "dhcp.%s.dns1", ifname);
+    snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns1", ifname);
     property_set(dns_prop_name, dns1 ? ipaddr_to_string(dns1) : "");
-    snprintf(dns_prop_name, sizeof(dns_prop_name), "dhcp.%s.dns2", ifname);
+    snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns2", ifname);
     property_set(dns_prop_name, dns2 ? ipaddr_to_string(dns2) : "");
 
     return 0;
 }
+
+int ifc_add_ipv6_route(const char *ifname, struct in6_addr dst, int prefix_length,
+      struct in6_addr gw)
+{
+    struct in6_rtmsg rtmsg;
+    int result;
+    int ifindex;
+
+    memset(&rtmsg, 0, sizeof(rtmsg));
+
+    ifindex = if_nametoindex(ifname);
+    if (ifindex == 0) {
+        printerr("if_nametoindex() failed: interface %s\n", ifname);
+        return -ENXIO;
+    }
+
+    rtmsg.rtmsg_ifindex = ifindex;
+    rtmsg.rtmsg_dst = dst;
+    rtmsg.rtmsg_dst_len = prefix_length;
+    rtmsg.rtmsg_flags = RTF_UP;
+
+    if (prefix_length == 128) {
+        rtmsg.rtmsg_flags |= RTF_HOST;
+    }
+
+    if (memcmp(&gw, &in6addr_any, sizeof(in6addr_any))) {
+        rtmsg.rtmsg_flags |= RTF_GATEWAY;
+        rtmsg.rtmsg_gateway = gw;
+    }
+
+    ifc_init6();
+
+    if (ifc_ctl_sock6 < 0) {
+        return -errno;
+    }
+
+    result = ioctl(ifc_ctl_sock6, SIOCADDRT, &rtmsg);
+    if (result < 0) {
+        if (errno == EEXIST) {
+            result = 0;
+        } else {
+            result = -errno;
+        }
+    }
+    ifc_close6();
+    return result;
+}
+
+int ifc_add_route(const char *ifname, const char *dst, int prefix_length,
+      const char *gw)
+{
+    int ret = 0;
+    struct sockaddr_in ipv4_dst, ipv4_gw;
+    struct sockaddr_in6 ipv6_dst, ipv6_gw;
+    struct addrinfo hints, *addr_ai, *gw_ai;
+
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;  /* Allow IPv4 or IPv6 */
+    hints.ai_flags = AI_NUMERICHOST;
+
+    ret = getaddrinfo(dst, NULL, &hints, &addr_ai);
+
+    if (ret != 0) {
+        printerr("getaddrinfo failed: invalid address %s\n", dst);
+        return -EINVAL;
+    }
+
+    if (gw == NULL) {
+        if (addr_ai->ai_family == AF_INET6) {
+            gw = "::";
+        } else if (addr_ai->ai_family == AF_INET) {
+            gw = "0.0.0.0";
+        }
+    }
+
+    ret = getaddrinfo(gw, NULL, &hints, &gw_ai);
+    if (ret != 0) {
+        printerr("getaddrinfo failed: invalid gateway %s\n", gw);
+        freeaddrinfo(addr_ai);
+        return -EINVAL;
+    }
+
+    if (addr_ai->ai_family != gw_ai->ai_family) {
+        printerr("ifc_add_route: different address families: %s and %s\n", dst, gw);
+        freeaddrinfo(addr_ai);
+        freeaddrinfo(gw_ai);
+        return -EINVAL;
+    }
+
+    if (addr_ai->ai_family == AF_INET6) {
+        memcpy(&ipv6_dst, addr_ai->ai_addr, sizeof(struct sockaddr_in6));
+        memcpy(&ipv6_gw, gw_ai->ai_addr, sizeof(struct sockaddr_in6));
+        ret = ifc_add_ipv6_route(ifname, ipv6_dst.sin6_addr, prefix_length,
+              ipv6_gw.sin6_addr);
+    } else if (addr_ai->ai_family == AF_INET) {
+        memcpy(&ipv4_dst, addr_ai->ai_addr, sizeof(struct sockaddr_in));
+        memcpy(&ipv4_gw, gw_ai->ai_addr, sizeof(struct sockaddr_in));
+        ret = ifc_add_ipv4_route(ifname, ipv4_dst.sin_addr, prefix_length,
+              ipv4_gw.sin_addr);
+    } else {
+        printerr("ifc_add_route: getaddrinfo returned un supported address family %d\n",
+                  addr_ai->ai_family);
+        ret = -EAFNOSUPPORT;
+    }
+
+    freeaddrinfo(addr_ai);
+    freeaddrinfo(gw_ai);
+    return ret;
+}
diff --git a/libnetutils/ifc_utils.h b/libnetutils/ifc_utils.h
deleted file mode 100644
index 49b8747..0000000
--- a/libnetutils/ifc_utils.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2008, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); 
- * you may not use this file except in compliance with the License. 
- * You may obtain a copy of the License at 
- *
- *     http://www.apache.org/licenses/LICENSE-2.0 
- *
- * Unless required by applicable law or agreed to in writing, software 
- * distributed under the License is distributed on an "AS IS" BASIS, 
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
- * See the License for the specific language governing permissions and 
- * limitations under the License.
- */
-
-#ifndef _IFC_UTILS_H_
-#define _IFC_UTILS_H_
-
-int ifc_init(void);
-
-int ifc_get_ifindex(const char *name, int *if_indexp);
-int ifc_get_hwaddr(const char *name, void *ptr);
-
-int ifc_up(const char *name);
-int ifc_down(const char *name);
-
-int ifc_set_addr(const char *name, unsigned addr);
-int ifc_set_mask(const char *name, unsigned mask);
-
-int ifc_create_default_route(const char *name, unsigned addr);
-
-int ifc_get_info(const char *name, unsigned *addr, unsigned *mask, unsigned *flags);
-
-#endif
diff --git a/libpixelflinger/Android.mk b/libpixelflinger/Android.mk
index 6491d24..ed2ab5e 100644
--- a/libpixelflinger/Android.mk
+++ b/libpixelflinger/Android.mk
@@ -2,17 +2,6 @@
 include $(CLEAR_VARS)
 
 #
-# ARMv6 specific objects
-#
-
-ifeq ($(TARGET_ARCH),arm)
-LOCAL_ASFLAGS := -march=armv6
-LOCAL_SRC_FILES := rotate90CW_4x4_16v6.S
-LOCAL_MODULE := libpixelflinger_armv6
-include $(BUILD_STATIC_LIBRARY)
-endif
-
-#
 # C/C++ and ARMv5 objects
 #
 
@@ -77,10 +66,6 @@
 LOCAL_SHARED_LIBRARIES += libhardware_legacy
 LOCAL_CFLAGS += -DWITH_LIB_HARDWARE
 endif
-
-ifeq ($(TARGET_ARCH),arm)
-LOCAL_WHOLE_STATIC_LIBRARIES := libpixelflinger_armv6
-endif
 include $(BUILD_SHARED_LIBRARY)
 
 #
@@ -91,9 +76,6 @@
 LOCAL_MODULE:= libpixelflinger_static
 LOCAL_SRC_FILES := $(PIXELFLINGER_SRC_FILES)
 LOCAL_CFLAGS := $(PIXELFLINGER_CFLAGS) 
-ifeq ($(TARGET_ARCH),arm)
-LOCAL_WHOLE_STATIC_LIBRARIES := libpixelflinger_armv6
-endif
 include $(BUILD_STATIC_LIBRARY)
 
 
diff --git a/libpixelflinger/codeflinger/ARMAssembler.cpp b/libpixelflinger/codeflinger/ARMAssembler.cpp
index d3720c3..4726a08 100644
--- a/libpixelflinger/codeflinger/ARMAssembler.cpp
+++ b/libpixelflinger/codeflinger/ARMAssembler.cpp
@@ -334,7 +334,7 @@
 
 void ARMAssembler::STM(int cc, int dir,
         int Rn, int W, uint32_t reg_list)
-{   //                    FA EA FD ED      IB IA DB DA
+{   //                    ED FD EA FA      IB IA DB DA
     const uint8_t P[8] = { 0, 1, 0, 1,      1, 0, 1, 0 };
     const uint8_t U[8] = { 0, 0, 1, 1,      1, 1, 0, 0 };
     *mPC++ = (cc<<28) | (4<<25) | (uint32_t(P[dir])<<24) |
@@ -433,6 +433,16 @@
 {
     *mPC++ = (cc<<28) | 0x6CF0070 | (Rd<<12) | ((rotate >> 3) << 10) | Rm;
 }
+#if 0
+#pragma mark -
+#pragma mark Bit manipulation (ARMv7+ only)...
+#endif
+
+// Bit manipulation (ARMv7+ only)...
+void ARMAssembler::UBFX(int cc, int Rd, int Rn, int lsb, int width)
+{
+    *mPC++ = (cc<<28) | 0x7E00000 | ((width-1)<<16) | (Rd<<12) | (lsb<<7) | 0x50 | Rn;
+}
 
 }; // namespace android
 
diff --git a/libpixelflinger/codeflinger/ARMAssembler.h b/libpixelflinger/codeflinger/ARMAssembler.h
index a667cb5..e7f038a 100644
--- a/libpixelflinger/codeflinger/ARMAssembler.h
+++ b/libpixelflinger/codeflinger/ARMAssembler.h
@@ -124,6 +124,7 @@
     virtual void SMLAW(int cc, int y,
                 int Rd, int Rm, int Rs, int Rn);
     virtual void UXTB16(int cc, int Rd, int Rm, int rotate);
+    virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width);
 
 private:
                 ARMAssembler(const ARMAssembler& rhs);
diff --git a/libpixelflinger/codeflinger/ARMAssemblerInterface.h b/libpixelflinger/codeflinger/ARMAssemblerInterface.h
index ff6af2a..796342a 100644
--- a/libpixelflinger/codeflinger/ARMAssemblerInterface.h
+++ b/libpixelflinger/codeflinger/ARMAssemblerInterface.h
@@ -206,6 +206,9 @@
     // byte/half word extract...
     virtual void UXTB16(int cc, int Rd, int Rm, int rotate) = 0;
 
+    // bit manipulation...
+    virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width) = 0;
+
     // -----------------------------------------------------------------------
     // convenience...
     // -----------------------------------------------------------------------
diff --git a/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp b/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp
index 7c422db..c57d7da 100644
--- a/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp
+++ b/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp
@@ -199,5 +199,9 @@
     mTarget->UXTB16(cc, Rd, Rm, rotate);
 }
 
+void ARMAssemblerProxy::UBFX(int cc, int Rd, int Rn, int lsb, int width) {
+    mTarget->UBFX(cc, Rd, Rn, lsb, width);
+}
+
 }; // namespace android
 
diff --git a/libpixelflinger/codeflinger/ARMAssemblerProxy.h b/libpixelflinger/codeflinger/ARMAssemblerProxy.h
index 9134cce..8c7f270 100644
--- a/libpixelflinger/codeflinger/ARMAssemblerProxy.h
+++ b/libpixelflinger/codeflinger/ARMAssemblerProxy.h
@@ -115,6 +115,7 @@
                 int Rd, int Rm, int Rs, int Rn);
 
     virtual void UXTB16(int cc, int Rd, int Rm, int rotate);
+    virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width);
 
 private:
     ARMAssemblerInterface*  mTarget;
diff --git a/libpixelflinger/codeflinger/disassem.c b/libpixelflinger/codeflinger/disassem.c
index c17f3ec..aeb8034 100644
--- a/libpixelflinger/codeflinger/disassem.c
+++ b/libpixelflinger/codeflinger/disassem.c
@@ -81,6 +81,8 @@
  * g - 2nd fp operand (register) (bits 16-18)
  * h - 3rd fp operand (register/immediate) (bits 0-4)
  * j - xtb rotate literal (bits 10-11)
+ * i - bfx lsb literal (bits 7-11)
+ * w - bfx width literal (bits 16-20)
  * b - branch address
  * t - thumb branch address (bits 24, 0-23)
  * k - breakpoint comment (bits 0-3, 8-19)
@@ -124,6 +126,7 @@
     { 0x0fe000f0, 0x00a00090, "umlal",	"Sdnms" },
     { 0x0fe000f0, 0x00e00090, "smlal",	"Sdnms" },
     { 0x0fff03f0, 0x06cf0070, "uxtb16", "dmj" },
+    { 0x0fe00070, 0x07e00050, "ubfx",   "dmiw" },
     { 0x0d700000, 0x04200000, "strt",	"daW" },
     { 0x0d700000, 0x04300000, "ldrt",	"daW" },
     { 0x0d700000, 0x04600000, "strbt",	"daW" },
@@ -412,6 +415,14 @@
 		case 'j':
 			di->di_printf("ror #%d", ((insn >> 10) & 3) << 3);
 			break;
+        /* i - bfx lsb literal (bits 7-11) */
+        case 'i':
+            di->di_printf("#%d", (insn >> 7) & 31);
+            break;
+        /* w - bfx width literal (bits 16-20) */
+        case 'w':
+            di->di_printf("#%d", 1 + ((insn >> 16) & 31));
+            break;
 		/* b - branch address */
 		case 'b':
 			branch = ((insn << 2) & 0x03ffffff);
diff --git a/libpixelflinger/codeflinger/load_store.cpp b/libpixelflinger/codeflinger/load_store.cpp
index 93c5825..ed20a00 100644
--- a/libpixelflinger/codeflinger/load_store.cpp
+++ b/libpixelflinger/codeflinger/load_store.cpp
@@ -18,9 +18,12 @@
 #include <assert.h>
 #include <stdio.h>
 #include <cutils/log.h>
-
 #include "codeflinger/GGLAssembler.h"
 
+#ifdef __ARM_ARCH__
+#include <machine/cpu-features.h>
+#endif
+
 namespace android {
 
 // ----------------------------------------------------------------------------
@@ -110,6 +113,20 @@
     assert(maskLen<=8);
     assert(h);
     
+#if __ARM_ARCH__ >= 7
+    const int mask = (1<<maskLen)-1;
+    if ((h == bits) && !l && (s != d.reg)) {
+        MOV(AL, 0, d.reg, s);                   // component = packed;
+    } else if ((h == bits) && l) {
+        MOV(AL, 0, d.reg, reg_imm(s, LSR, l));  // component = packed >> l;
+    } else if (!l && isValidImmediate(mask)) {
+        AND(AL, 0, d.reg, s, imm(mask));        // component = packed & mask;
+    } else if (!l && isValidImmediate(~mask)) {
+        BIC(AL, 0, d.reg, s, imm(~mask));       // component = packed & mask;
+    } else {
+        UBFX(AL, d.reg, s, l, maskLen);         // component = (packed & mask) >> l;
+    }
+#else
     if (h != bits) {
         const int mask = ((1<<maskLen)-1) << l;
         if (isValidImmediate(mask)) {
@@ -132,6 +149,7 @@
     if (s != d.reg) {
         MOV(AL, 0, d.reg, s);
     }
+#endif
 
     d.s = maskLen;
 }
diff --git a/libpixelflinger/col32cb16blend.S b/libpixelflinger/col32cb16blend.S
index 1450bde..1831255 100644
--- a/libpixelflinger/col32cb16blend.S
+++ b/libpixelflinger/col32cb16blend.S
@@ -1,20 +1,19 @@
 /* libs/pixelflinger/col32cb16blend.S
-**
-** (C) COPYRIGHT 2009 ARM Limited.
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-**
-*/
+ *
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
     .text
     .align
diff --git a/libpixelflinger/col32cb16blend_neon.S b/libpixelflinger/col32cb16blend_neon.S
index 17b0d01..cbd54d1 100644
--- a/libpixelflinger/col32cb16blend_neon.S
+++ b/libpixelflinger/col32cb16blend_neon.S
@@ -1,20 +1,20 @@
 /* libs/pixelflinger/col32cb16blend_neon.S
-**
-** (C) COPYRIGHT 2009 ARM Limited.
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-**
-*/
+ *
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 
     .text
     .align
diff --git a/libpixelflinger/raster.cpp b/libpixelflinger/raster.cpp
index d751202..32b2a97 100644
--- a/libpixelflinger/raster.cpp
+++ b/libpixelflinger/raster.cpp
@@ -143,7 +143,7 @@
 
 using namespace android;
 
-GGLint gglBitBlti(GGLContext* con, int tmu, GGLint crop[4], GGLint where[4])
+GGLint gglBitBlit(GGLContext* con, int tmu, GGLint crop[4], GGLint where[4])
 {
     GGL_CONTEXT(c, (void*)con);
 
diff --git a/libpixelflinger/scanline.cpp b/libpixelflinger/scanline.cpp
index a2f43eb..931d648 100644
--- a/libpixelflinger/scanline.cpp
+++ b/libpixelflinger/scanline.cpp
@@ -1518,26 +1518,3 @@
 // ----------------------------------------------------------------------------
 }; // namespace android
 
-using namespace android;
-extern "C" void ggl_test_codegen(uint32_t n, uint32_t p, uint32_t t0, uint32_t t1)
-{
-#if ANDROID_ARM_CODEGEN
-    GGLContext* c;
-    gglInit(&c);
-    needs_t needs;
-    needs.n = n;
-    needs.p = p;
-    needs.t[0] = t0;
-    needs.t[1] = t1;
-    sp<ScanlineAssembly> a(new ScanlineAssembly(needs, ASSEMBLY_SCRATCH_SIZE));
-    GGLAssembler assembler( new ARMAssembler(a) );
-    int err = assembler.scanline(needs, (context_t*)c);
-    if (err != 0) {
-        printf("error %08x (%s)\n", err, strerror(-err));
-    }
-    gglUninit(c);
-#else
-    printf("This test runs only on ARM\n");
-#endif
-}
-
diff --git a/libpixelflinger/tests/codegen/Android.mk b/libpixelflinger/tests/codegen/Android.mk
index 1bc4214..aa320fc 100644
--- a/libpixelflinger/tests/codegen/Android.mk
+++ b/libpixelflinger/tests/codegen/Android.mk
@@ -2,12 +2,15 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
-	codegen.cpp
+	codegen.cpp.arm
 
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
     libpixelflinger
 
+LOCAL_C_INCLUDES := \
+	system/core/libpixelflinger
+
 LOCAL_MODULE:= test-opengl-codegen
 
 LOCAL_MODULE_TAGS := tests
diff --git a/libpixelflinger/tests/codegen/codegen.cpp b/libpixelflinger/tests/codegen/codegen.cpp
index 1865888..94e2481 100644
--- a/libpixelflinger/tests/codegen/codegen.cpp
+++ b/libpixelflinger/tests/codegen/codegen.cpp
@@ -1,9 +1,54 @@
 #include <stdio.h>
 #include <stdint.h>
 
-extern "C" void ggl_test_codegen(
-        uint32_t n, uint32_t p, uint32_t t0, uint32_t t1);
+#include "private/pixelflinger/ggl_context.h"
 
+#include "buffer.h"
+#include "scanline.h"
+
+#include "codeflinger/CodeCache.h"
+#include "codeflinger/GGLAssembler.h"
+#include "codeflinger/ARMAssembler.h"
+
+#if defined(__arm__)
+#   define ANDROID_ARM_CODEGEN  1
+#else
+#   define ANDROID_ARM_CODEGEN  0
+#endif
+
+#define ASSEMBLY_SCRATCH_SIZE   2048
+
+using namespace android;
+
+class ScanlineAssembly : public Assembly {
+    AssemblyKey<needs_t> mKey;
+public:
+    ScanlineAssembly(needs_t needs, size_t size)
+        : Assembly(size), mKey(needs) { }
+    const AssemblyKey<needs_t>& key() const { return mKey; }
+};
+
+static void ggl_test_codegen(uint32_t n, uint32_t p, uint32_t t0, uint32_t t1)
+{
+#if ANDROID_ARM_CODEGEN
+    GGLContext* c;
+    gglInit(&c);
+    needs_t needs;
+    needs.n = n;
+    needs.p = p;
+    needs.t[0] = t0;
+    needs.t[1] = t1;
+    sp<ScanlineAssembly> a(new ScanlineAssembly(needs, ASSEMBLY_SCRATCH_SIZE));
+    GGLAssembler assembler( new ARMAssembler(a) );
+    int err = assembler.scanline(needs, (context_t*)c);
+    if (err != 0) {
+        printf("error %08x (%s)\n", err, strerror(-err));
+    }
+    gglUninit(c);
+#else
+    printf("This test runs only on ARM\n");
+#endif
+}
 
 int main(int argc, char** argv)
 {
diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index c2ba647..86c1f42 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -93,13 +93,11 @@
 }
 
 const char *NetlinkEvent::findParam(const char *paramName) {
-    int i;
-
-    for (i = 0; i < NL_PARAMS_MAX; i++) {
-        if (!mParams[i])
-            break;
-        if (!strncmp(mParams[i], paramName, strlen(paramName)))
-            return &mParams[i][strlen(paramName) + 1];
+    size_t len = strlen(paramName);
+    for (int i = 0; mParams[i] && i < NL_PARAMS_MAX; ++i) {
+        const char *ptr = mParams[i] + len;
+        if (!strncmp(mParams[i], paramName, len) && *ptr == '=')
+            return ++ptr;
     }
 
     SLOGE("NetlinkEvent::FindParam(): Parameter '%s' not found", paramName);
diff --git a/libsysutils/src/SocketClient.cpp b/libsysutils/src/SocketClient.cpp
index 8e5f154..90ca52e 100644
--- a/libsysutils/src/SocketClient.cpp
+++ b/libsysutils/src/SocketClient.cpp
@@ -15,8 +15,10 @@
         , mPid(-1)
         , mUid(-1)
         , mGid(-1)
+        , mRefCount(1)
 {
     pthread_mutex_init(&mWriteMutex, NULL);
+    pthread_mutex_init(&mRefCountMutex, NULL);
 
     struct ucred creds;
     socklen_t szCreds = sizeof(creds);
@@ -32,14 +34,24 @@
 
 int SocketClient::sendMsg(int code, const char *msg, bool addErrno) {
     char *buf;
+    const char* arg;
+    const char* fmt;
+    char tmp[1];
+    int  len;
 
     if (addErrno) {
-        buf = (char *) alloca(strlen(msg) + strlen(strerror(errno)) + 8);
-        sprintf(buf, "%.3d %s (%s)", code, msg, strerror(errno));
+        fmt = "%.3d %s (%s)";
+        arg = strerror(errno);
     } else {
-        buf = (char *) alloca(strlen(msg) + strlen("XXX "));
-        sprintf(buf, "%.3d %s", code, msg);
+        fmt = "%.3d %s";
+        arg = NULL;
     }
+    /* Measure length of required buffer */
+    len = snprintf(tmp, sizeof tmp, fmt, code, msg, arg);
+    /* Allocate in the stack, then write to it */
+    buf = (char*)alloca(len+1);
+    snprintf(buf, len+1, fmt, code, msg, arg);
+    /* Send the zero-terminated message */
     return sendMsg(buf);
 }
 
@@ -50,25 +62,65 @@
     }
 
     // Send the message including null character
+    if (sendData(msg, strlen(msg) + 1) != 0) {
+        SLOGW("Unable to send msg '%s'", msg);
+        return -1;
+    }
+    return 0;
+}
+
+int SocketClient::sendData(const void* data, int len) {
     int rc = 0;
-    const char *p = msg;
-    int brtw = strlen(msg) + 1;
+    const char *p = (const char*) data;
+    int brtw = len;
+
+    if (len == 0) {
+        return 0;
+    }
 
     pthread_mutex_lock(&mWriteMutex);
-    while(brtw) {
-        if ((rc = write(mSocket,p, brtw)) < 0) {
-            SLOGW("Unable to send msg '%s' (%s)", msg, strerror(errno));
-            pthread_mutex_unlock(&mWriteMutex);
-            return -1;
-        } else if (!rc) {
+    while (brtw > 0) {
+        rc = write(mSocket, p, brtw);
+        if (rc > 0) {
+            p += rc;
+            brtw -= rc;
+            continue;
+        }
+
+        if (rc < 0 && errno == EINTR)
+            continue;
+
+        pthread_mutex_unlock(&mWriteMutex);
+        if (rc == 0) {
             SLOGW("0 length write :(");
             errno = EIO;
-            pthread_mutex_unlock(&mWriteMutex);
-            return -1;
+        } else {
+            SLOGW("write error (%s)", strerror(errno));
         }
-        p += rc;
-        brtw -= rc;
+        return -1;
     }
     pthread_mutex_unlock(&mWriteMutex);
     return 0;
 }
+
+void SocketClient::incRef() {
+    pthread_mutex_lock(&mRefCountMutex);
+    mRefCount++;
+    pthread_mutex_unlock(&mRefCountMutex);
+}
+
+bool SocketClient::decRef() {
+    bool deleteSelf = false;
+    pthread_mutex_lock(&mRefCountMutex);
+    mRefCount--;
+    if (mRefCount == 0) {
+        deleteSelf = true;
+    } else if (mRefCount < 0) {
+        SLOGE("SocketClient refcount went negative!");
+    }
+    pthread_mutex_unlock(&mRefCountMutex);
+    if (deleteSelf) {
+        delete this;
+    }
+    return deleteSelf;
+}
diff --git a/libsysutils/src/SocketListener.cpp b/libsysutils/src/SocketListener.cpp
index 1bc06db..69ed79e 100644
--- a/libsysutils/src/SocketListener.cpp
+++ b/libsysutils/src/SocketListener.cpp
@@ -54,8 +54,8 @@
         close(mCtrlPipe[1]);
     }
     SocketClientCollection::iterator it;
-    for (it = mClients->begin(); it != mClients->end(); ++it) {
-        delete (*it);
+    for (it = mClients->begin(); it != mClients->end();) {
+        (*it)->decRef();
         it = mClients->erase(it);
     }
     delete mClients;
@@ -96,8 +96,10 @@
 
 int SocketListener::stopListener() {
     char c = 0;
+    int  rc;
 
-    if (write(mCtrlPipe[1], &c, 1) != 1) {
+    rc = TEMP_FAILURE_RETRY(write(mCtrlPipe[1], &c, 1));
+    if (rc != 1) {
         SLOGE("Error writing to control pipe (%s)", strerror(errno));
         return -1;
     }
@@ -118,7 +120,7 @@
     }
 
     SocketClientCollection::iterator it;
-    for (it = mClients->begin(); it != mClients->end(); ++it) {
+    for (it = mClients->begin(); it != mClients->end();) {
         delete (*it);
         it = mClients->erase(it);
     }
@@ -135,11 +137,13 @@
 
 void SocketListener::runListener() {
 
+    SocketClientCollection *pendingList = new SocketClientCollection();
+
     while(1) {
         SocketClientCollection::iterator it;
         fd_set read_fds;
         int rc = 0;
-        int max = 0;
+        int max = -1;
 
         FD_ZERO(&read_fds);
 
@@ -154,13 +158,16 @@
 
         pthread_mutex_lock(&mClientsLock);
         for (it = mClients->begin(); it != mClients->end(); ++it) {
-            FD_SET((*it)->getSocket(), &read_fds);
-            if ((*it)->getSocket() > max)
-                max = (*it)->getSocket();
+            int fd = (*it)->getSocket();
+            FD_SET(fd, &read_fds);
+            if (fd > max)
+                max = fd;
         }
         pthread_mutex_unlock(&mClientsLock);
 
         if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {
+            if (errno == EINTR)
+                continue;
             SLOGE("select failed (%s)", strerror(errno));
             sleep(1);
             continue;
@@ -171,10 +178,14 @@
             break;
         if (mListen && FD_ISSET(mSock, &read_fds)) {
             struct sockaddr addr;
-            socklen_t alen = sizeof(addr);
+            socklen_t alen;
             int c;
 
-            if ((c = accept(mSock, &addr, &alen)) < 0) {
+            do {
+                alen = sizeof(addr);
+                c = accept(mSock, &addr, &alen);
+            } while (c < 0 && errno == EINTR);
+            if (c < 0) {
                 SLOGE("accept failed (%s)", strerror(errno));
                 sleep(1);
                 continue;
@@ -184,27 +195,45 @@
             pthread_mutex_unlock(&mClientsLock);
         }
 
-        do {
-            pthread_mutex_lock(&mClientsLock);
-            for (it = mClients->begin(); it != mClients->end(); ++it) {
-                int fd = (*it)->getSocket();
-                if (FD_ISSET(fd, &read_fds)) {
-                    pthread_mutex_unlock(&mClientsLock);
-                    if (!onDataAvailable(*it)) {
-                        close(fd);
-                        pthread_mutex_lock(&mClientsLock);
-                        delete *it;
-                        it = mClients->erase(it);
-                        pthread_mutex_unlock(&mClientsLock);
+        /* Add all active clients to the pending list first */
+        pendingList->clear();
+        pthread_mutex_lock(&mClientsLock);
+        for (it = mClients->begin(); it != mClients->end(); ++it) {
+            int fd = (*it)->getSocket();
+            if (FD_ISSET(fd, &read_fds)) {
+                pendingList->push_back(*it);
+            }
+        }
+        pthread_mutex_unlock(&mClientsLock);
+
+        /* Process the pending list, since it is owned by the thread,
+         * there is no need to lock it */
+        while (!pendingList->empty()) {
+            /* Pop the first item from the list */
+            it = pendingList->begin();
+            SocketClient* c = *it;
+            pendingList->erase(it);
+            /* Process it, if false is returned, remove and destroy it */
+            if (!onDataAvailable(c)) {
+                /* Remove the client from our array */
+                pthread_mutex_lock(&mClientsLock);
+                for (it = mClients->begin(); it != mClients->end(); ++it) {
+                    if (*it == c) {
+                        mClients->erase(it);
+                        break;
                     }
-                    FD_CLR(fd, &read_fds);
-                    pthread_mutex_lock(&mClientsLock);
-                    continue;
+                }
+                pthread_mutex_unlock(&mClientsLock);
+                /* Destroy the client */
+                int socket = c->getSocket();
+                if (c->decRef()) {
+                    // Note: 'c' is deleted memory at this point.
+                    close(socket);
                 }
             }
-            pthread_mutex_unlock(&mClientsLock);
-        } while (0);
+        }
     }
+    delete pendingList;
 }
 
 void SocketListener::sendBroadcast(int code, const char *msg, bool addErrno) {
diff --git a/mksh/Android.mk b/mksh/Android.mk
new file mode 100644
index 0000000..e53b863
--- /dev/null
+++ b/mksh/Android.mk
@@ -0,0 +1,64 @@
+# Copyright © 2010
+#	Thorsten Glaser <t.glaser@tarent.de>
+# This file is provided under the same terms as mksh.
+
+LOCAL_PATH:=		$(call my-dir)
+
+
+# /system/etc/mkshrc
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE:=		mkshrc
+LOCAL_MODULE_TAGS:=	shell_mksh
+LOCAL_MODULE_CLASS:=	ETC
+LOCAL_MODULE_PATH:=	$(TARGET_OUT)/etc
+LOCAL_SRC_FILES:=	$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
+
+
+# /system/bin/mksh
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE:=		mksh
+LOCAL_MODULE_TAGS:=	shell_mksh
+
+# mksh source files
+LOCAL_SRC_FILES:=	src/lalloc.c src/edit.c src/eval.c src/exec.c \
+			src/expr.c src/funcs.c src/histrap.c src/jobs.c \
+			src/lex.c src/main.c src/misc.c src/shf.c \
+			src/syn.c src/tree.c src/var.c
+
+LOCAL_SYSTEM_SHARED_LIBRARIES:= libc
+
+LOCAL_C_INCLUDES:=	$(LOCAL_PATH)/src
+# additional flags first, then from Makefrag.inc: CFLAGS, CPPFLAGS
+LOCAL_CFLAGS:=		-DMKSH_DEFAULT_EXECSHELL=\"/system/bin/sh\" \
+			-DMKSH_DEFAULT_TMPDIR=\"/sqlite_stmt_journals\" \
+			-DMKSHRC_PATH=\"/system/etc/mkshrc\" \
+			-fwrapv \
+			-DMKSH_ASSUME_UTF8=0 -DMKSH_NOPWNAM \
+			-D_GNU_SOURCE \
+			-DHAVE_ATTRIBUTE_BOUNDED=0 -DHAVE_ATTRIBUTE_FORMAT=1 \
+			-DHAVE_ATTRIBUTE_NONNULL=1 -DHAVE_ATTRIBUTE_NORETURN=1 \
+			-DHAVE_ATTRIBUTE_UNUSED=1 -DHAVE_ATTRIBUTE_USED=1 \
+			-DHAVE_SYS_PARAM_H=1 -DHAVE_SYS_MKDEV_H=0 \
+			-DHAVE_SYS_MMAN_H=1 -DHAVE_SYS_SYSMACROS_H=1 \
+			-DHAVE_GRP_H=1 -DHAVE_LIBGEN_H=1 -DHAVE_LIBUTIL_H=0 \
+			-DHAVE_PATHS_H=1 -DHAVE_STDBOOL_H=1 -DHAVE_STDINT_H=1 \
+			-DHAVE_STRINGS_H=1 -DHAVE_ULIMIT_H=0 -DHAVE_VALUES_H=0 \
+			-DHAVE_CAN_INTTYPES=1 -DHAVE_CAN_UCBINTS=1 \
+			-DHAVE_CAN_INT8TYPE=1 -DHAVE_CAN_UCBINT8=1 \
+			-DHAVE_RLIM_T=1 -DHAVE_SIG_T=1 -DHAVE_SYS_SIGNAME=1 \
+			-DHAVE_SYS_SIGLIST=1 -DHAVE_STRSIGNAL=0 \
+			-DHAVE_GETRUSAGE=1 -DHAVE_KILLPG=1 -DHAVE_MKNOD=0 \
+			-DHAVE_MKSTEMP=1 -DHAVE_NICE=1 -DHAVE_REVOKE=0 \
+			-DHAVE_SETLOCALE_CTYPE=0 -DHAVE_LANGINFO_CODESET=0 \
+			-DHAVE_SETMODE=1 -DHAVE_SETRESUGID=1 \
+			-DHAVE_SETGROUPS=1 -DHAVE_STRCASESTR=1 \
+			-DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 \
+			-DHAVE_REVOKE_DECL=1 -DHAVE_SYS_SIGLIST_DECL=1 \
+			-DHAVE_PERSISTENT_HISTORY=0
+
+include $(BUILD_EXECUTABLE)
diff --git a/mksh/MODULE_LICENSE_BSD_LIKE b/mksh/MODULE_LICENSE_BSD_LIKE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mksh/MODULE_LICENSE_BSD_LIKE
diff --git a/mksh/NOTICE b/mksh/NOTICE
new file mode 100644
index 0000000..350061f
--- /dev/null
+++ b/mksh/NOTICE
@@ -0,0 +1,21 @@
+mksh is covered by The MirOS Licence:
+
+/*-
+ * Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un‐
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person’s immediate fault when using the work as intended.
+ */
diff --git a/mksh/mkmf.sh b/mksh/mkmf.sh
new file mode 100644
index 0000000..15d0432
--- /dev/null
+++ b/mksh/mkmf.sh
@@ -0,0 +1,124 @@
+# Copyright © 2010
+#	Thorsten Glaser <t.glaser@tarent.de>
+# This file is provided under the same terms as mksh.
+#-
+# Helper script to let src/Build.sh generate Makefrag.inc
+# which we in turn use in the manual creation of Android.mk
+#
+# This script is supposed to be run from/inside AOSP by the
+# porter of mksh to Android (and only manually).
+
+cd "$(dirname "$0")"
+srcdir=$(pwd)
+rm -rf tmp
+mkdir tmp
+cd ../../..
+aospdir=$(pwd)
+cd $srcdir/tmp
+
+addvar() {
+	_vn=$1; shift
+
+	eval $_vn=\"\$$_vn '$*"'
+}
+
+CFLAGS=
+CPPFLAGS=
+LDFLAGS=
+LIBS=
+
+# The definitions below were generated by examining the
+# output of the following command:
+# make showcommands out/target/product/generic/system/bin/mksh 2>&1 | tee log
+#
+# They are only used to let Build.sh find the compiler, header
+# files, linker and libraries to generate Makefrag.inc (similar
+# to what GNU autotools’ configure scripts do), and never used
+# during the real build process. We need this to port mksh to
+# the Android platform and it is crucial these are as close as
+# possible to the values used later. (You also must example the
+# results gathered from Makefrag.inc to see they are the same
+# across all Android platforms, or add appropriate ifdefs.)
+# Since we no longer use the NDK, the AOSP has to have been
+# built before using this script (targetting generic/emulator).
+
+CC=$aospdir/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi-gcc
+addvar CPPFLAGS -I$aospdir/system/core/include \
+    -I$aospdir/hardware/libhardware/include \
+    -I$aospdir/system/core/include \
+    -I$aospdir/hardware/libhardware/include \
+    -I$aospdir/hardware/libhardware_legacy/include \
+    -I$aospdir/hardware/ril/include \
+    -I$aospdir/dalvik/libnativehelper/include \
+    -I$aospdir/frameworks/base/include \
+    -I$aospdir/frameworks/base/opengl/include \
+    -I$aospdir/external/skia/include \
+    -I$aospdir/out/target/product/generic/obj/include \
+    -I$aospdir/bionic/libc/arch-arm/include \
+    -I$aospdir/bionic/libc/include \
+    -I$aospdir/bionic/libstdc++/include \
+    -I$aospdir/bionic/libc/kernel/common \
+    -I$aospdir/bionic/libc/kernel/arch-arm \
+    -I$aospdir/bionic/libm/include \
+    -I$aospdir/bionic/libm/include/arch/arm \
+    -I$aospdir/bionic/libthread_db/include \
+    -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ \
+    -I$aospdir/system/core/include/arch/linux-arm/ \
+    -include $aospdir/system/core/include/arch/linux-arm/AndroidConfig.h \
+    -DANDROID -DNDEBUG -UDEBUG
+addvar CFLAGS -fno-exceptions -Wno-multichar -msoft-float -fpic \
+    -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums \
+    -march=armv5te -mtune=xscale -mthumb-interwork -fmessage-length=0 \
+    -W -Wall -Wno-unused -Winit-self -Wpointer-arith -Werror=return-type \
+    -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point \
+    -Wstrict-aliasing=2 -finline-functions -fno-inline-functions-called-once \
+    -fgcse-after-reload -frerun-cse-after-loop -frename-registers -mthumb \
+    -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64
+addvar LDFLAGS -nostdlib -Bdynamic -Wl,-T,$aospdir/build/core/armelf.x \
+    -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections \
+    -Wl,-z,nocopyreloc -Wl,--no-undefined \
+    $aospdir/out/target/product/generic/obj/lib/crtbegin_dynamic.o
+addvar LIBS -L$aospdir/out/target/product/generic/obj/lib \
+    -Wl,-rpath-link=$aospdir/out/target/product/generic/obj/lib -lc \
+    $aospdir/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/../lib/gcc/arm-eabi/4.4.0/interwork/libgcc.a \
+    $aospdir/out/target/product/generic/obj/lib/crtend_android.o
+
+
+### Override flags
+# We don’t even *support* UTF-8 by default ☹
+addvar CPPFLAGS -DMKSH_ASSUME_UTF8=0
+# No getpwnam() calls (affects "cd ~username/" only)
+addvar CPPFLAGS -DMKSH_NOPWNAM
+# Compile an extra small mksh (optional)
+#addvar CPPFLAGS -DMKSH_SMALL
+# Leave out the ulimit builtin
+#addvar CPPFLAGS -DMKSH_NO_LIMITS
+
+# Set target platform
+TARGET_OS=Linux
+# Building with -std=c99 or -std=gnu99 clashes with Bionic headers
+HAVE_CAN_STDG99=0
+HAVE_CAN_STDC99=0
+export HAVE_CAN_STDG99 HAVE_CAN_STDC99
+
+# Android-x86 does not have helper functions for ProPolice SSP
+# and AOSP adds the flags by itself (same for warning flags)
+HAVE_CAN_FNOSTRICTALIASING=0
+HAVE_CAN_FSTACKPROTECTORALL=0
+HAVE_CAN_WALL=0
+export HAVE_CAN_FNOSTRICTALIASING HAVE_CAN_FSTACKPROTECTORALL HAVE_CAN_WALL
+
+# disable the mknod(8) built-in to get rid of needing setmode.c
+HAVE_MKNOD=0; export HAVE_MKNOD
+
+# even the idea of persistent history on a phone is funny
+HAVE_PERSISTENT_HISTORY=0; export HAVE_PERSISTENT_HISTORY
+
+# ... and run it!
+export CC CPPFLAGS CFLAGS LDFLAGS LIBS TARGET_OS
+sh ../src/Build.sh -M
+rv=$?
+test x0 = x"$rv" && mv -f Makefrag.inc ../
+cd ..
+rm -rf tmp
+exit $rv
diff --git a/mksh/mkshrc b/mksh/mkshrc
new file mode 100644
index 0000000..0da5ea6
--- /dev/null
+++ b/mksh/mkshrc
@@ -0,0 +1,29 @@
+# Copyright (c) 2010
+#	Thorsten Glaser <t.glaser@tarent.de>
+# This file is provided under the same terms as mksh.
+#-
+# Minimal /system/etc/mkshrc for Android
+
+: ${TERM:=vt100} ${HOME:=/data} ${MKSH:=/system/bin/sh} ${HOSTNAME:=android}
+: ${SHELL:=$MKSH} ${USER:=$(typeset x=$(id); x=${x#*\(}; print -r -- ${x%%\)*})}
+if (( USER_ID )); then PS1='$'; else PS1='#'; fi
+function precmd {
+	typeset e=$?
+
+	(( e )) && print -n "$e|"
+}
+PS1='$(precmd)$USER@$HOSTNAME:${PWD:-?} '"$PS1 "
+export HOME HOSTNAME MKSH PS1 SHELL TERM USER
+alias l='ls'
+alias la='l -a'
+alias ll='l -l'
+alias lo='l -a -l'
+
+for p in ~/.bin; do
+	[[ -d $p/. ]] || continue
+	[[ :$PATH: = *:$p:* ]] || PATH=$p:$PATH
+done
+
+unset p
+
+: place customisations above this line
diff --git a/mksh/src/00-NOTE.txt b/mksh/src/00-NOTE.txt
new file mode 100644
index 0000000..b51d541
--- /dev/null
+++ b/mksh/src/00-NOTE.txt
@@ -0,0 +1,22 @@
+This is mksh from AnonCVS on 2010-08-24 with the
+following files removed:
+• Makefile
+  (not part of regular mksh releases anyway)
+• dot.mkshrc
+  (not needed, we use our own for Android)
+• mksh.1
+  (manpage; also available from the web)
+• setmode.c
+  (not needed, we don’t use the mknod builtin)
+• strlcpy.c
+  (not needed, bionic provides this)
+
+The manual page can be downloaded as PDF (ISO A4 paper) from
+https://www.mirbsd.org/MirOS/dist/mir/mksh/mksh.pdf or read
+online at https://www.mirbsd.org/man1/mksh (HTML).
+
+The following changes are done to code in this subdirectory
+at the moment:
+• check.t main.sh: remove the 'stop' alias to 'kill -STOP'
+  since Android has a built in stop command that the alias
+  overrides, causing problems with testing tools
\ No newline at end of file
diff --git a/mksh/src/Build.sh b/mksh/src/Build.sh
new file mode 100644
index 0000000..c98b1ca
--- /dev/null
+++ b/mksh/src/Build.sh
@@ -0,0 +1,1600 @@
+#!/bin/sh
+srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.459 2010/08/24 15:46:06 tg Exp $'
+#-
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+#	Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+#-
+# People analysing the output must whitelist conftest.c for any kind
+# of compiler warning checks (mirtoconf is by design not quiet).
+#
+# Environment used:	CC CFLAGS CPPFLAGS LDFLAGS LIBS NOWARN NROFF
+#			TARGET_OS TARGET_OSREV
+# Feature selectors:	USE_PRINTF_BUILTIN
+# CPPFLAGS recognised:	MKSH_ASSUME_UTF8 MKSH_BINSHREDUCED MKSH_CLS_STRING
+#			MKSH_CONSERVATIVE_FDS MKSH_MIDNIGHTBSD01ASH_COMPAT
+#			MKSH_NOPWNAM MKSH_NO_LIMITS MKSH_SMALL MKSH_S_NOVI
+#			MKSH_UNEMPLOYED MKSH_DEFAULT_EXECSHELL MKSHRC_PATH
+#			MKSH_DEFAULT_TMPDIR MKSH_CLRTOEOL_STRING MKSH_A4PB
+
+LC_ALL=C
+export LC_ALL
+
+v() {
+	$e "$*"
+	eval "$@"
+}
+
+vv() {
+	_c=$1
+	shift
+	$e "\$ $*" 2>&1
+	eval "$@" >vv.out 2>&1
+	sed "s^${_c} " <vv.out
+}
+
+vq() {
+	eval "$@"
+}
+
+rmf() {
+	for _f in "$@"; do
+		case ${_f} in
+		mksh.1) ;;
+		*) rm -f "${_f}" ;;
+		esac
+	done
+}
+
+if test -d /usr/xpg4/bin/. >/dev/null 2>&1; then
+	# Solaris: some of the tools have weird behaviour, use portable ones
+	PATH=/usr/xpg4/bin:$PATH
+	export PATH
+fi
+
+if test -n "${ZSH_VERSION+x}" && (emulate sh) >/dev/null 2>&1; then
+	emulate sh
+	NULLCMD=:
+fi
+
+allu=QWERTYUIOPASDFGHJKLZXCVBNM
+alll=qwertyuiopasdfghjklzxcvbnm
+alln=0123456789
+alls=______________________________________________________________
+nl='
+'
+tcfn=no
+bi=
+ui=
+ao=
+fx=
+me=`basename "$0"`
+orig_CFLAGS=$CFLAGS
+phase=x
+oldish_ed=stdout-ed,no-stderr-ed
+
+if test -t 1; then
+	bi=''
+	ui=''
+	ao=''
+fi
+
+upper() {
+	echo :"$@" | sed 's/^://' | tr $alll $allu
+}
+
+# clean up after ac_testrun()
+ac_testdone() {
+	eval HAVE_$fu=$fv
+	fr=no
+	test 0 = $fv || fr=yes
+	$e "$bi==> $fd...$ao $ui$fr$ao$fx"
+	fx=
+}
+
+# ac_cache label: sets f, fu, fv?=0
+ac_cache() {
+	f=$1
+	fu=`upper $f`
+	eval fv=\$HAVE_$fu
+	case $fv in
+	0|1)
+		fx=' (cached)'
+		return 0
+		;;
+	esac
+	fv=0
+	return 1
+}
+
+# ac_testinit label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput
+# returns 1 if value was cached/implied, 0 otherwise: call ac_testdone
+ac_testinit() {
+	if ac_cache $1; then
+		test x"$2" = x"!" && shift
+		test x"$2" = x"" || shift
+		fd=${3-$f}
+		ac_testdone
+		return 1
+	fi
+	fc=0
+	if test x"$2" = x""; then
+		ft=1
+	else
+		if test x"$2" = x"!"; then
+			fc=1
+			shift
+		fi
+		eval ft=\$HAVE_`upper $2`
+		shift
+	fi
+	fd=${3-$f}
+	if test $fc = "$ft"; then
+		fv=$2
+		fx=' (implied)'
+		ac_testdone
+		return 1
+	fi
+	$e ... $fd
+	return 0
+}
+
+# pipe .c | ac_test[n] [!] label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput
+ac_testn() {
+	if test x"$1" = x"!"; then
+		fr=1
+		shift
+	else
+		fr=0
+	fi
+	ac_testinit "$@" || return
+	cat >conftest.c
+	vv ']' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN conftest.c $LIBS $ccpr"
+	test $tcfn = no && test -f a.out && tcfn=a.out
+	test $tcfn = no && test -f a.exe && tcfn=a.exe
+	test $tcfn = no && test -f conftest && tcfn=conftest
+	if test -f $tcfn; then
+		test 1 = $fr || fv=1
+	else
+		test 0 = $fr || fv=1
+	fi
+	vscan=
+	if test $phase = u; then
+		test $ct = gcc && vscan='unrecogni[sz]ed'
+		test $ct = hpcc && vscan='unsupported'
+		test $ct = pcc && vscan='unsupported'
+		test $ct = sunpro && vscan='-e ignored -e turned.off'
+	fi
+	test -n "$vscan" && grep $vscan vv.out >/dev/null 2>&1 && fv=$fr
+	rmf conftest.c conftest.o ${tcfn}* vv.out
+	ac_testdone
+}
+
+# ac_ifcpp cppexpr [!] label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput
+ac_ifcpp() {
+	expr=$1; shift
+	ac_testn "$@" <<-EOF
+		int main(void) { return (
+		#$expr
+		    0
+		#else
+		/* force a failure: expr is false */
+		    thiswillneverbedefinedIhope()
+		#endif
+		    ); }
+EOF
+	test x"$1" = x"!" && shift
+	f=$1
+	fu=`upper $f`
+	eval fv=\$HAVE_$fu
+	test x"$fv" = x"1"
+}
+
+ac_cppflags() {
+	test x"$1" = x"" || fu=$1
+	fv=$2
+	test x"$2" = x"" && eval fv=\$HAVE_$fu
+	CPPFLAGS="$CPPFLAGS -DHAVE_$fu=$fv"
+}
+
+ac_test() {
+	ac_testn "$@"
+	ac_cppflags
+}
+
+# ac_flags [-] add varname flags [text]
+ac_flags() {
+	if test x"$1" = x"-"; then
+		shift
+		hf=1
+	else
+		hf=0
+	fi
+	fa=$1
+	vn=$2
+	f=$3
+	ft=$4
+	test x"$ft" = x"" && ft="if $f can be used"
+	save_CFLAGS=$CFLAGS
+	CFLAGS="$CFLAGS $f"
+	if test 1 = $hf; then
+		ac_testn can_$vn '' "$ft"
+	else
+		ac_testn can_$vn '' "$ft" <<-'EOF'
+			/* evil apo'stroph in comment test */
+			int main(void) { return (0); }
+		EOF
+	fi
+	eval fv=\$HAVE_CAN_`upper $vn`
+	test 11 = $fa$fv || CFLAGS=$save_CFLAGS
+}
+
+# ac_header [!] header [prereq ...]
+ac_header() {
+	if test x"$1" = x"!"; then
+		na=1
+		shift
+	else
+		na=0
+	fi
+	hf=$1; shift
+	hv=`echo "$hf" | tr -d '\012\015' | tr -c $alll$allu$alln $alls`
+	for i
+	do
+		echo "#include <$i>" >>x
+	done
+	echo "#include <$hf>" >>x
+	echo 'int main(void) { return (0); }' >>x
+	ac_testn "$hv" "" "<$hf>" <x
+	rmf x
+	test 1 = $na || ac_cppflags
+}
+
+addsrcs() {
+	if test x"$1" = x"!"; then
+		fr=0
+		shift
+	else
+		fr=1
+	fi
+	eval i=\$$1
+	test $fr = "$i" && case " $SRCS " in
+	*\ $2\ *)	;;
+	*)		SRCS="$SRCS $2" ;;
+	esac
+}
+
+
+if test -d mksh || test -d mksh.exe; then
+	echo "$me: Error: ./mksh is a directory!" >&2
+	exit 1
+fi
+rmf a.exe* a.out* conftest.c *core lft mksh* no *.bc *.ll *.o \
+    Rebuild.sh signames.inc test.sh x vv.out
+
+curdir=`pwd` srcdir=`dirname "$0"` check_categories=
+test -n "$dirname" || dirname=.
+dstversion=`sed -n '/define MKSH_VERSION/s/^.*"\(.*\)".*$/\1/p' $srcdir/sh.h`
+
+e=echo
+r=0
+eq=0
+pm=0
+cm=normal
+optflags=-std-compile-opts
+last=
+
+for i
+do
+	case $last:$i in
+	c:combine|c:dragonegg|c:llvm)
+		cm=$i
+		last=
+		;;
+	c:*)
+		echo "$me: Unknown option -c '$i'!" >&2
+		exit 1
+		;;
+	o:*)
+		optflags=$i
+		last=
+		;;
+	:-c)
+		last=c
+		;;
+	:-combine)
+		cm=combine
+		echo "$me: Warning: '$i' is deprecated, use '-c combine' instead!" >&2
+		;;
+	:-g)
+		# checker, debug, valgrind build
+		CPPFLAGS="$CPPFLAGS -DDEBUG"
+		CFLAGS="$CFLAGS -g3 -fno-builtin"
+		;;
+	:-j)
+		pm=1
+		;;
+	:-llvm)
+		cm=llvm
+		optflags=-std-compile-opts
+		echo "$me: Warning: '$i' is deprecated, use '-c llvm -O' instead!" >&2
+		;;
+	:-llvm=*)
+		cm=llvm
+		optflags=`echo "x$i" | sed 's/^x-llvm=//'`
+		echo "$me: Warning: '$i' is deprecated, use '-c llvm -o $llvm' instead!" >&2
+		;;
+	:-M)
+		cm=makefile
+		;;
+	:-O)
+		optflags=-std-compile-opts
+		;;
+	:-o)
+		last=o
+		;;
+	:-Q)
+		eq=1
+		;;
+	:-r)
+		r=1
+		;;
+	:-v)
+		echo "Build.sh $srcversion"
+		echo "for mksh $dstversion"
+		exit 0
+		;;
+	:*)
+		echo "$me: Unknown option '$i'!" >&2
+		exit 1
+		;;
+	*)
+		echo "$me: Unknown option -'$last' '$i'!" >&2
+		exit 1
+		;;
+	esac
+done
+if test -n "$last"; then
+	echo "$me: Option -'$last' not followed by argument!" >&2
+	exit 1
+fi
+
+SRCS="lalloc.c edit.c eval.c exec.c expr.c funcs.c histrap.c"
+SRCS="$SRCS jobs.c lex.c main.c misc.c shf.c syn.c tree.c var.c"
+
+if test x"$srcdir" = x"."; then
+	CPPFLAGS="-I. $CPPFLAGS"
+else
+	CPPFLAGS="-I. -I'$srcdir' $CPPFLAGS"
+fi
+
+test x"$TARGET_OS" = x"" && TARGET_OS=`uname -s 2>/dev/null || uname`
+oswarn=
+ccpc=-Wc,
+ccpl=-Wl,
+tsts=
+ccpr='|| for _f in ${tcfn}*; do test x"${_f}" = x"mksh.1" || rm -f "${_f}"; done'
+
+# Configuration depending on OS revision, on OSes that need them
+case $TARGET_OS in
+QNX)
+	test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`uname -r`
+	;;
+esac
+
+# Configuration depending on OS name
+case $TARGET_OS in
+AIX)
+	CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE"
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+BeOS)
+	oswarn=' and will currently not work'
+	;;
+BSD/OS)
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+CYGWIN*)
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+Darwin)
+	;;
+DragonFly)
+	;;
+FreeBSD)
+	;;
+GNU)
+	# define NO_PATH_MAX to use Hurd-only functions
+	CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE -DNO_PATH_MAX"
+	;;
+GNU/kFreeBSD)
+	CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+	;;
+Haiku)
+	CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8"
+	;;
+HP-UX)
+	;;
+Interix)
+	ccpc='-X '
+	ccpl='-Y '
+	CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE"
+	: ${LIBS='-lcrypt'}
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+IRIX*)
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+Linux)
+	CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+	: ${HAVE_REVOKE=0}
+	;;
+MidnightBSD)
+	;;
+Minix)
+	CPPFLAGS="$CPPFLAGS -DMKSH_UNEMPLOYED -DMKSH_CONSERVATIVE_FDS"
+	CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_POSIX_1_SOURCE=2 -D_MINIX"
+	oldish_ed=no-stderr-ed		# /usr/bin/ed(!) is broken
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+MirBSD)
+	;;
+NetBSD)
+	;;
+OpenBSD)
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+OSF1)
+	HAVE_SIG_T=0	# incompatible
+	CPPFLAGS="$CPPFLAGS -D_OSF_SOURCE -D_POSIX_C_SOURCE=200112L"
+	CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED"
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+Plan9)
+	CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_LIMITS_EXTENSION"
+	CPPFLAGS="$CPPFLAGS -D_BSD_EXTENSION -D_SUSV2_SOURCE"
+	oswarn=' and will currently not work'
+	CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8 -DMKSH_UNEMPLOYED"
+	;;
+PW32*)
+	HAVE_SIG_T=0	# incompatible
+	oswarn=' and will currently not work'
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+QNX)
+	CPPFLAGS="$CPPFLAGS -D__NO_EXT_QNX"
+	case $TARGET_OSREV in
+	[012345].*|6.[0123].*|6.4.[01])
+		oldish_ed=no-stderr-ed		# oldish /bin/ed is broken
+		;;
+	esac
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+SunOS)
+	CPPFLAGS="$CPPFLAGS -D_BSD_SOURCE -D__EXTENSIONS__"
+	;;
+syllable)
+	CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+	oswarn=' and will currently not work'
+	;;
+ULTRIX)
+	: ${CC=cc -YPOSIX}
+	CPPFLAGS="$CPPFLAGS -Dssize_t=int"
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+UWIN*)
+	ccpc='-Yc,'
+	ccpl='-Yl,'
+	tsts=" 3<>/dev/tty"
+	oswarn="; it will compile, but the target"
+	oswarn="$oswarn${nl}platform itself is very flakey/unreliable"
+	: ${HAVE_SETLOCALE_CTYPE=0}
+	;;
+*)
+	oswarn='; it may or may not work'
+	;;
+esac
+
+: ${CC=cc} ${NROFF=nroff}
+test 0 = $r && echo | $NROFF -v 2>&1 | grep GNU >/dev/null 2>&1 && \
+    NROFF="$NROFF -c"
+
+# this aids me in tracing FTBFSen without access to the buildd
+$e "Hi from$ao $bi$srcversion$ao on:"
+case $TARGET_OS in
+Darwin)
+	vv '|' "hwprefs machine_type os_type os_class >&2"
+	vv '|' "uname -a >&2"
+	;;
+IRIX*)
+	vv '|' "uname -a >&2"
+	vv '|' "hinv -v >&2"
+	;;
+OSF1)
+	vv '|' "uname -a >&2"
+	vv '|' "/usr/sbin/sizer -v >&2"
+	;;
+*)
+	vv '|' "uname -a >&2"
+	;;
+esac
+test -z "$oswarn" || echo >&2 "
+Warning: mksh has not yet been ported to or tested on your
+operating system '$TARGET_OS'$oswarn. If you can provide
+a shell account to the developer, this may improve; please
+drop us a success or failure notice or even send in diffs.
+"
+$e "$bi$me: Building the MirBSD Korn Shell$ao $ui$dstversion$ao"
+
+#
+# Begin of mirtoconf checks
+#
+$e $bi$me: Scanning for functions... please ignore any errors.$ao
+
+#
+# Compiler: which one?
+#
+# notes:
+# - ICC defines __GNUC__ too
+# - GCC defines __hpux too
+# - LLVM+clang defines __GNUC__ too
+# - nwcc defines __GNUC__ too
+CPP="$CC -E"
+$e ... which compiler seems to be used
+cat >conftest.c <<'EOF'
+#if defined(__ICC) || defined(__INTEL_COMPILER)
+ct=icc
+#elif defined(__xlC__) || defined(__IBMC__)
+ct=xlc
+#elif defined(__SUNPRO_C)
+ct=sunpro
+#elif defined(__ACK__)
+ct=ack
+#elif defined(__BORLANDC__)
+ct=bcc
+#elif defined(__WATCOMC__)
+ct=watcom
+#elif defined(__MWERKS__)
+ct=metrowerks
+#elif defined(__HP_cc)
+ct=hpcc
+#elif defined(__DECC) || (defined(__osf__) && !defined(__GNUC__))
+ct=dec
+#elif defined(__PGI)
+ct=pgi
+#elif defined(__DMC__)
+ct=dmc
+#elif defined(_MSC_VER)
+ct=msc
+#elif defined(__ADSPBLACKFIN__) || defined(__ADSPTS__) || defined(__ADSP21000__)
+ct=adsp
+#elif defined(__IAR_SYSTEMS_ICC__)
+ct=iar
+#elif defined(SDCC)
+ct=sdcc
+#elif defined(__PCC__)
+ct=pcc
+#elif defined(__TenDRA__)
+ct=tendra
+#elif defined(__TINYC__)
+ct=tcc
+#elif defined(__llvm__) && defined(__clang__)
+ct=clang
+#elif defined(__NWCC__)
+ct=nwcc
+#elif defined(__GNUC__)
+ct=gcc
+#elif defined(_COMPILER_VERSION)
+ct=mipspro
+#elif defined(__sgi)
+ct=mipspro
+#elif defined(__hpux) || defined(__hpua)
+ct=hpcc
+#elif defined(__ultrix)
+ct=ucode
+#else
+ct=unknown
+#endif
+EOF
+ct=unknown
+vv ']' "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c | grep ct= | tr -d \\\\015 >x"
+sed 's/^/[ /' x
+eval `cat x`
+rmf x vv.out
+echo 'int main(void) { return (0); }' >conftest.c
+case $ct in
+ack)
+	# work around "the famous ACK const bug"
+	CPPFLAGS="-Dconst= $CPPFLAGS"
+	;;
+adsp)
+	echo >&2 'Warning: Analog Devices C++ compiler for Blackfin, TigerSHARC
+    and SHARC (21000) DSPs detected. This compiler has not yet
+    been tested for compatibility with mksh. Continue at your
+    own risk, please report success/failure to the developers.'
+	;;
+bcc)
+	echo >&2 "Warning: Borland C++ Builder detected. This compiler might
+    produce broken executables. Continue at your own risk,
+    please report success/failure to the developers."
+	;;
+clang)
+	# does not work with current "ccc" compiler driver
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version"
+	# this works, for now
+	vv '|' "${CLANG-clang} -version"
+	# ensure compiler and linker are in sync unless overridden
+	case $CCC_CC:$CCC_LD in
+	:*)	;;
+	*:)	CCC_LD=$CCC_CC; export CCC_LD ;;
+	esac
+	;;
+dec)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V"
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -Wl,-V conftest.c $LIBS"
+	;;
+dmc)
+	echo >&2 "Warning: Digital Mars Compiler detected. When running under"
+	echo >&2 "    UWIN, mksh tends to be unstable due to the limitations"
+	echo >&2 "    of this platform. Continue at your own risk,"
+	echo >&2 "    please report success/failure to the developers."
+	;;
+gcc)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS"
+	vv '|' 'echo `$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS \
+	    -dumpmachine` gcc`$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN \
+	    $LIBS -dumpversion`'
+	;;
+hpcc)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS"
+	;;
+iar)
+	echo >&2 'Warning: IAR Systems (http://www.iar.com) compiler for embedded
+    systems detected. This unsupported compiler has not yet
+    been tested for compatibility with mksh. Continue at your
+    own risk, please report success/failure to the developers.'
+	;;
+icc)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V"
+	;;
+metrowerks)
+	echo >&2 'Warning: Metrowerks C compiler detected. This has not yet
+    been tested for compatibility with mksh. Continue at your
+    own risk, please report success/failure to the developers.'
+	;;
+mipspro)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version"
+	;;
+msc)
+	ccpr=		# errorlevels are not reliable
+	case $TARGET_OS in
+	Interix)
+		if [[ -n $C89_COMPILER ]]; then
+			C89_COMPILER=`ntpath2posix -c "$C89_COMPILER"`
+		else
+			C89_COMPILER=CL.EXE
+		fi
+		if [[ -n $C89_LINKER ]]; then
+			C89_LINKER=`ntpath2posix -c "$C89_LINKER"`
+		else
+			C89_LINKER=LINK.EXE
+		fi
+		vv '|' "$C89_COMPILER /HELP >&2"
+		vv '|' "$C89_LINKER /LINK >&2"
+		;;
+	esac
+	;;
+nwcc)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version"
+	;;
+pcc)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -v"
+	;;
+pgi)
+	echo >&2 'Warning: PGI detected. This unknown compiler has not yet
+    been tested for compatibility with mksh. Continue at your
+    own risk, please report success/failure to the developers.'
+	;;
+sdcc)
+	echo >&2 'Warning: sdcc (http://sdcc.sourceforge.net), the small devices
+    C compiler for embedded systems detected. This has not yet
+    been tested for compatibility with mksh. Continue at your
+    own risk, please report success/failure to the developers.'
+	;;
+sunpro)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS"
+	;;
+tcc)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -v"
+	;;
+tendra)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V 2>&1 | \
+	    fgrep -i -e version -e release"
+	;;
+ucode)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V"
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -Wl,-V conftest.c $LIBS"
+	;;
+watcom)
+	echo >&2 'Warning: Watcom C Compiler detected. This compiler has not yet
+    been tested for compatibility with mksh. Continue at your
+    own risk, please report success/failure to the developers.'
+	;;
+xlc)
+	vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion=verbose"
+	vv '|' "ld -V"
+	;;
+*)
+	ct=unknown
+	;;
+esac
+case $cm in
+dragonegg|llvm)
+	vv '|' "llc -version"
+	;;
+esac
+$e "$bi==> which compiler seems to be used...$ao $ui$ct$ao"
+rmf conftest.c conftest.o conftest a.out* a.exe* vv.out
+
+#
+# Compiler: works as-is, with -Wno-error and -Werror
+#
+save_NOWARN=$NOWARN
+NOWARN=
+DOWARN=
+ac_flags 0 compiler_works '' 'if the compiler works'
+test 1 = $HAVE_CAN_COMPILER_WORKS || exit 1
+HAVE_COMPILER_KNOWN=0
+test $ct = unknown || HAVE_COMPILER_KNOWN=1
+if ac_ifcpp 'if 0' compiler_fails '' \
+    'if the compiler does not fail correctly'; then
+	save_CFLAGS=$CFLAGS
+	: ${HAVE_CAN_DELEXE=x}
+	if test $ct = dmc; then
+		CFLAGS="$CFLAGS ${ccpl}/DELEXECUTABLE"
+		ac_testn can_delexe compiler_fails 0 'for the /DELEXECUTABLE linker option' <<-EOF
+			int main(void) { return (0); }
+		EOF
+	elif test $ct = dec; then
+		CFLAGS="$CFLAGS ${ccpl}-non_shared"
+		ac_testn can_delexe compiler_fails 0 'for the -non_shared linker option' <<-EOF
+			int main(void) { return (0); }
+		EOF
+	else
+		exit 1
+	fi
+	test 1 = $HAVE_CAN_DELEXE || CFLAGS=$save_CFLAGS
+	ac_testn compiler_still_fails '' 'if the compiler still does not fail correctly' <<-EOF
+	EOF
+	test 1 = $HAVE_COMPILER_STILL_FAILS && exit 1
+fi
+if ac_ifcpp 'ifdef __TINYC__' couldbe_tcc '!' compiler_known 0 \
+    'if this could be tcc'; then
+	ct=tcc
+	CPP='cpp -D__TINYC__'
+fi
+
+if test $ct = sunpro; then
+	test x"$save_NOWARN" = x"" && save_NOWARN='-errwarn=%none'
+	ac_flags 0 errwarnnone "$save_NOWARN"
+	test 1 = $HAVE_CAN_ERRWARNNONE || save_NOWARN=
+	ac_flags 0 errwarnall "-errwarn=%all"
+	test 1 = $HAVE_CAN_ERRWARNALL && DOWARN="-errwarn=%all"
+elif test $ct = hpcc; then
+	save_NOWARN=
+	DOWARN=+We
+elif test $ct = mipspro; then
+	save_NOWARN=
+	DOWARN="-diag_error 1-10000"
+elif test $ct = msc; then
+	save_NOWARN="${ccpc}/w"
+	DOWARN="${ccpc}/WX"
+elif test $ct = dmc; then
+	save_NOWARN="${ccpc}-w"
+	DOWARN="${ccpc}-wx"
+elif test $ct = bcc; then
+	save_NOWARN="${ccpc}-w"
+	DOWARN="${ccpc}-w!"
+elif test $ct = dec; then
+	: -msg_* flags not used yet, or is -w2 correct?
+elif test $ct = xlc; then
+	save_NOWARN=-qflag=i:e
+	DOWARN=-qflag=i:i
+elif test $ct = tendra; then
+	save_NOWARN=-w
+elif test $ct = ucode; then
+	save_NOWARN=
+	DOWARN=-w2
+else
+	test x"$save_NOWARN" = x"" && save_NOWARN=-Wno-error
+	ac_flags 0 wnoerror "$save_NOWARN"
+	test 1 = $HAVE_CAN_WNOERROR || save_NOWARN=
+	ac_flags 0 werror -Werror
+	test 1 = $HAVE_CAN_WERROR && DOWARN=-Werror
+fi
+
+test $ct = icc && DOWARN="$DOWARN -wd1419"
+NOWARN=$save_NOWARN
+
+#
+# Compiler: extra flags (-O2 -f* -W* etc.)
+#
+i=`echo :"$orig_CFLAGS" | sed 's/^://' | tr -c -d $alll$allu$alln`
+# optimisation: only if orig_CFLAGS is empty
+test x"$i" = x"" && if test $ct = sunpro; then
+	cat >x <<-'EOF'
+		int main(void) { return (0); }
+		#define __IDSTRING_CONCAT(l,p)	__LINTED__ ## l ## _ ## p
+		#define __IDSTRING_EXPAND(l,p)	__IDSTRING_CONCAT(l,p)
+		#define pad			void __IDSTRING_EXPAND(__LINE__,x)(void) { }
+	EOF
+	yes pad | head -n 256 >>x
+	ac_flags - 1 otwo -xO2 <x
+	rmf x
+elif test $ct = hpcc; then
+	phase=u
+	ac_flags 1 otwo +O2
+	phase=x
+elif test $ct = xlc; then
+	ac_flags 1 othree "-O3 -qstrict"
+	test 1 = $HAVE_CAN_OTHREE || ac_flags 1 otwo -O2
+elif test $ct = tcc || test $ct = tendra; then
+	: no special optimisation
+else
+	ac_flags 1 otwo -O2
+	test 1 = $HAVE_CAN_OTWO || ac_flags 1 optimise -O
+fi
+# other flags: just add them if they are supported
+i=0
+if test $ct = gcc; then
+	# The following tests run with -Werror (gcc only) if possible
+	NOWARN=$DOWARN; phase=u
+	ac_flags 1 fnostrictaliasing -fno-strict-aliasing
+	ac_flags 1 fstackprotectorall -fstack-protector-all
+	ac_flags 1 fwrapv -fwrapv
+	test $cm = combine && ac_flags 0 combine \
+	    '-fwhole-program --combine' \
+	    'if gcc supports -fwhole-program --combine'
+	i=1
+elif test $ct = icc; then
+	ac_flags 1 fnobuiltinsetmode -fno-builtin-setmode
+	ac_flags 1 fnostrictaliasing -fno-strict-aliasing
+	ac_flags 1 fstacksecuritycheck -fstack-security-check
+	i=1
+elif test $ct = sunpro; then
+	phase=u
+	ac_flags 1 v -v
+	ac_flags 1 xc99 -xc99 'for support of ISO C99'
+	ac_flags 1 ipo -xipo 'for cross-module optimisation'
+	phase=x
+elif test $ct = hpcc; then
+	phase=u
+	ac_flags 1 agcc -Agcc 'for support of GCC extensions'
+	ac_flags 1 ac99 -AC99 'for support of ISO C99'
+	phase=x
+elif test $ct = dec; then
+	ac_flags 0 verb -verbose
+	ac_flags 1 rodata -readonly_strings
+elif test $ct = dmc; then
+	ac_flags 1 decl "${ccpc}-r" 'for strict prototype checks'
+	ac_flags 1 schk "${ccpc}-s" 'for stack overflow checking'
+elif test $ct = bcc; then
+	ac_flags 1 strpool "${ccpc}-d" 'if string pooling can be enabled'
+elif test $ct = mipspro; then
+	ac_flags 1 xc99 -c99 'for support of ISO C99'
+	ac_flags 1 fullwarn -fullwarn 'for remark output support'
+elif test $ct = msc; then
+	ac_flags 1 strpool "${ccpc}/GF" 'if string pooling can be enabled'
+	echo 'int main(void) { char test[64] = ""; return (*test); }' >x
+	ac_flags - 1 stackon "${ccpc}/GZ" 'if stack checks can be enabled' <x
+	ac_flags - 1 stckall "${ccpc}/Ge" 'stack checks for all functions' <x
+	ac_flags - 1 secuchk "${ccpc}/GS" 'for compiler security checks' <x
+	rmf x
+	ac_flags 1 wall "${ccpc}/Wall" 'to enable all warnings'
+	ac_flags 1 wp64 "${ccpc}/Wp64" 'to enable 64-bit warnings'
+elif test $ct = xlc; then
+	ac_flags 1 x99 -qlanglvl=extc99
+	test 1 = $HAVE_CAN_X99 || ac_flags 1 c99 -qlanglvl=stdc99
+	ac_flags 1 rodata "-qro -qroconst -qroptr"
+	ac_flags 1 rtcheck -qcheck=all
+	ac_flags 1 rtchkc -qextchk
+	ac_flags 1 wformat "-qformat=all -qformat=nozln"
+	#ac_flags 1 wp64 -qwarn64	# too verbose for now
+elif test $ct = tendra; then
+	ac_flags 0 ysystem -Ysystem
+	test 1 = $HAVE_CAN_YSYSTEM && CPPFLAGS="-Ysystem $CPPFLAGS"
+	ac_flags 1 extansi -Xa
+elif test $ct = tcc; then
+	ac_flags 1 boundschk -b
+elif test $ct = clang; then
+	i=1
+elif test $ct = nwcc; then
+	i=1
+	#broken# ac_flags 1 ssp -stackprotect
+fi
+# flags common to a subset of compilers (run with -Werror on gcc)
+if test 1 = $i; then
+	ac_flags 1 stdg99 -std=gnu99 'for support of ISO C99 + GCC extensions'
+	test 1 = $HAVE_CAN_STDG99 || \
+	    ac_flags 1 stdc99 -std=c99 'for support of ISO C99'
+	ac_flags 1 wall -Wall
+fi
+phase=x
+
+# The following tests run with -Werror or similar (all compilers) if possible
+NOWARN=$DOWARN
+test $ct = pcc && phase=u
+
+#
+# Compiler: check for stuff that only generates warnings
+#
+ac_test attribute_bounded '' 'for __attribute__((bounded))' <<-'EOF'
+	#if defined(__GNUC__) && (__GNUC__ < 2)
+	/* force a failure: gcc 1.42 has a false positive here */
+	int main(void) { return (thiswillneverbedefinedIhope()); }
+	#else
+	#include <string.h>
+	#undef __attribute__
+	int xcopy(const void *, void *, size_t)
+	    __attribute__((bounded (buffer, 1, 3)))
+	    __attribute__((bounded (buffer, 2, 3)));
+	int main(int ac, char *av[]) { return (xcopy(av[0], av[--ac], 1)); }
+	int xcopy(const void *s, void *d, size_t n) {
+		memmove(d, s, n); return ((int)n);
+	}
+	#endif
+EOF
+ac_test attribute_format '' 'for __attribute__((format))' <<-'EOF'
+	#if defined(__GNUC__) && (__GNUC__ < 2)
+	/* force a failure: gcc 1.42 has a false positive here */
+	int main(void) { return (thiswillneverbedefinedIhope()); }
+	#else
+	#include <stdio.h>
+	#undef __attribute__
+	#undef printf
+	extern int printf(const char *format, ...)
+	    __attribute__((format (printf, 1, 2)));
+	int main(int ac, char **av) { return (printf("%s%d", *av, ac)); }
+	#endif
+EOF
+ac_test attribute_nonnull '' 'for __attribute__((nonnull))' <<-'EOF'
+	#if defined(__GNUC__) && (__GNUC__ < 2)
+	/* force a failure: gcc 1.42 has a false positive here */
+	int main(void) { return (thiswillneverbedefinedIhope()); }
+	#else
+	int foo(char *s1, char *s2) __attribute__((nonnull));
+	int bar(char *s1, char *s2) __attribute__((nonnull (1, 2)));
+	int baz(char *s) __attribute__((nonnull (1)));
+	int foo(char *s1, char *s2) { return (bar(s2, s1)); }
+	int bar(char *s1, char *s2) { return (baz(s1) - baz(s2)); }
+	int baz(char *s) { return (*s); }
+	int main(int ac, char **av) { return (ac == foo(av[0], av[ac-1])); }
+	#endif
+EOF
+ac_test attribute_noreturn '' 'for __attribute__((noreturn))' <<-'EOF'
+	#if defined(__GNUC__) && (__GNUC__ < 2)
+	/* force a failure: gcc 1.42 has a false positive here */
+	int main(void) { return (thiswillneverbedefinedIhope()); }
+	#else
+	#include <stdlib.h>
+	#undef __attribute__
+	void fnord(void) __attribute__((noreturn));
+	int main(void) { fnord(); }
+	void fnord(void) { exit(0); }
+	#endif
+EOF
+ac_test attribute_unused '' 'for __attribute__((unused))' <<-'EOF'
+	#if defined(__GNUC__) && (__GNUC__ < 2)
+	/* force a failure: gcc 1.42 has a false positive here */
+	int main(void) { return (thiswillneverbedefinedIhope()); }
+	#else
+	int main(int ac __attribute__((unused)), char **av
+	    __attribute__((unused))) { return (0); }
+	#endif
+EOF
+ac_test attribute_used '' 'for __attribute__((used))' <<-'EOF'
+	#if defined(__GNUC__) && (__GNUC__ < 2)
+	/* force a failure: gcc 1.42 has a false positive here */
+	int main(void) { return (thiswillneverbedefinedIhope()); }
+	#else
+	static const char fnord[] __attribute__((used)) = "42";
+	int main(void) { return (0); }
+	#endif
+EOF
+
+# End of tests run with -Werror
+NOWARN=$save_NOWARN
+phase=x
+
+#
+# mksh: flavours (full/small mksh, omit certain stuff)
+#
+if ac_ifcpp 'ifdef MKSH_SMALL' isset_MKSH_SMALL '' \
+    "if a reduced-feature mksh is requested"; then
+	#XXX this sucks; fix it for *all* compilers
+	case $ct in
+	clang|icc|nwcc)
+		ac_flags 1 fnoinline -fno-inline
+		;;
+	gcc)
+		NOWARN=$DOWARN; phase=u
+		ac_flags 1 fnoinline -fno-inline
+		NOWARN=$save_NOWARN; phase=x
+		;;
+	sunpro)
+		ac_flags 1 fnoinline -xinline=
+		;;
+	xlc)
+		ac_flags 1 fnoinline -qnoinline
+		;;
+	esac
+
+	: ${HAVE_MKNOD=0}
+	: ${HAVE_NICE=0}
+	: ${HAVE_REVOKE=0}
+	: ${HAVE_PERSISTENT_HISTORY=0}
+	check_categories=$check_categories,smksh
+	HAVE_ISSET_MKSH_CONSERVATIVE_FDS=1	# from sh.h
+fi
+ac_ifcpp 'ifdef MKSH_BINSHREDUCED' isset_MKSH_BINSHREDUCED '' \
+    "if a reduced-feature sh is requested" && \
+    check_categories=$check_categories,binsh
+ac_ifcpp 'ifdef MKSH_UNEMPLOYED' isset_MKSH_UNEMPLOYED '' \
+    "if mksh will be built without job control" && \
+    check_categories=$check_categories,arge
+ac_ifcpp 'ifdef MKSH_ASSUME_UTF8' isset_MKSH_ASSUME_UTF8 '' \
+    'if the default UTF-8 mode is specified' && : ${HAVE_SETLOCALE_CTYPE=0}
+ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \
+    'if traditional/conservative fd use is requested' && \
+    check_categories=$check_categories,convfds
+
+#
+# Environment: headers
+#
+ac_header sys/param.h
+ac_header sys/mkdev.h sys/types.h
+ac_header sys/mman.h sys/types.h
+ac_header sys/sysmacros.h
+ac_header grp.h sys/types.h
+ac_header libgen.h
+ac_header libutil.h sys/types.h
+ac_header paths.h
+ac_header stdbool.h
+ac_header stdint.h stdarg.h
+ac_header strings.h sys/types.h
+ac_header ulimit.h sys/types.h
+ac_header values.h
+
+#
+# Environment: definitions
+#
+echo '#include <sys/types.h>
+/* check that off_t can represent 2^63-1 correctly, thx FSF */
+#define LARGE_OFF_T (((off_t)1 << 62) - 1 + ((off_t)1 << 62))
+int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 &&
+    LARGE_OFF_T % 2147483647 == 1) ? 1 : -1];
+int main(void) { return (0); }' >lft.c
+ac_testn can_lfs '' "for large file support" <lft.c
+save_CPPFLAGS=$CPPFLAGS
+CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=64"
+ac_testn can_lfs_sus '!' can_lfs 0 "... with -D_FILE_OFFSET_BITS=64" <lft.c
+if test 0 = $HAVE_CAN_LFS_SUS; then
+	CPPFLAGS="$save_CPPFLAGS -D_LARGE_FILES=1"
+	ac_testn can_lfs_aix '!' can_lfs 0 "... with -D_LARGE_FILES=1" <lft.c
+	test 1 = $HAVE_CAN_LFS_AIX || CPPFLAGS=$save_CPPFLAGS
+fi
+rmf lft*	# end of large file support test
+
+#
+# Environment: types
+#
+ac_test can_inttypes '!' stdint_h 1 "for standard 32-bit integer types" <<-'EOF'
+	#include <sys/types.h>
+	#include <stddef.h>
+	int main(int ac, char **av) { return ((uint32_t)(ptrdiff_t)*av + (int32_t)ac); }
+EOF
+ac_test can_ucbints '!' can_inttypes 1 "for UCB 32-bit integer types" <<-'EOF'
+	#include <sys/types.h>
+	#include <stddef.h>
+	int main(int ac, char **av) { return ((u_int32_t)(ptrdiff_t)*av + (int32_t)ac); }
+EOF
+ac_test can_int8type '!' stdint_h 1 "for standard 8-bit integer type" <<-'EOF'
+	#include <sys/types.h>
+	#include <stddef.h>
+	int main(int ac, char **av) { return ((uint8_t)(ptrdiff_t)av[ac]); }
+EOF
+ac_test can_ucbint8 '!' can_int8type 1 "for UCB 8-bit integer type" <<-'EOF'
+	#include <sys/types.h>
+	#include <stddef.h>
+	int main(int ac, char **av) { return ((u_int8_t)(ptrdiff_t)av[ac]); }
+EOF
+
+ac_test rlim_t <<-'EOF'
+	#include <sys/types.h>
+	#include <sys/time.h>
+	#include <sys/resource.h>
+	#include <unistd.h>
+	int main(void) { return ((int)(rlim_t)0); }
+EOF
+
+# only testn: added later below
+ac_testn sig_t <<-'EOF'
+	#include <sys/types.h>
+	#include <signal.h>
+	#include <stddef.h>
+	int main(void) { return ((int)(ptrdiff_t)(sig_t)kill(0,0)); }
+EOF
+
+ac_testn sighandler_t '!' sig_t 0 <<-'EOF'
+	#include <sys/types.h>
+	#include <signal.h>
+	#include <stddef.h>
+	int main(void) { return ((int)(ptrdiff_t)(sighandler_t)kill(0,0)); }
+EOF
+if test 1 = $HAVE_SIGHANDLER_T; then
+	CPPFLAGS="$CPPFLAGS -Dsig_t=sighandler_t"
+	HAVE_SIG_T=1
+fi
+
+ac_testn __sighandler_t '!' sig_t 0 <<-'EOF'
+	#include <sys/types.h>
+	#include <signal.h>
+	#include <stddef.h>
+	int main(void) { return ((int)(ptrdiff_t)(__sighandler_t)kill(0,0)); }
+EOF
+if test 1 = $HAVE___SIGHANDLER_T; then
+	CPPFLAGS="$CPPFLAGS -Dsig_t=__sighandler_t"
+	HAVE_SIG_T=1
+fi
+
+test 1 = $HAVE_SIG_T || CPPFLAGS="$CPPFLAGS -Dsig_t=nosig_t"
+ac_cppflags SIG_T
+
+#
+# Environment: signals
+#
+test x"NetBSD" = x"$TARGET_OS" && $e Ignore the compatibility warning.
+
+for what in name list; do
+	uwhat=`upper $what`
+	ac_testn sys_sig$what '' "the sys_sig${what}[] array" <<-EOF
+		extern const char *const sys_sig${what}[];
+		int main(void) { return (sys_sig${what}[0][0]); }
+	EOF
+	ac_testn _sys_sig$what '!' sys_sig$what 0 "the _sys_sig${what}[] array" <<-EOF
+		extern const char *const _sys_sig${what}[];
+		int main(void) { return (_sys_sig${what}[0][0]); }
+	EOF
+	if eval "test 1 = \$HAVE__SYS_SIG$uwhat"; then
+		CPPFLAGS="$CPPFLAGS -Dsys_sig$what=_sys_sig$what"
+		eval "HAVE_SYS_SIG$uwhat=1"
+	fi
+	ac_cppflags SYS_SIG$uwhat
+done
+
+ac_test strsignal '!' sys_siglist 0 <<-'EOF'
+	#include <string.h>
+	#include <signal.h>
+	int main(void) { return (strsignal(1)[0]); }
+EOF
+
+#
+# Environment: library functions
+#
+ac_testn flock_ex '' 'flock and mmap' <<-'EOF'
+	#include <sys/types.h>
+	#include <sys/file.h>
+	#include <sys/mman.h>
+	#include <fcntl.h>
+	#include <stdlib.h>
+	int main(void) { return ((void *)mmap(NULL, (size_t)flock(0, LOCK_EX),
+	    PROT_READ, MAP_PRIVATE, 0, (off_t)0) == (void *)NULL ? 1 :
+	    munmap(NULL, 0)); }
+EOF
+
+ac_test getrusage <<-'EOF'
+	#define MKSH_INCLUDES_ONLY
+	#include "sh.h"
+	int main(void) {
+		struct rusage ru;
+		return (getrusage(RUSAGE_SELF, &ru) +
+		    getrusage(RUSAGE_CHILDREN, &ru));
+	}
+EOF
+
+ac_test killpg <<-'EOF'
+	#include <signal.h>
+	int main(int ac, char *av[]) { return (av[0][killpg(123, ac)]); }
+EOF
+
+ac_test mknod '' 'if to use mknod(), makedev() and friends' <<-'EOF'
+	#define MKSH_INCLUDES_ONLY
+	#include "sh.h"
+	int main(int ac, char *av[]) {
+		dev_t dv;
+		dv = makedev((unsigned int)ac, (unsigned int)av[0][0]);
+		return (mknod(av[0], (mode_t)0, dv) ? (int)major(dv) :
+		    (int)minor(dv));
+	}
+EOF
+
+ac_test mkstemp <<-'EOF'
+	#include <stdlib.h>
+	#include <unistd.h>
+	int main(void) { char tmpl[] = "X"; return (mkstemp(tmpl)); }
+EOF
+
+ac_test nice <<-'EOF'
+	#include <unistd.h>
+	int main(void) { return (nice(4)); }
+EOF
+
+ac_test revoke <<-'EOF'
+	#include <sys/types.h>
+	#if HAVE_LIBUTIL_H
+	#include <libutil.h>
+	#endif
+	#include <unistd.h>
+	int main(int ac, char *av[]) { return (ac + revoke(av[0])); }
+EOF
+
+ac_test setlocale_ctype '' 'setlocale(LC_CTYPE, "")' <<-'EOF'
+	#include <locale.h>
+	#include <stddef.h>
+	int main(void) { return ((int)(ptrdiff_t)(void *)setlocale(LC_CTYPE, "")); }
+EOF
+
+ac_test langinfo_codeset setlocale_ctype 0 'nl_langinfo(CODESET)' <<-'EOF'
+	#include <langinfo.h>
+	#include <stddef.h>
+	int main(void) { return ((int)(ptrdiff_t)(void *)nl_langinfo(CODESET)); }
+EOF
+
+ac_test setmode mknod 1 <<-'EOF'
+	/* XXX imake style */
+	/* XXX conditions correct? */
+	#if defined(__MSVCRT__) || defined(__CYGWIN__)
+	/* force a failure: Win32 setmode() is not what we want... */
+	int main(void) { return (thiswillneverbedefinedIhope()); }
+	#else
+	#include <sys/types.h>
+	#include <unistd.h>
+	int main(int ac, char *av[]) { return (getmode(setmode(av[0]),
+	    (mode_t)ac)); }
+	#endif
+EOF
+
+ac_test setresugid <<-'EOF'
+	#include <sys/types.h>
+	#include <unistd.h>
+	int main(void) { setresuid(0,0,0); return (setresgid(0,0,0)); }
+EOF
+
+ac_test setgroups setresugid 0 <<-'EOF'
+	#include <sys/types.h>
+	#if HAVE_GRP_H
+	#include <grp.h>
+	#endif
+	#include <unistd.h>
+	int main(void) { gid_t gid = 0; return (setgroups(0, &gid)); }
+EOF
+
+ac_test strcasestr <<-'EOF'
+	#include <sys/types.h>
+	#include <stddef.h>
+	#include <string.h>
+	#if HAVE_STRINGS_H
+	#include <strings.h>
+	#endif
+	int main(int ac, char *av[]) {
+		return ((int)(ptrdiff_t)(void *)strcasestr(*av, av[ac]));
+	}
+EOF
+
+ac_test strlcpy <<-'EOF'
+	#include <string.h>
+	int main(int ac, char *av[]) { return (strlcpy(*av, av[1],
+	    (size_t)ac)); }
+EOF
+
+#
+# check headers for declarations
+#
+save_CC=$CC; save_LDFLAGS=$LDFLAGS; save_LIBS=$LIBS
+CC="$CC -c -o $tcfn"; LDFLAGS=; LIBS=
+ac_test '!' flock_decl flock_ex 1 'if flock() does not need to be declared' <<-'EOF'
+	#define MKSH_INCLUDES_ONLY
+	#include "sh.h"
+	long flock(void);		/* this clashes if defined before */
+	int main(void) { return ((int)flock()); }
+EOF
+ac_test '!' revoke_decl revoke 1 'if revoke() does not need to be declared' <<-'EOF'
+	#define MKSH_INCLUDES_ONLY
+	#include "sh.h"
+	long revoke(void);		/* this clashes if defined before */
+	int main(void) { return ((int)revoke()); }
+EOF
+ac_test sys_siglist_decl sys_siglist 1 'if sys_siglist[] does not need to be declared' <<-'EOF'
+	#define MKSH_INCLUDES_ONLY
+	#include "sh.h"
+	int main(void) { return (sys_siglist[0][0]); }
+EOF
+CC=$save_CC; LDFLAGS=$save_LDFLAGS; LIBS=$save_LIBS
+
+#
+# other checks
+#
+fd='if to use persistent history'
+ac_cache PERSISTENT_HISTORY || test 0 = $HAVE_FLOCK_EX || fv=1
+test 1 = $fv || check_categories=$check_categories,no-histfile
+ac_testdone
+ac_cppflags
+
+#
+# Compiler: Praeprocessor (only if needed)
+#
+test 0 = $HAVE_SYS_SIGNAME && if ac_testinit cpp_dd '' \
+    'checking if the C Preprocessor supports -dD'; then
+	echo '#define foo bar' >conftest.c
+	vv ']' "$CPP $CFLAGS $CPPFLAGS $NOWARN -dD conftest.c >x"
+	grep '#define foo bar' x >/dev/null 2>&1 && fv=1
+	rmf conftest.c x vv.out
+	ac_testdone
+fi
+
+#
+# End of mirtoconf checks
+#
+$e ... done.
+
+# Some operating systems have ancient versions of ed(1) writing
+# the character count to standard output; cope for that
+echo wq >x
+ed x <x 2>/dev/null | grep 3 >/dev/null 2>&1 && \
+    check_categories=$check_categories,$oldish_ed
+rmf x vv.out
+
+if test 0 = $HAVE_SYS_SIGNAME; then
+	if test 1 = $HAVE_CPP_DD; then
+		$e Generating list of signal names...
+	else
+		$e No list of signal names available via cpp. Falling back...
+	fi
+	sigseen=:
+	echo '#include <signal.h>
+#ifndef NSIG
+#if defined(_NSIG)
+#define NSIG _NSIG
+#elif defined(SIGMAX)
+#define NSIG (SIGMAX+1)
+#endif
+#endif
+mksh_cfg: NSIG' >conftest.c
+	NSIG=`vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \
+	    grep mksh_cfg: | sed 's/^mksh_cfg:[	 ]*\([0-9x ()+-]*\).*$/\1/'`
+	case $NSIG in
+	*[\ \(\)+-]*) NSIG=`awk "BEGIN { print $NSIG }"` ;;
+	esac
+	printf=printf
+	(printf hallo) >/dev/null 2>&1 || printf=echo
+	test $printf = echo || NSIG=`printf %d "$NSIG" 2>/dev/null`
+	$printf "NSIG=$NSIG ... "
+	sigs="ABRT ALRM BUS CHLD CLD CONT DIL EMT FPE HUP ILL INFO INT IO IOT"
+	sigs="$sigs KILL LOST PIPE PROF PWR QUIT RESV SAK SEGV STOP SYS TERM"
+	sigs="$sigs TRAP TSTP TTIN TTOU URG USR1 USR2 VTALRM WINCH XCPU XFSZ"
+	test 1 = $HAVE_CPP_DD && test $NSIG -gt 1 && sigs="$sigs "`vq \
+	    "$CPP $CFLAGS $CPPFLAGS $NOWARN -dD conftest.c" | \
+	    grep '[	 ]SIG[A-Z0-9]*[	 ]' | \
+	    sed 's/^\(.*[	 ]SIG\)\([A-Z0-9]*\)\([	 ].*\)$/\2/' | sort`
+	test $NSIG -gt 1 || sigs=
+	for name in $sigs; do
+		echo '#include <signal.h>' >conftest.c
+		echo mksh_cfg: SIG$name >>conftest.c
+		vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \
+		    grep mksh_cfg: | \
+		    sed 's/^mksh_cfg:[	 ]*\([0-9x]*\).*$/\1:'$name/
+	done | grep -v '^:' | while IFS=: read nr name; do
+		test $printf = echo || nr=`printf %d "$nr" 2>/dev/null`
+		test $nr -gt 0 && test $nr -le $NSIG || continue
+		case $sigseen in
+		*:$nr:*) ;;
+		*)	echo "		{ \"$name\", $nr },"
+			sigseen=$sigseen$nr:
+			$printf "$name=$nr " >&2
+			;;
+		esac
+	done 2>&1 >signames.inc
+	rmf conftest.c
+	$e done.
+fi
+
+addsrcs '!' HAVE_SETMODE setmode.c
+addsrcs '!' HAVE_STRLCPY strlcpy.c
+addsrcs USE_PRINTF_BUILTIN printf.c
+test 1 = "$USE_PRINTF_BUILTIN" && CPPFLAGS="$CPPFLAGS -DMKSH_PRINTF_BUILTIN"
+test 0 = "$HAVE_SETMODE" && CPPFLAGS="$CPPFLAGS -DHAVE_CONFIG_H -DCONFIG_H_FILENAME=\\\"sh.h\\\""
+test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose"
+
+$e $bi$me: Finished configuration testing, now producing output.$ao
+
+files=
+objs=
+sp=
+case $curdir in
+*\ *)	echo "#!./mksh" >test.sh ;;
+*)	echo "#!$curdir/mksh" >test.sh ;;
+esac
+cat >>test.sh <<-EOF
+	LC_ALL=C PATH='$PATH'; export LC_ALL PATH
+	test -n "\$KSH_VERSION" || exit 1
+	check_categories=$check_categories
+	print Testing mksh for conformance:
+	fgrep MirOS: '$srcdir/check.t'
+	fgrep MIRBSD '$srcdir/check.t'
+	print "This shell is actually:\\n\\t\$KSH_VERSION"
+	print 'test.sh built for mksh $dstversion'
+	cstr='\$os = defined \$^O ? \$^O : "unknown";'
+	cstr="\$cstr"'print \$os . ", Perl version " . \$];'
+	for perli in \$PERL perl5 perl no; do
+		[[ \$perli = no ]] && exit 1
+		perlos=\$(\$perli -e "\$cstr") 2>&- || continue
+		print "Perl interpreter '\$perli' running on '\$perlos'"
+		[[ -n \$perlos ]] && break
+	done
+	exec \$perli '$srcdir/check.pl' -s '$srcdir/check.t' -p '$curdir/mksh' \${check_categories:+-C} \${check_categories#,} \$*$tsts
+EOF
+chmod 755 test.sh
+test $HAVE_CAN_COMBINE$cm = 0combine && cm=normal
+if test $cm = llvm; then
+	emitbc="-emit-llvm -c"
+elif test $cm = dragonegg; then
+	emitbc="-S -flto"
+else
+	emitbc=-c
+fi
+echo set -x >Rebuild.sh
+for file in $SRCS; do
+	op=`echo x"$file" | sed 's/^x\(.*\)\.c$/\1./'`
+	test -f $file || file=$srcdir/$file
+	files="$files$sp$file"
+	sp=' '
+	echo "$CC $CFLAGS $CPPFLAGS $emitbc $file || exit 1" >>Rebuild.sh
+	if test $cm = dragonegg; then
+		echo "mv ${op}s ${op}ll" >>Rebuild.sh
+		echo "llvm-as ${op}ll || exit 1" >>Rebuild.sh
+		objs="$objs$sp${op}bc"
+	else
+		objs="$objs$sp${op}o"
+	fi
+done
+case $cm in
+dragonegg|llvm)
+	echo "rm -f mksh.s" >>Rebuild.sh
+	echo "llvm-link -o - $objs | opt $optflags | llc -o mksh.s" >>Rebuild.sh
+	lobjs=mksh.s
+	;;
+*)
+	lobjs=$objs
+	;;
+esac
+case $tcfn in
+a.exe)	mkshexe=mksh.exe ;;
+*)	mkshexe=mksh ;;
+esac
+echo tcfn=$mkshexe >>Rebuild.sh
+echo "$CC $CFLAGS $LDFLAGS -o \$tcfn $lobjs $LIBS $ccpr" >>Rebuild.sh
+echo 'test -f $tcfn || exit 1; size $tcfn' >>Rebuild.sh
+if test $cm = makefile; then
+	extras='emacsfn.h sh.h sh_flags.h var_spec.h'
+	test 0 = $HAVE_SYS_SIGNAME && extras="$extras signames.inc"
+	cat >Makefrag.inc <<EOF
+# Makefile fragment for building mksh $dstversion
+
+PROG=		$mkshexe
+MAN=		mksh.1
+SRCS=		$SRCS
+SRCS_FP=	$files
+OBJS_BP=	$objs
+INDSRCS=	$extras
+NONSRCS_INST=	dot.mkshrc \$(MAN)
+NONSRCS_NOINST=	Build.sh Makefile Rebuild.sh check.pl check.t test.sh
+CC=		$CC
+CFLAGS=		$CFLAGS
+CPPFLAGS=	$CPPFLAGS
+LDFLAGS=	$LDFLAGS
+LIBS=		$LIBS
+
+# not BSD make only:
+#VPATH=		$srcdir
+#all: \$(PROG)
+#\$(PROG): \$(OBJS_BP)
+#	\$(CC) \$(CFLAGS) \$(LDFLAGS) -o \$@ \$(OBJS_BP) \$(LIBS)
+#\$(OBJS_BP): \$(SRCS_FP) \$(NONSRCS)
+#.c.o:
+#	\$(CC) \$(CFLAGS) \$(CPPFLAGS) -c \$<
+
+# for all make variants:
+#REGRESS_FLAGS=	-v
+#regress:
+#	./test.sh \$(REGRESS_FLAGS)
+
+# for BSD make only:
+#.PATH: $srcdir
+#.include <bsd.prog.mk>
+EOF
+	$e
+	$e Generated Makefrag.inc successfully.
+	exit 0
+fi
+if test $cm = combine; then
+	objs="-o $mkshexe"
+	for file in $SRCS; do
+		test -f $file || file=$srcdir/$file
+		objs="$objs $file"
+	done
+	emitbc="-fwhole-program --combine"
+	v "$CC $CFLAGS $CPPFLAGS $LDFLAGS $emitbc $objs $LIBS $ccpr"
+elif test 1 = $pm; then
+	for file in $SRCS; do
+		test -f $file || file=$srcdir/$file
+		v "$CC $CFLAGS $CPPFLAGS $emitbc $file" &
+	done
+	wait
+else
+	for file in $SRCS; do
+		test $cm = dragonegg && \
+		    op=`echo x"$file" | sed 's/^x\(.*\)\.c$/\1./'`
+		test -f $file || file=$srcdir/$file
+		v "$CC $CFLAGS $CPPFLAGS $emitbc $file" || exit 1
+		if test $cm = dragonegg; then
+			v "mv ${op}s ${op}ll"
+			v "llvm-as ${op}ll" || exit 1
+		fi
+	done
+fi
+case $cm in
+dragonegg|llvm)
+	rmf mksh.s
+	v "llvm-link -o - $objs | opt $optflags | llc -o mksh.s"
+	;;
+esac
+tcfn=$mkshexe
+test $cm = combine || v "$CC $CFLAGS $LDFLAGS -o $tcfn $lobjs $LIBS $ccpr"
+test -f $tcfn || exit 1
+test 1 = $r || v "$NROFF -mdoc <'$srcdir/mksh.1' >mksh.cat1" || \
+    rmf mksh.cat1
+test 0 = $eq && v size $tcfn
+i=install
+test -f /usr/ucb/$i && i=/usr/ucb/$i
+test 1 = $eq && e=:
+$e
+$e Installing the shell:
+$e "# $i -c -s -o root -g bin -m 555 mksh /bin/mksh"
+$e "# grep -x /bin/mksh /etc/shells >/dev/null || echo /bin/mksh >>/etc/shells"
+$e "# $i -c -o root -g bin -m 444 dot.mkshrc /usr/share/doc/mksh/examples/"
+$e
+$e Installing the manual:
+if test -f mksh.cat1; then
+	$e "# $i -c -o root -g bin -m 444 mksh.cat1" \
+	    "/usr/share/man/cat1/mksh.0"
+	$e or
+fi
+$e "# $i -c -o root -g bin -m 444 mksh.1 /usr/share/man/man1/mksh.1"
+$e
+$e Run the regression test suite: ./test.sh
+$e Please also read the sample file dot.mkshrc and the fine manual.
+exit 0
diff --git a/mksh/src/check.pl b/mksh/src/check.pl
new file mode 100644
index 0000000..e793e95
--- /dev/null
+++ b/mksh/src/check.pl
@@ -0,0 +1,1241 @@
+# $MirOS: src/bin/mksh/check.pl,v 1.23 2009/06/10 18:12:43 tg Rel $
+# $OpenBSD: th,v 1.13 2006/05/18 21:27:23 miod Exp $
+#-
+# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+#	Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un-
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person's immediate fault when using the work as intended.
+#-
+# Example test:
+#		name: a-test
+#		description:
+#			a test to show how tests are done
+#		arguments: !-x!-f!
+#		stdin:
+#			echo -n *
+#			false
+#		expected-stdout: !
+#			*
+#		expected-stderr:
+#			+ echo -n *
+#			+ false
+#		expected-exit: 1
+#		---
+#	This runs the test-program (eg, mksh) with the arguments -x and -f,
+#	standard input is a file containing "echo hi*\nfalse\n". The program
+#	is expected to produce "hi*" (no trailing newline) on standard output,
+#	"+ echo hi*\n+false\n" on standard error, and an exit code of 1.
+#
+#
+# Format of test files:
+# - blank lines and lines starting with # are ignored
+# - a test file contains a series of tests
+# - a test is a series of tag:value pairs ended with a "---" line
+#   (leading/trailing spaces are stripped from the first line of value)
+# - test tags are:
+#	Tag			Flag	Description
+#	-----			----	-----------
+#	name			r	The name of the test; should be unique
+#	description		m	What test does
+#	arguments		M	Arguments to pass to the program;
+#					default is no arguments.
+#	script			m	Value is written to a file which
+#					is passed as an argument to the program
+#					(after the arguments arguments)
+#	stdin			m	Value is written to a file which is
+#					used as standard-input for the program;
+#					default is to use /dev/null.
+#	perl-setup		m	Value is a perl script which is executed
+#					just before the test is run. Try to
+#					avoid using this...
+#	perl-cleanup		m	Value is a perl script which is executed
+#					just after the test is run. Try to
+#					avoid using this...
+#	env-setup		M	Value is a list of NAME=VALUE elements
+#					which are put in the environment before
+#					the test is run. If the =VALUE is
+#					missing, NAME is removed from the
+#					environment. Programs are run with
+#					the following minimal environment:
+#					    HOME, LD_LIBRARY_PATH, LOCPATH,
+#					    LOGNAME, PATH, SHELL, USER
+#					(values taken from the environment of
+#					the test harness).
+#					ENV is set to /nonexistant.
+#					__progname is set to the -p argument.
+#					__perlname is set to $^X (perlexe).
+#	file-setup		mps	Used to create files, directories
+#					and symlinks. First word is either
+#					file, dir or symlink; second word is
+#					permissions; this is followed by a
+#					quoted word that is the name of the
+#					file; the end-quote should be followed
+#					by a newline, then the file data
+#					(if any). The first word may be
+#					preceded by a ! to strip the trailing
+#					newline in a symlink.
+#	file-result		mps	Used to verify a file, symlink or
+#					directory is created correctly.
+#					The first word is either
+#					file, dir or symlink; second word is
+#					expected permissions; third word
+#					is user-id; fourth is group-id;
+#					fifth is "exact" or "pattern"
+#					indicating whether the file contents
+#					which follow is to be matched exactly
+#					or if it is a regular expression.
+#					The fifth argument is the quoted name
+#					of the file that should be created.
+#					The end-quote should be followed
+#					by a newline, then the file data
+#					(if any). The first word may be
+#					preceded by a ! to strip the trailing
+#					newline in the file contents.
+#					The permissions, user and group fields
+#					may be * meaning accept any value.
+#	time-limit			Time limit - the program is sent a
+#					SIGKILL N seconds. Default is no
+#					limit.
+#	expected-fail			'yes' if the test is expected to fail.
+#	expected-exit			expected exit code. Can be a number,
+#					or a C expression using the variables
+#					e, s and w (exit code, termination
+#					signal, and status code).
+#	expected-stdout		m	What the test should generate on stdout;
+#					default is to expect no output.
+#	expected-stdout-pattern	m	A perl pattern which matches the
+#					expected output.
+#	expected-stderr		m	What the test should generate on stderr;
+#					default is to expect no output.
+#	expected-stderr-pattern	m	A perl pattern which matches the
+#					expected standard error.
+#	category		m	Specify a comma separated list of
+#					'categories' of program that the test
+#					is to be run for. A category can be
+#					negated by prefixing the name with a !.
+#					The idea is that some tests in a
+#					test suite may apply to a particular
+#					program version and shouldn't be run
+#					on other versions. The category(s) of
+#					the program being tested can be
+#					specified on the command line.
+#					One category os:XXX is predefined
+#					(XXX is the operating system name,
+#					eg, linux, dec_osf).
+# Flag meanings:
+#	r	tag is required (eg, a test must have a name tag).
+#	m	value can be multiple lines. Lines must be prefixed with
+#		a tab. If the value part of the initial tag:value line is
+#			- empty: the initial blank line is stripped.
+#			- a lone !: the last newline in the value is stripped;
+#	M	value can be multiple lines (prefixed by a tab) and consists
+#		of multiple fields, delimited by a field separator character.
+#		The value must start and end with the f-s-c.
+#	p	tag takes parameters (used with m).
+#	s	tag can be used several times.
+
+use POSIX qw(EINTR);
+use Getopt::Std;
+use Config;
+
+$os = defined $^O ? $^O : 'unknown';
+
+($prog = $0) =~ s#.*/##;
+
+$Usage = <<EOF ;
+Usage: $prog [-s test-set] [-C category] [-p prog] [-v] [-e e=v] name ...
+	-p p	Use p as the program to test
+	-C c	Specify the comma separated list of categories the program
+		belongs to (see category field).
+	-s s	Read tests from file s; if s is a directory, it is recursively
+		scaned for test files (which end in .t).
+	-t t	Use t as default time limit for tests (default is unlimited)
+	-P	program (-p) string has multiple words, and the program is in
+		the path (kludge option)
+	-v	Verbose mode: print reason test failed.
+	-e e=v	Set the environment variable e to v for all tests
+		(if no =v is given, the current value is used)
+		Only one -e option can be given at the moment, sadly.
+	name	specifies the name of the test(s) to run; if none are
+		specified, all tests are run.
+EOF
+
+# See comment above for flag meanings
+%test_fields = (
+	'name',				'r',
+	'description',			'm',
+	'arguments',			'M',
+	'script',			'm',
+	'stdin',			'm',
+	'perl-setup',			'm',
+	'perl-cleanup',			'm',
+	'env-setup',			'M',
+	'file-setup',			'mps',
+	'file-result',			'mps',
+	'time-limit',			'',
+	'expected-fail',		'',
+	'expected-exit',		'',
+	'expected-stdout',		'm',
+	'expected-stdout-pattern',	'm',
+	'expected-stderr',		'm',
+	'expected-stderr-pattern',	'm',
+	'category',			'm',
+	);
+# Filled in by read_test()
+%internal_test_fields = (
+	':full-name', 1,		# file:name
+	':long-name', 1,		# dir/file:lineno:name
+	);
+
+# Categories of the program under test. Provide the current
+# os by default.
+%categories = (
+	"os:$os", '1'
+	);
+
+$temps = "/tmp/rts$$";
+$tempi = "/tmp/rti$$";
+$tempo = "/tmp/rto$$";
+$tempe = "/tmp/rte$$";
+$tempdir = "/tmp/rtd$$";
+
+$nfailed = 0;
+$nxfailed = 0;
+$npassed = 0;
+$nxpassed = 0;
+
+%known_tests = ();
+
+if (!getopts('C:p:Ps:t:ve:')) {
+    print STDERR $Usage;
+    exit 1;
+}
+
+die "$prog: no program specified (use -p)\n" if !defined $opt_p;
+die "$prog: no test set specified (use -s)\n" if !defined $opt_s;
+$test_prog = $opt_p;
+$verbose = defined $opt_v && $opt_v;
+$test_set = $opt_s;
+if (defined $opt_t) {
+    die "$prog: bad -t argument (should be number > 0): $opt_t\n"
+	if $opt_t !~ /^\d+$/ || $opt_t <= 0;
+    $default_time_limit = $opt_t;
+}
+$program_kludge = defined $opt_P ? $opt_P : 0;
+
+if (defined $opt_C) {
+    foreach $c (split(',', $opt_C)) {
+	$c =~ s/\s+//;
+	die "$prog: categories can't be negated on the command line\n"
+	    if ($c =~ /^!/);
+	$categories{$c} = 1;
+    }
+}
+
+# Note which tests are to be run.
+%do_test = ();
+grep($do_test{$_} = 1, @ARGV);
+$all_tests = @ARGV == 0;
+
+# Set up a very minimal environment
+%new_env = ();
+foreach $env (('HOME', 'LD_LIBRARY_PATH', 'LOCPATH', 'LOGNAME',
+  'PATH', 'SHELL', 'USER')) {
+    $new_env{$env} = $ENV{$env} if defined $ENV{$env};
+}
+$new_env{'ENV'} = '/nonexistant';
+if (($os eq 'VMS') || ($Config{perlpath} =~ m/$Config{_exe}$/i)) {
+	$new_env{'__perlname'} = $Config{perlpath};
+} else {
+	$new_env{'__perlname'} = $Config{perlpath} . $Config{_exe};
+}
+if (defined $opt_e) {
+    # XXX need a way to allow many -e arguments...
+    if ($opt_e =~ /^([a-zA-Z_]\w*)(|=(.*))$/) {
+	$new_env{$1} = $2 eq '' ? $ENV{$1} : $3;
+    } else {
+	die "$0: bad -e argument: $opt_e\n";
+    }
+}
+%old_env = %ENV;
+
+die "$prog: couldn't make directory $tempdir - $!\n" if !mkdir($tempdir, 0777);
+
+chop($pwd = `pwd 2>/dev/null`);
+die "$prog: couldn't get current working directory\n" if $pwd eq '';
+die "$prog: couldn't cd to $pwd - $!\n" if !chdir($pwd);
+
+if (!$program_kludge) {
+    $test_prog = "$pwd/$test_prog" if substr($test_prog, 0, 1) ne '/';
+    die "$prog: $test_prog is not executable - bye\n"
+	if (! -x $test_prog && $os ne 'os2');
+}
+
+@trap_sigs = ('TERM', 'QUIT', 'INT', 'PIPE', 'HUP');
+@SIG{@trap_sigs} = ('cleanup_exit') x @trap_sigs;
+$child_kill_ok = 0;
+$SIG{'ALRM'} = 'catch_sigalrm';
+
+$| = 1;
+
+if (-d $test_set) {
+    $file_prefix_skip = length($test_set) + 1;
+    $ret = &process_test_dir($test_set);
+} else {
+    $file_prefix_skip = 0;
+    $ret = &process_test_file($test_set);
+}
+&cleanup_exit() if !defined $ret;
+
+$tot_failed = $nfailed + $nxfailed;
+$tot_passed = $npassed + $nxpassed;
+if ($tot_failed || $tot_passed) {
+    print "Total failed: $tot_failed";
+    print " ($nxfailed unexpected)" if $nxfailed;
+    print " (as expected)" if $nfailed && !$nxfailed;
+    print "\nTotal passed: $tot_passed";
+    print " ($nxpassed unexpected)" if $nxpassed;
+    print "\n";
+}
+
+&cleanup_exit('ok');
+
+sub
+cleanup_exit
+{
+    local($sig, $exitcode) = ('', 1);
+
+    if ($_[0] eq 'ok') {
+	$exitcode = 0;
+    } elsif ($_[0] ne '') {
+	$sig = $_[0];
+    }
+
+    unlink($tempi, $tempo, $tempe, $temps);
+    &scrub_dir($tempdir) if defined $tempdir;
+    rmdir($tempdir) if defined $tempdir;
+
+    if ($sig) {
+	$SIG{$sig} = 'DEFAULT';
+	kill $sig, $$;
+	return;
+    }
+    exit $exitcode;
+}
+
+sub
+catch_sigalrm
+{
+    $SIG{'ALRM'} = 'catch_sigalrm';
+    kill(9, $child_pid) if $child_kill_ok;
+    $child_killed = 1;
+}
+
+sub
+process_test_dir
+{
+    local($dir) = @_;
+    local($ret, $file);
+    local(@todo) = ();
+
+    if (!opendir(DIR, $dir)) {
+	print STDERR "$prog: can't open directory $dir - $!\n";
+	return undef;
+    }
+    while (defined ($file = readdir(DIR))) {
+	push(@todo, $file) if $file =~ /^[^.].*\.t$/;
+    }
+    closedir(DIR);
+
+    foreach $file (@todo) {
+	$file = "$dir/$file";
+	if (-d $file) {
+	    $ret = &process_test_dir($file);
+	} elsif (-f _) {
+	    $ret = &process_test_file($file);
+	}
+	last if !defined $ret;
+    }
+
+    return $ret;
+}
+
+sub
+process_test_file
+{
+    local($file) = @_;
+    local($ret);
+
+    if (!open(IN, $file)) {
+	print STDERR "$prog: can't open $file - $!\n";
+	return undef;
+    }
+    binmode(IN);
+    while (1) {
+	$ret = &read_test($file, IN, *test);
+	last if !defined $ret || !$ret;
+	next if !$all_tests && !$do_test{$test{'name'}};
+	next if !&category_check(*test);
+	$ret = &run_test(*test);
+	last if !defined $ret;
+    }
+    close(IN);
+
+    return $ret;
+}
+
+sub
+run_test
+{
+    local(*test) = @_;
+    local($name) = $test{':full-name'};
+
+    if (defined $test{'stdin'}) {
+	return undef if !&write_file($tempi, $test{'stdin'});
+	$ifile = $tempi;
+    } else {
+	$ifile = '/dev/null';
+    }
+
+    if (defined $test{'script'}) {
+	return undef if !&write_file($temps, $test{'script'});
+    }
+
+    return undef if !&scrub_dir($tempdir);
+
+    if (!chdir($tempdir)) {
+	print STDERR "$prog: couldn't cd to $tempdir - $!\n";
+	return undef;
+    }
+
+    if (defined $test{'file-setup'}) {
+	local($i);
+	local($type, $perm, $rest, $c, $len, $name);
+
+	for ($i = 0; $i < $test{'file-setup'}; $i++) {
+	    $val = $test{"file-setup:$i"};
+
+	    # format is: type perm "name"
+	    ($type, $perm, $rest) =
+		split(' ', $val, 3);
+	    $c = substr($rest, 0, 1);
+	    $len = index($rest, $c, 1) - 1;
+	    $name = substr($rest, 1, $len);
+	    $rest = substr($rest, 2 + $len);
+	    $perm = oct($perm) if $perm =~ /^\d+$/;
+	    if ($type eq 'file') {
+		return undef if !&write_file($name, $rest);
+		if (!chmod($perm, $name)) {
+		    print STDERR
+		  "$prog:$test{':long-name'}: can't chmod $perm $name - $!\n";
+		    return undef;
+		}
+	    } elsif ($type eq 'dir') {
+		if (!mkdir($name, $perm)) {
+		    print STDERR
+		  "$prog:$test{':long-name'}: can't mkdir $perm $name - $!\n";
+		    return undef;
+		}
+	    } elsif ($type eq 'symlink') {
+		local($oumask) = umask($perm);
+		local($ret) = symlink($rest, $name);
+		umask($oumask);
+		if (!$ret) {
+		    print STDERR
+	    "$prog:$test{':long-name'}: couldn't create symlink $name - $!\n";
+		    return undef;
+		}
+	    }
+	}
+    }
+
+    if (defined $test{'perl-setup'}) {
+	eval $test{'perl-setup'};
+	if ($@ ne '') {
+	    print STDERR "$prog:$test{':long-name'}: error running perl-setup - $@\n";
+	    return undef;
+	}
+    }
+
+    $pid = fork;
+    if (!defined $pid) {
+	print STDERR "$prog: can't fork - $!\n";
+	return undef;
+    }
+    if (!$pid) {
+	@SIG{@trap_sigs} = ('DEFAULT') x @trap_sigs;
+	$SIG{'ALRM'} = 'DEFAULT';
+	if (defined $test{'env-setup'}) {
+	    local($var, $val, $i);
+
+	    foreach $var (split(substr($test{'env-setup'}, 0, 1),
+		$test{'env-setup'}))
+	    {
+		$i = index($var, '=');
+		next if $i == 0 || $var eq '';
+		if ($i < 0) {
+		    delete $new_env{$var};
+		} else {
+		    $new_env{substr($var, 0, $i)} = substr($var, $i + 1);
+		}
+	    }
+	}
+	if (!open(STDIN, "< $ifile")) {
+		print STDERR "$prog: couldn't open $ifile in child - $!\n";
+		kill('TERM', $$);
+	}
+	binmode(STDIN);
+	if (!open(STDOUT, "> $tempo")) {
+		print STDERR "$prog: couldn't open $tempo in child - $!\n";
+		kill('TERM', $$);
+	}
+	binmode(STDOUT);
+	if (!open(STDERR, "> $tempe")) {
+		print STDOUT "$prog: couldn't open $tempe in child - $!\n";
+		kill('TERM', $$);
+	}
+	binmode(STDERR);
+	if ($program_kludge) {
+	    @argv = split(' ', $test_prog);
+	} else {
+	    @argv = ($test_prog);
+	}
+	if (defined $test{'arguments'}) {
+		push(@argv,
+		     split(substr($test{'arguments'}, 0, 1),
+			   substr($test{'arguments'}, 1)));
+	}
+	push(@argv, $temps) if defined $test{'script'};
+
+	#XXX realpathise, use which/whence -p, or sth. like that
+	#XXX if !$program_kludge, we get by with not doing it for now tho
+	$new_env{'__progname'} = $argv[0];
+
+	# The following doesn't work with perl5...  Need to do it explicitly - yuck.
+	#%ENV = %new_env;
+	foreach $k (keys(%ENV)) {
+	    delete $ENV{$k};
+	}
+	$ENV{$k} = $v while ($k,$v) = each %new_env;
+
+	exec { $argv[0] } @argv;
+	print STDERR "$prog: couldn't execute $test_prog - $!\n";
+	kill('TERM', $$);
+	exit(95);
+    }
+    $child_pid = $pid;
+    $child_killed = 0;
+    $child_kill_ok = 1;
+    alarm($test{'time-limit'}) if defined $test{'time-limit'};
+    while (1) {
+	$xpid = waitpid($pid, 0);
+	$child_kill_ok = 0;
+	if ($xpid < 0) {
+	    next if $! == EINTR;
+	    print STDERR "$prog: error waiting for child - $!\n";
+	    return undef;
+	}
+	last;
+    }
+    $status = $?;
+    alarm(0) if defined $test{'time-limit'};
+
+    $failed = 0;
+    $why = '';
+
+    if ($child_killed) {
+	$failed = 1;
+	$why .= "\ttest timed out (limit of $test{'time-limit'} seconds)\n";
+    }
+
+    $ret = &eval_exit($test{'long-name'}, $status, $test{'expected-exit'});
+    return undef if !defined $ret;
+    if (!$ret) {
+	local($expl);
+
+	$failed = 1;
+	if (($status & 0xff) == 0x7f) {
+	    $expl = "stopped";
+	} elsif (($status & 0xff)) {
+	    $expl = "signal " . ($status & 0x7f);
+	} else {
+	    $expl = "exit-code " . (($status >> 8) & 0xff);
+	}
+	$why .=
+	"\tunexpected exit status $status ($expl), expected $test{'expected-exit'}\n";
+    }
+
+    $tmp = &check_output($test{'long-name'}, $tempo, 'stdout',
+		$test{'expected-stdout'}, $test{'expected-stdout-pattern'});
+    return undef if !defined $tmp;
+    if ($tmp ne '') {
+	$failed = 1;
+	$why .= $tmp;
+    }
+
+    $tmp = &check_output($test{'long-name'}, $tempe, 'stderr',
+		$test{'expected-stderr'}, $test{'expected-stderr-pattern'});
+    return undef if !defined $tmp;
+    if ($tmp ne '') {
+	$failed = 1;
+	$why .= $tmp;
+    }
+
+    $tmp = &check_file_result(*test);
+    return undef if !defined $tmp;
+    if ($tmp ne '') {
+	$failed = 1;
+	$why .= $tmp;
+    }
+
+    if (defined $test{'perl-cleanup'}) {
+	eval $test{'perl-cleanup'};
+	if ($@ ne '') {
+	    print STDERR "$prog:$test{':long-name'}: error running perl-cleanup - $@\n";
+	    return undef;
+	}
+    }
+
+    if (!chdir($pwd)) {
+	print STDERR "$prog: couldn't cd to $pwd - $!\n";
+	return undef;
+    }
+
+    if ($failed) {
+	if (!$test{'expected-fail'}) {
+	    print "FAIL $name\n";
+	    $nxfailed++;
+	} else {
+	    print "fail $name (as expected)\n";
+	    $nfailed++;
+	}
+	$why = "\tDescription"
+		. &wrap_lines($test{'description'}, " (missing)\n")
+		. $why;
+    } elsif ($test{'expected-fail'}) {
+	print "PASS $name (unexpectedly)\n";
+	$nxpassed++;
+    } else {
+	print "pass $name\n";
+	$npassed++;
+    }
+    print $why if $verbose;
+    return 0;
+}
+
+sub
+category_check
+{
+    local(*test) = @_;
+    local($c);
+
+    return 1 if (!defined $test{'category'});
+    local($ok) = 0;
+    foreach $c (split(',', $test{'category'})) {
+	$c =~ s/\s+//;
+	if ($c =~ /^!/) {
+	    $c = $';
+	    return 0 if (defined $categories{$c});
+	    $ok = 1;
+	} else {
+	    $ok = 1 if (defined $categories{$c});
+	}
+    }
+    return $ok;
+}
+
+sub
+scrub_dir
+{
+    local($dir) = @_;
+    local(@todo) = ();
+    local($file);
+
+    if (!opendir(DIR, $dir)) {
+	print STDERR "$prog: couldn't open directory $dir - $!\n";
+	return undef;
+    }
+    while (defined ($file = readdir(DIR))) {
+	push(@todo, $file) if $file ne '.' && $file ne '..';
+    }
+    closedir(DIR);
+    foreach $file (@todo) {
+	$file = "$dir/$file";
+	if (-d $file) {
+	    return undef if !&scrub_dir($file);
+	    if (!rmdir($file)) {
+		print STDERR "$prog: couldn't rmdir $file - $!\n";
+		return undef;
+	    }
+	} else {
+	    if (!unlink($file)) {
+		print STDERR "$prog: couldn't unlink $file - $!\n";
+		return undef;
+	    }
+	}
+    }
+    return 1;
+}
+
+sub
+write_file
+{
+    local($file, $str) = @_;
+
+    if (!open(TEMP, "> $file")) {
+	print STDERR "$prog: can't open $file - $!\n";
+	return undef;
+    }
+    binmode(TEMP);
+    print TEMP $str;
+    if (!close(TEMP)) {
+	print STDERR "$prog: error writing $file - $!\n";
+	return undef;
+    }
+    return 1;
+}
+
+sub
+check_output
+{
+    local($name, $file, $what, $expect, $expect_pat) = @_;
+    local($got) = '';
+    local($why) = '';
+    local($ret);
+
+    if (!open(TEMP, "< $file")) {
+	print STDERR "$prog:$name($what): couldn't open $file after running program - $!\n";
+	return undef;
+    }
+    binmode(TEMP);
+    while (<TEMP>) {
+	$got .= $_;
+    }
+    close(TEMP);
+    return compare_output($name, $what, $expect, $expect_pat, $got);
+}
+
+sub
+compare_output
+{
+    local($name, $what, $expect, $expect_pat, $got) = @_;
+    local($why) = '';
+
+    if (defined $expect_pat) {
+	$_ = $got;
+	$ret = eval "$expect_pat";
+	if ($@ ne '') {
+	    print STDERR "$prog:$name($what): error evaluating $what pattern: $expect_pat - $@\n";
+	    return undef;
+	}
+	if (!$ret) {
+	    $why = "\tunexpected $what - wanted pattern";
+	    $why .= &wrap_lines($expect_pat);
+	    $why .= "\tgot";
+	    $why .= &wrap_lines($got);
+	}
+    } else {
+	$expect = '' if !defined $expect;
+	if ($got ne $expect) {
+	    $why .= "\tunexpected $what - " . &first_diff($expect, $got) . "\n";
+	    $why .= "\twanted";
+	    $why .= &wrap_lines($expect);
+	    $why .= "\tgot";
+	    $why .= &wrap_lines($got);
+	}
+    }
+    return $why;
+}
+
+sub
+wrap_lines
+{
+    local($str, $empty) = @_;
+    local($nonl) = substr($str, -1, 1) ne "\n";
+
+    return (defined $empty ? $empty : " nothing\n") if $str eq '';
+    substr($str, 0, 0) = ":\n";
+    $str =~ s/\n/\n\t\t/g;
+    if ($nonl) {
+	$str .= "\n\t[incomplete last line]\n";
+    } else {
+	chop($str);
+	chop($str);
+    }
+    return $str;
+}
+
+sub
+first_diff
+{
+    local($exp, $got) = @_;
+    local($lineno, $char) = (1, 1);
+    local($i, $exp_len, $got_len);
+    local($ce, $cg);
+
+    $exp_len = length($exp);
+    $got_len = length($got);
+    if ($exp_len != $got_len) {
+	if ($exp_len < $got_len) {
+	    if (substr($got, 0, $exp_len) eq $exp) {
+		return "got too much output";
+	    }
+	} elsif (substr($exp, 0, $got_len) eq $got) {
+	    return "got too little output";
+	}
+    }
+    for ($i = 0; $i < $exp_len; $i++) {
+	$ce = substr($exp, $i, 1);
+	$cg = substr($got, $i, 1);
+	last if $ce ne $cg;
+	$char++;
+	if ($ce eq "\n") {
+	    $lineno++;
+	    $char = 1;
+	}
+    }
+    return "first difference: line $lineno, char $char (wanted '"
+	. &format_char($ce) . "', got '"
+	. &format_char($cg) . "'";
+}
+
+sub
+format_char
+{
+    local($ch, $s);
+
+    $ch = ord($_[0]);
+    if ($ch == 10) {
+	return '\n';
+    } elsif ($ch == 13) {
+	return '\r';
+    } elsif ($ch == 8) {
+	return '\b';
+    } elsif ($ch == 9) {
+	return '\t';
+    } elsif ($ch > 127) {
+	$ch -= 127;
+	$s = "M-";
+    } else {
+	$s = '';
+    }
+    if ($ch < 32) {
+	$s .= '^';
+	$ch += ord('@');
+    } elsif ($ch == 127) {
+	return $s . "^?";
+    }
+    return $s . sprintf("%c", $ch);
+}
+
+sub
+eval_exit
+{
+    local($name, $status, $expect) = @_;
+    local($expr);
+    local($w, $e, $s) = ($status, ($status >> 8) & 0xff, $status & 0x7f);
+
+    $e = -1000 if $status & 0xff;
+    $s = -1000 if $s == 0x7f;
+    if (!defined $expect) {
+	$expr = '$w == 0';
+    } elsif ($expect =~ /^(|-)\d+$/) {
+	$expr = "\$e == $expect";
+    } else {
+	$expr = $expect;
+	$expr =~ s/\b([wse])\b/\$$1/g;
+	$expr =~ s/\b(SIG[A-Z0-9]+)\b/&$1/g;
+    }
+    $w = eval $expr;
+    if ($@ ne '') {
+	print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $expect ($@)\n";
+	return undef;
+    }
+    return $w;
+}
+
+sub
+read_test
+{
+    local($file, $in, *test) = @_;
+    local($field, $val, $flags, $do_chop, $need_redo, $start_lineno);
+    local(%cnt, $sfield);
+
+    %test = ();
+    %cnt = ();
+    while (<$in>) {
+	next if /^\s*$/;
+	next if /^ *#/;
+	last if /^\s*---\s*$/;
+	$start_lineno = $. if !defined $start_lineno;
+	if (!/^([-\w]+):\s*(|\S|\S.*\S)\s*$/) {
+	    print STDERR "$prog:$file:$.: unrecognised line\n";
+	    return undef;
+	}
+	($field, $val) = ($1, $2);
+	$sfield = $field;
+	$flags = $test_fields{$field};
+	if (!defined $flags) {
+	    print STDERR "$prog:$file:$.: unrecognised field \"$field\"\n";
+	    return undef;
+	}
+	if ($flags =~ /s/) {
+	    local($cnt) = $cnt{$field}++;
+	    $test{$field} = $cnt{$field};
+	    $cnt = 0 if $cnt eq '';
+	    $sfield .= ":$cnt";
+	} elsif (defined $test{$field}) {
+	    print STDERR "$prog:$file:$.: multiple \"$field\" fields\n";
+	    return undef;
+	}
+	$do_chop = $flags !~ /m/;
+	$need_redo = 0;
+	if ($val eq '' || $val eq '!' || $flags =~ /p/) {
+	    if ($flags =~ /[Mm]/) {
+		if ($flags =~ /p/) {
+		    if ($val =~ /^!/) {
+			$do_chop = 1;
+			$val = $';
+		    } else {
+			$do_chop = 0;
+		    }
+		    if ($val eq '') {
+			print STDERR
+		"$prog:$file:$.: no parameters given for field \"$field\"\n";
+			return undef;
+		    }
+		} else {
+		    if ($val eq '!') {
+			$do_chop = 1;
+		    }
+		    $val = '';
+		}
+		while (<$in>) {
+		    last if !/^\t/;
+		    $val .= $';
+		}
+		chop $val if $do_chop;
+		$do_chop = 1;
+		$need_redo = 1;
+
+		# Syntax check on fields that can several instances
+		# (can give useful line numbers this way)
+
+		if ($field eq 'file-setup') {
+		    local($type, $perm, $rest, $c, $len, $name);
+
+		    # format is: type perm "name"
+		    if ($val !~ /^[ \t]*(\S+)[ \t]+(\S+)[ \t]+([^ \t].*)/) {
+			print STDERR
+		    "$prog:$file:$.: bad parameter line for file-setup field\n";
+			return undef;
+		    }
+		    ($type, $perm, $rest) = ($1, $2, $3);
+		    if ($type !~ /^(file|dir|symlink)$/) {
+			print STDERR
+		    "$prog:$file:$.: bad file type for file-setup: $type\n";
+			return undef;
+		    }
+		    if ($perm !~ /^\d+$/) {
+			print STDERR
+		    "$prog:$file:$.: bad permissions for file-setup: $type\n";
+			return undef;
+		    }
+		    $c = substr($rest, 0, 1);
+		    if (($len = index($rest, $c, 1) - 1) <= 0) {
+			print STDERR
+    "$prog:$file:$.: missing end quote for file name in file-setup: $rest\n";
+			return undef;
+		    }
+		    $name = substr($rest, 1, $len);
+		    if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) {
+			# Note: this is not a security thing - just a sanity
+			# check - a test can still use symlinks to get at files
+			# outside the test directory.
+			print STDERR
+"$prog:$file:$.: file name in file-setup is absolute or contains ..: $name\n";
+			return undef;
+		    }
+		}
+		if ($field eq 'file-result') {
+		    local($type, $perm, $uid, $gid, $matchType,
+			  $rest, $c, $len, $name);
+
+		    # format is: type perm uid gid matchType "name"
+		    if ($val !~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S.*)/) {
+			print STDERR
+		    "$prog:$file:$.: bad parameter line for file-result field\n";
+			return undef;
+		    }
+		    ($type, $perm, $uid, $gid, $matchType, $rest)
+			= ($1, $2, $3, $4, $5, $6);
+		    if ($type !~ /^(file|dir|symlink)$/) {
+			print STDERR
+		    "$prog:$file:$.: bad file type for file-result: $type\n";
+			return undef;
+		    }
+		    if ($perm !~ /^\d+$/ && $perm ne '*') {
+			print STDERR
+		    "$prog:$file:$.: bad permissions for file-result: $perm\n";
+			return undef;
+		    }
+		    if ($uid !~ /^\d+$/ && $uid ne '*') {
+			print STDERR
+		    "$prog:$file:$.: bad user-id for file-result: $uid\n";
+			return undef;
+		    }
+		    if ($gid !~ /^\d+$/ && $gid ne '*') {
+			print STDERR
+		    "$prog:$file:$.: bad group-id for file-result: $gid\n";
+			return undef;
+		    }
+		    if ($matchType !~ /^(exact|pattern)$/) {
+			print STDERR
+		"$prog:$file:$.: bad match type for file-result: $matchType\n";
+			return undef;
+		    }
+		    $c = substr($rest, 0, 1);
+		    if (($len = index($rest, $c, 1) - 1) <= 0) {
+			print STDERR
+    "$prog:$file:$.: missing end quote for file name in file-result: $rest\n";
+			return undef;
+		    }
+		    $name = substr($rest, 1, $len);
+		    if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) {
+			# Note: this is not a security thing - just a sanity
+			# check - a test can still use symlinks to get at files
+			# outside the test directory.
+			print STDERR
+"$prog:$file:$.: file name in file-result is absolute or contains ..: $name\n";
+			return undef;
+		    }
+		}
+	    } elsif ($val eq '') {
+		print STDERR
+		    "$prog:$file:$.: no value given for field \"$field\"\n";
+		return undef;
+	    }
+	}
+	$val .= "\n" if !$do_chop;
+	$test{$sfield} = $val;
+	redo if $need_redo;
+    }
+    if ($_ eq '') {
+	if (%test) {
+	    print STDERR
+	      "$prog:$file:$start_lineno: end-of-file while reading test\n";
+	    return undef;
+	}
+	return 0;
+    }
+
+    while (($field, $val) = each %test_fields) {
+	if ($val =~ /r/ && !defined $test{$field}) {
+	    print STDERR
+	      "$prog:$file:$start_lineno: required field \"$field\" missing\n";
+	    return undef;
+	}
+    }
+
+    $test{':full-name'} = substr($file, $file_prefix_skip) . ":$test{'name'}";
+    $test{':long-name'} = "$file:$start_lineno:$test{'name'}";
+
+    # Syntax check on specific fields
+    if (defined $test{'expected-fail'}) {
+	if ($test{'expected-fail'} !~ /^(yes|no)$/) {
+	    print STDERR
+	      "$prog:$test{':long-name'}: bad value for expected-fail field\n";
+	    return undef;
+	}
+	$test{'expected-fail'} = $1 eq 'yes';
+    } else {
+	$test{'expected-fail'} = 0;
+    }
+    if (defined $test{'arguments'}) {
+	local($firstc) = substr($test{'arguments'}, 0, 1);
+
+	if (substr($test{'arguments'}, -1, 1) ne $firstc) {
+	    print STDERR "$prog:$test{':long-name'}: arguments field doesn't start and end with the same character\n";
+	    return undef;
+	}
+    }
+    if (defined $test{'env-setup'}) {
+	local($firstc) = substr($test{'env-setup'}, 0, 1);
+
+	if (substr($test{'env-setup'}, -1, 1) ne $firstc) {
+	    print STDERR "$prog:$test{':long-name'}: env-setup field doesn't start and end with the same character\n";
+	    return undef;
+	}
+    }
+    if (defined $test{'expected-exit'}) {
+	local($val) = $test{'expected-exit'};
+
+	if ($val =~ /^(|-)\d+$/) {
+	    if ($val < 0 || $val > 255) {
+		print STDERR "$prog:$test{':long-name'}: expected-exit value $val not in 0..255\n";
+		return undef;
+	    }
+	} elsif ($val !~ /^([\s<>+-=*%\/&|!()]|\b[wse]\b|\bSIG[A-Z0-9]+\b)+$/) {
+	    print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $val\n";
+	    return undef;
+	}
+    } else {
+	$test{'expected-exit'} = 0;
+    }
+    if (defined $test{'expected-stdout'}
+	&& defined $test{'expected-stdout-pattern'})
+    {
+	print STDERR "$prog:$test{':long-name'}: can't use both expected-stdout and expected-stdout-pattern\n";
+	return undef;
+    }
+    if (defined $test{'expected-stderr'}
+	&& defined $test{'expected-stderr-pattern'})
+    {
+	print STDERR "$prog:$test{':long-name'}: can't use both expected-stderr and expected-stderr-pattern\n";
+	return undef;
+    }
+    if (defined $test{'time-limit'}) {
+	if ($test{'time-limit'} !~ /^\d+$/ || $test{'time-limit'} == 0) {
+	    print STDERR
+	      "$prog:$test{':long-name'}: bad value for time-limit field\n";
+	    return undef;
+	}
+    } elsif (defined $default_time_limit) {
+	$test{'time-limit'} = $default_time_limit;
+    }
+
+    if (defined $known_tests{$test{'name'}}) {
+	print STDERR "$prog:$test{':long-name'}: warning: duplicate test name ${test{'name'}}\n";
+    }
+    $known_tests{$test{'name'}} = 1;
+
+    return 1;
+}
+
+sub
+tty_msg
+{
+    local($msg) = @_;
+
+    open(TTY, "> /dev/tty") || return 0;
+    print TTY $msg;
+    close(TTY);
+    return 1;
+}
+
+sub
+never_called_funcs
+{
+	return 0;
+	&tty_msg("hi\n");
+	&never_called_funcs();
+	&catch_sigalrm();
+	$old_env{'foo'} = 'bar';
+	$internal_test_fields{'foo'} = 'bar';
+}
+
+sub
+check_file_result
+{
+    local(*test) = @_;
+
+    return '' if (!defined $test{'file-result'});
+
+    local($why) = '';
+    local($i);
+    local($type, $perm, $uid, $gid, $rest, $c, $len, $name);
+    local(@stbuf);
+
+    for ($i = 0; $i < $test{'file-result'}; $i++) {
+	$val = $test{"file-result:$i"};
+
+	# format is: type perm "name"
+	($type, $perm, $uid, $gid, $matchType, $rest) =
+	    split(' ', $val, 6);
+	$c = substr($rest, 0, 1);
+	$len = index($rest, $c, 1) - 1;
+	$name = substr($rest, 1, $len);
+	$rest = substr($rest, 2 + $len);
+	$perm = oct($perm) if $perm =~ /^\d+$/;
+
+	@stbuf = lstat($name);
+	if (!@stbuf) {
+	    $why .= "\texpected $type \"$name\" not created\n";
+	    next;
+	}
+	if ($perm ne '*' && ($stbuf[2] & 07777) != $perm) {
+	    $why .= "\t$type \"$name\" has unexpected permissions\n";
+	    $why .= sprintf("\t\texpected 0%o, found 0%o\n",
+		    $perm, $stbuf[2] & 07777);
+	}
+	if ($uid ne '*' && $stbuf[4] != $uid) {
+	    $why .= "\t$type \"$name\" has unexpected user-id\n";
+	    $why .= sprintf("\t\texpected %d, found %d\n",
+		    $uid, $stbuf[4]);
+	}
+	if ($gid ne '*' && $stbuf[5] != $gid) {
+	    $why .= "\t$type \"$name\" has unexpected group-id\n";
+	    $why .= sprintf("\t\texpected %d, found %d\n",
+		    $gid, $stbuf[5]);
+	}
+
+	if ($type eq 'file') {
+	    if (-l _ || ! -f _) {
+		$why .= "\t$type \"$name\" is not a regular file\n";
+	    } else {
+		local $tmp = &check_output($test{'long-name'}, $name,
+			    "$type contents in \"$name\"",
+			    $matchType eq 'exact' ? $rest : undef
+			    $matchType eq 'pattern' ? $rest : undef);
+		return undef if (!defined $tmp);
+		$why .= $tmp;
+	    }
+	} elsif ($type eq 'dir') {
+	    if ($rest !~ /^\s*$/) {
+		print STDERR "$prog:$test{':long-name'}: file-result test for directory $name should not have content specified\n";
+		return undef;
+	    }
+	    if (-l _ || ! -d _) {
+		$why .= "\t$type \"$name\" is not a directory\n";
+	    }
+	} elsif ($type eq 'symlink') {
+	    if (!-l _) {
+		$why .= "\t$type \"$name\" is not a symlink\n";
+	    } else {
+		local $content = readlink($name);
+		if (!defined $content) {
+		    print STDERR "$prog:$test{':long-name'}: file-result test for $type $name failed - could not readlink - $!\n";
+		    return undef;
+		}
+		local $tmp = &compare_output($test{'long-name'},
+			    "$type contents in \"$name\"",
+			    $matchType eq 'exact' ? $rest : undef
+			    $matchType eq 'pattern' ? $rest : undef);
+		return undef if (!defined $tmp);
+		$why .= $tmp;
+	    }
+	}
+    }
+
+    return $why;
+}
+
+sub
+HELP_MESSAGE
+{
+    print STDERR $Usage;
+    exit 0;
+}
diff --git a/mksh/src/check.t b/mksh/src/check.t
new file mode 100644
index 0000000..c8e8caf
--- /dev/null
+++ b/mksh/src/check.t
@@ -0,0 +1,7443 @@
+# $MirOS: src/bin/mksh/check.t,v 1.388 2010/08/24 15:47:44 tg Exp $
+# $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $
+# $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $
+# $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $
+#-
+# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+#	Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# You may also want to test IFS with the script at
+# http://www.research.att.com/~gsf/public/ifs.sh
+
+expected-stdout:
+	@(#)MIRBSD KSH R39 2010/08/24
+description:
+	Check version of shell.
+stdin:
+	echo $KSH_VERSION
+name: KSH_VERSION
+---
+name: selftest-1
+description:
+	Regression test self-testing
+stdin:
+	echo ${foo:-baz}
+expected-stdout:
+	baz
+---
+name: selftest-2
+description:
+	Regression test self-testing
+env-setup: !foo=bar!
+stdin:
+	echo ${foo:-baz}
+expected-stdout:
+	bar
+---
+name: selftest-3
+description:
+	Regression test self-testing
+env-setup: !ENV=fnord!
+stdin:
+	echo "<$ENV>"
+expected-stdout:
+	<fnord>
+---
+name: selftest-env
+description:
+	Just output the environment variables set (always fails)
+category: disabled
+stdin:
+	set
+---
+name: alias-1
+description:
+	Check that recursion is detected/avoided in aliases.
+stdin:
+	alias fooBar=fooBar
+	fooBar
+	exit 0
+expected-stderr-pattern:
+	/fooBar.*not found.*/
+---
+name: alias-2
+description:
+	Check that recursion is detected/avoided in aliases.
+stdin:
+	alias fooBar=barFoo
+	alias barFoo=fooBar
+	fooBar
+	barFoo
+	exit 0
+expected-stderr-pattern:
+	/fooBar.*not found.*\n.*barFoo.*not found/
+---
+name: alias-3
+description:
+	Check that recursion is detected/avoided in aliases.
+stdin:
+	alias Echo='echo '
+	alias fooBar=barFoo
+	alias barFoo=fooBar
+	Echo fooBar
+	unalias barFoo
+	Echo fooBar
+expected-stdout:
+	fooBar
+	barFoo
+---
+name: alias-4
+description:
+	Check that alias expansion isn't done on keywords (in keyword
+	postitions).
+stdin:
+	alias Echo='echo '
+	alias while=While
+	while false; do echo hi ; done
+	Echo while
+expected-stdout:
+	While
+---
+name: alias-5
+description:
+	Check that alias expansion done after alias with trailing space.
+stdin:
+	alias Echo='echo '
+	alias foo='bar stuff '
+	alias bar='Bar1 Bar2 '
+	alias stuff='Stuff'
+	alias blah='Blah'
+	Echo foo blah
+expected-stdout:
+	Bar1 Bar2 Stuff Blah
+---
+name: alias-6
+description:
+	Check that alias expansion done after alias with trailing space.
+stdin:
+	alias Echo='echo '
+	alias foo='bar bar'
+	alias bar='Bar '
+	alias blah=Blah
+	Echo foo blah
+expected-stdout:
+	Bar Bar Blah
+---
+name: alias-7
+description:
+	Check that alias expansion done after alias with trailing space
+	after a keyword.
+stdin:
+	alias X='case '
+	alias Y=Z
+	X Y in 'Y') echo is y ;; Z) echo is z ; esac
+expected-stdout:
+	is z
+---
+name: alias-8
+description:
+	Check that newlines in an alias don't cause the command to be lost.
+stdin:
+	alias foo='
+	
+	
+	echo hi
+	
+	
+	
+	echo there
+	
+	
+	'
+	foo
+expected-stdout:
+	hi
+	there
+---
+name: alias-9
+description:
+	Check that recursion is detected/avoided in aliases.
+	This check fails for slow machines or Cygwin, raise
+	the time-limit clause (e.g. to 7) if this occurs.
+time-limit: 3
+stdin:
+	echo -n >tf
+	alias ls=ls
+	ls
+	echo $(ls)
+	exit 0
+expected-stdout:
+	tf
+	tf
+---
+name: alias-10
+description:
+	Check that recursion is detected/avoided in aliases.
+	Regression, introduced during an old bugfix.
+stdin:
+	alias foo='print hello '
+	alias bar='foo world'
+	echo $(bar)
+expected-stdout:
+	hello world
+---
+name: arith-lazy-1
+description:
+	Check that only one side of ternary operator is evaluated
+stdin:
+	x=i+=2
+	y=j+=2
+	typeset -i i=1 j=1
+	echo $((1 ? 20 : (x+=2)))
+	echo $i,$x
+	echo $((0 ? (y+=2) : 30))
+	echo $j,$y
+expected-stdout:
+	20
+	1,i+=2
+	30
+	1,j+=2
+---
+name: arith-lazy-2
+description:
+	Check that assignments not done on non-evaluated side of ternary
+	operator
+stdin:
+	x=i+=2
+	y=j+=2
+	typeset -i i=1 j=1
+	echo $((1 ? 20 : (x+=2)))
+	echo $i,$x
+	echo $((0 ? (y+=2) : 30))
+	echo $i,$y
+expected-stdout:
+	20
+	1,i+=2
+	30
+	1,j+=2
+---
+name: arith-lazy-3
+description:
+	Check that assignments not done on non-evaluated side of ternary
+	operator and this construct is parsed correctly (Debian #445651)
+stdin:
+	x=4
+	y=$((0 ? x=1 : 2))
+	echo = $x $y =
+expected-stdout:
+	= 4 2 =
+---
+name: arith-ternary-prec-1
+description:
+	Check precedence of ternary operator vs assignment
+stdin:
+	typeset -i x=2
+	y=$((1 ? 20 : x+=2))
+expected-exit: e != 0
+expected-stderr-pattern:
+	/.*:.*1 \? 20 : x\+=2.*lvalue.*\n$/
+---
+name: arith-ternary-prec-2
+description:
+	Check precedence of ternary operator vs assignment
+stdin:
+	typeset -i x=2
+	echo $((0 ? x+=2 : 20))
+expected-stdout:
+	20
+---
+name: arith-div-assoc-1
+description:
+	Check associativity of division operator
+stdin:
+	echo $((20 / 2 / 2))
+expected-stdout:
+	5
+---
+name: arith-assop-assoc-1
+description:
+	Check associativity of assignment-operator operator
+stdin:
+	typeset -i i=1 j=2 k=3
+	echo $((i += j += k))
+	echo $i,$j,$k
+expected-stdout:
+	6
+	6,5,3
+---
+name: arith-unsigned-1
+description:
+	Check if unsigned arithmetics work
+stdin:
+	# signed vs unsigned
+	echo x1 $((-1)) $((#-1))
+	# calculating
+	typeset -i vs
+	typeset -Ui vu
+	vs=4123456789; vu=4123456789
+	echo x2 $vs $vu
+	(( vs %= 2147483647 ))
+	(( vu %= 2147483647 ))
+	echo x3 $vs $vu
+	vs=4123456789; vu=4123456789
+	(( # vs %= 2147483647 ))
+	(( # vu %= 2147483647 ))
+	echo x4 $vs $vu
+	# make sure the calculation does not change unsigned flag
+	vs=4123456789; vu=4123456789
+	echo x5 $vs $vu
+	# short form
+	echo x6 $((# vs % 2147483647)) $((# vu % 2147483647))
+	# array refs
+	set -A va
+	va[1975973142]=right
+	va[4123456789]=wrong
+	echo x7 ${va[#4123456789%2147483647]}
+expected-stdout:
+	x1 -1 4294967295
+	x2 -171510507 4123456789
+	x3 -171510507 4123456789
+	x4 1975973142 1975973142
+	x5 -171510507 4123456789
+	x6 1975973142 1975973142
+	x7 right
+---
+name: arith-limit32-1
+description:
+	Check if arithmetics are 32 bit
+stdin:
+	# signed vs unsigned
+	echo x1 $((-1)) $((#-1))
+	# calculating
+	typeset -i vs
+	typeset -Ui vu
+	vs=2147483647; vu=2147483647
+	echo x2 $vs $vu
+	let vs++ vu++
+	echo x3 $vs $vu
+	vs=4294967295; vu=4294967295
+	echo x4 $vs $vu
+	let vs++ vu++
+	echo x5 $vs $vu
+	let vs++ vu++
+	echo x6 $vs $vu
+expected-stdout:
+	x1 -1 4294967295
+	x2 2147483647 2147483647
+	x3 -2147483648 2147483648
+	x4 -1 4294967295
+	x5 0 0
+	x6 1 1
+---
+name: bksl-nl-ign-1
+description:
+	Check that \newline is not collasped after #
+stdin:
+	echo hi #there \
+	echo folks
+expected-stdout:
+	hi
+	folks
+---
+name: bksl-nl-ign-2
+description:
+	Check that \newline is not collasped inside single quotes
+stdin:
+	echo 'hi \
+	there'
+	echo folks
+expected-stdout:
+	hi \
+	there
+	folks
+---
+name: bksl-nl-ign-3
+description:
+	Check that \newline is not collasped inside single quotes
+stdin:
+	cat << \EOF
+	hi \
+	there
+	EOF
+expected-stdout:
+	hi \
+	there
+---
+name: bksl-nl-ign-4
+description:
+	Check interaction of aliases, single quotes and here-documents
+	with backslash-newline
+	(don't know what POSIX has to say about this)
+stdin:
+	a=2
+	alias x='echo hi
+	cat << "EOF"
+	foo\
+	bar
+	some'
+	x
+	more\
+	stuff$a
+	EOF
+expected-stdout:
+	hi
+	foo\
+	bar
+	some
+	more\
+	stuff$a
+---
+name: bksl-nl-ign-5
+description:
+	Check what happens with backslash at end of input
+	(the old Bourne shell trashes them; so do we)
+stdin: !
+	echo `echo foo\\`bar
+	echo hi\
+expected-stdout:
+	foobar
+	hi
+---
+#
+# Places \newline should be collapsed
+#
+name: bksl-nl-1
+description:
+	Check that \newline is collasped before, in the middle of, and
+	after words
+stdin:
+	 	 	\
+			 echo hi\
+	There, \
+	folks
+expected-stdout:
+	hiThere, folks
+---
+name: bksl-nl-2
+description:
+	Check that \newline is collasped in $ sequences
+	(ksh93 fails this)
+stdin:
+	a=12
+	ab=19
+	echo $\
+	a
+	echo $a\
+	b
+	echo $\
+	{a}
+	echo ${a\
+	b}
+	echo ${ab\
+	}
+expected-stdout:
+	12
+	19
+	12
+	19
+	19
+---
+name: bksl-nl-3
+description:
+	Check that \newline is collasped in $(..) and `...` sequences
+	(ksh93 fails this)
+stdin:
+	echo $\
+	(echo foobar1)
+	echo $(\
+	echo foobar2)
+	echo $(echo foo\
+	bar3)
+	echo $(echo foobar4\
+	)
+	echo `
+	echo stuff1`
+	echo `echo st\
+	uff2`
+expected-stdout:
+	foobar1
+	foobar2
+	foobar3
+	foobar4
+	stuff1
+	stuff2
+---
+name: bksl-nl-4
+description:
+	Check that \newline is collasped in $((..)) sequences
+	(ksh93 fails this)
+stdin:
+	echo $\
+	((1+2))
+	echo $(\
+	(1+2+3))
+	echo $((\
+	1+2+3+4))
+	echo $((1+\
+	2+3+4+5))
+	echo $((1+2+3+4+5+6)\
+	)
+expected-stdout:
+	3
+	6
+	10
+	15
+	21
+---
+name: bksl-nl-5
+description:
+	Check that \newline is collasped in double quoted strings
+stdin:
+	echo "\
+	hi"
+	echo "foo\
+	bar"
+	echo "folks\
+	"
+expected-stdout:
+	hi
+	foobar
+	folks
+---
+name: bksl-nl-6
+description:
+	Check that \newline is collasped in here document delimiters
+	(ksh93 fails second part of this)
+stdin:
+	a=12
+	cat << EO\
+	F
+	a=$a
+	foo\
+	bar
+	EOF
+	cat << E_O_F
+	foo
+	E_O_\
+	F
+	echo done
+expected-stdout:
+	a=12
+	foobar
+	foo
+	done
+---
+name: bksl-nl-7
+description:
+	Check that \newline is collasped in double-quoted here-document
+	delimiter.
+stdin:
+	a=12
+	cat << "EO\
+	F"
+	a=$a
+	foo\
+	bar
+	EOF
+	echo done
+expected-stdout:
+	a=$a
+	foo\
+	bar
+	done
+---
+name: bksl-nl-8
+description:
+	Check that \newline is collasped in various 2+ character tokens
+	delimiter.
+	(ksh93 fails this)
+stdin:
+	echo hi &\
+	& echo there
+	echo foo |\
+	| echo bar
+	cat <\
+	< EOF
+	stuff
+	EOF
+	cat <\
+	<\
+	- EOF
+		more stuff
+	EOF
+	cat <<\
+	EOF
+	abcdef
+	EOF
+	echo hi >\
+	> /dev/null
+	echo $?
+	i=1
+	case $i in
+	(\
+	x|\
+	1\
+	) echo hi;\
+	;
+	(*) echo oops
+	esac
+expected-stdout:
+	hi
+	there
+	foo
+	stuff
+	more stuff
+	abcdef
+	0
+	hi
+---
+name: bksl-nl-9
+description:
+	Check that \ at the end of an alias is collapsed when followed
+	by a newline
+	(don't know what POSIX has to say about this)
+stdin:
+	alias x='echo hi\'
+	x
+	echo there
+expected-stdout:
+	hiecho there
+---
+name: bksl-nl-10
+description:
+	Check that \newline in a keyword is collapsed
+stdin:
+	i\
+	f true; then\
+	 echo pass; el\
+	se echo fail; fi
+expected-stdout:
+	pass
+---
+#
+# Places \newline should be collapsed (ksh extensions)
+#
+name: bksl-nl-ksh-1
+description:
+	Check that \newline is collapsed in extended globbing
+	(ksh93 fails this)
+stdin:
+	xxx=foo
+	case $xxx in
+	(f*\
+	(\
+	o\
+	)\
+	) echo ok ;;
+	*) echo bad
+	esac
+expected-stdout:
+	ok
+---
+name: bksl-nl-ksh-2
+description:
+	Check that \newline is collapsed in ((...)) expressions
+	(ksh93 fails this)
+stdin:
+	i=1
+	(\
+	(\
+	i=i+2\
+	)\
+	)
+	echo $i
+expected-stdout:
+	3
+---
+name: break-1
+description:
+	See if break breaks out of loops
+stdin:
+	for i in a b c; do echo $i; break; echo bad-$i; done
+	echo end-1
+	for i in a b c; do echo $i; break 1; echo bad-$i; done
+	echo end-2
+	for i in a b c; do
+	    for j in x y z; do
+		echo $i:$j
+		break
+		echo bad-$i
+	    done
+	    echo end-$i
+	done
+	echo end-3
+expected-stdout:
+	a
+	end-1
+	a
+	end-2
+	a:x
+	end-a
+	b:x
+	end-b
+	c:x
+	end-c
+	end-3
+---
+name: break-2
+description:
+	See if break breaks out of nested loops
+stdin:
+	for i in a b c; do
+	    for j in x y z; do
+		echo $i:$j
+		break 2
+		echo bad-$i
+	    done
+	    echo end-$i
+	done
+	echo end
+expected-stdout:
+	a:x
+	end
+---
+name: break-3
+description:
+	What if break used outside of any loops
+	(ksh88,ksh93 don't print error messages here)
+stdin:
+	break
+expected-stderr-pattern:
+	/.*break.*/
+---
+name: break-4
+description:
+	What if break N used when only N-1 loops
+	(ksh88,ksh93 don't print error messages here)
+stdin:
+	for i in a b c; do echo $i; break 2; echo bad-$i; done
+	echo end
+expected-stdout:
+	a
+	end
+expected-stderr-pattern:
+	/.*break.*/
+---
+name: break-5
+description:
+	Error if break argument isn't a number
+stdin:
+	for i in a b c; do echo $i; break abc; echo more-$i; done
+	echo end
+expected-stdout:
+	a
+expected-exit: e != 0
+expected-stderr-pattern:
+	/.*break.*/
+---
+name: continue-1
+description:
+	See if continue continues loops
+stdin:
+	for i in a b c; do echo $i; continue; echo bad-$i ; done
+	echo end-1
+	for i in a b c; do echo $i; continue 1; echo bad-$i; done
+	echo end-2
+	for i in a b c; do
+	    for j in x y z; do
+		echo $i:$j
+		continue
+		echo bad-$i-$j
+	    done
+	    echo end-$i
+	done
+	echo end-3
+expected-stdout:
+	a
+	b
+	c
+	end-1
+	a
+	b
+	c
+	end-2
+	a:x
+	a:y
+	a:z
+	end-a
+	b:x
+	b:y
+	b:z
+	end-b
+	c:x
+	c:y
+	c:z
+	end-c
+	end-3
+---
+name: continue-2
+description:
+	See if continue breaks out of nested loops
+stdin:
+	for i in a b c; do
+	    for j in x y z; do
+		echo $i:$j
+		continue 2
+		echo bad-$i-$j
+	    done
+	    echo end-$i
+	done
+	echo end
+expected-stdout:
+	a:x
+	b:x
+	c:x
+	end
+---
+name: continue-3
+description:
+	What if continue used outside of any loops
+	(ksh88,ksh93 don't print error messages here)
+stdin:
+	continue
+expected-stderr-pattern:
+	/.*continue.*/
+---
+name: continue-4
+description:
+	What if continue N used when only N-1 loops
+	(ksh88,ksh93 don't print error messages here)
+stdin:
+	for i in a b c; do echo $i; continue 2; echo bad-$i; done
+	echo end
+expected-stdout:
+	a
+	b
+	c
+	end
+expected-stderr-pattern:
+	/.*continue.*/
+---
+name: continue-5
+description:
+	Error if continue argument isn't a number
+stdin:
+	for i in a b c; do echo $i; continue abc; echo more-$i; done
+	echo end
+expected-stdout:
+	a
+expected-exit: e != 0
+expected-stderr-pattern:
+	/.*continue.*/
+---
+name: cd-history
+description:
+	Test someone's CD history package (uses arrays)
+stdin:
+	# go to known place before doing anything
+	cd /
+	
+	alias cd=_cd
+	function _cd
+	{
+		typeset -i cdlen i
+		typeset t
+	
+		if [ $# -eq 0 ]
+		then
+			set -- $HOME
+		fi
+	
+		if [ "$CDHISTFILE" -a -r "$CDHISTFILE" ] # if directory history exists
+		then
+			typeset CDHIST
+			i=-1
+			while read -r t			# read directory history file
+			do
+				CDHIST[i=i+1]=$t
+			done <$CDHISTFILE
+		fi
+	
+		if [ "${CDHIST[0]}" != "$PWD" -a "$PWD" != "" ]
+		then
+			_cdins				# insert $PWD into cd history
+		fi
+	
+		cdlen=${#CDHIST[*]}			# number of elements in history
+	
+		case "$@" in
+		-)					# cd to new dir
+			if [ "$OLDPWD" = "" ] && ((cdlen>1))
+			then
+				'print' ${CDHIST[1]}
+				'cd' ${CDHIST[1]}
+				_pwd
+			else
+				'cd' $@
+				_pwd
+			fi
+			;;
+		-l)					# print directory list
+			typeset -R3 num
+			((i=cdlen))
+			while (((i=i-1)>=0))
+			do
+				num=$i
+				'print' "$num ${CDHIST[i]}"
+			done
+			return
+			;;
+		-[0-9]|-[0-9][0-9])			# cd to dir in list
+			if (((i=${1#-})<cdlen))
+			then
+				'print' ${CDHIST[i]}
+				'cd' ${CDHIST[i]}
+				_pwd
+			else
+				'cd' $@
+				_pwd
+			fi
+			;;
+		-*)					# cd to matched dir in list
+			t=${1#-}
+			i=1
+			while ((i<cdlen))
+			do
+				case ${CDHIST[i]} in
+				*$t*)
+					'print' ${CDHIST[i]}
+					'cd' ${CDHIST[i]}
+					_pwd
+					break
+					;;
+				esac
+				((i=i+1))
+			done
+			if ((i>=cdlen))
+			then
+				'cd' $@
+				_pwd
+			fi
+			;;
+		*)					# cd to new dir
+			'cd' $@
+			_pwd
+			;;
+		esac
+	
+		_cdins					# insert $PWD into cd history
+	
+		if [ "$CDHISTFILE" ]
+		then
+			cdlen=${#CDHIST[*]}		# number of elements in history
+	
+			i=0
+			while ((i<cdlen))
+			do
+				'print' -r ${CDHIST[i]}	# update directory history
+				((i=i+1))
+			done >$CDHISTFILE
+		fi
+	}
+	
+	function _cdins					# insert $PWD into cd history
+	{						# meant to be called only by _cd
+		typeset -i i
+	
+		((i=0))
+		while ((i<${#CDHIST[*]}))		# see if dir is already in list
+		do
+			if [ "${CDHIST[$i]}" = "$PWD" ]
+			then
+				break
+			fi
+			((i=i+1))
+		done
+	
+		if ((i>22))				# limit max size of list
+		then
+			i=22
+		fi
+	
+		while (((i=i-1)>=0))			# bump old dirs in list
+		do
+			CDHIST[i+1]=${CDHIST[i]}
+		done
+	
+		CDHIST[0]=$PWD				# insert new directory in list
+	}
+	
+	
+	function _pwd
+	{
+		if [ -n "$ECD" ]
+		then
+			pwd 1>&6
+		fi
+	}
+	# Start of test
+	cd /tmp
+	cd /bin
+	cd /etc
+	cd -
+	cd -2
+	cd -l
+expected-stdout:
+	/bin
+	/tmp
+	  3 /
+	  2 /etc
+	  1 /bin
+	  0 /tmp
+---
+name: env-prompt
+description:
+	Check that prompt not printed when processing ENV
+env-setup: !ENV=./foo!
+file-setup: file 644 "foo"
+	XXX=_
+	PS1=X
+	false && echo hmmm
+arguments: !-i!
+stdin:
+	echo hi${XXX}there
+expected-stdout:
+	hi_there
+expected-stderr: !
+	XX
+---
+name: expand-ugly
+description:
+	Check that weird ${foo+bar} constructs are parsed correctly
+stdin:
+	(echo 1 ${IFS+'}'z}) 2>&- || echo failed in 1
+	(echo 2 "${IFS+'}'z}") 2>&- || echo failed in 2
+	(echo 3 "foo ${IFS+'bar} baz") 2>&- || echo failed in 3
+	(echo -n '4 '; printf '%s\n' "foo ${IFS+"b   c"} baz") 2>&- || echo failed in 4
+	(echo -n '5 '; printf '%s\n' "foo ${IFS+b   c} baz") 2>&- || echo failed in 5
+	(echo 6 ${IFS+"}"z}) 2>&- || echo failed in 6
+	(echo 7 "${IFS+"}"z}") 2>&- || echo failed in 7
+	(echo 8 "${IFS+\"}\"z}") 2>&- || echo failed in 8
+	(echo 9 "${IFS+\"\}\"z}") 2>&- || echo failed in 9
+	(echo 10 foo ${IFS+'bar} baz'}) 2>&- || echo failed in 10
+	(echo 11 "$(echo "${IFS+'}'z}")") 2>&- || echo failed in 11
+	(echo 12 "$(echo ${IFS+'}'z})") 2>&- || echo failed in 12
+	(echo 13 ${IFS+\}z}) 2>&- || echo failed in 13
+	(echo 14 "${IFS+\}z}") 2>&- || echo failed in 14
+	u=x; (echo -n '15 '; printf '<%s> ' "foo ${IFS+a"b$u{ {"{{\}b} c ${IFS+d{}} bar" ${IFS-e{}} baz; echo .) 2>&- || echo failed in 15
+	l=t; (echo 16 ${IFS+h`echo -n i ${IFS+$l}h`ere}) 2>&- || echo failed in 16
+	l=t; (echo 17 ${IFS+h$(echo -n i ${IFS+$l}h)ere}) 2>&- || echo failed in 17
+	l=t; (echo 18 "${IFS+h`echo -n i ${IFS+$l}h`ere}") 2>&- || echo failed in 18
+	l=t; (echo 19 "${IFS+h$(echo -n i ${IFS+$l}h)ere}") 2>&- || echo failed in 19
+	l=t; (echo 20 ${IFS+h`echo -n i "${IFS+$l}"h`ere}) 2>&- || echo failed in 20
+	l=t; (echo 21 ${IFS+h$(echo -n i "${IFS+$l}"h)ere}) 2>&- || echo failed in 21
+	l=t; (echo 22 "${IFS+h`echo -n i "${IFS+$l}"h`ere}") 2>&- || echo failed in 22
+	l=t; (echo 23 "${IFS+h$(echo -n i "${IFS+$l}"h)ere}") 2>&- || echo failed in 23
+	key=value; (echo -n '24 '; printf '%s\n' "${IFS+'$key'}") 2>&- || echo failed in 24
+	key=value; (echo -n '25 '; printf '%s\n' "${IFS+"'$key'"}") 2>&- || echo failed in 25	# ksh93: “'$key'”
+	key=value; (echo -n '26 '; printf '%s\n' ${IFS+'$key'}) 2>&- || echo failed in 26
+	key=value; (echo -n '27 '; printf '%s\n' ${IFS+"'$key'"}) 2>&- || echo failed in 27
+	(echo -n '28 '; printf '%s\n' "${IFS+"'"x ~ x'}'x"'}"x}" #') 2>&- || echo failed in 28
+	u=x; (echo -n '29 '; printf '<%s> ' foo ${IFS+a"b$u{ {"{ {\}b} c ${IFS+d{}} bar ${IFS-e{}} baz; echo .) 2>&- || echo failed in 29
+	(echo -n '30 '; printf '<%s> ' ${IFS+foo 'b\
+	ar' baz}; echo .) 2>&- || (echo failed in 30; echo failed in 31)
+	(echo -n '32 '; printf '<%s> ' ${IFS+foo "b\
+	ar" baz}; echo .) 2>&- || echo failed in 32
+	(echo -n '33 '; printf '<%s> ' "${IFS+foo 'b\
+	ar' baz}"; echo .) 2>&- || echo failed in 33
+	(echo -n '34 '; printf '<%s> ' "${IFS+foo "b\
+	ar" baz}"; echo .) 2>&- || echo failed in 34
+	(echo -n '35 '; printf '<%s> ' ${v=a\ b} x ${v=c\ d}; echo .) 2>&- || echo failed in 35
+	(echo -n '36 '; printf '<%s> ' "${v=a\ b}" x "${v=c\ d}"; echo .) 2>&- || echo failed in 36
+	(echo -n '37 '; printf '<%s> ' ${v-a\ b} x ${v-c\ d}; echo .) 2>&- || echo failed in 37
+	(echo 38 ${IFS+x'a'y} / "${IFS+x'a'y}" .) 2>&- || echo failed in 38
+	foo="x'a'y"; (echo 39 ${foo%*'a'*} / "${foo%*'a'*}" .) 2>&- || echo failed in 39
+	foo="a b c"; (echo -n '40 '; printf '<%s> ' "${foo#a}"; echo .) 2>&- || echo failed in 40
+expected-stdout:
+	1 }z
+	2 ''z}
+	3 foo 'bar baz
+	4 foo b   c baz
+	5 foo b   c baz
+	6 }z
+	7 }z
+	8 ""z}
+	9 "}"z
+	10 foo bar} baz
+	11 ''z}
+	12 }z
+	13 }z
+	14 }z
+	15 <foo abx{ {{{}b c d{} bar> <}> <baz> .
+	16 hi there
+	17 hi there
+	18 hi there
+	19 hi there
+	20 hi there
+	21 hi there
+	22 hi there
+	23 hi there
+	24 'value'
+	25 'value'
+	26 $key
+	27 'value'
+	28 'x ~ x''x}"x}" #
+	29 <foo> <abx{ {{> <{}b> <c> <d{}> <bar> <}> <baz> .
+	30 <foo> <b\
+	ar> <baz> .
+	32 <foo> <bar> <baz> .
+	33 <foo 'bar' baz> .
+	34 <foo bar baz> .
+	35 <a> <b> <x> <a> <b> .
+	36 <a\ b> <x> <a\ b> .
+	37 <a b> <x> <c d> .
+	38 xay / x'a'y .
+	39 x' / x' .
+	40 < b c> .
+---
+name: expand-unglob-dblq
+description:
+	Check that regular "${foo+bar}" constructs are parsed correctly
+stdin:
+	u=x
+	tl_norm() {
+		v=$2
+		test x"$v" = x"-" && unset v
+		(echo "$1 plus norm foo ${v+'bar'} baz")
+		(echo "$1 dash norm foo ${v-'bar'} baz")
+		(echo "$1 eqal norm foo ${v='bar'} baz")
+		(echo "$1 qstn norm foo ${v?'bar'} baz") 2>&- || \
+		    echo "$1 qstn norm -> error"
+		(echo "$1 PLUS norm foo ${v:+'bar'} baz")
+		(echo "$1 DASH norm foo ${v:-'bar'} baz")
+		(echo "$1 EQAL norm foo ${v:='bar'} baz")
+		(echo "$1 QSTN norm foo ${v:?'bar'} baz") 2>&- || \
+		    echo "$1 QSTN norm -> error"
+	}
+	tl_paren() {
+		v=$2
+		test x"$v" = x"-" && unset v
+		(echo "$1 plus parn foo ${v+(bar)} baz")
+		(echo "$1 dash parn foo ${v-(bar)} baz")
+		(echo "$1 eqal parn foo ${v=(bar)} baz")
+		(echo "$1 qstn parn foo ${v?(bar)} baz") 2>&- || \
+		    echo "$1 qstn parn -> error"
+		(echo "$1 PLUS parn foo ${v:+(bar)} baz")
+		(echo "$1 DASH parn foo ${v:-(bar)} baz")
+		(echo "$1 EQAL parn foo ${v:=(bar)} baz")
+		(echo "$1 QSTN parn foo ${v:?(bar)} baz") 2>&- || \
+		    echo "$1 QSTN parn -> error"
+	}
+	tl_brace() {
+		v=$2
+		test x"$v" = x"-" && unset v
+		(echo "$1 plus brac foo ${v+a$u{{{\}b} c ${v+d{}} baz")
+		(echo "$1 dash brac foo ${v-a$u{{{\}b} c ${v-d{}} baz")
+		(echo "$1 eqal brac foo ${v=a$u{{{\}b} c ${v=d{}} baz")
+		(echo "$1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz") 2>&- || \
+		    echo "$1 qstn brac -> error"
+		(echo "$1 PLUS brac foo ${v:+a$u{{{\}b} c ${v:+d{}} baz")
+		(echo "$1 DASH brac foo ${v:-a$u{{{\}b} c ${v:-d{}} baz")
+		(echo "$1 EQAL brac foo ${v:=a$u{{{\}b} c ${v:=d{}} baz")
+		(echo "$1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz") 2>&- || \
+		    echo "$1 QSTN brac -> error"
+	}
+	tl_norm 1 -
+	tl_norm 2 ''
+	tl_norm 3 x
+	tl_paren 4 -
+	tl_paren 5 ''
+	tl_paren 6 x
+	tl_brace 7 -
+	tl_brace 8 ''
+	tl_brace 9 x
+expected-stdout:
+	1 plus norm foo  baz
+	1 dash norm foo 'bar' baz
+	1 eqal norm foo 'bar' baz
+	1 qstn norm -> error
+	1 PLUS norm foo  baz
+	1 DASH norm foo 'bar' baz
+	1 EQAL norm foo 'bar' baz
+	1 QSTN norm -> error
+	2 plus norm foo 'bar' baz
+	2 dash norm foo  baz
+	2 eqal norm foo  baz
+	2 qstn norm foo  baz
+	2 PLUS norm foo  baz
+	2 DASH norm foo 'bar' baz
+	2 EQAL norm foo 'bar' baz
+	2 QSTN norm -> error
+	3 plus norm foo 'bar' baz
+	3 dash norm foo x baz
+	3 eqal norm foo x baz
+	3 qstn norm foo x baz
+	3 PLUS norm foo 'bar' baz
+	3 DASH norm foo x baz
+	3 EQAL norm foo x baz
+	3 QSTN norm foo x baz
+	4 plus parn foo  baz
+	4 dash parn foo (bar) baz
+	4 eqal parn foo (bar) baz
+	4 qstn parn -> error
+	4 PLUS parn foo  baz
+	4 DASH parn foo (bar) baz
+	4 EQAL parn foo (bar) baz
+	4 QSTN parn -> error
+	5 plus parn foo (bar) baz
+	5 dash parn foo  baz
+	5 eqal parn foo  baz
+	5 qstn parn foo  baz
+	5 PLUS parn foo  baz
+	5 DASH parn foo (bar) baz
+	5 EQAL parn foo (bar) baz
+	5 QSTN parn -> error
+	6 plus parn foo (bar) baz
+	6 dash parn foo x baz
+	6 eqal parn foo x baz
+	6 qstn parn foo x baz
+	6 PLUS parn foo (bar) baz
+	6 DASH parn foo x baz
+	6 EQAL parn foo x baz
+	6 QSTN parn foo x baz
+	7 plus brac foo  c } baz
+	7 dash brac foo ax{{{}b c d{} baz
+	7 eqal brac foo ax{{{}b c ax{{{}b} baz
+	7 qstn brac -> error
+	7 PLUS brac foo  c } baz
+	7 DASH brac foo ax{{{}b c d{} baz
+	7 EQAL brac foo ax{{{}b c ax{{{}b} baz
+	7 QSTN brac -> error
+	8 plus brac foo ax{{{}b c d{} baz
+	8 dash brac foo  c } baz
+	8 eqal brac foo  c } baz
+	8 qstn brac foo  c } baz
+	8 PLUS brac foo  c } baz
+	8 DASH brac foo ax{{{}b c d{} baz
+	8 EQAL brac foo ax{{{}b c ax{{{}b} baz
+	8 QSTN brac -> error
+	9 plus brac foo ax{{{}b c d{} baz
+	9 dash brac foo x c x} baz
+	9 eqal brac foo x c x} baz
+	9 qstn brac foo x c x} baz
+	9 PLUS brac foo ax{{{}b c d{} baz
+	9 DASH brac foo x c x} baz
+	9 EQAL brac foo x c x} baz
+	9 QSTN brac foo x c x} baz
+---
+name: expand-unglob-unq
+description:
+	Check that regular ${foo+bar} constructs are parsed correctly
+stdin:
+	u=x
+	tl_norm() {
+		v=$2
+		test x"$v" = x"-" && unset v
+		(echo $1 plus norm foo ${v+'bar'} baz)
+		(echo $1 dash norm foo ${v-'bar'} baz)
+		(echo $1 eqal norm foo ${v='bar'} baz)
+		(echo $1 qstn norm foo ${v?'bar'} baz) 2>&- || \
+		    echo "$1 qstn norm -> error"
+		(echo $1 PLUS norm foo ${v:+'bar'} baz)
+		(echo $1 DASH norm foo ${v:-'bar'} baz)
+		(echo $1 EQAL norm foo ${v:='bar'} baz)
+		(echo $1 QSTN norm foo ${v:?'bar'} baz) 2>&- || \
+		    echo "$1 QSTN norm -> error"
+	}
+	tl_paren() {
+		v=$2
+		test x"$v" = x"-" && unset v
+		(echo $1 plus parn foo ${v+\(bar')'} baz)
+		(echo $1 dash parn foo ${v-\(bar')'} baz)
+		(echo $1 eqal parn foo ${v=\(bar')'} baz)
+		(echo $1 qstn parn foo ${v?\(bar')'} baz) 2>&- || \
+		    echo "$1 qstn parn -> error"
+		(echo $1 PLUS parn foo ${v:+\(bar')'} baz)
+		(echo $1 DASH parn foo ${v:-\(bar')'} baz)
+		(echo $1 EQAL parn foo ${v:=\(bar')'} baz)
+		(echo $1 QSTN parn foo ${v:?\(bar')'} baz) 2>&- || \
+		    echo "$1 QSTN parn -> error"
+	}
+	tl_brace() {
+		v=$2
+		test x"$v" = x"-" && unset v
+		(echo $1 plus brac foo ${v+a$u{{{\}b} c ${v+d{}} baz)
+		(echo $1 dash brac foo ${v-a$u{{{\}b} c ${v-d{}} baz)
+		(echo $1 eqal brac foo ${v=a$u{{{\}b} c ${v=d{}} baz)
+		(echo $1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz) 2>&- || \
+		    echo "$1 qstn brac -> error"
+		(echo $1 PLUS brac foo ${v:+a$u{{{\}b} c ${v:+d{}} baz)
+		(echo $1 DASH brac foo ${v:-a$u{{{\}b} c ${v:-d{}} baz)
+		(echo $1 EQAL brac foo ${v:=a$u{{{\}b} c ${v:=d{}} baz)
+		(echo $1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz) 2>&- || \
+		    echo "$1 QSTN brac -> error"
+	}
+	tl_norm 1 -
+	tl_norm 2 ''
+	tl_norm 3 x
+	tl_paren 4 -
+	tl_paren 5 ''
+	tl_paren 6 x
+	tl_brace 7 -
+	tl_brace 8 ''
+	tl_brace 9 x
+expected-stdout:
+	1 plus norm foo baz
+	1 dash norm foo bar baz
+	1 eqal norm foo bar baz
+	1 qstn norm -> error
+	1 PLUS norm foo baz
+	1 DASH norm foo bar baz
+	1 EQAL norm foo bar baz
+	1 QSTN norm -> error
+	2 plus norm foo bar baz
+	2 dash norm foo baz
+	2 eqal norm foo baz
+	2 qstn norm foo baz
+	2 PLUS norm foo baz
+	2 DASH norm foo bar baz
+	2 EQAL norm foo bar baz
+	2 QSTN norm -> error
+	3 plus norm foo bar baz
+	3 dash norm foo x baz
+	3 eqal norm foo x baz
+	3 qstn norm foo x baz
+	3 PLUS norm foo bar baz
+	3 DASH norm foo x baz
+	3 EQAL norm foo x baz
+	3 QSTN norm foo x baz
+	4 plus parn foo baz
+	4 dash parn foo (bar) baz
+	4 eqal parn foo (bar) baz
+	4 qstn parn -> error
+	4 PLUS parn foo baz
+	4 DASH parn foo (bar) baz
+	4 EQAL parn foo (bar) baz
+	4 QSTN parn -> error
+	5 plus parn foo (bar) baz
+	5 dash parn foo baz
+	5 eqal parn foo baz
+	5 qstn parn foo baz
+	5 PLUS parn foo baz
+	5 DASH parn foo (bar) baz
+	5 EQAL parn foo (bar) baz
+	5 QSTN parn -> error
+	6 plus parn foo (bar) baz
+	6 dash parn foo x baz
+	6 eqal parn foo x baz
+	6 qstn parn foo x baz
+	6 PLUS parn foo (bar) baz
+	6 DASH parn foo x baz
+	6 EQAL parn foo x baz
+	6 QSTN parn foo x baz
+	7 plus brac foo c } baz
+	7 dash brac foo ax{{{}b c d{} baz
+	7 eqal brac foo ax{{{}b c ax{{{}b} baz
+	7 qstn brac -> error
+	7 PLUS brac foo c } baz
+	7 DASH brac foo ax{{{}b c d{} baz
+	7 EQAL brac foo ax{{{}b c ax{{{}b} baz
+	7 QSTN brac -> error
+	8 plus brac foo ax{{{}b c d{} baz
+	8 dash brac foo c } baz
+	8 eqal brac foo c } baz
+	8 qstn brac foo c } baz
+	8 PLUS brac foo c } baz
+	8 DASH brac foo ax{{{}b c d{} baz
+	8 EQAL brac foo ax{{{}b c ax{{{}b} baz
+	8 QSTN brac -> error
+	9 plus brac foo ax{{{}b c d{} baz
+	9 dash brac foo x c x} baz
+	9 eqal brac foo x c x} baz
+	9 qstn brac foo x c x} baz
+	9 PLUS brac foo ax{{{}b c d{} baz
+	9 DASH brac foo x c x} baz
+	9 EQAL brac foo x c x} baz
+	9 QSTN brac foo x c x} baz
+---
+name: eglob-bad-1
+description:
+	Check that globbing isn't done when glob has syntax error
+file-setup: file 644 "abcx"
+file-setup: file 644 "abcz"
+file-setup: file 644 "bbc"
+stdin:
+	echo !([*)*
+	echo +(a|b[)*
+expected-stdout:
+	!([*)*
+	+(a|b[)*
+---
+name: eglob-bad-2
+description:
+	Check that globbing isn't done when glob has syntax error
+	(AT&T ksh fails this test)
+file-setup: file 644 "abcx"
+file-setup: file 644 "abcz"
+file-setup: file 644 "bbc"
+stdin:
+	echo [a*(]*)z
+expected-stdout:
+	[a*(]*)z
+---
+name: eglob-infinite-plus
+description:
+	Check that shell doesn't go into infinite loop expanding +(...)
+	expressions.
+file-setup: file 644 "abc"
+time-limit: 3
+stdin:
+	echo +()c
+	echo +()x
+	echo +(*)c
+	echo +(*)x
+expected-stdout:
+	+()c
+	+()x
+	abc
+	+(*)x
+---
+name: eglob-subst-1
+description:
+	Check that eglobbing isn't done on substitution results
+file-setup: file 644 "abc"
+stdin:
+	x='@(*)'
+	echo $x
+expected-stdout:
+	@(*)
+---
+name: eglob-nomatch-1
+description:
+	Check that the pattern doesn't match
+stdin:
+	echo 1: no-file+(a|b)stuff
+	echo 2: no-file+(a*(c)|b)stuff
+	echo 3: no-file+((((c)))|b)stuff
+expected-stdout:
+	1: no-file+(a|b)stuff
+	2: no-file+(a*(c)|b)stuff
+	3: no-file+((((c)))|b)stuff
+---
+name: eglob-match-1
+description:
+	Check that the pattern matches correctly
+file-setup: file 644 "abd"
+file-setup: file 644 "acd"
+file-setup: file 644 "abac"
+stdin:
+	echo 1: a+(b|c)d
+	echo 2: a!(@(b|B))d
+	echo 3: *(a(b|c))		# (...|...) can be used within X(..)
+	echo 4: a[b*(foo|bar)]d		# patterns not special inside [...]
+expected-stdout:
+	1: abd acd
+	2: acd
+	3: abac
+	4: abd
+---
+name: eglob-case-1
+description:
+	Simple negation tests
+stdin:
+	case foo in !(foo|bar)) echo yes;; *) echo no;; esac
+	case bar in !(foo|bar)) echo yes;; *) echo no;; esac
+expected-stdout:
+	no
+	no
+---
+name: eglob-case-2
+description:
+	Simple kleene tests
+stdin:
+	case foo in *(a|b[)) echo yes;; *) echo no;; esac
+	case foo in *(a|b[)|f*) echo yes;; *) echo no;; esac
+	case '*(a|b[)' in *(a|b[)) echo yes;; *) echo no;; esac
+expected-stdout:
+	no
+	yes
+	yes
+---
+name: eglob-trim-1
+description:
+	Eglobbing in trim expressions...
+	(AT&T ksh fails this - docs say # matches shortest string, ## matches
+	longest...)
+stdin:
+	x=abcdef
+	echo 1: ${x#a|abc}
+	echo 2: ${x##a|abc}
+	echo 3: ${x%def|f}
+	echo 4: ${x%%f|def}
+expected-stdout:
+	1: bcdef
+	2: def
+	3: abcde
+	4: abc
+---
+name: eglob-trim-2
+description:
+	Check eglobbing works in trims...
+stdin:
+	x=abcdef
+	echo 1: ${x#*(a|b)cd}
+	echo 2: "${x#*(a|b)cd}"
+	echo 3: ${x#"*(a|b)cd"}
+	echo 4: ${x#a(b|c)}
+expected-stdout:
+	1: ef
+	2: ef
+	3: abcdef
+	4: cdef
+---
+name: eglob-substrpl-1
+description:
+	Check eglobbing works in substs... and they work at all
+stdin:
+	[[ -n $BASH_VERSION ]] && shopt -s extglob
+	x=1222321_ab/cde_b/c_1221
+	y=xyz
+	echo 1: ${x/2}
+	echo 2: ${x//2}
+	echo 3: ${x/+(2)}
+	echo 4: ${x//+(2)}
+	echo 5: ${x/2/4}
+	echo 6: ${x//2/4}
+	echo 7: ${x/+(2)/4}
+	echo 8: ${x//+(2)/4}
+	echo 9: ${x/b/c/e/f}
+	echo 10: ${x/b\/c/e/f}
+	echo 11: ${x/b\/c/e\/f}
+	echo 12: ${x/b\/c/e\\/f}
+	echo 13: ${x/b\\/c/e\\/f}
+	echo 14: ${x//b/c/e/f}
+	echo 15: ${x//b\/c/e/f}
+	echo 16: ${x//b\/c/e\/f}
+	echo 17: ${x//b\/c/e\\/f}
+	echo 18: ${x//b\\/c/e\\/f}
+	echo 19: ${x/b\/*\/c/x}
+	echo 20: ${x/\//.}
+	echo 21: ${x//\//.}
+	echo 22: ${x///.}
+	echo 23: ${x//#1/9}
+	echo 24: ${x//%1/9}
+	echo 25: ${x//\%1/9}
+	echo 26: ${x//\\%1/9}
+	echo 27: ${x//\a/9}
+	echo 28: ${x//\\a/9}
+	echo 29: ${x/2/$y}
+expected-stdout:
+	1: 122321_ab/cde_b/c_1221
+	2: 131_ab/cde_b/c_11
+	3: 1321_ab/cde_b/c_1221
+	4: 131_ab/cde_b/c_11
+	5: 1422321_ab/cde_b/c_1221
+	6: 1444341_ab/cde_b/c_1441
+	7: 14321_ab/cde_b/c_1221
+	8: 14341_ab/cde_b/c_141
+	9: 1222321_ac/e/f/cde_b/c_1221
+	10: 1222321_ae/fde_b/c_1221
+	11: 1222321_ae/fde_b/c_1221
+	12: 1222321_ae\/fde_b/c_1221
+	13: 1222321_ab/cde_b/c_1221
+	14: 1222321_ac/e/f/cde_c/e/f/c_1221
+	15: 1222321_ae/fde_e/f_1221
+	16: 1222321_ae/fde_e/f_1221
+	17: 1222321_ae\/fde_e\/f_1221
+	18: 1222321_ab/cde_b/c_1221
+	19: 1222321_ax_1221
+	20: 1222321_ab.cde_b/c_1221
+	21: 1222321_ab.cde_b.c_1221
+	22: 1222321_ab/cde_b/c_1221
+	23: 9222321_ab/cde_b/c_1221
+	24: 1222321_ab/cde_b/c_1229
+	25: 1222321_ab/cde_b/c_1229
+	26: 1222321_ab/cde_b/c_1221
+	27: 1222321_9b/cde_b/c_1221
+	28: 1222321_9b/cde_b/c_1221
+	29: 1xyz22321_ab/cde_b/c_1221
+---
+name: eglob-substrpl-2
+description:
+	Check anchored substring replacement works, corner cases
+stdin:
+	foo=123
+	echo 1: ${foo/#/x}
+	echo 2: ${foo/%/x}
+	echo 3: ${foo/#/}
+	echo 4: ${foo/#}
+	echo 5: ${foo/%/}
+	echo 6: ${foo/%}
+expected-stdout:
+	1: x123
+	2: 123x
+	3: 123
+	4: 123
+	5: 123
+	6: 123
+---
+name: eglob-substrpl-3a
+description:
+	Check substring replacement works with variables and slashes, too
+stdin:
+	pfx=/home/user
+	wd=/home/user/tmp
+	echo "${wd/#$pfx/~}"
+	echo "${wd/#\$pfx/~}"
+	echo "${wd/#"$pfx"/~}"
+	echo "${wd/#'$pfx'/~}"
+	echo "${wd/#"\$pfx"/~}"
+	echo "${wd/#'\$pfx'/~}"
+expected-stdout:
+	~/tmp
+	/home/user/tmp
+	~/tmp
+	/home/user/tmp
+	/home/user/tmp
+	/home/user/tmp
+---
+name: eglob-substrpl-3b
+description:
+	More of this, bash fails it (bash4 passes)
+stdin:
+	pfx=/home/user
+	wd=/home/user/tmp
+	echo "${wd/#$(echo /home/user)/~}"
+	echo "${wd/#"$(echo /home/user)"/~}"
+	echo "${wd/#'$(echo /home/user)'/~}"
+expected-stdout:
+	~/tmp
+	~/tmp
+	/home/user/tmp
+---
+name: eglob-substrpl-3c
+description:
+	Even more weird cases
+stdin:
+	pfx=/home/user
+	wd='$pfx/tmp'
+	echo 1: ${wd/#$pfx/~}
+	echo 2: ${wd/#\$pfx/~}
+	echo 3: ${wd/#"$pfx"/~}
+	echo 4: ${wd/#'$pfx'/~}
+	echo 5: ${wd/#"\$pfx"/~}
+	echo 6: ${wd/#'\$pfx'/~}
+	ts='a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp)'
+	tp=a/b
+	tr=c/d
+	[[ -n $BASH_VERSION ]] && shopt -s extglob
+	echo 7: ${ts/a\/b/$tr}
+	echo 8: ${ts/a\/b/\$tr}
+	echo 9: ${ts/$tp/$tr}
+	echo 10: ${ts/\$tp/$tr}
+	echo 11: ${ts/\\$tp/$tr}
+	echo 12: ${ts/$tp/c/d}
+	echo 13: ${ts/$tp/c\/d}
+	echo 14: ${ts/$tp/c\\/d}
+	echo 15: ${ts/+(a\/b)/$tr}
+	echo 16: ${ts/+(a\/b)/\$tr}
+	echo 17: ${ts/+($tp)/$tr}
+	echo 18: ${ts/+($tp)/c/d}
+	echo 19: ${ts/+($tp)/c\/d}
+	echo 25: ${ts//a\/b/$tr}
+	echo 26: ${ts//a\/b/\$tr}
+	echo 27: ${ts//$tp/$tr}
+	echo 28: ${ts//$tp/c/d}
+	echo 29: ${ts//$tp/c\/d}
+	echo 30: ${ts//+(a\/b)/$tr}
+	echo 31: ${ts//+(a\/b)/\$tr}
+	echo 32: ${ts//+($tp)/$tr}
+	echo 33: ${ts//+($tp)/c/d}
+	echo 34: ${ts//+($tp)/c\/d}
+	tp="+($tp)"
+	echo 40: ${ts/$tp/$tr}
+	echo 41: ${ts//$tp/$tr}
+expected-stdout:
+	1: $pfx/tmp
+	2: ~/tmp
+	3: $pfx/tmp
+	4: ~/tmp
+	5: ~/tmp
+	6: ~/tmp
+	7: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+	8: $tra/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+	9: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+	10: a/ba/bc/d$tp_a/b$tp_*(a/b)_*($tp)
+	11: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+	12: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+	13: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+	14: c\/da/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+	15: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+	16: $tr$tp$tp_a/b$tp_*(a/b)_*($tp)
+	17: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+	18: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+	19: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+	25: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+	26: $tr$tr$tp$tp_$tr$tp_*($tr)_*($tp)
+	27: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+	28: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+	29: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+	30: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+	31: $tr$tp$tp_$tr$tp_*($tr)_*($tp)
+	32: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+	33: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+	34: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+	40: a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+	41: a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp)
+#	This is what GNU bash does:
+#	40: c/d$tp$tp_a/b$tp_*(a/b)_*($tp)
+#	41: c/d$tp$tp_c/d$tp_*(c/d)_*($tp)
+---
+name: eglob-utf8-1
+description:
+	UTF-8 mode differences for eglobbing
+stdin:
+	s=blöd
+	set +U
+	print 1: ${s%???} .
+	print 2: ${s/b???d/x} .
+	set -U
+	print 3: ${s%???} .
+	print 4: ${s/b??d/x} .
+	x=nö
+	print 5: ${x%?} ${x%%?} .
+	x=äh
+	print 6: ${x#?} ${x##?} .
+	x=‚
+	print 7: ${x%?} ${x%%?} .
+	x=mä€
+	print 8: ${x%?} ${x%%?} .
+	x=何
+	print 9: ${x%?} ${x%%?} .
+expected-stdout:
+	1: bl .
+	2: x .
+	3: b .
+	4: x .
+	5: n n .
+	6: h h .
+	7:   .
+	8: mä mä .
+	9: .
+---
+name: glob-bad-1
+description:
+	Check that globbing isn't done when glob has syntax error
+file-setup: dir 755 "[x"
+file-setup: file 644 "[x/foo"
+stdin:
+	echo [*
+	echo *[x
+	echo [x/*
+expected-stdout:
+	[*
+	*[x
+	[x/foo
+---
+name: glob-bad-2
+description:
+	Check that symbolic links aren't stat()'d
+file-setup: dir 755 "dir"
+file-setup: symlink 644 "dir/abc"
+	non-existent-file
+stdin:
+	echo d*/*
+	echo d*/abc
+expected-stdout:
+	dir/abc
+	dir/abc
+---
+name: glob-range-1
+description:
+	Test range matching
+file-setup: file 644 ".bc"
+file-setup: file 644 "abc"
+file-setup: file 644 "bbc"
+file-setup: file 644 "cbc"
+file-setup: file 644 "-bc"
+stdin:
+	echo [ab-]*
+	echo [-ab]*
+	echo [!-ab]*
+	echo [!ab]*
+	echo []ab]*
+expected-stdout:
+	-bc abc bbc
+	-bc abc bbc
+	cbc
+	-bc cbc
+	abc bbc
+---
+name: glob-range-2
+description:
+	Test range matching
+	(AT&T ksh fails this; POSIX says invalid)
+file-setup: file 644 "abc"
+stdin:
+	echo [a--]*
+expected-stdout:
+	[a--]*
+---
+name: glob-range-3
+description:
+	Check that globbing matches the right things...
+# breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition)
+category: !os:darwin
+file-setup: file 644 "aÂc"
+stdin:
+	echo a[Á-Ú]*
+expected-stdout:
+	aÂc
+---
+name: glob-range-4
+description:
+	Results unspecified according to POSIX
+file-setup: file 644 ".bc"
+stdin:
+	echo [a.]*
+expected-stdout:
+	[a.]*
+---
+name: glob-range-5
+description:
+	Results unspecified according to POSIX
+	(AT&T ksh treats this like [a-cc-e]*)
+file-setup: file 644 "abc"
+file-setup: file 644 "bbc"
+file-setup: file 644 "cbc"
+file-setup: file 644 "dbc"
+file-setup: file 644 "ebc"
+file-setup: file 644 "-bc"
+stdin:
+	echo [a-c-e]*
+expected-stdout:
+	-bc abc bbc cbc ebc
+---
+name: heredoc-1
+description:
+	Check ordering/content of redundent here documents.
+stdin:
+	cat << EOF1 << EOF2
+	hi
+	EOF1
+	there
+	EOF2
+expected-stdout:
+	there
+---
+name: heredoc-2
+description:
+	Check quoted here-doc is protected.
+stdin:
+	a=foo
+	cat << 'EOF'
+	hi\
+	there$a
+	stuff
+	EO\
+	F
+	EOF
+expected-stdout:
+	hi\
+	there$a
+	stuff
+	EO\
+	F
+---
+name: heredoc-3
+description:
+	Check that newline isn't needed after heredoc-delimiter marker.
+stdin: !
+	cat << EOF
+	hi
+	there
+	EOF
+expected-stdout:
+	hi
+	there
+---
+name: heredoc-4
+description:
+	Check that an error occurs if the heredoc-delimiter is missing.
+stdin: !
+	cat << EOF
+	hi
+	there
+expected-exit: e > 0
+expected-stderr-pattern: /.*/
+---
+name: heredoc-5
+description:
+	Check that backslash quotes a $, ` and \ and kills a \newline
+stdin:
+	a=BAD
+	b=ok
+	cat << EOF
+	h\${a}i
+	h\\${b}i
+	th\`echo not-run\`ere
+	th\\`echo is-run`ere
+	fol\\ks
+	more\\
+	last \
+	line
+	EOF
+expected-stdout:
+	h${a}i
+	h\oki
+	th`echo not-run`ere
+	th\is-runere
+	fol\ks
+	more\
+	last line
+---
+name: heredoc-6
+description:
+	Check that \newline in initial here-delim word doesn't imply
+	a quoted here-doc.
+stdin:
+	a=i
+	cat << EO\
+	F
+	h$a
+	there
+	EOF
+expected-stdout:
+	hi
+	there
+---
+name: heredoc-7
+description:
+	Check that double quoted $ expressions in here delimiters are
+	not expanded and match the delimiter.
+	POSIX says only quote removal is applied to the delimiter.
+stdin:
+	a=b
+	cat << "E$a"
+	hi
+	h$a
+	hb
+	E$a
+	echo done
+expected-stdout:
+	hi
+	h$a
+	hb
+	done
+---
+name: heredoc-8
+description:
+	Check that double quoted escaped $ expressions in here
+	delimiters are not expanded and match the delimiter.
+	POSIX says only quote removal is applied to the delimiter
+	(\ counts as a quote).
+stdin:
+	a=b
+	cat << "E\$a"
+	hi
+	h$a
+	h\$a
+	hb
+	h\b
+	E$a
+	echo done
+expected-stdout:
+	hi
+	h$a
+	h\$a
+	hb
+	h\b
+	done
+---
+name: heredoc-9a
+description:
+	Check that here strings work.
+stdin:
+	bar="bar
+		baz"
+	tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<foo
+	"$__progname" -c "tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<foo"
+	tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<"$bar"
+	tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<'$bar'
+	tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<\$bar
+	tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<-foo
+expected-stdout:
+	sbb
+	sbb
+	one
+		onm
+	$one
+	$one
+	-sbb
+---
+name: heredoc-9b
+description:
+	Check that a corner case of here strings works like bash
+stdin:
+	fnord=42
+	bar="bar
+		 \$fnord baz"
+	tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<$bar
+expected-stdout:
+	one $sabeq onm
+category: bash
+---
+name: heredoc-9c
+description:
+	Check that a corner case of here strings works like ksh93, zsh
+stdin:
+	fnord=42
+	bar="bar
+		 \$fnord baz"
+	tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<$bar
+expected-stdout:
+	one
+		 $sabeq onm
+---
+name: heredoc-9d
+description:
+	Check another corner case of here strings
+stdin:
+	tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<< bar
+expected-stdout:
+	one
+---
+name: heredoc-quoting-unsubst
+description:
+	Check for correct handling of quoted characters in
+	here documents without substitution (marker is quoted).
+stdin:
+	foo=bar
+	cat <<-'EOF'
+		x " \" \ \\ $ \$ `echo baz` \`echo baz\` $foo \$foo x
+	EOF
+expected-stdout:
+	x " \" \ \\ $ \$ `echo baz` \`echo baz\` $foo \$foo x
+---
+name: heredoc-quoting-subst
+description:
+	Check for correct handling of quoted characters in
+	here documents with substitution (marker is not quoted).
+stdin:
+	foo=bar
+	cat <<-EOF
+		x " \" \ \\ $ \$ `echo baz` \`echo baz\` $foo \$foo x
+	EOF
+expected-stdout:
+	x " \" \ \ $ $ baz `echo baz` bar $foo x
+---
+name: heredoc-tmpfile-1
+description:
+	Check that heredoc temp files aren't removed too soon or too late.
+	Heredoc in simple command.
+stdin:
+	TMPDIR=$PWD
+	eval '
+		cat <<- EOF
+		hi
+		EOF
+		for i in a b ; do
+			cat <<- EOF
+			more
+			EOF
+		done
+	    ' &
+	sleep 1
+	echo Left overs: *
+expected-stdout:
+	hi
+	more
+	more
+	Left overs: *
+---
+name: heredoc-tmpfile-2
+description:
+	Check that heredoc temp files aren't removed too soon or too late.
+	Heredoc in function, multiple calls to function.
+stdin:
+	TMPDIR=$PWD
+	eval '
+		foo() {
+			cat <<- EOF
+			hi
+			EOF
+		}
+		foo
+		foo
+	    ' &
+	sleep 1
+	echo Left overs: *
+expected-stdout:
+	hi
+	hi
+	Left overs: *
+---
+name: heredoc-tmpfile-3
+description:
+	Check that heredoc temp files aren't removed too soon or too late.
+	Heredoc in function in loop, multiple calls to function.
+stdin:
+	TMPDIR=$PWD
+	eval '
+		foo() {
+			cat <<- EOF
+			hi
+			EOF
+		}
+		for i in a b; do
+			foo
+			foo() {
+				cat <<- EOF
+				folks $i
+				EOF
+			}
+		done
+		foo
+	    ' &
+	sleep 1
+	echo Left overs: *
+expected-stdout:
+	hi
+	folks b
+	folks b
+	Left overs: *
+---
+name: heredoc-tmpfile-4
+description:
+	Check that heredoc temp files aren't removed too soon or too late.
+	Backgrounded simple command with here doc
+stdin:
+	TMPDIR=$PWD
+	eval '
+		cat <<- EOF &
+		hi
+		EOF
+	    ' &
+	sleep 1
+	echo Left overs: *
+expected-stdout:
+	hi
+	Left overs: *
+---
+name: heredoc-tmpfile-5
+description:
+	Check that heredoc temp files aren't removed too soon or too late.
+	Backgrounded subshell command with here doc
+stdin:
+	TMPDIR=$PWD
+	eval '
+	      (
+		sleep 1	# so parent exits
+		echo A
+		cat <<- EOF
+		hi
+		EOF
+		echo B
+	      ) &
+	    ' &
+	sleep 2
+	echo Left overs: *
+expected-stdout:
+	A
+	hi
+	B
+	Left overs: *
+---
+name: heredoc-tmpfile-6
+description:
+	Check that heredoc temp files aren't removed too soon or too late.
+	Heredoc in pipeline.
+stdin:
+	TMPDIR=$PWD
+	eval '
+		cat <<- EOF | sed "s/hi/HI/"
+		hi
+		EOF
+	    ' &
+	sleep 1
+	echo Left overs: *
+expected-stdout:
+	HI
+	Left overs: *
+---
+name: heredoc-tmpfile-7
+description:
+	Check that heredoc temp files aren't removed too soon or too late.
+	Heredoc in backgrounded pipeline.
+stdin:
+	TMPDIR=$PWD
+	eval '
+		cat <<- EOF | sed 's/hi/HI/' &
+		hi
+		EOF
+	    ' &
+	sleep 1
+	echo Left overs: *
+expected-stdout:
+	HI
+	Left overs: *
+---
+name: heredoc-tmpfile-8
+description:
+	Check that heredoc temp files aren't removed too soon or too
+	late. Heredoc in function, backgrounded call to function.
+	This check can fail on slow machines (<100 MHz), or Cygwin,
+	that's normal.
+stdin:
+	TMPDIR=$PWD
+	# Background eval so main shell doesn't do parsing
+	eval '
+		foo() {
+			cat <<- EOF
+			hi
+			EOF
+		}
+		foo
+		# sleep so eval can die
+		(sleep 1; foo) &
+		(sleep 1; foo) &
+		foo
+	    ' &
+	sleep 2
+	echo Left overs: *
+expected-stdout:
+	hi
+	hi
+	hi
+	hi
+	Left overs: *
+---
+name: history-basic
+description:
+	See if we can test history at all
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo hi
+	fc -l
+expected-stdout:
+	hi
+	1	echo hi
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-dups
+description:
+	Verify duplicates and spaces are not entered
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo hi
+	 echo yo
+	echo hi
+	fc -l
+expected-stdout:
+	hi
+	yo
+	hi
+	1	echo hi
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-unlink
+description:
+	Check if broken HISTFILEs do not cause trouble
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=foo/hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+file-setup: dir 755 "foo"
+file-setup: file 644 "foo/hist.file"
+	sometext
+time-limit: 5
+perl-setup: chmod(0555, "foo");
+stdin:
+	echo hi
+	fc -l
+	chmod 0755 foo
+expected-stdout:
+	hi
+	1	echo hi
+expected-stderr-pattern:
+	/(.*cannot unlink HISTFILE.*\n)?X*$/
+---
+name: history-e-minus-1
+description:
+	Check if more recent command is executed
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo hi
+	echo there
+	fc -e -
+expected-stdout:
+	hi
+	there
+	there
+expected-stderr-pattern:
+	/^X*echo there\nX*$/
+---
+name: history-e-minus-2
+description:
+	Check that repeated command is printed before command
+	is re-executed.
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	exec 2>&1
+	echo hi
+	echo there
+	fc -e -
+expected-stdout-pattern:
+	/X*hi\nX*there\nX*echo there\nthere\nX*/
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-e-minus-3
+description:
+	fc -e - fails when there is no history
+	(ksh93 has a bug that causes this to fail)
+	(ksh88 loops on this)
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	fc -e -
+	echo ok
+expected-stdout:
+	ok
+expected-stderr-pattern:
+	/^X*.*:.*history.*\nX*$/
+---
+name: history-e-minus-4
+description:
+	Check if "fc -e -" command output goes to stdout.
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc
+	fc -e - | (read x; echo "A $x")
+	echo ok
+expected-stdout:
+	abc
+	A abc
+	ok
+expected-stderr-pattern:
+	/^X*echo abc\nX*/
+---
+name: history-e-minus-5
+description:
+	fc is replaced in history by new command.
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def
+	echo ghi jkl
+	:
+	fc -e - echo
+	fc -l 2 5
+expected-stdout:
+	abc def
+	ghi jkl
+	ghi jkl
+	2	echo ghi jkl
+	3	:
+	4	echo ghi jkl
+	5	fc -l 2 5
+expected-stderr-pattern:
+	/^X*echo ghi jkl\nX*$/
+---
+name: history-list-1
+description:
+	List lists correct range
+	(ksh88 fails 'cause it lists the fc command)
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	fc -l -- -2
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	2	echo line 2
+	3	echo line 3
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-list-2
+description:
+	Lists oldest history if given pre-historic number
+	(ksh93 has a bug that causes this to fail)
+	(ksh88 fails 'cause it lists the fc command)
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	fc -l -- -40
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	1	echo line 1
+	2	echo line 2
+	3	echo line 3
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-list-3
+description:
+	Can give number 'options' to fc
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	echo line 4
+	fc -l -3 -2
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	line 4
+	2	echo line 2
+	3	echo line 3
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-list-4
+description:
+	-1 refers to previous command
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	echo line 4
+	fc -l -1 -1
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	line 4
+	4	echo line 4
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-list-5
+description:
+	List command stays in history
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	echo line 4
+	fc -l -1 -1
+	fc -l -2 -1
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	line 4
+	4	echo line 4
+	4	echo line 4
+	5	fc -l -1 -1
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-list-6
+description:
+	HISTSIZE limits about of history kept.
+	(ksh88 fails 'cause it lists the fc command)
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	echo line 4
+	echo line 5
+	fc -l
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	line 4
+	line 5
+	4	echo line 4
+	5	echo line 5
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-list-7
+description:
+	fc allows too old/new errors in range specification
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	echo line 4
+	echo line 5
+	fc -l 1 30
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	line 4
+	line 5
+	4	echo line 4
+	5	echo line 5
+	6	fc -l 1 30
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-list-r-1
+description:
+	test -r flag in history
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	echo line 4
+	echo line 5
+	fc -l -r 2 4
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	line 4
+	line 5
+	4	echo line 4
+	3	echo line 3
+	2	echo line 2
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-list-r-2
+description:
+	If first is newer than last, -r is implied.
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	echo line 4
+	echo line 5
+	fc -l 4 2
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	line 4
+	line 5
+	4	echo line 4
+	3	echo line 3
+	2	echo line 2
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-list-r-3
+description:
+	If first is newer than last, -r is cancelled.
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2
+	echo line 3
+	echo line 4
+	echo line 5
+	fc -l -r 4 2
+expected-stdout:
+	line 1
+	line 2
+	line 3
+	line 4
+	line 5
+	2	echo line 2
+	3	echo line 3
+	4	echo line 4
+expected-stderr-pattern:
+	/^X*$/
+---
+name: history-subst-1
+description:
+	Basic substitution
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def
+	echo ghi jkl
+	fc -e - abc=AB 'echo a'
+expected-stdout:
+	abc def
+	ghi jkl
+	AB def
+expected-stderr-pattern:
+	/^X*echo AB def\nX*$/
+---
+name: history-subst-2
+description:
+	Does subst find previous command?
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def
+	echo ghi jkl
+	fc -e - jkl=XYZQRT 'echo g'
+expected-stdout:
+	abc def
+	ghi jkl
+	ghi XYZQRT
+expected-stderr-pattern:
+	/^X*echo ghi XYZQRT\nX*$/
+---
+name: history-subst-3
+description:
+	Does subst find previous command when no arguments given
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def
+	echo ghi jkl
+	fc -e - jkl=XYZQRT
+expected-stdout:
+	abc def
+	ghi jkl
+	ghi XYZQRT
+expected-stderr-pattern:
+	/^X*echo ghi XYZQRT\nX*$/
+---
+name: history-subst-4
+description:
+	Global substitutions work
+	(ksh88 and ksh93 do not have -g option)
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def asjj sadjhasdjh asdjhasd
+	fc -e - -g a=FooBAR
+expected-stdout:
+	abc def asjj sadjhasdjh asdjhasd
+	FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd
+expected-stderr-pattern:
+	/^X*echo FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd\nX*$/
+---
+name: history-subst-5
+description:
+	Make sure searches don't find current (fc) command
+	(ksh88/ksh93 don't have the ? prefix thing so they fail this test)
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def
+	echo ghi jkl
+	fc -e - abc=AB \?abc
+expected-stdout:
+	abc def
+	ghi jkl
+	AB def
+expected-stderr-pattern:
+	/^X*echo AB def\nX*$/
+---
+name: history-ed-1-old
+description:
+	Basic (ed) editing works (assumes you have generic ed editor
+	that prints no prompts). This is for oldish ed(1) which write
+	the character count to stdout.
+category: stdout-ed
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def
+	fc echo
+	s/abc/FOOBAR/
+	w
+	q
+expected-stdout:
+	abc def
+	13
+	16
+	FOOBAR def
+expected-stderr-pattern:
+	/^X*echo FOOBAR def\nX*$/
+---
+name: history-ed-2-old
+description:
+	Correct command is edited when number given
+category: stdout-ed
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2 is here
+	echo line 3
+	echo line 4
+	fc 2
+	s/is here/is changed/
+	w
+	q
+expected-stdout:
+	line 1
+	line 2 is here
+	line 3
+	line 4
+	20
+	23
+	line 2 is changed
+expected-stderr-pattern:
+	/^X*echo line 2 is changed\nX*$/
+---
+name: history-ed-3-old
+description:
+	Newly created multi line commands show up as single command
+	in history.
+	(NOTE: adjusted for COMPLEX HISTORY compile time option)
+	(ksh88 fails 'cause it lists the fc command)
+category: stdout-ed
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def
+	fc echo
+	s/abc/FOOBAR/
+	$a
+	echo a new line
+	.
+	w
+	q
+	fc -l
+expected-stdout:
+	abc def
+	13
+	32
+	FOOBAR def
+	a new line
+	1	echo abc def
+	2	echo FOOBAR def
+	3	echo a new line
+expected-stderr-pattern:
+	/^X*echo FOOBAR def\necho a new line\nX*$/
+---
+name: history-ed-1
+description:
+	Basic (ed) editing works (assumes you have generic ed editor
+	that prints no prompts). This is for newish ed(1) and stderr.
+category: !no-stderr-ed
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def
+	fc echo
+	s/abc/FOOBAR/
+	w
+	q
+expected-stdout:
+	abc def
+	FOOBAR def
+expected-stderr-pattern:
+	/^X*13\n16\necho FOOBAR def\nX*$/
+---
+name: history-ed-2
+description:
+	Correct command is edited when number given
+category: !no-stderr-ed
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo line 1
+	echo line 2 is here
+	echo line 3
+	echo line 4
+	fc 2
+	s/is here/is changed/
+	w
+	q
+expected-stdout:
+	line 1
+	line 2 is here
+	line 3
+	line 4
+	line 2 is changed
+expected-stderr-pattern:
+	/^X*20\n23\necho line 2 is changed\nX*$/
+---
+name: history-ed-3
+description:
+	Newly created multi line commands show up as single command
+	in history.
+category: !no-stderr-ed
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	echo abc def
+	fc echo
+	s/abc/FOOBAR/
+	$a
+	echo a new line
+	.
+	w
+	q
+	fc -l
+expected-stdout:
+	abc def
+	FOOBAR def
+	a new line
+	1	echo abc def
+	2	echo FOOBAR def
+	3	echo a new line
+expected-stderr-pattern:
+	/^X*13\n32\necho FOOBAR def\necho a new line\nX*$/
+---
+name: IFS-space-1
+description:
+	Simple test, default IFS
+stdin:
+	showargs() { for i; do echo -n " <$i>"; done; echo; }
+	set -- A B C
+	showargs 1 $*
+	showargs 2 "$*"
+	showargs 3 $@
+	showargs 4 "$@"
+expected-stdout:
+	 <1> <A> <B> <C>
+	 <2> <A B C>
+	 <3> <A> <B> <C>
+	 <4> <A> <B> <C>
+---
+name: IFS-colon-1
+description:
+	Simple test, IFS=:
+stdin:
+	showargs() { for i; do echo -n " <$i>"; done; echo; }
+	IFS=:
+	set -- A B C
+	showargs 1 $*
+	showargs 2 "$*"
+	showargs 3 $@
+	showargs 4 "$@"
+expected-stdout:
+	 <1> <A> <B> <C>
+	 <2> <A:B:C>
+	 <3> <A> <B> <C>
+	 <4> <A> <B> <C>
+---
+name: IFS-null-1
+description:
+	Simple test, IFS=""
+stdin:
+	showargs() { for i; do echo -n " <$i>"; done; echo; }
+	IFS=""
+	set -- A B C
+	showargs 1 $*
+	showargs 2 "$*"
+	showargs 3 $@
+	showargs 4 "$@"
+expected-stdout:
+	 <1> <A B C>
+	 <2> <ABC>
+	 <3> <A B C>
+	 <4> <A B C>
+---
+name: IFS-space-colon-1
+description:
+	Simple test, IFS=<white-space>:
+stdin:
+	showargs() { for i; do echo -n " <$i>"; done; echo; }
+	IFS="$IFS:"
+	set --
+	showargs 1 $*
+	showargs 2 "$*"
+	showargs 3 $@
+	showargs 4 "$@"
+	showargs 5 : "$@"
+expected-stdout:
+	 <1>
+	 <2> <>
+	 <3>
+	 <4>
+	 <5> <:>
+---
+name: IFS-space-colon-2
+description:
+	Simple test, IFS=<white-space>:
+	AT&T ksh fails this, POSIX says the test is correct.
+stdin:
+	showargs() { for i; do echo -n " <$i>"; done; echo; }
+	IFS="$IFS:"
+	set --
+	showargs :"$@"
+expected-stdout:
+	 <:>
+---
+name: IFS-space-colon-3
+description:
+	Simple test, IFS=<white-space>:
+	pdksh fails both of these tests
+	not sure whether #2 is correct
+stdin:
+	showargs() { for i; do echo -n " <$i>"; done; echo; }
+	IFS="$IFS:"
+	x=
+	set --
+	showargs "$x$@" 1
+	showargs "$@$x" 2
+expected-fail: yes
+expected-stdout:
+	 <> <1>
+	 <> <2>
+---
+name: IFS-space-colon-4
+description:
+	Simple test, IFS=<white-space>:
+stdin:
+	showargs() { for i; do echo -n " <$i>"; done; echo; }
+	IFS="$IFS:"
+	set --
+	showargs "$@$@"
+expected-stdout:
+	
+---
+name: IFS-space-colon-5
+description:
+	Simple test, IFS=<white-space>:
+	Don't know what POSIX thinks of this.  AT&T ksh does not do this.
+stdin:
+	showargs() { for i; do echo -n " <$i>"; done; echo; }
+	IFS="$IFS:"
+	set --
+	showargs "${@:-}"
+expected-stdout:
+	 <>
+---
+name: IFS-subst-1
+description:
+	Simple test, IFS=<white-space>:
+stdin:
+	showargs() { for i; do echo -n " <$i>"; done; echo; }
+	IFS="$IFS:"
+	x=":b: :"
+	echo -n '1:'; for i in $x ; do echo -n " [$i]" ; done ; echo
+	echo -n '2:'; for i in :b:: ; do echo -n " [$i]" ; done ; echo
+	showargs 3 $x
+	showargs 4 :b::
+	x="a:b:"
+	echo -n '5:'; for i in $x ; do echo -n " [$i]" ; done ; echo
+	showargs 6 $x
+	x="a::c"
+	echo -n '7:'; for i in $x ; do echo -n " [$i]" ; done ; echo
+	showargs 8 $x
+	echo -n '9:'; for i in ${FOO-`echo -n h:i`th:ere} ; do echo -n " [$i]" ; done ; echo
+	showargs 10 ${FOO-`echo -n h:i`th:ere}
+	showargs 11 "${FOO-`echo -n h:i`th:ere}"
+	x=" A :  B::D"
+	echo -n '12:'; for i in $x ; do echo -n " [$i]" ; done ; echo
+	showargs 13 $x
+expected-stdout:
+	1: [] [b] []
+	2: [:b::]
+	 <3> <> <b> <>
+	 <4> <:b::>
+	5: [a] [b]
+	 <6> <a> <b>
+	7: [a] [] [c]
+	 <8> <a> <> <c>
+	9: [h] [ith] [ere]
+	 <10> <h> <ith> <ere>
+	 <11> <h:ith:ere>
+	12: [A] [B] [] [D]
+	 <13> <A> <B> <> <D>
+---
+name: integer-base-err-1
+description:
+	Can't have 0 base (causes shell to exit)
+expected-exit: e != 0
+stdin:
+	typeset -i i
+	i=3
+	i=0#4
+	echo $i
+expected-stderr-pattern:
+	/^.*:.*0#4.*\n$/
+---
+name: integer-base-err-2
+description:
+	Can't have multiple bases in a 'constant' (causes shell to exit)
+	(ksh88 fails this test)
+expected-exit: e != 0
+stdin:
+	typeset -i i
+	i=3
+	i=2#110#11
+	echo $i
+expected-stderr-pattern:
+	/^.*:.*2#110#11.*\n$/
+---
+name: integer-base-err-3
+description:
+	Syntax errors in expressions and effects on bases
+	(interactive so errors don't cause exits)
+	(ksh88 fails this test - shell exits, even with -i)
+arguments: !-i!
+stdin:
+	PS1= # minimise prompt hassles
+	typeset -i4 a=10
+	typeset -i a=2+
+	echo $a
+	typeset -i4 a=10
+	typeset -i2 a=2+
+	echo $a
+expected-stderr-pattern:
+	/^([#\$] )?.*:.*2+.*\n.*:.*2+.*\n$/
+expected-stdout:
+	4#22
+	4#22
+---
+name: integer-base-err-4
+description:
+	Are invalid digits (according to base) errors?
+	(ksh93 fails this test)
+expected-exit: e != 0
+stdin:
+	typeset -i i;
+	i=3#4
+expected-stderr-pattern:
+	/^([#\$] )?.*:.*3#4.*\n$/
+---
+name: integer-base-1
+description:
+	Missing number after base is treated as 0.
+stdin:
+	typeset -i i
+	i=3
+	i=2#
+	echo $i
+expected-stdout:
+	0
+---
+name: integer-base-2
+description:
+	Check 'stickyness' of base in various situations
+stdin:
+	typeset -i i=8
+	echo $i
+	echo ---------- A
+	typeset -i4 j=8
+	echo $j
+	echo ---------- B
+	typeset -i k=8
+	typeset -i4 k=8
+	echo $k
+	echo ---------- C
+	typeset -i4 l
+	l=3#10
+	echo $l
+	echo ---------- D
+	typeset -i m
+	m=3#10
+	echo $m
+	echo ---------- E
+	n=2#11
+	typeset -i n
+	echo $n
+	n=10
+	echo $n
+	echo ---------- F
+	typeset -i8 o=12
+	typeset -i4 o
+	echo $o
+	echo ---------- G
+	typeset -i p
+	let p=8#12
+	echo $p
+expected-stdout:
+	8
+	---------- A
+	4#20
+	---------- B
+	4#20
+	---------- C
+	4#3
+	---------- D
+	3#10
+	---------- E
+	2#11
+	2#1010
+	---------- F
+	4#30
+	---------- G
+	8#12
+---
+name: integer-base-3
+description:
+	More base parsing (hmm doesn't test much..)
+stdin:
+	typeset -i aa
+	aa=1+12#10+2
+	echo $aa
+	typeset -i bb
+	bb=1+$aa
+	echo $bb
+	typeset -i bb
+	bb=$aa
+	echo $bb
+	typeset -i cc
+	cc=$aa
+	echo $cc
+expected-stdout:
+	15
+	16
+	15
+	15
+---
+name: integer-base-4
+description:
+	Check that things not declared as integers are not made integers,
+	also, check if base is not reset by -i with no arguments.
+	(ksh93 fails - prints 10#20 - go figure)
+stdin:
+	xx=20
+	let xx=10
+	typeset -i | grep '^xx='
+	typeset -i4 a=10
+	typeset -i a=20
+	echo $a
+expected-stdout:
+	4#110
+---
+name: integer-base-5
+description:
+	More base stuff
+stdin:
+	typeset -i4 a=3#10
+	echo $a
+	echo --
+	typeset -i j=3
+	j='~3'
+	echo $j
+	echo --
+	typeset -i k=1
+	x[k=k+1]=3
+	echo $k
+	echo --
+	typeset -i l
+	for l in 1 2+3 4; do echo $l; done
+expected-stdout:
+	4#3
+	--
+	-4
+	--
+	2
+	--
+	1
+	5
+	4
+---
+name: integer-base-6
+description:
+	Even more base stuff
+	(ksh93 fails this test - prints 0)
+stdin:
+	typeset -i7 i
+	i=
+	echo $i
+expected-stdout:
+	7#0
+---
+name: integer-base-7
+description:
+	Check that non-integer parameters don't get bases assigned
+stdin:
+	echo $(( zz = 8#100 ))
+	echo $zz
+expected-stdout:
+	64
+	64
+---
+name: lineno-stdin
+description:
+	See if $LINENO is updated and can be modified.
+stdin:
+	echo A $LINENO
+	echo B $LINENO
+	LINENO=20
+	echo C $LINENO
+expected-stdout:
+	A 1
+	B 2
+	C 20
+---
+name: lineno-inc
+description:
+	See if $LINENO is set for .'d files.
+file-setup: file 644 "dotfile"
+	echo dot A $LINENO
+	echo dot B $LINENO
+	LINENO=20
+	echo dot C $LINENO
+stdin:
+	echo A $LINENO
+	echo B $LINENO
+	. ./dotfile
+expected-stdout:
+	A 1
+	B 2
+	dot A 1
+	dot B 2
+	dot C 20
+---
+name: lineno-func
+description:
+	See if $LINENO is set for commands in a function.
+stdin:
+	echo A $LINENO
+	echo B $LINENO
+	bar() {
+	    echo func A $LINENO
+	    echo func B $LINENO
+	}
+	bar
+	echo C $LINENO
+expected-stdout:
+	A 1
+	B 2
+	func A 4
+	func B 5
+	C 8
+---
+name: lineno-unset
+description:
+	See if unsetting LINENO makes it non-magic.
+file-setup: file 644 "dotfile"
+	echo dot A $LINENO
+	echo dot B $LINENO
+stdin:
+	unset LINENO
+	echo A $LINENO
+	echo B $LINENO
+	bar() {
+	    echo func A $LINENO
+	    echo func B $LINENO
+	}
+	bar
+	. ./dotfile
+	echo C $LINENO
+expected-stdout:
+	A
+	B
+	func A
+	func B
+	dot A
+	dot B
+	C
+---
+name: lineno-unset-use
+description:
+	See if unsetting LINENO makes it non-magic even
+	when it is re-used.
+file-setup: file 644 "dotfile"
+	echo dot A $LINENO
+	echo dot B $LINENO
+stdin:
+	unset LINENO
+	LINENO=3
+	echo A $LINENO
+	echo B $LINENO
+	bar() {
+	    echo func A $LINENO
+	    echo func B $LINENO
+	}
+	bar
+	. ./dotfile
+	echo C $LINENO
+expected-stdout:
+	A 3
+	B 3
+	func A 3
+	func B 3
+	dot A 3
+	dot B 3
+	C 3
+---
+name: lineno-trap
+description:
+	Check if LINENO is tracked in traps
+stdin:
+	fail() {
+		echo "line <$1>"
+		exit 1
+	}
+	trap 'fail $LINENO' INT ERR
+	false
+expected-stdout:
+	line <6>
+expected-exit: 1
+---
+name: read-IFS-1
+description:
+	Simple test, default IFS
+stdin:
+	echo "A B " > IN
+	unset x y z
+	read x y z < IN
+	echo 1: "x[$x] y[$y] z[$z]"
+	echo 1a: ${z-z not set}
+	read x < IN
+	echo 2: "x[$x]"
+expected-stdout:
+	1: x[A] y[B] z[]
+	1a:
+	2: x[A B]
+---
+name: read-ksh-1
+description:
+	If no var specified, REPLY is used
+stdin:
+	echo "abc" > IN
+	read < IN
+	echo "[$REPLY]";
+expected-stdout:
+	[abc]
+---
+name: regression-1
+description:
+	Lex array code had problems with this.
+stdin:
+	echo foo[
+	n=bar
+	echo "hi[ $n ]=1"
+expected-stdout:
+	foo[
+	hi[ bar ]=1
+---
+name: regression-2
+description:
+	When PATH is set before running a command, the new path is
+	not used in doing the path search
+		$ echo echo hi > /tmp/q ; chmod a+rx /tmp/q
+		$ PATH=/tmp q
+		q: not found
+		$
+	in comexec() the two lines
+		while (*vp != NULL)
+			(void) typeset(*vp++, xxx, 0);
+	need to be moved out of the switch to before findcom() is
+	called - I don't know what this will break.
+stdin:
+	: ${PWD:-`pwd 2> /dev/null`}
+	: ${PWD:?"PWD not set - can't do test"}
+	mkdir Y
+	cat > Y/xxxscript << EOF
+	#!/bin/sh
+	# Need to restore path so echo can be found (some shells don't have
+	# it as a built-in)
+	PATH=\$OLDPATH
+	echo hi
+	exit 0
+	EOF
+	chmod a+rx Y/xxxscript
+	export OLDPATH="$PATH"
+	PATH=$PWD/Y xxxscript
+	exit $?
+expected-stdout:
+	hi
+---
+name: regression-6
+description:
+	Parsing of $(..) expressions is non-optimal.  It is
+	impossible to have any parentheses inside the expression.
+	I.e.,
+		$ ksh -c 'echo $(echo \( )'
+		no closing quote
+		$ ksh -c 'echo $(echo "(" )'
+		no closing quote
+		$
+	The solution is to hack the parsing clode in lex.c, the
+	question is how to hack it: should any parentheses be
+	escaped by a backslash, or should recursive parsing be done
+	(so quotes could also be used to hide hem).  The former is
+	easier, the later better...
+stdin:
+	echo $(echo \()
+expected-stdout:
+	(
+---
+name: regression-9
+description:
+	Continue in a for loop does not work right:
+		for i in a b c ; do
+			if [ $i = b ] ; then
+				continue
+			fi
+			echo $i
+		done
+	Prints a forever...
+stdin:
+	first=yes
+	for i in a b c ; do
+		if [ $i = b ] ; then
+			if [ $first = no ] ; then
+				echo 'continue in for loop broken'
+				break	# hope break isn't broken too :-)
+			fi
+			first=no
+			continue
+		fi
+	done
+	echo bye
+expected-stdout:
+	bye
+---
+name: regression-10
+description:
+	The following:
+		set -- `false`
+		echo $?
+	should print 0 according to POSIX (dash, bash, ksh93, posh)
+	but not 0 according to the getopt(1) manual page, ksh88, and
+	Bourne sh (such as /bin/sh on Solaris).
+	In mksh R39b, we honour POSIX except when -o sh is set.
+stdin:
+	showf() {
+		[[ -o posix ]]; FPOSIX=$((1-$?))
+		[[ -o sh ]]; FSH=$((1-$?))
+		echo -n "FPOSIX=$FPOSIX FSH=$FSH "
+	}
+	set +o posix +o sh
+	showf
+	set -- `false`
+	echo rv=$?
+	set -o sh
+	showf
+	set -- `false`
+	echo rv=$?
+	set -o posix
+	showf
+	set -- `false`
+	echo rv=$?
+expected-stdout:
+	FPOSIX=0 FSH=0 rv=0
+	FPOSIX=0 FSH=1 rv=1
+	FPOSIX=1 FSH=0 rv=0
+---
+name: regression-11
+description:
+	The following:
+		x=/foo/bar/blah
+		echo ${x##*/}
+	should echo blah but on some machines echos /foo/bar/blah.
+stdin:
+	x=/foo/bar/blah
+	echo ${x##*/}
+expected-stdout:
+	blah
+---
+name: regression-12
+description:
+	Both of the following echos produce the same output under sh/ksh.att:
+		#!/bin/sh
+		x="foo	bar"
+		echo "`echo \"$x\"`"
+		echo "`echo "$x"`"
+	pdksh produces different output for the former (foo instead of foo\tbar)
+stdin:
+	x="foo	bar"
+	echo "`echo \"$x\"`"
+	echo "`echo "$x"`"
+expected-stdout:
+	foo	bar
+	foo	bar
+---
+name: regression-13
+description:
+	The following command hangs forever:
+		$ (: ; cat /etc/termcap) | sleep 2
+	This is because the shell forks a shell to run the (..) command
+	and this shell has the pipe open.  When the sleep dies, the cat
+	doesn't get a SIGPIPE 'cause a process (ie, the second shell)
+	still has the pipe open.
+	
+	NOTE: this test provokes a bizarre bug in ksh93 (shell starts reading
+	      commands from /etc/termcap..)
+time-limit: 10
+stdin:
+	echo A line of text that will be duplicated quite a number of times.> t1
+	cat t1 t1 t1 t1  t1 t1 t1 t1  t1 t1 t1 t1  t1 t1 t1 t1  > t2
+	cat t2 t2 t2 t2  t2 t2 t2 t2  t2 t2 t2 t2  t2 t2 t2 t2  > t1
+	cat t1 t1 t1 t1 > t2
+	(: ; cat t2 2>&-) | sleep 1
+---
+name: regression-14
+description:
+	The command
+		$ (foobar) 2> /dev/null
+	generates no output under /bin/sh, but pdksh produces the error
+		foobar: not found
+	Also, the command
+		$ foobar 2> /dev/null
+	generates an error under /bin/sh and pdksh, but AT&T ksh88 produces
+	no error (redirected to /dev/null).
+stdin:
+	(you/should/not/see/this/error/1) 2> /dev/null
+	you/should/not/see/this/error/2 2> /dev/null
+	true
+---
+name: regression-15
+description:
+	The command
+		$ whence foobar
+	generates a blank line under pdksh and sets the exit status to 0.
+	AT&T ksh88 generates no output and sets the exit status to 1.  Also,
+	the command
+		$ whence foobar cat
+	generates no output under AT&T ksh88 (pdksh generates a blank line
+	and /bin/cat).
+stdin:
+	whence does/not/exist > /dev/null
+	echo 1: $?
+	echo 2: $(whence does/not/exist | wc -l)
+	echo 3: $(whence does/not/exist cat | wc -l)
+expected-stdout:
+	1: 1
+	2: 0
+	3: 0
+---
+name: regression-16
+description:
+	${var%%expr} seems to be broken in many places.  On the mips
+	the commands
+		$ read line < /etc/passwd
+		$ echo $line
+		root:0:1:...
+		$ echo ${line%%:*}
+		root
+		$ echo $line
+		root
+		$
+	change the value of line.  On sun4s & pas, the echo ${line%%:*} doesn't
+	work.  Haven't checked elsewhere...
+script:
+	read x
+	y=$x
+	echo ${x%%:*}
+	echo $x
+stdin:
+	root:asdjhasdasjhs:0:1:Root:/:/bin/sh
+expected-stdout:
+	root
+	root:asdjhasdasjhs:0:1:Root:/:/bin/sh
+---
+name: regression-17
+description:
+	The command
+		. /foo/bar
+	should set the exit status to non-zero (sh and AT&T ksh88 do).
+	XXX doting a non existent file is a fatal error for a script
+stdin:
+	. does/not/exist
+expected-exit: e != 0
+expected-stderr-pattern: /.?/
+---
+name: regression-19
+description:
+	Both of the following echos should produce the same thing, but don't:
+		$ x=foo/bar
+		$ echo ${x%/*}
+		foo
+		$ echo "${x%/*}"
+		foo/bar
+stdin:
+	x=foo/bar
+	echo "${x%/*}"
+expected-stdout:
+	foo
+---
+name: regression-21
+description:
+	backslash does not work as expected in case labels:
+	$ x='-x'
+	$ case $x in
+	-\?) echo hi
+	esac
+	hi
+	$ x='-?'
+	$ case $x in
+	-\\?) echo hi
+	esac
+	hi
+	$
+stdin:
+	case -x in
+	-\?)	echo fail
+	esac
+---
+name: regression-22
+description:
+	Quoting backquotes inside backquotes doesn't work:
+	$ echo `echo hi \`echo there\` folks`
+	asks for more info.  sh and AT&T ksh88 both echo
+	hi there folks
+stdin:
+	echo `echo hi \`echo there\` folks`
+expected-stdout:
+	hi there folks
+---
+name: regression-23
+description:
+	)) is not treated `correctly':
+	    $ (echo hi ; (echo there ; echo folks))
+	    missing ((
+	    $
+	instead of (as sh and ksh.att)
+	    $ (echo hi ; (echo there ; echo folks))
+	    hi
+	    there
+	    folks
+	    $
+stdin:
+	( : ; ( : ; echo hi))
+expected-stdout:
+	hi
+---
+name: regression-25
+description:
+	Check reading stdin in a while loop.  The read should only read
+	a single line, not a whole stdio buffer; the cat should get
+	the rest.
+stdin:
+	(echo a; echo b) | while read x ; do
+	    echo $x
+	    cat > /dev/null
+	done
+expected-stdout:
+	a
+---
+name: regression-26
+description:
+	Check reading stdin in a while loop.  The read should read both
+	lines, not just the first.
+script:
+	a=
+	while [ "$a" != xxx ] ; do
+	    last=$x
+	    read x
+	    cat /dev/null | sed 's/x/y/'
+	    a=x$a
+	done
+	echo $last
+stdin:
+	a
+	b
+expected-stdout:
+	b
+---
+name: regression-27
+description:
+	The command
+		. /does/not/exist
+	should cause a script to exit.
+stdin:
+	. does/not/exist
+	echo hi
+expected-exit: e != 0
+expected-stderr-pattern: /does\/not\/exist/
+---
+name: regression-28
+description:
+	variable assignements not detected well
+stdin:
+	a.x=1 echo hi
+expected-exit: e != 0
+expected-stderr-pattern: /a\.x=1/
+---
+name: regression-29
+description:
+	alias expansion different from AT&T ksh88
+stdin:
+	alias a='for ' b='i in'
+	a b hi ; do echo $i ; done
+expected-stdout:
+	hi
+---
+name: regression-30
+description:
+	strange characters allowed inside ${...}
+stdin:
+	echo ${a{b}}
+expected-exit: e != 0
+expected-stderr-pattern: /.?/
+---
+name: regression-31
+description:
+	Does read handle partial lines correctly
+script:
+	a= ret=
+	while [ "$a" != xxx ] ; do
+	    read x y z
+	    ret=$?
+	    a=x$a
+	done
+	echo "[$x]"
+	echo $ret
+stdin: !
+	a A aA
+	b B Bb
+	c
+expected-stdout:
+	[c]
+	1
+---
+name: regression-32
+description:
+	Does read set variables to null at eof?
+script:
+	a=
+	while [ "$a" != xxx ] ; do
+	    read x y z
+	    a=x$a
+	done
+	echo 1: ${x-x not set} ${y-y not set} ${z-z not set}
+	echo 2: ${x:+x not null} ${y:+y not null} ${z:+z not null}
+stdin:
+	a A Aa
+	b B Bb
+expected-stdout:
+	1:
+	2:
+---
+name: regression-33
+description:
+	Does umask print a leading 0 when umask is 3 digits?
+stdin:
+	umask 222
+	umask
+expected-stdout:
+	0222
+---
+name: regression-35
+description:
+	Tempory files used for here-docs in functions get trashed after
+	the function is parsed (before it is executed)
+stdin:
+	f1() {
+		cat <<- EOF
+			F1
+		EOF
+		f2() {
+			cat <<- EOF
+				F2
+			EOF
+		}
+	}
+	f1
+	f2
+	unset -f f1
+	f2
+expected-stdout:
+	F1
+	F2
+	F2
+---
+name: regression-36
+description:
+	Command substitution breaks reading in while loop
+	(test from <sjg@void.zen.oz.au>)
+stdin:
+	(echo abcdef; echo; echo 123) |
+	    while read line
+	    do
+	      # the following line breaks it
+	      c=`echo $line | wc -c`
+	      echo $c
+	    done
+expected-stdout:
+	7
+	1
+	4
+---
+name: regression-37
+description:
+	Machines with broken times() (reported by <sjg@void.zen.oz.au>)
+	time does not report correct real time
+stdin:
+	time sleep 1
+expected-stderr-pattern: !/^\s*0\.0[\s\d]+real|^\s*real[\s]+0+\.0/
+---
+name: regression-38
+description:
+	set -e doesn't ignore exit codes for if/while/until/&&/||/!.
+arguments: !-e!
+stdin:
+	if false; then echo hi ; fi
+	false || true
+	false && true
+	while false; do echo hi; done
+	echo ok
+expected-stdout:
+	ok
+---
+name: regression-39
+description:
+	set -e: errors in command substitutions aren't ignored
+	Not clear if they should be or not... bash passes here
+	this may actually be required for make, so changed the
+	test to make this an mksh feature, not a bug
+arguments: !-e!
+stdin:
+	echo `false; echo hi`
+#expected-fail: yes
+#expected-stdout:
+#	hi
+expected-stdout:
+	
+---
+name: regression-40
+description:
+	This used to cause a core dump
+env-setup: !RANDOM=12!
+stdin:
+	echo hi
+expected-stdout:
+	hi
+---
+name: regression-41
+description:
+	foo should be set to bar (should not be empty)
+stdin:
+	foo=`
+	echo bar`
+	echo "($foo)"
+expected-stdout:
+	(bar)
+---
+name: regression-42
+description:
+	Can't use command line assignments to assign readonly parameters.
+stdin:
+	foo=bar
+	readonly foo
+	foo=stuff env | grep '^foo'
+expected-exit: e != 0
+expected-stderr-pattern:
+	/.*read *only.*/
+---
+name: regression-43
+description:
+	Can subshells be prefixed by redirections (historical shells allow
+	this)
+stdin:
+	< /dev/null (sed 's/^/X/')
+---
+name: regression-45
+description:
+	Parameter assignments with [] recognised correctly
+stdin:
+	FOO=*[12]
+	BAR=abc[
+	MORE=[abc]
+	JUNK=a[bc
+	echo "<$FOO>"
+	echo "<$BAR>"
+	echo "<$MORE>"
+	echo "<$JUNK>"
+expected-stdout:
+	<*[12]>
+	<abc[>
+	<[abc]>
+	<a[bc>
+---
+name: regression-46
+description:
+	Check that alias expansion works in command substitutions and
+	at the end of file.
+stdin:
+	alias x='echo hi'
+	FOO="`x` "
+	echo "[$FOO]"
+	x
+expected-stdout:
+	[hi ]
+	hi
+---
+name: regression-47
+description:
+	Check that aliases are fully read.
+stdin:
+	alias x='echo hi;
+	echo there'
+	x
+	echo done
+expected-stdout:
+	hi
+	there
+	done
+---
+name: regression-48
+description:
+	Check that (here doc) temp files are not left behind after an exec.
+stdin:
+	mkdir foo || exit 1
+	TMPDIR=$PWD/foo "$__progname" <<- 'EOF'
+		x() {
+			sed 's/^/X /' << E_O_F
+			hi
+			there
+			folks
+			E_O_F
+			echo "done ($?)"
+		}
+		echo=echo; [ -x /bin/echo ] && echo=/bin/echo
+		exec $echo subtest-1 hi
+	EOF
+	echo subtest-1 foo/*
+	TMPDIR=$PWD/foo "$__progname" <<- 'EOF'
+		echo=echo; [ -x /bin/echo ] && echo=/bin/echo
+		sed 's/^/X /' << E_O_F; exec $echo subtest-2 hi
+		a
+		few
+		lines
+		E_O_F
+	EOF
+	echo subtest-2 foo/*
+expected-stdout:
+	subtest-1 hi
+	subtest-1 foo/*
+	X a
+	X few
+	X lines
+	subtest-2 hi
+	subtest-2 foo/*
+---
+name: regression-49
+description:
+	Check that unset params with attributes are reported by set, those
+	sans attributes are not.
+stdin:
+	unset FOO BAR
+	echo X$FOO
+	export BAR
+	typeset -i BLAH
+	set | grep FOO
+	set | grep BAR
+	set | grep BLAH
+expected-stdout:
+	X
+	BAR
+	BLAH
+---
+name: regression-50
+description:
+	Check that aliases do not use continuation prompt after trailing
+	semi-colon.
+file-setup: file 644 "env"
+	PS1=Y
+	PS2=X
+env-setup: !ENV=./env!
+arguments: !-i!
+stdin:
+	alias foo='echo hi ; '
+	foo
+	foo echo there
+expected-stdout:
+	hi
+	hi
+	there
+expected-stderr: !
+	YYYY
+---
+name: regression-51
+description:
+	Check that set allows both +o and -o options on same command line.
+stdin:
+	set a b c
+	set -o noglob +o allexport
+	echo A: $*, *
+expected-stdout:
+	A: a b c, *
+---
+name: regression-52
+description:
+	Check that globbing works in pipelined commands
+file-setup: file 644 "env"
+	PS1=P
+file-setup: file 644 "abc"
+	stuff
+env-setup: !ENV=./env!
+arguments: !-i!
+stdin:
+	sed 's/^/X /' < ab*
+	echo mark 1
+	sed 's/^/X /' < ab* | sed 's/^/Y /'
+	echo mark 2
+expected-stdout:
+	X stuff
+	mark 1
+	Y X stuff
+	mark 2
+expected-stderr: !
+	PPPPP
+---
+name: regression-53
+description:
+	Check that getopts works in functions
+stdin:
+	bfunc() {
+	    echo bfunc: enter "(args: $*; OPTIND=$OPTIND)"
+	    while getopts B oc; do
+		case $oc in
+		  (B)
+		    echo bfunc: B option
+		    ;;
+		  (*)
+		    echo bfunc: odd option "($oc)"
+		    ;;
+		esac
+	    done
+	    echo bfunc: leave
+	}
+	
+	function kfunc {
+	    echo kfunc: enter "(args: $*; OPTIND=$OPTIND)"
+	    while getopts K oc; do
+		case $oc in
+		  (K)
+		    echo kfunc: K option
+		    ;;
+		  (*)
+		    echo bfunc: odd option "($oc)"
+		    ;;
+		esac
+	    done
+	    echo kfunc: leave
+	}
+	
+	set -- -f -b -k -l
+	echo "line 1: OPTIND=$OPTIND"
+	getopts kbfl optc
+	echo "line 2: ret=$?, optc=$optc, OPTIND=$OPTIND"
+	bfunc -BBB blah
+	echo "line 3: OPTIND=$OPTIND"
+	getopts kbfl optc
+	echo "line 4: ret=$?, optc=$optc, OPTIND=$OPTIND"
+	kfunc -KKK blah
+	echo "line 5: OPTIND=$OPTIND"
+	getopts kbfl optc
+	echo "line 6: ret=$?, optc=$optc, OPTIND=$OPTIND"
+	echo
+	
+	OPTIND=1
+	set -- -fbkl
+	echo "line 10: OPTIND=$OPTIND"
+	getopts kbfl optc
+	echo "line 20: ret=$?, optc=$optc, OPTIND=$OPTIND"
+	bfunc -BBB blah
+	echo "line 30: OPTIND=$OPTIND"
+	getopts kbfl optc
+	echo "line 40: ret=$?, optc=$optc, OPTIND=$OPTIND"
+	kfunc -KKK blah
+	echo "line 50: OPTIND=$OPTIND"
+	getopts kbfl optc
+	echo "line 60: ret=$?, optc=$optc, OPTIND=$OPTIND"
+expected-stdout:
+	line 1: OPTIND=1
+	line 2: ret=0, optc=f, OPTIND=2
+	bfunc: enter (args: -BBB blah; OPTIND=2)
+	bfunc: B option
+	bfunc: B option
+	bfunc: leave
+	line 3: OPTIND=2
+	line 4: ret=0, optc=b, OPTIND=3
+	kfunc: enter (args: -KKK blah; OPTIND=1)
+	kfunc: K option
+	kfunc: K option
+	kfunc: K option
+	kfunc: leave
+	line 5: OPTIND=3
+	line 6: ret=0, optc=k, OPTIND=4
+	
+	line 10: OPTIND=1
+	line 20: ret=0, optc=f, OPTIND=2
+	bfunc: enter (args: -BBB blah; OPTIND=2)
+	bfunc: B option
+	bfunc: B option
+	bfunc: leave
+	line 30: OPTIND=2
+	line 40: ret=1, optc=?, OPTIND=2
+	kfunc: enter (args: -KKK blah; OPTIND=1)
+	kfunc: K option
+	kfunc: K option
+	kfunc: K option
+	kfunc: leave
+	line 50: OPTIND=2
+	line 60: ret=1, optc=?, OPTIND=2
+---
+name: regression-54
+description:
+	Check that ; is not required before the then in if (( ... )) then ...
+stdin:
+	if (( 1 )) then
+	    echo ok dparen
+	fi
+	if [[ -n 1 ]] then
+	    echo ok dbrackets
+	fi
+expected-stdout:
+	ok dparen
+	ok dbrackets
+---
+name: regression-55
+description:
+	Check ${foo:%bar} is allowed (ksh88 allows it...)
+stdin:
+	x=fooXbarXblah
+	echo 1 ${x%X*}
+	echo 2 ${x:%X*}
+	echo 3 ${x%%X*}
+	echo 4 ${x:%%X*}
+	echo 5 ${x#*X}
+	echo 6 ${x:#*X}
+	echo 7 ${x##*X}
+	echo 8 ${x:##*X}
+expected-stdout:
+	1 fooXbar
+	2 fooXbar
+	3 foo
+	4 foo
+	5 barXblah
+	6 barXblah
+	7 blah
+	8 blah
+---
+name: regression-57
+description:
+	Check if typeset output is correct for
+	uninitialised array elements.
+stdin:
+	typeset -i xxx[4]
+	echo A
+	typeset -i | grep xxx | sed 's/^/    /'
+	echo B
+	typeset | grep xxx | sed 's/^/    /'
+	
+	xxx[1]=2+5
+	echo M
+	typeset -i | grep xxx | sed 's/^/    /'
+	echo N
+	typeset | grep xxx | sed 's/^/    /'
+expected-stdout:
+	A
+	    xxx
+	B
+	    typeset -i xxx
+	M
+	    xxx[1]=7
+	N
+	    typeset -i xxx
+---
+name: regression-58
+description:
+	Check if trap exit is ok (exit not mistaken for signal name)
+stdin:
+	trap 'echo hi' exit
+	trap exit 1
+expected-stdout:
+	hi
+---
+name: regression-59
+description:
+	Check if ${#array[*]} is calculated correctly.
+stdin:
+	a[12]=hi
+	a[8]=there
+	echo ${#a[*]}
+expected-stdout:
+	2
+---
+name: regression-60
+description:
+	Check if default exit status is previous command
+stdin:
+	(true; exit)
+	echo A $?
+	(false; exit)
+	echo B $?
+	( (exit 103) ; exit)
+	echo C $?
+expected-stdout:
+	A 0
+	B 1
+	C 103
+---
+name: regression-61
+description:
+	Check if EXIT trap is executed for sub shells.
+stdin:
+	trap 'echo parent exit' EXIT
+	echo start
+	(echo A; echo A last)
+	echo B
+	(echo C; trap 'echo sub exit' EXIT; echo C last)
+	echo parent last
+expected-stdout:
+	start
+	A
+	A last
+	B
+	C
+	C last
+	sub exit
+	parent last
+	parent exit
+---
+name: regression-62
+description:
+	Check if test -nt/-ot succeeds if second(first) file is missing.
+stdin:
+	touch a
+	test a -nt b && echo nt OK || echo nt BAD
+	test b -ot a && echo ot OK || echo ot BAD
+expected-stdout:
+	nt OK
+	ot OK
+---
+name: regression-63
+description:
+	Check if typeset, export, and readonly work
+stdin:
+	{
+		echo FNORD-0
+		FNORD_A=1
+		FNORD_B=2
+		FNORD_C=3
+		FNORD_D=4
+		FNORD_E=5
+		FNORD_F=6
+		FNORD_G=7
+		FNORD_H=8
+		integer FNORD_E FNORD_F FNORD_G FNORD_H
+		export FNORD_C FNORD_D FNORD_G FNORD_H
+		readonly FNORD_B FNORD_D FNORD_F FNORD_H
+		echo FNORD-1
+		export
+		echo FNORD-2
+		export -p
+		echo FNORD-3
+		readonly
+		echo FNORD-4
+		readonly -p
+		echo FNORD-5
+		typeset
+		echo FNORD-6
+		typeset -p
+		echo FNORD-7
+		typeset -
+		echo FNORD-8
+	} | fgrep FNORD
+expected-stdout:
+	FNORD-0
+	FNORD-1
+	FNORD_C
+	FNORD_D
+	FNORD_G
+	FNORD_H
+	FNORD-2
+	export FNORD_C=3
+	export FNORD_D=4
+	export FNORD_G=7
+	export FNORD_H=8
+	FNORD-3
+	FNORD_B
+	FNORD_D
+	FNORD_F
+	FNORD_H
+	FNORD-4
+	readonly FNORD_B=2
+	readonly FNORD_D=4
+	readonly FNORD_F=6
+	readonly FNORD_H=8
+	FNORD-5
+	typeset FNORD_A
+	typeset -r FNORD_B
+	typeset -x FNORD_C
+	typeset -x -r FNORD_D
+	typeset -i FNORD_E
+	typeset -i -r FNORD_F
+	typeset -i -x FNORD_G
+	typeset -i -x -r FNORD_H
+	FNORD-6
+	typeset FNORD_A=1
+	typeset -r FNORD_B=2
+	typeset -x FNORD_C=3
+	typeset -x -r FNORD_D=4
+	typeset -i FNORD_E=5
+	typeset -i -r FNORD_F=6
+	typeset -i -x FNORD_G=7
+	typeset -i -x -r FNORD_H=8
+	FNORD-7
+	FNORD_A=1
+	FNORD_B=2
+	FNORD_C=3
+	FNORD_D=4
+	FNORD_E=5
+	FNORD_F=6
+	FNORD_G=7
+	FNORD_H=8
+	FNORD-8
+---
+name: regression-64
+description:
+	Check that we can redefine functions calling time builtin
+stdin:
+	t() {
+		time >/dev/null
+	}
+	t 2>/dev/null
+	t() {
+		time
+	}
+---
+name: syntax-1
+description:
+	Check that lone ampersand is a syntax error
+stdin:
+	 &
+expected-exit: e != 0
+expected-stderr-pattern:
+	/syntax error/
+---
+name: xxx-quoted-newline-1
+description:
+	Check that \<newline> works inside of ${}
+stdin:
+	abc=2
+	echo ${ab\
+	c}
+expected-stdout:
+	2
+---
+name: xxx-quoted-newline-2
+description:
+	Check that \<newline> works at the start of a here document
+stdin:
+	cat << EO\
+	F
+	hi
+	EOF
+expected-stdout:
+	hi
+---
+name: xxx-quoted-newline-3
+description:
+	Check that \<newline> works at the end of a here document
+stdin:
+	cat << EOF
+	hi
+	EO\
+	F
+expected-stdout:
+	hi
+---
+name: xxx-multi-assignment-cmd
+description:
+	Check that assignments in a command affect subsequent assignments
+	in the same command
+stdin:
+	FOO=abc
+	FOO=123 BAR=$FOO
+	echo $BAR
+expected-stdout:
+	123
+---
+name: xxx-multi-assignment-posix-cmd
+description:
+	Check that the behaviour for multiple assignments with a
+	command name matches POSIX. See:
+	http://thread.gmane.org/gmane.comp.standards.posix.austin.general/1925
+stdin:
+	X=a Y=b; X=$Y Y=$X "$__progname" -c 'echo 1 $X $Y .'; echo 2 $X $Y .
+	unset X Y Z
+	X=a Y=${X=b} Z=$X "$__progname" -c 'echo 3 $Z .'
+	unset X Y Z
+	X=a Y=${X=b} Z=$X; echo 4 $Z .
+expected-stdout:
+	1 b a .
+	2 a b .
+	3 b .
+	4 a .
+---
+name: xxx-multi-assignment-posix-nocmd
+description:
+	Check that the behaviour for multiple assignments with no
+	command name matches POSIX (Debian #334182). See:
+	http://thread.gmane.org/gmane.comp.standards.posix.austin.general/1925
+stdin:
+	X=a Y=b; X=$Y Y=$X; echo 1 $X $Y .
+expected-stdout:
+	1 b b .
+---
+name: xxx-multi-assignment-posix-subassign
+description:
+	Check that the behaviour for multiple assignments matches POSIX:
+	- The assignment words shall be expanded in the current execution
+	  environment.
+	- The assignments happen in the temporary execution environment.
+stdin:
+	unset X Y Z
+	Z=a Y=${X:=b} sh -c 'echo +$X+ +$Y+ +$Z+'
+	echo /$X/
+	# Now for the special case:
+	unset X Y Z
+	X= Y=${X:=b} sh -c 'echo +$X+ +$Y+'
+	echo /$X/
+expected-stdout:
+	++ +b+ +a+
+	/b/
+	++ +b+
+	/b/
+---
+name: xxx-exec-environment-1
+description:
+	Check to see if exec sets it's environment correctly
+stdin:
+	FOO=bar exec env
+expected-stdout-pattern:
+	/(^|.*\n)FOO=bar\n/
+---
+name: xxx-exec-environment-2
+description:
+	Check to make sure exec doesn't change environment if a program
+	isn't exec-ed
+stdin:
+	sortprog=$(whence -p sort) || sortprog=cat
+	env | $sortprog | grep -v '^RANDOM=' >bar1
+	FOO=bar exec; env | $sortprog | grep -v '^RANDOM=' >bar2
+	cmp -s bar1 bar2
+---
+name: exec-function-environment-1
+description:
+	Check assignments in function calls and whether they affect
+	the current execution environment (ksh93, SUSv4)
+stdin:
+	f() { a=2; }; g() { b=3; echo y$c-; }; a=1 f; b=2; c=1 g
+	echo x$a-$b- z$c-
+expected-stdout:
+	y1-
+	x2-3- z1-
+---
+name: xxx-what-do-you-call-this-1
+stdin:
+	echo "${foo:-"a"}*"
+expected-stdout:
+	a*
+---
+name: xxx-prefix-strip-1
+stdin:
+	foo='a cdef'
+	echo ${foo#a c}
+expected-stdout:
+	def
+---
+name: xxx-prefix-strip-2
+stdin:
+	set a c
+	x='a cdef'
+	echo ${x#$*}
+expected-stdout:
+	def
+---
+name: xxx-variable-syntax-1
+stdin:
+	echo ${:}
+expected-stderr-pattern:
+	/bad substitution/
+expected-exit: 1
+---
+name: xxx-variable-syntax-2
+stdin:
+	set 0
+	echo ${*:0}
+expected-stderr-pattern:
+	/bad substitution/
+expected-exit: 1
+---
+name: xxx-variable-syntax-3
+stdin:
+	set -A foo 0
+	echo ${foo[*]:0}
+expected-stderr-pattern:
+	/bad substitution/
+expected-exit: 1
+---
+name: xxx-substitution-eval-order
+description:
+	Check order of evaluation of expressions
+stdin:
+	i=1 x= y=
+	set -A A abc def GHI j G k
+	echo ${A[x=(i+=1)]#${A[y=(i+=2)]}}
+	echo $x $y
+expected-stdout:
+	HI
+	2 4
+---
+name: xxx-set-option-1
+description:
+	Check option parsing in set
+stdin:
+	set -vsA foo -- A 1 3 2
+	echo ${foo[*]}
+expected-stderr:
+	echo ${foo[*]}
+expected-stdout:
+	1 2 3 A
+---
+name: xxx-exec-1
+description:
+	Check that exec exits for built-ins
+arguments: !-i!
+stdin:
+	exec echo hi
+	echo still herre
+expected-stdout:
+	hi
+expected-stderr-pattern: /.*/
+---
+name: xxx-while-1
+description:
+	Check the return value of while loops
+	XXX need to do same for for/select/until loops
+stdin:
+	i=x
+	while [ $i != xxx ] ; do
+	    i=x$i
+	    if [ $i = xxx ] ; then
+		false
+		continue
+	    fi
+	done
+	echo loop1=$?
+	
+	i=x
+	while [ $i != xxx ] ; do
+	    i=x$i
+	    if [ $i = xxx ] ; then
+		false
+		break
+	    fi
+	done
+	echo loop2=$?
+	
+	i=x
+	while [ $i != xxx ] ; do
+	    i=x$i
+	    false
+	done
+	echo loop3=$?
+expected-stdout:
+	loop1=0
+	loop2=0
+	loop3=1
+---
+name: xxx-status-1
+description:
+	Check that blank lines don't clear $?
+arguments: !-i!
+stdin:
+	(exit 1)
+	echo $?
+	(exit 1)
+	
+	echo $?
+	true
+expected-stdout:
+	1
+	1
+expected-stderr-pattern: /.*/
+---
+name: xxx-status-2
+description:
+	Check that $? is preserved in subshells, includes, traps.
+stdin:
+	(exit 1)
+	
+	echo blank: $?
+	
+	(exit 2)
+	(echo subshell: $?)
+	
+	echo 'echo include: $?' > foo
+	(exit 3)
+	. ./foo
+	
+	trap 'echo trap: $?' ERR
+	(exit 4)
+	echo exit: $?
+expected-stdout:
+	blank: 1
+	subshell: 2
+	include: 3
+	trap: 4
+	exit: 4
+---
+name: xxx-clean-chars-1
+description:
+	Check MAGIC character is stuffed correctly
+stdin:
+	echo `echo [£`
+expected-stdout:
+	[£
+---
+name: xxx-param-subst-qmark-1
+description:
+	Check suppresion of error message with null string.  According to
+	POSIX, it shouldn't print the error as 'word' isn't ommitted.
+	ksh88/93, Solaris /bin/sh and /usr/xpg4/bin/sh all print the error,
+	that's why the condition is reversed.
+stdin:
+	unset foo
+	x=
+	echo x${foo?$x}
+expected-exit: 1
+# POSIX
+#expected-fail: yes
+#expected-stderr-pattern: !/not set/
+# common use
+expected-stderr-pattern: /parameter null or not set/
+---
+name: xxx-param-_-1
+# fails due to weirdness of execv stuff
+category: !os:uwin-nt
+description:
+	Check c flag is set.
+arguments: !-c!echo "[$-]"!
+expected-stdout-pattern: /^\[.*c.*\]$/
+---
+name: tilde-expand-1
+description:
+	Check tilde expansion after equal signs
+env-setup: !HOME=/sweet!
+stdin:
+	echo ${A=a=}~ b=~ c=d~ ~
+	set +o braceexpand
+	echo ${A=a=}~ b=~ c=d~ ~
+expected-stdout:
+	a=/sweet b=/sweet c=d~ /sweet
+	a=~ b=~ c=d~ /sweet
+---
+name: exit-err-1
+description:
+	Check some "exit on error" conditions
+stdin:
+	set -ex
+	/usr/bin/env false && echo something
+	echo END
+expected-stdout:
+	END
+expected-stderr:
+	+ /usr/bin/env false
+	+ echo END
+---
+name: exit-err-2
+description:
+	Check some "exit on error" edge conditions (POSIXly)
+stdin:
+	set -ex
+	if /usr/bin/env true; then
+		/usr/bin/env false && echo something
+	fi
+	echo END
+expected-stdout:
+	END
+expected-stderr:
+	+ /usr/bin/env true
+	+ /usr/bin/env false
+	+ echo END
+---
+name: exit-err-3
+description:
+	pdksh regression which AT&T ksh does right
+	TFM says: [set] -e | errexit
+		Exit (after executing the ERR trap) ...
+stdin:
+	trap 'echo EXIT' EXIT
+	trap 'echo ERR' ERR
+	set -e
+	cd /XXXXX 2>/dev/null
+	echo DONE
+	exit 0
+expected-stdout:
+	ERR
+	EXIT
+expected-exit: e != 0
+---
+name: exit-err-4
+description:
+	"set -e" test suite (POSIX)
+stdin:
+	set -e
+	echo pre
+	if true ; then
+		false && echo foo
+	fi
+	echo bar
+expected-stdout:
+	pre
+	bar
+---
+name: exit-err-5
+description:
+	"set -e" test suite (POSIX)
+stdin:
+	set -e
+	foo() {
+		while [ "$1" ]; do
+			for E in $x; do
+				[ "$1" = "$E" ] && { shift ; continue 2 ; }
+			done
+			x="$x $1"
+			shift
+		done
+		echo $x
+	}
+	echo pre
+	foo a b b c
+	echo post
+expected-stdout:
+	pre
+	a b c
+	post
+---
+name: exit-err-6
+description:
+	"set -e" test suite (BSD make)
+category: os:mirbsd
+stdin:
+	mkdir zd zd/a zd/b
+	print 'all:\n\t@echo eins\n\t@exit 42\n' >zd/a/Makefile
+	print 'all:\n\t@echo zwei\n' >zd/b/Makefile
+	wd=$(pwd)
+	set -e
+	for entry in a b; do (  set -e;  if [[ -d $wd/zd/$entry.i386 ]]; then  _newdir_="$entry.i386";  else  _newdir_="$entry";  fi;  if [[ -z $_THISDIR_ ]]; then  _nextdir_="$_newdir_";  else  _nextdir_="$_THISDIR_/$_newdir_";  fi;  _makefile_spec_=;  [[ ! -f $wd/zd/$_newdir_/Makefile.bsd-wrapper ]]  || _makefile_spec_="-f Makefile.bsd-wrapper";  subskipdir=;  for skipdir in ; do  subentry=${skipdir#$entry};  if [[ $subentry != $skipdir ]]; then  if [[ -z $subentry ]]; then  echo "($_nextdir_ skipped)";  break;  fi;  subskipdir="$subskipdir ${subentry#/}";  fi;  done;  if [[ -z $skipdir || -n $subentry ]]; then  echo "===> $_nextdir_";  cd $wd/zd/$_newdir_;  make SKIPDIR="$subskipdir" $_makefile_spec_  _THISDIR_="$_nextdir_"   all;  fi;  ) done 2>&1 | sed "s!$wd!WD!g"
+expected-stdout:
+	===> a
+	eins
+	*** Error code 42
+	
+	Stop in WD/zd/a (line 2 of Makefile).
+---
+name: exit-enoent-1
+description:
+	SUSv4 says that the shell should exit with 126/127 in some situations
+stdin:
+	i=0
+	(echo; echo :) >x
+	"$__progname" ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+	"$__progname" -c ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+	echo exit 42 >x
+	"$__progname" ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+	"$__progname" -c ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+	rm -f x
+	"$__progname" ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+	"$__progname" -c ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r .
+expected-stdout:
+	0 0 .
+	1 126 .
+	2 42 .
+	3 126 .
+	4 127 .
+	5 127 .
+---
+name: exit-eval-1
+description:
+	Check eval vs substitution exit codes (ksh93 alike)
+stdin:
+	eval $(false)
+	echo A $?
+	eval ' $(false)'
+	echo B $?
+	eval " $(false)"
+	echo C $?
+	eval "eval $(false)"
+	echo D $?
+	eval 'eval '"$(false)"
+	echo E $?
+	IFS="$IFS:"
+	eval $(echo :; false)
+	echo F $?
+expected-stdout:
+	A 0
+	B 1
+	C 0
+	D 0
+	E 0
+	F 0
+---
+name: test-stlt-1
+description:
+	Check that test also can handle string1 < string2 etc.
+stdin:
+	test 2005/10/08 '<' 2005/08/21 && echo ja || echo nein
+	test 2005/08/21 \< 2005/10/08 && echo ja || echo nein
+	test 2005/10/08 '>' 2005/08/21 && echo ja || echo nein
+	test 2005/08/21 \> 2005/10/08 && echo ja || echo nein
+expected-stdout:
+	nein
+	ja
+	ja
+	nein
+expected-stderr-pattern: !/unexpected op/
+---
+name: test-precedence-1
+description:
+	Check a weird precedence case (and POSIX echo)
+stdin:
+	test \( -f = -f \)
+	rv=$?
+	test -n "$POSH_VERSION" || set -o sh
+	echo -e $rv
+expected-stdout:
+	-e 0
+---
+name: test-option-1
+description:
+	Test the test -o operator
+stdin:
+	runtest() {
+		test -o $1; echo $?
+		[ -o $1 ]; echo $?
+		[[ -o $1 ]]; echo $?
+	}
+	if_test() {
+		test -o $1 -o -o !$1; echo $?
+		[ -o $1 -o -o !$1 ]; echo $?
+		[[ -o $1 || -o !$1 ]]; echo $?
+		test -o ?$1; echo $?
+	}
+	echo 0y $(if_test utf8-mode) =
+	echo 0n $(if_test utf8-hack) =
+	echo 1= $(runtest utf8-hack) =
+	echo 2= $(runtest !utf8-hack) =
+	echo 3= $(runtest ?utf8-hack) =
+	set +U
+	echo 1+ $(runtest utf8-mode) =
+	echo 2+ $(runtest !utf8-mode) =
+	echo 3+ $(runtest ?utf8-mode) =
+	set -U
+	echo 1- $(runtest utf8-mode) =
+	echo 2- $(runtest !utf8-mode) =
+	echo 3- $(runtest ?utf8-mode) =
+	echo = short flags =
+	echo 0y $(if_test -U) =
+	echo 0y $(if_test +U) =
+	echo 0n $(if_test -_) =
+	echo 0n $(if_test -U-) =
+	echo 1= $(runtest -_) =
+	echo 2= $(runtest !-_) =
+	echo 3= $(runtest ?-_) =
+	set +U
+	echo 1+ $(runtest -U) =
+	echo 2+ $(runtest !-U) =
+	echo 3+ $(runtest ?-U) =
+	echo 1+ $(runtest +U) =
+	echo 2+ $(runtest !+U) =
+	echo 3+ $(runtest ?+U) =
+	set -U
+	echo 1- $(runtest -U) =
+	echo 2- $(runtest !-U) =
+	echo 3- $(runtest ?-U) =
+	echo 1- $(runtest +U) =
+	echo 2- $(runtest !+U) =
+	echo 3- $(runtest ?+U) =
+expected-stdout:
+	0y 0 0 0 0 =
+	0n 1 1 1 1 =
+	1= 1 1 1 =
+	2= 1 1 1 =
+	3= 1 1 1 =
+	1+ 1 1 1 =
+	2+ 0 0 0 =
+	3+ 0 0 0 =
+	1- 0 0 0 =
+	2- 1 1 1 =
+	3- 0 0 0 =
+	= short flags =
+	0y 0 0 0 0 =
+	0y 0 0 0 0 =
+	0n 1 1 1 1 =
+	0n 1 1 1 1 =
+	1= 1 1 1 =
+	2= 1 1 1 =
+	3= 1 1 1 =
+	1+ 1 1 1 =
+	2+ 0 0 0 =
+	3+ 0 0 0 =
+	1+ 1 1 1 =
+	2+ 0 0 0 =
+	3+ 0 0 0 =
+	1- 0 0 0 =
+	2- 1 1 1 =
+	3- 0 0 0 =
+	1- 0 0 0 =
+	2- 1 1 1 =
+	3- 0 0 0 =
+---
+name: mkshrc-1
+description:
+	Check that ~/.mkshrc works correctly.
+	Part 1: verify user environment is not read (internal)
+stdin:
+	echo x $FNORD
+expected-stdout:
+	x
+---
+name: mkshrc-2a
+description:
+	Check that ~/.mkshrc works correctly.
+	Part 2: verify mkshrc is not read (non-interactive shells)
+file-setup: file 644 ".mkshrc"
+	FNORD=42
+env-setup: !HOME=.!ENV=!
+stdin:
+	echo x $FNORD
+expected-stdout:
+	x
+---
+name: mkshrc-2b
+description:
+	Check that ~/.mkshrc works correctly.
+	Part 2: verify mkshrc can be read (interactive shells)
+file-setup: file 644 ".mkshrc"
+	FNORD=42
+arguments: !-i!
+env-setup: !HOME=.!ENV=!PS1=!
+stdin:
+	echo x $FNORD
+expected-stdout:
+	x 42
+expected-stderr-pattern:
+	/(# )*/
+---
+name: mkshrc-3
+description:
+	Check that ~/.mkshrc works correctly.
+	Part 3: verify mkshrc can be turned off
+file-setup: file 644 ".mkshrc"
+	FNORD=42
+env-setup: !HOME=.!ENV=nonexistant!
+stdin:
+	echo x $FNORD
+expected-stdout:
+	x
+---
+name: sh-mode-1
+description:
+	Check that sh mode turns braceexpand off
+	and that that works correctly
+stdin:
+	set -o braceexpand
+	set +o sh
+	[[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh
+	[[ $(set +o) == *@(-o braceexpand)@(| *) ]] && echo brex || echo nobrex
+	echo {a,b,c}
+	set +o braceexpand
+	echo {a,b,c}
+	set -o braceexpand
+	echo {a,b,c}
+	set -o sh
+	echo {a,b,c}
+	[[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh
+	[[ $(set +o) == *@(-o braceexpand)@(| *) ]] && echo brex || echo nobrex
+	set -o braceexpand
+	echo {a,b,c}
+	[[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh
+	[[ $(set +o) == *@(-o braceexpand)@(| *) ]] && echo brex || echo nobrex
+expected-stdout:
+	nosh
+	brex
+	a b c
+	{a,b,c}
+	a b c
+	{a,b,c}
+	sh
+	nobrex
+	a b c
+	sh
+	brex
+---
+name: sh-mode-2a
+description:
+	Check that sh mode is *not* automatically turned on
+category: !binsh
+stdin:
+	ln -s "$__progname" ksh
+	ln -s "$__progname" sh
+	ln -s "$__progname" ./-ksh
+	ln -s "$__progname" ./-sh
+	for shell in {,-}{,k}sh; do
+		print -- $shell $(./$shell +l -c \
+		    '[[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh')
+	done
+expected-stdout:
+	sh nosh
+	ksh nosh
+	-sh nosh
+	-ksh nosh
+---
+name: sh-mode-2b
+description:
+	Check that sh mode *is* automatically turned on
+category: binsh
+stdin:
+	ln -s "$__progname" ksh
+	ln -s "$__progname" sh
+	ln -s "$__progname" ./-ksh
+	ln -s "$__progname" ./-sh
+	for shell in {,-}{,k}sh; do
+		print -- $shell $(./$shell +l -c \
+		    '[[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh')
+	done
+expected-stdout:
+	sh sh
+	ksh nosh
+	-sh sh
+	-ksh nosh
+---
+name: pipeline-1
+description:
+	pdksh bug: last command of a pipeline is executed in a
+	subshell - make sure it still is, scripts depend on it
+file-setup: file 644 "abcx"
+file-setup: file 644 "abcy"
+stdin:
+	echo *
+	echo a | while read d; do
+		echo $d
+		echo $d*
+		echo *
+		set -o noglob
+		echo $d*
+		echo *
+	done
+	echo *
+expected-stdout:
+	abcx abcy
+	a
+	abcx abcy
+	abcx abcy
+	a*
+	*
+	abcx abcy
+---
+name: pipeline-2
+description:
+	check that co-processes work with TCOMs, TPIPEs and TPARENs
+stdin:
+	"$__progname" -c 'i=100; echo hi |& while read -p line; do echo "$((i++)) $line"; done'
+	"$__progname" -c 'i=200; echo hi | cat |& while read -p line; do echo "$((i++)) $line"; done'
+	"$__progname" -c 'i=300; (echo hi | cat) |& while read -p line; do echo "$((i++)) $line"; done'
+expected-stdout:
+	100 hi
+	200 hi
+	300 hi
+---
+name: persist-history-1
+description:
+	Check if persistent history saving works
+category: !no-histfile
+arguments: !-i!
+env-setup: !ENV=./Env!HISTFILE=hist.file!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	cat hist.file
+expected-stdout-pattern:
+	/cat hist.file/
+expected-stderr-pattern:
+	/^X*$/
+---
+name: typeset-padding-1
+description:
+	Check if left/right justification works as per TFM
+stdin:
+	typeset -L10 ln=0hall0
+	typeset -R10 rn=0hall0
+	typeset -ZL10 lz=0hall0
+	typeset -ZR10 rz=0hall0
+	typeset -Z10 rx=" hallo "
+	echo "<$ln> <$rn> <$lz> <$rz> <$rx>"
+expected-stdout:
+	<0hall0    > <    0hall0> <hall0     > <00000hall0> <0000 hallo>
+---
+name: typeset-padding-2
+description:
+	Check if base-!10 integers are padded right
+stdin:
+	typeset -Uui16 -L9 ln=16#1
+	typeset -Uui16 -R9 rn=16#1
+	typeset -Uui16 -Z9 zn=16#1
+	typeset -L9 ls=16#1
+	typeset -R9 rs=16#1
+	typeset -Z9 zs=16#1
+	echo "<$ln> <$rn> <$zn> <$ls> <$rs> <$zs>"
+expected-stdout:
+	<16#1     > <     16#1> <16#000001> <16#1     > <     16#1> <0000016#1>
+---
+name: utf8bom-1
+description:
+	Check that the UTF-8 Byte Order Mark is ignored as the first
+	multibyte character of the shell input (with -c, from standard
+	input, as file, or as eval argument), but nowhere else
+# breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition)
+category: !os:darwin
+stdin:
+	mkdir foo
+	print '#!/bin/sh\necho ohne' >foo/fnord
+	print '#!/bin/sh\necho mit' >foo/fnord
+	print 'fnord\nfnord\nfnord\nfnord' >foo/bar
+	print eval \''fnord\nfnord\nfnord\nfnord'\' >foo/zoo
+	set -A anzahl -- foo/*
+	echo got ${#anzahl[*]} files
+	chmod +x foo/*
+	export PATH=$(pwd)/foo:$PATH
+	"$__progname" -c 'fnord'
+	echo =
+	"$__progname" -c 'fnord; fnord; fnord; fnord'
+	echo =
+	"$__progname" foo/bar
+	echo =
+	"$__progname" <foo/bar
+	echo =
+	"$__progname" foo/zoo
+	echo =
+	"$__progname" -c 'echo : $(fnord)'
+	rm -rf foo
+expected-stdout:
+	got 4 files
+	ohne
+	=
+	ohne
+	ohne
+	mit
+	ohne
+	=
+	ohne
+	ohne
+	mit
+	ohne
+	=
+	ohne
+	ohne
+	mit
+	ohne
+	=
+	ohne
+	ohne
+	mit
+	ohne
+	=
+	: mit
+---
+name: utf8bom-2
+description:
+	Check that we can execute BOM-shebangs (failures not fatal)
+	XXX if the OS can already execute them, we lose
+	note: cygwin execve(2) doesn't return to us with ENOEXEC, we lose
+	note: Ultrix perl5 t4 returns 65280 (exit-code 255) and no text
+category: !os:cygwin,!os:uwin-nt,!os:ultrix,!smksh
+env-setup: !FOO=BAR!
+stdin:
+	print '#!'"$__progname"'\nprint "1 a=$ENV{FOO}";' >t1
+	print '#!'"$__progname"'\nprint "2 a=$ENV{FOO}";' >t2
+	print '#!'"$__perlname"'\nprint "3 a=$ENV{FOO}\n";' >t3
+	print '#!'"$__perlname"'\nprint "4 a=$ENV{FOO}\n";' >t4
+	chmod +x t?
+	./t1
+	./t2
+	./t3
+	./t4
+expected-stdout:
+	1 a=/nonexistant{FOO}
+	2 a=/nonexistant{FOO}
+	3 a=BAR
+	4 a=BAR
+expected-stderr-pattern:
+	/(Unrecognized character .... ignored at \..t4 line 1)*/
+---
+name: utf8bom-3
+description:
+	Reading the UTF-8 BOM should enable the utf8-mode flag
+stdin:
+	"$__progname" -c ':; if [[ $- = *U* ]]; then echo 1 on; else echo 1 off; fi'
+	"$__progname" -c ':; if [[ $- = *U* ]]; then echo 2 on; else echo 2 off; fi'
+expected-stdout:
+	1 off
+	2 on
+---
+name: utf8opt-1a
+description:
+	Check that the utf8-mode flag is not set at non-interactive startup
+category: !os:hpux
+env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8!
+stdin:
+	if [[ $- = *U* ]]; then
+		echo is set
+	else
+		echo is not set
+	fi
+expected-stdout:
+	is not set
+---
+name: utf8opt-1b
+description:
+	Check that the utf8-mode flag is not set at non-interactive startup
+category: os:hpux
+env-setup: !PS1=!PS2=!LC_CTYPE=en_US.utf8!
+stdin:
+	if [[ $- = *U* ]]; then
+		echo is set
+	else
+		echo is not set
+	fi
+expected-stdout:
+	is not set
+---
+name: utf8opt-2a
+description:
+	Check that the utf8-mode flag is set at interactive startup.
+	-DMKSH_ASSUME_UTF8=0 => expected failure, please ignore
+	-DMKSH_ASSUME_UTF8=1 => not expected, please investigate
+	-UMKSH_ASSUME_UTF8 => not expected, but if your OS is old,
+	 try passing HAVE_SETLOCALE_CTYPE=0 to Build.sh
+category: !os:hpux
+arguments: !-i!
+env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8!
+stdin:
+	if [[ $- = *U* ]]; then
+		echo is set
+	else
+		echo is not set
+	fi
+expected-stdout:
+	is set
+expected-stderr-pattern:
+	/(# )*/
+---
+name: utf8opt-2b
+description:
+	Check that the utf8-mode flag is set at interactive startup
+	Expected failure if -DMKSH_ASSUME_UTF8=0
+category: os:hpux
+arguments: !-i!
+env-setup: !PS1=!PS2=!LC_CTYPE=en_US.utf8!
+stdin:
+	if [[ $- = *U* ]]; then
+		echo is set
+	else
+		echo is not set
+	fi
+expected-stdout:
+	is set
+expected-stderr-pattern:
+	/(# )*/
+---
+name: utf8opt-3
+description:
+	Ensure ±U on the command line is honoured
+	(this test may pass falsely depending on CPPFLAGS)
+stdin:
+	export i=0
+	code='if [[ $- = *U* ]]; then echo $i on; else echo $i off; fi'
+	let i++; "$__progname" -U -c "$code"
+	let i++; "$__progname" +U -c "$code"
+	let i++; "$__progname" -U -ic "$code"
+	let i++; "$__progname" +U -ic "$code"
+	echo $((++i)) done
+expected-stdout:
+	1 on
+	2 off
+	3 on
+	4 off
+	5 done
+---
+name: aliases-1
+description:
+	Check if built-in shell aliases are okay
+category: !arge
+stdin:
+	alias
+	typeset -f
+expected-stdout:
+	autoload='typeset -fu'
+	functions='typeset -f'
+	hash='alias -t'
+	history='fc -l'
+	integer='typeset -i'
+	local=typeset
+	login='exec login'
+	nameref='typeset -n'
+	nohup='nohup '
+	r='fc -e -'
+	source='PATH=$PATH:. command .'
+	suspend='kill -STOP $$'
+	type='whence -v'
+---
+name: aliases-1-hartz4
+description:
+	Check if built-in shell aliases are okay
+category: arge
+stdin:
+	alias
+	typeset -f
+expected-stdout:
+	autoload='typeset -fu'
+	functions='typeset -f'
+	hash='alias -t'
+	history='fc -l'
+	integer='typeset -i'
+	local=typeset
+	login='exec login'
+	nameref='typeset -n'
+	nohup='nohup '
+	r='fc -e -'
+	source='PATH=$PATH:. command .'
+	type='whence -v'
+---
+name: aliases-2a
+description:
+	Check if “set -o sh” disables built-in aliases (except a few)
+category: disabled
+arguments: !-o!sh!
+stdin:
+	alias
+	typeset -f
+expected-stdout:
+	integer='typeset -i'
+	local=typeset
+---
+name: aliases-3a
+description:
+	Check if running as sh disables built-in aliases (except a few)
+category: disabled
+arguments: !-o!sh!
+stdin:
+	cp "$__progname" sh
+	./sh -c 'alias; typeset -f'
+	rm -f sh
+expected-stdout:
+	integer='typeset -i'
+	local=typeset
+---
+name: aliases-2b
+description:
+	Check if “set -o sh” does not influence built-in aliases
+category: !arge
+arguments: !-o!sh!
+stdin:
+	alias
+	typeset -f
+expected-stdout:
+	autoload='typeset -fu'
+	functions='typeset -f'
+	hash='alias -t'
+	history='fc -l'
+	integer='typeset -i'
+	local=typeset
+	login='exec login'
+	nameref='typeset -n'
+	nohup='nohup '
+	r='fc -e -'
+	source='PATH=$PATH:. command .'
+	suspend='kill -STOP $$'
+	type='whence -v'
+---
+name: aliases-3b
+description:
+	Check if running as sh does not influence built-in aliases
+category: !arge
+arguments: !-o!sh!
+stdin:
+	cp "$__progname" sh
+	./sh -c 'alias; typeset -f'
+	rm -f sh
+expected-stdout:
+	autoload='typeset -fu'
+	functions='typeset -f'
+	hash='alias -t'
+	history='fc -l'
+	integer='typeset -i'
+	local=typeset
+	login='exec login'
+	nameref='typeset -n'
+	nohup='nohup '
+	r='fc -e -'
+	source='PATH=$PATH:. command .'
+	suspend='kill -STOP $$'
+	type='whence -v'
+---
+name: aliases-2b-hartz4
+description:
+	Check if “set -o sh” does not influence built-in aliases
+category: arge
+arguments: !-o!sh!
+stdin:
+	alias
+	typeset -f
+expected-stdout:
+	autoload='typeset -fu'
+	functions='typeset -f'
+	hash='alias -t'
+	history='fc -l'
+	integer='typeset -i'
+	local=typeset
+	login='exec login'
+	nameref='typeset -n'
+	nohup='nohup '
+	r='fc -e -'
+	source='PATH=$PATH:. command .'
+	type='whence -v'
+---
+name: aliases-3b-hartz4
+description:
+	Check if running as sh does not influence built-in aliases
+category: arge
+arguments: !-o!sh!
+stdin:
+	cp "$__progname" sh
+	./sh -c 'alias; typeset -f'
+	rm -f sh
+expected-stdout:
+	autoload='typeset -fu'
+	functions='typeset -f'
+	hash='alias -t'
+	history='fc -l'
+	integer='typeset -i'
+	local=typeset
+	login='exec login'
+	nameref='typeset -n'
+	nohup='nohup '
+	r='fc -e -'
+	source='PATH=$PATH:. command .'
+	type='whence -v'
+---
+name: aliases-funcdef-1
+description:
+	Check if POSIX functions take precedences over aliases
+stdin:
+	alias foo='echo makro'
+	foo() {
+		echo funktion
+	}
+	foo
+expected-stdout:
+	funktion
+---
+name: aliases-funcdef-2
+description:
+	Check if POSIX functions take precedences over aliases
+stdin:
+	alias foo='echo makro'
+	foo () {
+		echo funktion
+	}
+	foo
+expected-stdout:
+	funktion
+---
+name: aliases-funcdef-3
+description:
+	Check if aliases take precedences over Korn functions
+stdin:
+	alias foo='echo makro'
+	function foo {
+		echo funktion
+	}
+	foo
+expected-stdout:
+	makro
+---
+name: arrays-1
+description:
+	Check if Korn Shell arrays work as expected
+stdin:
+	v="c d"
+	set -A foo -- a \$v "$v" '$v' b
+	echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|"
+expected-stdout:
+	5|a|$v|c d|$v|b|
+---
+name: arrays-2
+description:
+	Check if bash-style arrays work as expected
+category: !smksh
+stdin:
+	v="c d"
+	foo=(a \$v "$v" '$v' b)
+	echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|"
+expected-stdout:
+	5|a|$v|c d|$v|b|
+---
+name: arrays-3
+description:
+	Check if array bounds are uint32_t
+stdin:
+	set -A foo a b c
+	foo[4097]=d
+	foo[2147483637]=e
+	echo ${foo[*]}
+	foo[-1]=f
+	echo ${foo[4294967295]} g ${foo[*]}
+expected-stdout:
+	a b c d e
+	f g a b c d e f
+---
+name: arrays-4
+description:
+	Check if Korn Shell arrays with specified indices work as expected
+category: !smksh
+stdin:
+	v="c d"
+	set -A foo -- [1]=\$v [2]="$v" [4]='$v' [0]=a [5]=b
+	echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|${foo[5]}|"
+expected-stdout:
+	5|a|$v|c d||$v|b|
+---
+name: arrays-5
+description:
+	Check if bash-style arrays with specified indices work as expected
+category: !smksh
+stdin:
+	v="c d"
+	foo=([1]=\$v [2]="$v" [4]='$v' [0]=a [5]=b)
+	echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|${foo[5]}|"
+	x=([128]=foo bar baz)
+	echo k= ${!x[*]} .
+	echo v= ${x[*]} .
+expected-stdout:
+	5|a|$v|c d||$v|b|
+	k= 128 129 130 .
+	v= foo bar baz .
+---
+name: arrays-6
+description:
+	Check if we can get the array keys (indices) for indexed arrays,
+	Korn shell style
+stdin:
+	of() {
+		i=0
+		for x in "$@"; do
+			echo -n "$((i++))<$x>"
+		done
+		echo
+	}
+	foo[1]=eins
+	set | grep '^foo'
+	echo =
+	foo[0]=zwei
+	foo[4]=drei
+	set | grep '^foo'
+	echo =
+	echo a $(of ${foo[*]}) = $(of ${bar[*]}) a
+	echo b $(of "${foo[*]}") = $(of "${bar[*]}") b
+	echo c $(of ${foo[@]}) = $(of ${bar[@]}) c
+	echo d $(of "${foo[@]}") = $(of "${bar[@]}") d
+	echo e $(of ${!foo[*]}) = $(of ${!bar[*]}) e
+	echo f $(of "${!foo[*]}") = $(of "${!bar[*]}") f
+	echo g $(of ${!foo[@]}) = $(of ${!bar[@]}) g
+	echo h $(of "${!foo[@]}") = $(of "${!bar[@]}") h
+expected-stdout:
+	foo[1]=eins
+	=
+	foo[0]=zwei
+	foo[1]=eins
+	foo[4]=drei
+	=
+	a 0<zwei>1<eins>2<drei> = a
+	b 0<zwei eins drei> = 0<> b
+	c 0<zwei>1<eins>2<drei> = c
+	d 0<zwei>1<eins>2<drei> = d
+	e 0<0>1<1>2<4> = e
+	f 0<0 1 4> = 0<> f
+	g 0<0>1<1>2<4> = g
+	h 0<0>1<1>2<4> = h
+---
+name: arrays-7
+description:
+	Check if we can get the array keys (indices) for indexed arrays,
+	Korn shell style, in some corner cases
+stdin:
+	echo !arz: ${!arz}
+	echo !arz[0]: ${!arz[0]}
+	echo !arz[1]: ${!arz[1]}
+	arz=foo
+	echo !arz: ${!arz}
+	echo !arz[0]: ${!arz[0]}
+	echo !arz[1]: ${!arz[1]}
+	unset arz
+	echo !arz: ${!arz}
+	echo !arz[0]: ${!arz[0]}
+	echo !arz[1]: ${!arz[1]}
+expected-stdout:
+	!arz: 0
+	!arz[0]:
+	!arz[1]:
+	!arz: arz
+	!arz[0]: 0
+	!arz[1]:
+	!arz: 0
+	!arz[0]:
+	!arz[1]:
+---
+name: arrays-8
+description:
+	Check some behavioural rules for arrays.
+stdin:
+	fna() {
+		set -A aa 9
+	}
+	fnb() {
+		typeset ab
+		set -A ab 9
+	}
+	fnc() {
+		typeset ac
+		set -A ac 91
+		unset ac
+		set -A ac 92
+	}
+	fnd() {
+		set +A ad 9
+	}
+	fne() {
+		unset ae
+		set +A ae 9
+	}
+	fnf() {
+		unset af[0]
+		set +A af 9
+	}
+	fng() {
+		unset ag[*]
+		set +A ag 9
+	}
+	set -A aa 1 2
+	set -A ab 1 2
+	set -A ac 1 2
+	set -A ad 1 2
+	set -A ae 1 2
+	set -A af 1 2
+	set -A ag 1 2
+	set -A ah 1 2
+	typeset -Z3 aa ab ac ad ae af ag
+	print 1a ${aa[*]} .
+	print 1b ${ab[*]} .
+	print 1c ${ac[*]} .
+	print 1d ${ad[*]} .
+	print 1e ${ae[*]} .
+	print 1f ${af[*]} .
+	print 1g ${ag[*]} .
+	print 1h ${ah[*]} .
+	fna
+	fnb
+	fnc
+	fnd
+	fne
+	fnf
+	fng
+	typeset -Z5 ah[*]
+	print 2a ${aa[*]} .
+	print 2b ${ab[*]} .
+	print 2c ${ac[*]} .
+	print 2d ${ad[*]} .
+	print 2e ${ae[*]} .
+	print 2f ${af[*]} .
+	print 2g ${ag[*]} .
+	print 2h ${ah[*]} .
+expected-stdout:
+	1a 001 002 .
+	1b 001 002 .
+	1c 001 002 .
+	1d 001 002 .
+	1e 001 002 .
+	1f 001 002 .
+	1g 001 002 .
+	1h 1 2 .
+	2a 9 .
+	2b 001 002 .
+	2c 92 .
+	2d 009 002 .
+	2e 9 .
+	2f 9 002 .
+	2g 009 .
+	2h 00001 00002 .
+---
+name: varexpand-substr-1
+description:
+	Check if bash-style substring expansion works
+	when using positive numerics
+stdin:
+	x=abcdefghi
+	typeset -i y=123456789
+	typeset -i 16 z=123456789	# 16#75bcd15
+	echo a t${x:2:2} ${y:2:3} ${z:2:3} a
+	echo b ${x::3} ${y::3} ${z::3} b
+	echo c ${x:2:} ${y:2:} ${z:2:} c
+	echo d ${x:2} ${y:2} ${z:2} d
+	echo e ${x:2:6} ${y:2:6} ${z:2:7} e
+	echo f ${x:2:7} ${y:2:7} ${z:2:8} f
+	echo g ${x:2:8} ${y:2:8} ${z:2:9} g
+expected-stdout:
+	a tcd 345 #75 a
+	b abc 123 16# b
+	c c
+	d cdefghi 3456789 #75bcd15 d
+	e cdefgh 345678 #75bcd1 e
+	f cdefghi 3456789 #75bcd15 f
+	g cdefghi 3456789 #75bcd15 g
+---
+name: varexpand-substr-2
+description:
+	Check if bash-style substring expansion works
+	when using negative numerics or expressions
+stdin:
+	x=abcdefghi
+	typeset -i y=123456789
+	typeset -i 16 z=123456789	# 16#75bcd15
+	n=2
+	echo a ${x:$n:3} ${y:$n:3} ${z:$n:3} a
+	echo b ${x:(n):3} ${y:(n):3} ${z:(n):3} b
+	echo c ${x:(-2):1} ${y:(-2):1} ${z:(-2):1} c
+	echo d t${x: n:2} ${y: n:3} ${z: n:3} d
+expected-stdout:
+	a cde 345 #75 a
+	b cde 345 #75 b
+	c h 8 1 c
+	d tcd 345 #75 d
+---
+name: varexpand-substr-3
+description:
+	Check that some things that work in bash fail.
+	This is by design. And that some things fail in both.
+stdin:
+	export x=abcdefghi n=2
+	"$__progname" -c 'echo v${x:(n)}x'
+	"$__progname" -c 'echo w${x: n}x'
+	"$__progname" -c 'echo x${x:n}x'
+	"$__progname" -c 'echo y${x:}x'
+	"$__progname" -c 'echo z${x}x'
+	"$__progname" -c 'x=abcdef;y=123;echo ${x:${y:2:1}:2}' >/dev/null 2>&1; echo $?
+expected-stdout:
+	vcdefghix
+	wcdefghix
+	zabcdefghix
+	1
+expected-stderr-pattern:
+	/x:n.*bad substitution.*\n.*bad substitution/
+---
+name: varexpand-substr-4
+description:
+	Check corner cases for substring expansion
+stdin:
+	x=abcdefghi
+	integer y=2
+	echo a ${x:(y == 1 ? 2 : 3):4} a
+expected-stdout:
+	a defg a
+---
+name: varexpand-substr-5A
+description:
+	Check that substring expansions work on characters
+stdin:
+	set +U
+	x=mäh
+	echo a ${x::1} ${x: -1} a
+	echo b ${x::3} ${x: -3} b
+	echo c ${x:1:2} ${x: -3:2} c
+	echo d ${#x} d
+expected-stdout:
+	a m h a
+	b mä äh b
+	c ä ä c
+	d 4 d
+---
+name: varexpand-substr-5W
+description:
+	Check that substring expansions work on characters
+stdin:
+	set -U
+	x=mäh
+	echo a ${x::1} ${x: -1} a
+	echo b ${x::2} ${x: -2} b
+	echo c ${x:1:1} ${x: -2:1} c
+	echo d ${#x} d
+expected-stdout:
+	a m h a
+	b mä äh b
+	c ä ä c
+	d 3 d
+---
+name: varexpand-substr-6
+description:
+	Check that string substitution works correctly
+stdin:
+	foo=1
+	bar=2
+	baz=qwertyuiop
+	echo a ${baz: foo: bar}
+	echo b ${baz: foo: $bar}
+	echo c ${baz: $foo: bar}
+	echo d ${baz: $foo: $bar}
+expected-stdout:
+	a we
+	b we
+	c we
+	d we
+---
+name: varexpand-null-1
+description:
+	Ensure empty strings expand emptily
+stdin:
+	print x ${a} ${b} y
+	print z ${a#?} ${b%?} w
+	print v ${a=} ${b/c/d} u
+expected-stdout:
+	x y
+	z w
+	v u
+---
+name: varexpand-null-2
+description:
+	Ensure empty strings, when quoted, are expanded as empty strings
+stdin:
+	printf '<%s> ' 1 "${a}" 2 "${a#?}" + "${b%?}" 3 "${a=}" + "${b/c/d}"
+	echo .
+expected-stdout:
+	<1> <> <2> <> <+> <> <3> <> <+> <> .
+---
+name: print-funny-chars
+description:
+	Check print builtin's capability to output designated characters
+stdin:
+	print '<\0144\0344\xDB\u00DB\u20AC\uDB\x40>'
+expected-stdout:
+	<däÛÛ€Û@>
+---
+name: print-bksl-c
+description:
+	Check print builtin's \c escape
+stdin:
+	print '\ca'; print b
+expected-stdout:
+	ab
+---
+name: print-nul-chars
+description:
+	Check handling of NUL characters for print and read
+	note: second line should output “4 3” but we cannot
+	handle NUL characters in strings yet
+stdin:
+	print $(($(print '<\0>' | wc -c)))
+	x=$(print '<\0>')
+	print $(($(print "$x" | wc -c))) ${#x}
+expected-stdout:
+	4
+	3 2
+---
+name: print-escapes
+description:
+	Check backslash expansion by the print builtin
+stdin:
+	print '\ \!\"\#\$\%\&'\\\''\(\)\*\+\,\-\.\/\0\1\2\3\4\5\6\7\8' \
+	    '\9\:\;\<\=\>\?\@\A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T' \
+	    '\U\V\W\X\Y\Z\[\\\]\^\_\`\a\b  \d\e\f\g\h\i\j\k\l\m\n\o\p' \
+	    '\q\r\s\t\u\v\w\x\y\z\{\|\}\~' '\u20acd' '\U20acd' '\x123' \
+	    '\0x' '\0123' '\01234' | {
+		typeset -Uui16 -Z11 pos=0
+		typeset -Uui16 -Z5 hv
+		typeset -i1 wc=0x0A
+		dasc=
+		nl=${wc#1#}
+		while IFS= read -r line; do
+			line=$line$nl
+			while [[ -n $line ]]; do
+				hv=1#${line::1}
+				if (( (pos & 15) == 0 )); then
+					(( pos )) && print "$dasc|"
+					print -n "${pos#16#}  "
+					dasc=' |'
+				fi
+				print -n "${hv#16#} "
+				if (( (hv < 32) || (hv > 126) )); then
+					dasc=$dasc.
+				else
+					dasc=$dasc${line::1}
+				fi
+				(( (pos++ & 15) == 7 )) && print -n -- '- '
+				line=${line:1}
+			done
+		done
+		if (( (pos & 15) != 1 )); then
+			while (( pos & 15 )); do
+				print -n '   '
+				(( (pos++ & 15) == 7 )) && print -n -- '- '
+			done
+			print "$dasc|"
+		fi
+	}
+expected-stdout:
+	00000000  5C 20 5C 21 5C 22 5C 23 - 5C 24 5C 25 5C 26 5C 27  |\ \!\"\#\$\%\&\'|
+	00000010  5C 28 5C 29 5C 2A 5C 2B - 5C 2C 5C 2D 5C 2E 5C 2F  |\(\)\*\+\,\-\.\/|
+	00000020  5C 31 5C 32 5C 33 5C 34 - 5C 35 5C 36 5C 37 5C 38  |\1\2\3\4\5\6\7\8|
+	00000030  20 5C 39 5C 3A 5C 3B 5C - 3C 5C 3D 5C 3E 5C 3F 5C  | \9\:\;\<\=\>\?\|
+	00000040  40 5C 41 5C 42 5C 43 5C - 44 1B 5C 46 5C 47 5C 48  |@\A\B\C\D.\F\G\H|
+	00000050  5C 49 5C 4A 5C 4B 5C 4C - 5C 4D 5C 4E 5C 4F 5C 50  |\I\J\K\L\M\N\O\P|
+	00000060  5C 51 5C 52 5C 53 5C 54 - 20 5C 56 5C 57 5C 58 5C  |\Q\R\S\T \V\W\X\|
+	00000070  59 5C 5A 5C 5B 5C 5C 5D - 5C 5E 5C 5F 5C 60 07 08  |Y\Z\[\]\^\_\`..|
+	00000080  20 20 5C 64 1B 0C 5C 67 - 5C 68 5C 69 5C 6A 5C 6B  |  \d..\g\h\i\j\k|
+	00000090  5C 6C 5C 6D 0A 5C 6F 5C - 70 20 5C 71 0D 5C 73 09  |\l\m.\o\p \q.\s.|
+	000000A0  0B 5C 77 5C 79 5C 7A 5C - 7B 5C 7C 5C 7D 5C 7E 20  |.\w\y\z\{\|\}\~ |
+	000000B0  E2 82 AC 64 20 EF BF BD - 20 12 33 20 78 20 53 20  |...d ... .3 x S |
+	000000C0  53 34 0A                -                          |S4.|
+---
+name: dollar-quoted-strings
+description:
+	Check backslash expansion by $'…' strings
+stdin:
+	printf '%s\n' $'\ \!\"\#\$\%\&\'\(\)\*\+\,\-\.\/ \1\2\3\4\5\6' \
+	    $'a\0b' $'a\01b' $'\7\8\9\:\;\<\=\>\?\@\A\B\C\D\E\F\G\H\I' \
+	    $'\J\K\L\M\N\O\P\Q\R\S\T\U1\V\W\X\Y\Z\[\\\]\^\_\`\a\b\d\e' \
+	    $'\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u1\v\w\x1\y\z\{\|\}\~ $x' \
+	    $'\u20acd' $'\U20acd' $'\x123' $'fn\x0rd' $'\0234' $'\234' \
+	    $'\2345' $'\ca' $'\c!' $'\c?' $'\c€' $'a\
+	b' | {
+		typeset -Uui16 -Z11 pos=0
+		typeset -Uui16 -Z5 hv
+		typeset -i1 wc=0x0A
+		dasc=
+		nl=${wc#1#}
+		while IFS= read -r line; do
+			line=$line$nl
+			while [[ -n $line ]]; do
+				hv=1#${line::1}
+				if (( (pos & 15) == 0 )); then
+					(( pos )) && print "$dasc|"
+					print -n "${pos#16#}  "
+					dasc=' |'
+				fi
+				print -n "${hv#16#} "
+				if (( (hv < 32) || (hv > 126) )); then
+					dasc=$dasc.
+				else
+					dasc=$dasc${line::1}
+				fi
+				(( (pos++ & 15) == 7 )) && print -n -- '- '
+				line=${line:1}
+			done
+		done
+		if (( (pos & 15) != 1 )); then
+			while (( pos & 15 )); do
+				print -n '   '
+				(( (pos++ & 15) == 7 )) && print -n -- '- '
+			done
+			print "$dasc|"
+		fi
+	}
+expected-stdout:
+	00000000  20 21 22 23 24 25 26 27 - 28 29 2A 2B 2C 2D 2E 2F  | !"#$%&'()*+,-./|
+	00000010  20 01 02 03 04 05 06 0A - 61 0A 61 01 62 0A 07 38  | .......a.a.b..8|
+	00000020  39 3A 3B 3C 3D 3E 3F 40 - 41 42 43 44 1B 46 47 48  |9:;<=>?@ABCD.FGH|
+	00000030  49 0A 4A 4B 4C 4D 4E 4F - 50 51 52 53 54 01 56 57  |I.JKLMNOPQRST.VW|
+	00000040  58 59 5A 5B 5C 5D 5E 5F - 60 07 08 64 1B 0A 0C 67  |XYZ[\]^_`..d...g|
+	00000050  68 69 6A 6B 6C 6D 0A 6F - 70 71 0D 73 09 01 0B 77  |hijklm.opq.s...w|
+	00000060  01 79 7A 7B 7C 7D 7E 20 - 24 78 0A E2 82 AC 64 0A  |.yz{|}~ $x....d.|
+	00000070  EF BF BD 0A C4 A3 0A 66 - 6E 0A 13 34 0A 9C 0A 9C  |.......fn..4....|
+	00000080  35 0A 01 0A 01 0A 7F 0A - 02 82 AC 0A 61 0A 62 0A  |5...........a.b.|
+---
+name: dollar-quotes-in-heredocs
+description:
+	They are, however, not parsed in here documents
+stdin:
+	cat <<EOF
+		dollar = strchr(s, '$');	/* ' */
+	EOF
+	cat <<$'a\tb'
+	a\tb
+	a	b
+expected-stdout:
+		dollar = strchr(s, '$');	/* ' */
+	a\tb
+---
+name: dollar-quotes-in-herestrings
+description:
+	They are, not parsed in here strings either
+stdin:
+	cat <<<"dollar = strchr(s, '$');	/* ' */"
+	cat <<<'dollar = strchr(s, '\''$'\'');	/* '\'' */'
+	x="dollar = strchr(s, '$');	/* ' */"
+	cat <<<"$x"
+	cat <<<$'a\E[0m\tb'
+expected-stdout:
+	dollar = strchr(s, '$');	/* ' */
+	dollar = strchr(s, '$');	/* ' */
+	dollar = strchr(s, '$');	/* ' */
+	a	b
+---
+name: dot-needs-argument
+description:
+	check Debian #415167 solution: '.' without arguments should fail
+stdin:
+	"$__progname" -c .
+	"$__progname" -c source
+expected-exit: e != 0
+expected-stderr-pattern:
+	/\.: missing argument.*\n.*\.: missing argument/
+---
+name: alias-function-no-conflict
+description:
+	make aliases not conflict with functions
+	note: for ksh-like functions, the order of preference is
+	different; bash outputs baz instead of bar in line 2 below
+stdin:
+	alias foo='echo bar'
+	foo() {
+		echo baz
+	}
+	alias korn='echo bar'
+	function korn {
+		echo baz
+	}
+	foo
+	korn
+	unset -f foo
+	foo 2>&- || echo rab
+expected-stdout:
+	baz
+	bar
+	rab
+---
+name: bash-function-parens
+description:
+	ensure the keyword function is ignored when preceding
+	POSIX style function declarations (bashism)
+stdin:
+	mk() {
+		echo '#!'"$__progname"
+		echo "$1 {"
+		echo '	echo "bar='\''$0'\'\"
+		echo '}'
+		echo ${2:-foo}
+	}
+	mk 'function foo' >f-korn
+	mk 'foo ()' >f-dash
+	mk 'function foo ()' >f-bash
+	mk 'function stop ()' stop >f-stop
+	chmod +x f-*
+	echo "korn: $(./f-korn)"
+	echo "dash: $(./f-dash)"
+	echo "bash: $(./f-bash)"
+	echo "stop: $(./f-stop)"
+expected-stdout:
+	korn: bar='foo'
+	dash: bar='./f-dash'
+	bash: bar='./f-bash'
+	stop: bar='./f-stop'
+---
+name: integer-base-one-1
+description:
+	check if the use of fake integer base 1 works
+stdin:
+	set -U
+	typeset -Uui16 i0=1#ï i1=1#€
+	typeset -i1 o0a=64
+	typeset -i1 o1a=0x263A
+	typeset -Uui1 o0b=0x7E
+	typeset -Uui1 o1b=0xFDD0
+	integer px=0xCAFE 'p0=1# ' p1=1#… pl=1#f
+	echo "in <$i0> <$i1>"
+	echo "out <${o0a#1#}|${o0b#1#}> <${o1a#1#}|${o1b#1#}>"
+	typeset -Uui1 i0 i1
+	echo "pass <$px> <$p0> <$p1> <$pl> <${i0#1#}|${i1#1#}>"
+	typeset -Uui16 tv1=1#~ tv2=1# tv3=1#€ tv4=1# tv5=1#À tv6=1#Á tv7=1#  tv8=1#€
+	echo "specX <${tv1#16#}> <${tv2#16#}> <${tv3#16#}> <${tv4#16#}> <${tv5#16#}> <${tv6#16#}> <${tv7#16#}> <${tv8#16#}>"
+	typeset -i1 tv1 tv2 tv3 tv4 tv5 tv6 tv7 tv8
+	echo "specW <${tv1#1#}> <${tv2#1#}> <${tv3#1#}> <${tv4#1#}> <${tv5#1#}> <${tv6#1#}> <${tv7#1#}> <${tv8#1#}>"
+	typeset -i1 xs1=0xEF7F xs2=0xEF80 xs3=0xFDD0
+	echo "specU <${xs1#1#}> <${xs2#1#}> <${xs3#1#}>"
+expected-stdout:
+	in <16#EFEF> <16#20AC>
+	out <@|~> <☺|﷐>
+	pass <16#cafe> <1# > <1#…> <1#f> <ï|€>
+	specX <7E> <7F> <EF80> <EF81> <EFC0> <EFC1> <A0> <80>
+	specW <~> <> <€> <> <À> <Á> < > <€>
+	specU <> <€> <﷐>
+---
+name: integer-base-one-2a
+description:
+	check if the use of fake integer base 1 stops at correct characters
+stdin:
+	set -U
+	integer x=1#foo
+	echo /$x/
+expected-stderr-pattern:
+	/1#foo: unexpected 'oo'/
+expected-exit: e != 0
+---
+name: integer-base-one-2b
+description:
+	check if the use of fake integer base 1 stops at correct characters
+stdin:
+	set -U
+	integer x=1#
+	echo /$x/
+expected-stderr-pattern:
+	/1#À€: unexpected '€'/
+expected-exit: e != 0
+---
+name: integer-base-one-2c1
+description:
+	check if the use of fake integer base 1 stops at correct characters
+stdin:
+	set -U
+	integer x=1#…
+	echo /$x/
+expected-stdout:
+	/1#…/
+---
+name: integer-base-one-2c2
+description:
+	check if the use of fake integer base 1 stops at correct characters
+stdin:
+	set +U
+	integer x=1#…
+	echo /$x/
+expected-stderr-pattern:
+	/1#…: unexpected '€'/
+expected-exit: e != 0
+---
+name: integer-base-one-2d1
+description:
+	check if the use of fake integer base 1 handles octets okay
+stdin:
+	set -U
+	typeset -i16 x=1#ÿ
+	echo /$x/	# invalid utf-8
+expected-stdout:
+	/16#efff/
+---
+name: integer-base-one-2d2
+description:
+	check if the use of fake integer base 1 handles octets
+stdin:
+	set -U
+	typeset -i16 x=1#Â
+	echo /$x/	# invalid 2-byte
+expected-stdout:
+	/16#efc2/
+---
+name: integer-base-one-2d3
+description:
+	check if the use of fake integer base 1 handles octets
+stdin:
+	set -U
+	typeset -i16 x=1#ï
+	echo /$x/	# invalid 2-byte
+expected-stdout:
+	/16#efef/
+---
+name: integer-base-one-2d4
+description:
+	check if the use of fake integer base 1 stops at invalid input
+stdin:
+	set -U
+	typeset -i16 x=1#ï¿À
+	echo /$x/	# invalid 3-byte
+expected-stderr-pattern:
+	/1#ï¿À: unexpected '¿'/
+expected-exit: e != 0
+---
+name: integer-base-one-2d5
+description:
+	check if the use of fake integer base 1 stops at invalid input
+stdin:
+	set -U
+	typeset -i16 x=1#
+	echo /$x/	# non-minimalistic
+expected-stderr-pattern:
+	/1#À€: unexpected '€'/
+expected-exit: e != 0
+---
+name: integer-base-one-2d6
+description:
+	check if the use of fake integer base 1 stops at invalid input
+stdin:
+	set -U
+	typeset -i16 x=1#à€€
+	echo /$x/	# non-minimalistic
+expected-stderr-pattern:
+	/1#à€€: unexpected '€'/
+expected-exit: e != 0
+---
+name: integer-base-one-3A
+description:
+	some sample code for hexdumping
+stdin:
+	{
+		print 'Hello, World!\\\nこんにちは!'
+		typeset -Uui16 i=0x100
+		# change that to 0xFF once we can handle embedded
+		# NUL characters in strings / here documents
+		while (( i++ < 0x1FF )); do
+			print -n "\x${i#16#1}"
+		done
+		print
+	} | {
+		typeset -Uui16 -Z11 pos=0
+		typeset -Uui16 -Z5 hv
+		typeset -i1 wc=0x0A
+		dasc=
+		nl=${wc#1#}
+		while IFS= read -r line; do
+			line=$line$nl
+			while [[ -n $line ]]; do
+				hv=1#${line::1}
+				if (( (pos & 15) == 0 )); then
+					(( pos )) && print "$dasc|"
+					print -n "${pos#16#}  "
+					dasc=' |'
+				fi
+				print -n "${hv#16#} "
+				if (( (hv < 32) || (hv > 126) )); then
+					dasc=$dasc.
+				else
+					dasc=$dasc${line::1}
+				fi
+				(( (pos++ & 15) == 7 )) && print -n -- '- '
+				line=${line:1}
+			done
+		done
+		if (( (pos & 15) != 1 )); then
+			while (( pos & 15 )); do
+				print -n '   '
+				(( (pos++ & 15) == 7 )) && print -n -- '- '
+			done
+			print "$dasc|"
+		fi
+	}
+expected-stdout:
+	00000000  48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3  |Hello, World!\..|
+	00000010  81 93 E3 82 93 E3 81 AB - E3 81 A1 E3 81 AF EF BC  |................|
+	00000020  81 0A 01 02 03 04 05 06 - 07 08 09 0A 0B 0C 0D 0E  |................|
+	00000030  0F 10 11 12 13 14 15 16 - 17 18 19 1A 1B 1C 1D 1E  |................|
+	00000040  1F 20 21 22 23 24 25 26 - 27 28 29 2A 2B 2C 2D 2E  |. !"#$%&'()*+,-.|
+	00000050  2F 30 31 32 33 34 35 36 - 37 38 39 3A 3B 3C 3D 3E  |/0123456789:;<=>|
+	00000060  3F 40 41 42 43 44 45 46 - 47 48 49 4A 4B 4C 4D 4E  |?@ABCDEFGHIJKLMN|
+	00000070  4F 50 51 52 53 54 55 56 - 57 58 59 5A 5B 5C 5D 5E  |OPQRSTUVWXYZ[\]^|
+	00000080  5F 60 61 62 63 64 65 66 - 67 68 69 6A 6B 6C 6D 6E  |_`abcdefghijklmn|
+	00000090  6F 70 71 72 73 74 75 76 - 77 78 79 7A 7B 7C 7D 7E  |opqrstuvwxyz{|}~|
+	000000A0  7F 80 81 82 83 84 85 86 - 87 88 89 8A 8B 8C 8D 8E  |................|
+	000000B0  8F 90 91 92 93 94 95 96 - 97 98 99 9A 9B 9C 9D 9E  |................|
+	000000C0  9F A0 A1 A2 A3 A4 A5 A6 - A7 A8 A9 AA AB AC AD AE  |................|
+	000000D0  AF B0 B1 B2 B3 B4 B5 B6 - B7 B8 B9 BA BB BC BD BE  |................|
+	000000E0  BF C0 C1 C2 C3 C4 C5 C6 - C7 C8 C9 CA CB CC CD CE  |................|
+	000000F0  CF D0 D1 D2 D3 D4 D5 D6 - D7 D8 D9 DA DB DC DD DE  |................|
+	00000100  DF E0 E1 E2 E3 E4 E5 E6 - E7 E8 E9 EA EB EC ED EE  |................|
+	00000110  EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE  |................|
+	00000120  FF 0A                   -                          |..|
+---
+name: integer-base-one-3W
+description:
+	some sample code for hexdumping Unicode
+stdin:
+	set -U
+	{
+		print 'Hello, World!\\\nこんにちは!'
+		typeset -Uui16 i=0x100
+		# change that to 0xFF once we can handle embedded
+		# NUL characters in strings / here documents
+		while (( i++ < 0x1FF )); do
+			print -n "\u${i#16#1}"
+		done
+		print
+		print \\xff		# invalid utf-8
+		print \\xc2		# invalid 2-byte
+		print \\xef\\xbf\\xc0	# invalid 3-byte
+		print \\xc0\\x80	# non-minimalistic
+		print \\xe0\\x80\\x80	# non-minimalistic
+		print '�￾￿'	# end of range
+	} | {
+		typeset -Uui16 -Z11 pos=0
+		typeset -Uui16 -Z7 hv
+		typeset -i1 wc=0x0A
+		typeset -i lpos
+		dasc=
+		nl=${wc#1#}
+		while IFS= read -r line; do
+			line=$line$nl
+			lpos=0
+			while (( lpos < ${#line} )); do
+				wc=1#${line:(lpos++):1}
+				if (( (wc < 32) || \
+				    ((wc > 126) && (wc < 160)) )); then
+					dch=.
+				elif (( (wc & 0xFF80) == 0xEF80 )); then
+					dch=�
+				else
+					dch=${wc#1#}
+				fi
+				if (( (pos & 7) == 7 )); then
+					dasc=$dasc$dch
+					dch=
+				elif (( (pos & 7) == 0 )); then
+					(( pos )) && print "$dasc|"
+					print -n "${pos#16#}  "
+					dasc=' |'
+				fi
+				let hv=wc
+				print -n "${hv#16#} "
+				(( (pos++ & 7) == 3 )) && \
+				    print -n -- '- '
+				dasc=$dasc$dch
+			done
+		done
+		if (( pos & 7 )); then
+			while (( pos & 7 )); do
+				print -n '     '
+				(( (pos++ & 7) == 3 )) && print -n -- '- '
+			done
+			print "$dasc|"
+		fi
+	}
+expected-stdout:
+	00000000  0048 0065 006C 006C - 006F 002C 0020 0057  |Hello, W|
+	00000008  006F 0072 006C 0064 - 0021 005C 000A 3053  |orld!\.こ|
+	00000010  3093 306B 3061 306F - FF01 000A 0001 0002  |んにちは!...|
+	00000018  0003 0004 0005 0006 - 0007 0008 0009 000A  |........|
+	00000020  000B 000C 000D 000E - 000F 0010 0011 0012  |........|
+	00000028  0013 0014 0015 0016 - 0017 0018 0019 001A  |........|
+	00000030  001B 001C 001D 001E - 001F 0020 0021 0022  |..... !"|
+	00000038  0023 0024 0025 0026 - 0027 0028 0029 002A  |#$%&'()*|
+	00000040  002B 002C 002D 002E - 002F 0030 0031 0032  |+,-./012|
+	00000048  0033 0034 0035 0036 - 0037 0038 0039 003A  |3456789:|
+	00000050  003B 003C 003D 003E - 003F 0040 0041 0042  |;<=>?@AB|
+	00000058  0043 0044 0045 0046 - 0047 0048 0049 004A  |CDEFGHIJ|
+	00000060  004B 004C 004D 004E - 004F 0050 0051 0052  |KLMNOPQR|
+	00000068  0053 0054 0055 0056 - 0057 0058 0059 005A  |STUVWXYZ|
+	00000070  005B 005C 005D 005E - 005F 0060 0061 0062  |[\]^_`ab|
+	00000078  0063 0064 0065 0066 - 0067 0068 0069 006A  |cdefghij|
+	00000080  006B 006C 006D 006E - 006F 0070 0071 0072  |klmnopqr|
+	00000088  0073 0074 0075 0076 - 0077 0078 0079 007A  |stuvwxyz|
+	00000090  007B 007C 007D 007E - 007F 0080 0081 0082  |{|}~....|
+	00000098  0083 0084 0085 0086 - 0087 0088 0089 008A  |........|
+	000000A0  008B 008C 008D 008E - 008F 0090 0091 0092  |........|
+	000000A8  0093 0094 0095 0096 - 0097 0098 0099 009A  |........|
+	000000B0  009B 009C 009D 009E - 009F 00A0 00A1 00A2  |..... ¡¢|
+	000000B8  00A3 00A4 00A5 00A6 - 00A7 00A8 00A9 00AA  |£¤¥¦§¨©ª|
+	000000C0  00AB 00AC 00AD 00AE - 00AF 00B0 00B1 00B2  |«¬­®¯°±²|
+	000000C8  00B3 00B4 00B5 00B6 - 00B7 00B8 00B9 00BA  |³´µ¶·¸¹º|
+	000000D0  00BB 00BC 00BD 00BE - 00BF 00C0 00C1 00C2  |»¼½¾¿ÀÁÂ|
+	000000D8  00C3 00C4 00C5 00C6 - 00C7 00C8 00C9 00CA  |ÃÄÅÆÇÈÉÊ|
+	000000E0  00CB 00CC 00CD 00CE - 00CF 00D0 00D1 00D2  |ËÌÍÎÏÐÑÒ|
+	000000E8  00D3 00D4 00D5 00D6 - 00D7 00D8 00D9 00DA  |ÓÔÕÖ×ØÙÚ|
+	000000F0  00DB 00DC 00DD 00DE - 00DF 00E0 00E1 00E2  |ÛÜÝÞßàáâ|
+	000000F8  00E3 00E4 00E5 00E6 - 00E7 00E8 00E9 00EA  |ãäåæçèéê|
+	00000100  00EB 00EC 00ED 00EE - 00EF 00F0 00F1 00F2  |ëìíîïðñò|
+	00000108  00F3 00F4 00F5 00F6 - 00F7 00F8 00F9 00FA  |óôõö÷øùú|
+	00000110  00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A  |ûüýþÿ.�.|
+	00000118  EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80  |�.���.��|
+	00000120  000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF  |.���.���|
+	00000128  EFBE EFEF EFBF EFBF - 000A                 |����.|
+---
+name: integer-base-one-4
+description:
+	Check if ksh93-style base-one integers work
+category: !smksh
+stdin:
+	set -U
+	echo 1 $(('a'))
+	(echo 2f $(('aa'))) 2>&1 | sed "s/^[^']*'/2p '/"
+	echo 3 $(('…'))
+	x="'a'"
+	echo "4 <$x>"
+	echo 5 $(($x))
+	echo 6 $((x))
+expected-stdout:
+	1 97
+	2p 'aa': multi-character character constant
+	3 8230
+	4 <'a'>
+	5 97
+	6 97
+---
+name: ulimit-1
+description:
+	Check if we can use a specific syntax idiom for ulimit
+stdin:
+	if ! x=$(ulimit -d) || [[ $x = unknown ]]; then
+		#echo expected to fail on this OS
+		echo okay
+	else
+		ulimit -dS $x && echo okay
+	fi
+expected-stdout:
+	okay
+---
+name: bashiop-1
+description:
+	Check if GNU bash-like I/O redirection works
+	Part 1: this is also supported by GNU bash
+stdin:
+	exec 3>&1
+	function threeout {
+		echo ras
+		echo dwa >&2
+		echo tri >&3
+	}
+	threeout &>foo
+	echo ===
+	cat foo
+expected-stdout:
+	tri
+	===
+	ras
+	dwa
+---
+name: bashiop-2a
+description:
+	Check if GNU bash-like I/O redirection works
+	Part 2: this is *not* supported by GNU bash
+stdin:
+	exec 3>&1
+	function threeout {
+		echo ras
+		echo dwa >&2
+		echo tri >&3
+	}
+	threeout 3&>foo
+	echo ===
+	cat foo
+expected-stdout:
+	ras
+	===
+	dwa
+	tri
+---
+name: bashiop-2b
+description:
+	Check if GNU bash-like I/O redirection works
+	Part 2: this is *not* supported by GNU bash
+stdin:
+	exec 3>&1
+	function threeout {
+		echo ras
+		echo dwa >&2
+		echo tri >&3
+	}
+	threeout 3>foo &>&3
+	echo ===
+	cat foo
+expected-stdout:
+	===
+	ras
+	dwa
+	tri
+---
+name: bashiop-2c
+description:
+	Check if GNU bash-like I/O redirection works
+	Part 2: this is supported by GNU bash 4 only
+stdin:
+	echo mir >foo
+	set -o noclobber
+	exec 3>&1
+	function threeout {
+		echo ras
+		echo dwa >&2
+		echo tri >&3
+	}
+	threeout &>>foo
+	echo ===
+	cat foo
+expected-stdout:
+	tri
+	===
+	mir
+	ras
+	dwa
+---
+name: bashiop-3a
+description:
+	Check if GNU bash-like I/O redirection fails correctly
+	Part 1: this is also supported by GNU bash
+stdin:
+	echo mir >foo
+	set -o noclobber
+	exec 3>&1
+	function threeout {
+		echo ras
+		echo dwa >&2
+		echo tri >&3
+	}
+	threeout &>foo
+	echo ===
+	cat foo
+expected-stdout:
+	===
+	mir
+expected-stderr-pattern: /.*: cannot (create|overwrite) .*/
+---
+name: bashiop-3b
+description:
+	Check if GNU bash-like I/O redirection fails correctly
+	Part 2: this is *not* supported by GNU bash
+stdin:
+	echo mir >foo
+	set -o noclobber
+	exec 3>&1
+	function threeout {
+		echo ras
+		echo dwa >&2
+		echo tri >&3
+	}
+	threeout &>|foo
+	echo ===
+	cat foo
+expected-stdout:
+	tri
+	===
+	ras
+	dwa
+---
+name: bashiop-4
+description:
+	Check if GNU bash-like I/O redirection works
+	Part 4: this is also supported by GNU bash,
+	but failed in some mksh versions
+stdin:
+	exec 3>&1
+	function threeout {
+		echo ras
+		echo dwa >&2
+		echo tri >&3
+	}
+	function blubb {
+		[[ -e bar ]] && threeout "$bf" &>foo
+	}
+	blubb
+	echo -n >bar
+	blubb
+	echo ===
+	cat foo
+expected-stdout:
+	tri
+	===
+	ras
+	dwa
+---
+name: mkshiop-1
+description:
+	Check for support of more than 9 file descriptors
+category: !convfds
+stdin:
+	read -u10 foo 10<<< bar
+	echo x$foo
+expected-stdout:
+	xbar
+---
+name: mkshiop-2
+description:
+	Check for support of more than 9 file descriptors
+category: !convfds
+stdin:
+	exec 12>foo
+	print -u12 bar
+	echo baz >&12
+	cat foo
+expected-stdout:
+	bar
+	baz
+---
+name: oksh-shcrash
+description:
+	src/regress/bin/ksh/shcrash.sh,v 1.1
+stdin:
+	deplibs="-lz -lpng /usr/local/lib/libjpeg.la -ltiff -lm -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libglib.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgtk.la -ltiff -ljpeg -lz -lpng -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk_pixbuf.la -lz -lpng /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libglib.la -lm -lm /usr/local/lib/libaudiofile.la -lm -lm -laudiofile -L/usr/local/lib /usr/local/lib/libesd.la -lm -lz -L/usr/local/lib /usr/local/lib/libgnomesupport.la -lm -lz -lm -lglib -L/usr/local/lib /usr/local/lib/libgnome.la -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgtk.la -lICE -lSM -lz -lpng /usr/local/lib/libungif.la /usr/local/lib/libjpeg.la -ltiff -lm -lz -lpng /usr/local/lib/libungif.la -lz /usr/local/lib/libjpeg.la -ltiff -L/usr/local/lib -L/usr/X11R6/lib /usr/local/lib/libgdk_imlib.la -lm -L/usr/local/lib /usr/local/lib/libart_lgpl.la -lm -lz -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lICE -lSM -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -L/usr/X11R6/lib -lm -lz -lpng -lungif -lz -ljpeg -ltiff -ljpeg -lgdk_imlib -lglib -lm -laudiofile -lm -laudiofile -lesd -L/usr/local/lib /usr/local/lib/libgnomeui.la -lz -lz /usr/local/lib/libxml.la -lz -lz -lz /usr/local/lib/libxml.la -lm -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libglib.la /usr/local/lib/libgmodule.la -lintl -lglib -lgmodule /usr/local/lib/libgdk.la /usr/local/lib/libgtk.la -L/usr/X11R6/lib -L/usr/local/lib /usr/local/lib/libglade.la -lz -lz -lz /usr/local/lib/libxml.la /usr/local/lib/libglib.la -lm -lm /usr/local/lib/libaudiofile.la -lm -lm -laudiofile /usr/local/lib/libesd.la -lm -lz /usr/local/lib/libgnomesupport.la -lm -lz -lm -lglib /usr/local/lib/libgnome.la -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -lglib -lgmodule /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -lglib -lgmodule /usr/local/lib/libgtk.la -lICE -lSM -lz -lpng /usr/local/lib/libungif.la /usr/local/lib/libjpeg.la -ltiff -lm -lz -lz /usr/local/lib/libgdk_imlib.la /usr/local/lib/libart_lgpl.la -lm -lz -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lm -lz -lungif -lz -ljpeg -ljpeg -lgdk_imlib -lglib -lm -laudiofile -lm -laudiofile -lesd /usr/local/lib/libgnomeui.la -L/usr/X11R6/lib -L/usr/local/lib /usr/local/lib/libglade-gnome.la /usr/local/lib/libglib.la -lm -lm /usr/local/lib/libaudiofile.la -lm -lm -laudiofile -L/usr/local/lib /usr/local/lib/libesd.la -lm -lz -L/usr/local/lib /usr/local/lib/libgnomesupport.la -lm -lz -lm -lglib -L/usr/local/lib /usr/local/lib/libgnome.la -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgtk.la -lICE -lSM -lz -lpng /usr/local/lib/libungif.la /usr/local/lib/libjpeg.la -ltiff -lm -lz -lpng /usr/local/lib/libungif.la -lz /usr/local/lib/libjpeg.la -ltiff -L/usr/local/lib -L/usr/X11R6/lib /usr/local/lib/libgdk_imlib.la -lm -L/usr/local/lib /usr/local/lib/libart_lgpl.la -lm -lz -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lICE -lSM -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -L/usr/X11R6/lib -lm -lz -lpng -lungif -lz -ljpeg -ltiff -ljpeg -lgdk_imlib -lglib -lm -laudiofile -lm -laudiofile -lesd -L/usr/local/lib /usr/local/lib/libgnomeui.la -L/usr/X11R6/lib -L/usr/local/lib"
+	specialdeplibs="-lgnomeui -lart_lgpl -lgdk_imlib -ltiff -ljpeg -lungif -lpng -lz -lSM -lICE -lgtk -lgdk -lgmodule -lintl -lXext -lX11 -lgnome -lgnomesupport -lesd -laudiofile -lm -lglib"
+	for deplib in $deplibs; do
+		case $deplib in
+		-L*)
+			new_libs="$deplib $new_libs"
+			;;
+		*)
+			case " $specialdeplibs " in
+			*" $deplib "*)
+				new_libs="$deplib $new_libs";;
+			esac
+			;;
+		esac
+	done
+---
+name: oksh-varfunction-mod1
+description:
+	$OpenBSD: varfunction.sh,v 1.1 2003/12/15 05:28:40 otto Exp $
+	Calling
+		FOO=bar f
+	where f is a ksh style function, should not set FOO in the current
+	env. If f is a Bourne style function, FOO should be set. Furthermore,
+	the function should receive a correct value of FOO. However, differing
+	from oksh, setting FOO in the function itself must change the value in
+	setting FOO in the function itself should not change the value in
+	global environment.
+	Inspired by PR 2450.
+stdin:
+	function k {
+		if [ x$FOO != xbar ]; then
+			echo 1
+			return 1
+		fi
+		x=$(env | grep FOO)
+		if [ "x$x" != "xFOO=bar" ]; then
+			echo 2
+			return 1;
+		fi
+		FOO=foo
+		return 0
+	}
+	b () {
+		if [ x$FOO != xbar ]; then
+			echo 3
+			return 1
+		fi
+		x=$(env | grep FOO)
+		if [ "x$x" != "xFOO=bar" ]; then
+			echo 4
+			return 1;
+		fi
+		FOO=foo
+		return 0
+	}
+	FOO=bar k
+	if [ $? != 0 ]; then
+		exit 1
+	fi
+	if [ x$FOO != x ]; then
+		exit 1
+	fi
+	FOO=bar b
+	if [ $? != 0 ]; then
+		exit 1
+	fi
+	if [ x$FOO != xfoo ]; then
+		exit 1
+	fi
+	FOO=barbar
+	FOO=bar k
+	if [ $? != 0 ]; then
+		exit 1
+	fi
+	if [ x$FOO != xbarbar ]; then
+		exit 1
+	fi
+	FOO=bar b
+	if [ $? != 0 ]; then
+		exit 1
+	fi
+	if [ x$FOO != xfoo ]; then
+		exit 1
+	fi
+---
+name: fd-cloexec-1
+description:
+	Verify that file descriptors > 2 are private for Korn shells
+file-setup: file 644 "test.sh"
+	print -u3 Fowl
+stdin:
+	exec 3>&1
+	"$__progname" test.sh
+expected-exit: e != 0
+expected-stderr:
+	test.sh[1]: print: -u: 3: bad file descriptor
+---
+name: fd-cloexec-2
+description:
+	Verify that file descriptors > 2 are not private for POSIX shells
+	See Debian Bug #154540, Closes: #499139
+file-setup: file 644 "test.sh"
+	print -u3 Fowl
+stdin:
+	test -n "$POSH_VERSION" || set -o sh
+	exec 3>&1
+	"$__progname" test.sh
+expected-stdout:
+	Fowl
+---
+name: comsub-1
+description:
+	COMSUB are currently parsed by hacking lex.c instead of
+	recursively (see regression-6): matching parenthesēs bug
+	Fails on: pdksh mksh bash2 bash3 zsh
+	Passes on: bash4 ksh93
+expected-fail: yes
+stdin:
+	echo $(case 1 in (1) echo yes;; (2) echo no;; esac)
+	echo $(case 1 in 1) echo yes;; 2) echo no;; esac)
+expected-stdout:
+	yes
+	yes
+---
+name: comsub-2
+description:
+	RedHat BZ#496791 – another case of missing recursion
+	in parsing COMSUB expressions
+	Fails on: pdksh mksh bash2 bash3¹ bash4¹ zsh
+	Passes on: ksh93
+	① bash[34] seem to choke on comment ending with backslash-newline
+expected-fail: yes
+stdin:
+	# a comment with " ' \
+	x=$(
+	echo yes
+	# a comment with " ' \
+	)
+	echo $x
+expected-stdout:
+	yes
+---
+name: test-stnze-1
+description:
+	Check that the short form [ $x ] works
+stdin:
+	i=0
+	[ -n $x ]
+	rv=$?; echo $((++i)) $rv
+	[ $x ]
+	rv=$?; echo $((++i)) $rv
+	[ -n "$x" ]
+	rv=$?; echo $((++i)) $rv
+	[ "$x" ]
+	rv=$?; echo $((++i)) $rv
+	x=0
+	[ -n $x ]
+	rv=$?; echo $((++i)) $rv
+	[ $x ]
+	rv=$?; echo $((++i)) $rv
+	[ -n "$x" ]
+	rv=$?; echo $((++i)) $rv
+	[ "$x" ]
+	rv=$?; echo $((++i)) $rv
+	x='1 -a 1 = 2'
+	[ -n $x ]
+	rv=$?; echo $((++i)) $rv
+	[ $x ]
+	rv=$?; echo $((++i)) $rv
+	[ -n "$x" ]
+	rv=$?; echo $((++i)) $rv
+	[ "$x" ]
+	rv=$?; echo $((++i)) $rv
+expected-stdout:
+	1 0
+	2 1
+	3 1
+	4 1
+	5 0
+	6 0
+	7 0
+	8 0
+	9 1
+	10 1
+	11 0
+	12 0
+---
+name: test-stnze-2
+description:
+	Check that the short form [[ $x ]] works (ksh93 extension)
+stdin:
+	i=0
+	[[ -n $x ]]
+	rv=$?; echo $((++i)) $rv
+	[[ $x ]]
+	rv=$?; echo $((++i)) $rv
+	[[ -n "$x" ]]
+	rv=$?; echo $((++i)) $rv
+	[[ "$x" ]]
+	rv=$?; echo $((++i)) $rv
+	x=0
+	[[ -n $x ]]
+	rv=$?; echo $((++i)) $rv
+	[[ $x ]]
+	rv=$?; echo $((++i)) $rv
+	[[ -n "$x" ]]
+	rv=$?; echo $((++i)) $rv
+	[[ "$x" ]]
+	rv=$?; echo $((++i)) $rv
+	x='1 -a 1 = 2'
+	[[ -n $x ]]
+	rv=$?; echo $((++i)) $rv
+	[[ $x ]]
+	rv=$?; echo $((++i)) $rv
+	[[ -n "$x" ]]
+	rv=$?; echo $((++i)) $rv
+	[[ "$x" ]]
+	rv=$?; echo $((++i)) $rv
+expected-stdout:
+	1 1
+	2 1
+	3 1
+	4 1
+	5 0
+	6 0
+	7 0
+	8 0
+	9 0
+	10 0
+	11 0
+	12 0
+---
+name: event-subst-1a
+description:
+	Check that '!' substitution in interactive mode works
+category: !smksh
+file-setup: file 755 "falsetto"
+	#! /bin/sh
+	echo molto bene
+	exit 42
+file-setup: file 755 "!false"
+	#! /bin/sh
+	echo si
+arguments: !-i!
+stdin:
+	export PATH=.:$PATH
+	falsetto
+	echo yeap
+	!false
+expected-exit: 42
+expected-stdout:
+	molto bene
+	yeap
+	molto bene
+expected-stderr-pattern:
+	/.*/
+---
+name: event-subst-1b
+description:
+	Check that '!' substitution in interactive mode works
+	even when a space separates it from the search command,
+	which is not what GNU bash provides but required for the
+	other regression tests below to check
+category: !smksh
+file-setup: file 755 "falsetto"
+	#! /bin/sh
+	echo molto bene
+	exit 42
+file-setup: file 755 "!"
+	#! /bin/sh
+	echo si
+arguments: !-i!
+stdin:
+	export PATH=.:$PATH
+	falsetto
+	echo yeap
+	! false
+expected-exit: 42
+expected-stdout:
+	molto bene
+	yeap
+	molto bene
+expected-stderr-pattern:
+	/.*/
+---
+name: event-subst-2
+description:
+	Check that '!' substitution in interactive mode
+	does not break things
+category: !smksh
+file-setup: file 755 "falsetto"
+	#! /bin/sh
+	echo molto bene
+	exit 42
+file-setup: file 755 "!"
+	#! /bin/sh
+	echo si
+arguments: !-i!
+env-setup: !ENV=./Env!
+file-setup: file 644 "Env"
+	PS1=X
+stdin:
+	export PATH=.:$PATH
+	falsetto
+	echo yeap
+	!false
+	echo meow
+	! false
+	echo = $?
+	if
+	! false; then echo foo; else echo bar; fi
+expected-stdout:
+	molto bene
+	yeap
+	molto bene
+	meow
+	molto bene
+	= 42
+	foo
+expected-stderr-pattern:
+	/.*/
+---
+name: event-subst-3
+description:
+	Check that '!' substitution in noninteractive mode is ignored
+category: !smksh
+file-setup: file 755 "falsetto"
+	#! /bin/sh
+	echo molto bene
+	exit 42
+file-setup: file 755 "!false"
+	#! /bin/sh
+	echo si
+stdin:
+	export PATH=.:$PATH
+	falsetto
+	echo yeap
+	!false
+	echo meow
+	! false
+	echo = $?
+	if
+	! false; then echo foo; else echo bar; fi
+expected-stdout:
+	molto bene
+	yeap
+	si
+	meow
+	= 0
+	foo
+---
+name: nounset-1
+description:
+	Check that "set -u" matches (future) SUSv4 requirement
+stdin:
+	(set -u
+	try() {
+		local v
+		eval v=\$$1
+		if [[ -n $v ]]; then
+			echo $1=nz
+		else
+			echo $1=zf
+		fi
+	}
+	x=y
+	(echo $x)
+	echo =1
+	(echo $y)
+	echo =2
+	(try x)
+	echo =3
+	(try y)
+	echo =4
+	(try 0)
+	echo =5
+	(try 2)
+	echo =6
+	(try)
+	echo =7
+	(echo at=$@)
+	echo =8
+	(echo asterisk=$*)
+	echo =9
+	(echo $?)
+	echo =10
+	(echo $!)
+	echo =11
+	(echo $-)
+	echo =12
+	#(echo $_)
+	#echo =13
+	(echo $#)
+	echo =14
+	(mypid=$$; try mypid)
+	echo =15
+	) 2>&1 | sed -e 's/^[^]]*]//' -e 's/^[^:]*: *//'
+expected-stdout:
+	y
+	=1
+	y: parameter not set
+	=2
+	x=nz
+	=3
+	y: parameter not set
+	=4
+	0=nz
+	=5
+	2: parameter not set
+	=6
+	1: parameter not set
+	=7
+	at=
+	=8
+	asterisk=
+	=9
+	0
+	=10
+	!: parameter not set
+	=11
+	ush
+	=12
+	0
+	=14
+	mypid=nz
+	=15
+---
+name: nameref-1
+description:
+	Testsuite for nameref (bound variables)
+stdin:
+	bar=global
+	typeset -n ir2=bar
+	typeset -n ind=ir2
+	echo !ind: ${!ind}
+	echo ind: $ind
+	echo !ir2: ${!ir2}
+	echo ir2: $ir2
+	typeset +n ind
+	echo !ind: ${!ind}
+	echo ind: $ind
+	typeset -n ir2=ind
+	echo !ir2: ${!ir2}
+	echo ir2: $ir2
+	set|grep ^ir2|sed 's/^/s1: /'
+	typeset|grep ' ir2'|sed -e 's/^/s2: /' -e 's/nameref/typeset -n/'
+	set -A blub -- e1 e2 e3
+	typeset -n ind=blub
+	typeset -n ir2=blub[2]
+	echo !ind[1]: ${!ind[1]}
+	echo !ir2: $!ir2
+	echo ind[1]: ${ind[1]}
+	echo ir2: $ir2
+expected-stdout:
+	!ind: bar
+	ind: global
+	!ir2: bar
+	ir2: global
+	!ind: ind
+	ind: ir2
+	!ir2: ind
+	ir2: ir2
+	s1: ir2=ind
+	s2: typeset -n ir2
+	!ind[1]: 1
+	!ir2: ir2
+	ind[1]: e2
+	ir2: e3
+---
+name: nameref-2da
+description:
+	Testsuite for nameref (bound variables)
+	Functions, argument given directly, after local
+stdin:
+	function foo {
+		typeset bar=lokal baz=auch
+		typeset -n v=bar
+		echo entering
+		echo !v: ${!v}
+		echo !bar: ${!bar}
+		echo !baz: ${!baz}
+		echo bar: $bar
+		echo v: $v
+		v=123
+		echo bar: $bar
+		echo v: $v
+		echo exiting
+	}
+	bar=global
+	echo bar: $bar
+	foo bar
+	echo bar: $bar
+expected-stdout:
+	bar: global
+	entering
+	!v: bar
+	!bar: bar
+	!baz: baz
+	bar: lokal
+	v: lokal
+	bar: 123
+	v: 123
+	exiting
+	bar: global
+---
+name: nameref-3
+description:
+	Advanced testsuite for bound variables (ksh93 fails this)
+stdin:
+	typeset -n foo=bar[i]
+	set -A bar -- b c a
+	for i in 0 1 2 3; do
+		print $i $foo .
+	done
+expected-stdout:
+	0 b .
+	1 c .
+	2 a .
+	3 .
+---
+name: better-parens-1a
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	if ( (echo fubar) | tr u x); then
+		echo ja
+	else
+		echo nein
+	fi
+expected-stdout:
+	fxbar
+	ja
+---
+name: better-parens-1b
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	echo $( (echo fubar) | tr u x) $?
+expected-stdout:
+	fxbar 0
+---
+name: better-parens-2a
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	if ((echo fubar) | tr u x); then
+		echo ja
+	else
+		echo nein
+	fi
+expected-stdout:
+	fxbar
+	ja
+---
+name: better-parens-2b
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	echo $((echo fubar) | tr u x) $?
+expected-stdout:
+	fxbar 0
+---
+name: better-parens-3a
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	if ( (echo fubar) | (tr u x)); then
+		echo ja
+	else
+		echo nein
+	fi
+expected-stdout:
+	fxbar
+	ja
+---
+name: better-parens-3b
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	echo $( (echo fubar) | (tr u x)) $?
+expected-stdout:
+	fxbar 0
+---
+name: better-parens-4a
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	if ((echo fubar) | (tr u x)); then
+		echo ja
+	else
+		echo nein
+	fi
+expected-stdout:
+	fxbar
+	ja
+---
+name: better-parens-4b
+description:
+	Check support for ((…)) and $((…)) vs (…) and $(…)
+stdin:
+	echo $((echo fubar) | (tr u x)) $?
+expected-stdout:
+	fxbar 0
+---
+name: echo-test-1
+description:
+	Test what the echo builtin does (mksh)
+stdin:
+	echo -n 'foo\x40bar'
+	echo -e '\tbaz'
+expected-stdout:
+	foo@bar	baz
+---
+name: echo-test-2
+description:
+	Test what the echo builtin does (POSIX)
+	Note: this follows Debian Policy 10.4 which mandates
+	that -n shall be treated as an option, not XSI which
+	mandates it shall be treated as string but escapes
+	shall be expanded.
+stdin:
+	test -n "$POSH_VERSION" || set -o sh
+	echo -n 'foo\x40bar'
+	echo -e '\tbaz'
+expected-stdout:
+	foo\x40bar-e \tbaz
+---
+name: utilities-getopts-1
+description:
+	getopts sets OPTIND correctly for unparsed option
+stdin:
+	set -- -a -a -x
+	while getopts :a optc; do
+	    echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc."
+	done
+	echo done
+expected-stdout:
+	OPTARG=, OPTIND=2, optc=a.
+	OPTARG=, OPTIND=3, optc=a.
+	OPTARG=x, OPTIND=4, optc=?.
+	done
+---
+name: utilities-getopts-2
+description:
+	Check OPTARG
+stdin:
+	set -- -a Mary -x
+	while getopts a: optc; do
+	    echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc."
+	done
+	echo done
+expected-stdout:
+	OPTARG=Mary, OPTIND=3, optc=a.
+	OPTARG=, OPTIND=4, optc=?.
+	done
+expected-stderr-pattern: /.*-x.*option/
+---
+name: wcswidth-1
+description:
+	Check the new wcswidth feature
+stdin:
+	s=何
+	set +U
+	print octets: ${#s} .
+	print 8-bit width: ${%s} .
+	set -U
+	print characters: ${#s} .
+	print columns: ${%s} .
+	s=�
+	set +U
+	print octets: ${#s} .
+	print 8-bit width: ${%s} .
+	set -U
+	print characters: ${#s} .
+	print columns: ${%s} .
+expected-stdout:
+	octets: 3 .
+	8-bit width: -1 .
+	characters: 1 .
+	columns: 2 .
+	octets: 3 .
+	8-bit width: 3 .
+	characters: 1 .
+	columns: 1 .
+---
+name: wcswidth-2
+description:
+	Check some corner cases
+stdin:
+	print % $% .
+	set -U
+	x='a	b'
+	print c ${%x} .
+	set +U
+	x='a	b'
+	print d ${%x} .
+expected-stdout:
+	% $% .
+	c -1 .
+	d -1 .
+---
+name: wcswidth-3
+description:
+	Check some corner cases
+stdin:
+	print ${%} .
+expected-stderr-pattern:
+	/bad substitution/
+expected-exit: 1
+---
+name: wcswidth-4a
+description:
+	Check some corner cases
+stdin:
+	print ${%*} .
+expected-stderr-pattern:
+	/bad substitution/
+expected-exit: 1
+---
+name: wcswidth-4b
+description:
+	Check some corner cases
+stdin:
+	print ${%@} .
+expected-stderr-pattern:
+	/bad substitution/
+expected-exit: 1
+---
+name: wcswidth-4c
+description:
+	Check some corner cases
+stdin:
+	:
+	print ${%?} .
+expected-stdout:
+	1 .
+---
+name: realpath-1
+description:
+	Check proper return values for realpath
+category: os:mirbsd
+stdin:
+	wd=$(realpath .)
+	mkdir dir
+	:>file
+	:>dir/file
+	ln -s dir lndir
+	ln -s file lnfile
+	ln -s nix lnnix
+	ln -s . lnself
+	i=0
+	chk() {
+		typeset x y
+		x=$(realpath "$wd/$1" 2>&1); y=$?
+		print $((++i)) "?$1" =${x##*$wd/} !$y
+	}
+	chk dir
+	chk dir/
+	chk dir/file
+	chk dir/nix
+	chk file
+	chk file/
+	chk file/file
+	chk file/nix
+	chk nix
+	chk nix/
+	chk nix/file
+	chk nix/nix
+	chk lndir
+	chk lndir/
+	chk lndir/file
+	chk lndir/nix
+	chk lnfile
+	chk lnfile/
+	chk lnfile/file
+	chk lnfile/nix
+	chk lnnix
+	chk lnnix/
+	chk lnnix/file
+	chk lnnix/nix
+	chk lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself
+	rm lnself
+expected-stdout:
+	1 ?dir =dir !0
+	2 ?dir/ =dir !0
+	3 ?dir/file =dir/file !0
+	4 ?dir/nix =dir/nix !0
+	5 ?file =file !0
+	6 ?file/ =file/: Not a directory !20
+	7 ?file/file =file/file: Not a directory !20
+	8 ?file/nix =file/nix: Not a directory !20
+	9 ?nix =nix !0
+	10 ?nix/ =nix !0
+	11 ?nix/file =nix/file: No such file or directory !2
+	12 ?nix/nix =nix/nix: No such file or directory !2
+	13 ?lndir =dir !0
+	14 ?lndir/ =dir !0
+	15 ?lndir/file =dir/file !0
+	16 ?lndir/nix =dir/nix !0
+	17 ?lnfile =file !0
+	18 ?lnfile/ =lnfile/: Not a directory !20
+	19 ?lnfile/file =lnfile/file: Not a directory !20
+	20 ?lnfile/nix =lnfile/nix: Not a directory !20
+	21 ?lnnix =nix !0
+	22 ?lnnix/ =nix !0
+	23 ?lnnix/file =lnnix/file: No such file or directory !2
+	24 ?lnnix/nix =lnnix/nix: No such file or directory !2
+	25 ?lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself =lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself: Too many levels of symbolic links !62
+---
diff --git a/mksh/src/edit.c b/mksh/src/edit.c
new file mode 100644
index 0000000..905de7e
--- /dev/null
+++ b/mksh/src/edit.c
@@ -0,0 +1,5249 @@
+/*	$OpenBSD: edit.c,v 1.34 2010/05/20 01:13:07 fgsch Exp $	*/
+/*	$OpenBSD: edit.h,v 1.8 2005/03/28 21:28:22 deraadt Exp $	*/
+/*	$OpenBSD: emacs.c,v 1.42 2009/06/02 06:47:47 halex Exp $	*/
+/*	$OpenBSD: vi.c,v 1.26 2009/06/29 22:50:19 martynas Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.196 2010/07/25 11:35:40 tg Exp $");
+
+/*
+ * in later versions we might use libtermcap for this, but since external
+ * dependencies are problematic, this has not yet been decided on; another
+ * good string is "\033c" except on hardware terminals like the DEC VT420
+ * which do a full power cycle then...
+ */
+#ifndef MKSH_CLS_STRING
+#define MKSH_CLS_STRING		"\033[;H\033[J"
+#endif
+#ifndef MKSH_CLRTOEOL_STRING
+#define MKSH_CLRTOEOL_STRING	"\033[K"
+#endif
+
+/* tty driver characters we are interested in */
+typedef struct {
+	int erase;
+	int kill;
+	int werase;
+	int intr;
+	int quit;
+	int eof;
+} X_chars;
+
+static X_chars edchars;
+
+/* x_fc_glob() flags */
+#define XCF_COMMAND	BIT(0)	/* Do command completion */
+#define XCF_FILE	BIT(1)	/* Do file completion */
+#define XCF_FULLPATH	BIT(2)	/* command completion: store full path */
+#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE)
+
+static char editmode;
+static int xx_cols;			/* for Emacs mode */
+static int modified;			/* buffer has been "modified" */
+static char holdbuf[LINE];		/* place to hold last edit buffer */
+
+static int x_getc(void);
+static void x_putcf(int);
+static bool x_mode(bool);
+static int x_do_comment(char *, int, int *);
+static void x_print_expansions(int, char *const *, bool);
+static int x_cf_glob(int, const char *, int, int, int *, int *, char ***,
+    bool *);
+static int x_longest_prefix(int, char *const *);
+static int x_basename(const char *, const char *);
+static void x_free_words(int, char **);
+static int x_escape(const char *, size_t, int (*)(const char *, size_t));
+static int x_emacs(char *, size_t);
+static void x_init_emacs(void);
+static void x_init_prompt(void);
+#if !MKSH_S_NOVI
+static int x_vi(char *, size_t);
+#endif
+
+#define x_flush()	shf_flush(shl_out)
+#ifdef MKSH_SMALL
+#define x_putc(c)	x_putcf(c)
+#else
+#define x_putc(c)	shf_putc((c), shl_out)
+#endif
+
+static int path_order_cmp(const void *aa, const void *bb);
+static char *add_glob(const char *, int)
+    MKSH_A_NONNULL((nonnull (1)))
+    MKSH_A_BOUNDED(string, 1, 2);
+static void glob_table(const char *, XPtrV *, struct table *);
+static void glob_path(int flags, const char *, XPtrV *, const char *);
+static int x_file_glob(int, const char *, int, char ***)
+    MKSH_A_NONNULL((nonnull (2)))
+    MKSH_A_BOUNDED(string, 2, 3);
+static int x_command_glob(int, const char *, int, char ***)
+    MKSH_A_NONNULL((nonnull (2)))
+    MKSH_A_BOUNDED(string, 2, 3);
+static int x_locate_word(const char *, int, int, int *, bool *);
+
+static int x_e_getmbc(char *);
+static int x_e_rebuildline(const char *);
+
+/* +++ generic editing functions +++ */
+
+/* Called from main */
+void
+x_init(void)
+{
+	/* set to -2 to force initial binding */
+	edchars.erase = edchars.kill = edchars.intr = edchars.quit =
+	    edchars.eof = -2;
+	/* default value for deficient systems */
+	edchars.werase = 027;	/* ^W */
+	x_init_emacs();
+}
+
+/*
+ * read an edited command line
+ */
+int
+x_read(char *buf, size_t len)
+{
+	int i;
+
+	x_mode(true);
+	modified = 1;
+	if (Flag(FEMACS) || Flag(FGMACS))
+		i = x_emacs(buf, len);
+#if !MKSH_S_NOVI
+	else if (Flag(FVI))
+		i = x_vi(buf, len);
+#endif
+	else
+		i = -1;		/* internal error */
+	editmode = 0;
+	x_mode(false);
+	return (i);
+}
+
+/* tty I/O */
+
+static int
+x_getc(void)
+{
+	char c;
+	int n;
+
+	while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
+		if (trap) {
+			x_mode(false);
+			runtraps(0);
+#ifdef SIGWINCH
+			if (got_winch) {
+				change_winsz();
+				if (x_cols != xx_cols && editmode == 1) {
+					/* redraw line in Emacs mode */
+					xx_cols = x_cols;
+					x_e_rebuildline(MKSH_CLRTOEOL_STRING);
+				}
+			}
+#endif
+			x_mode(true);
+		}
+	return ((n == 1) ? (int)(unsigned char)c : -1);
+}
+
+static void
+x_putcf(int c)
+{
+	shf_putc(c, shl_out);
+}
+
+/*********************************
+ * Misc common code for vi/emacs *
+ *********************************/
+
+/* Handle the commenting/uncommenting of a line.
+ * Returns:
+ *	1 if a carriage return is indicated (comment added)
+ *	0 if no return (comment removed)
+ *	-1 if there is an error (not enough room for comment chars)
+ * If successful, *lenp contains the new length. Note: cursor should be
+ * moved to the start of the line after (un)commenting.
+ */
+static int
+x_do_comment(char *buf, int bsize, int *lenp)
+{
+	int i, j, len = *lenp;
+
+	if (len == 0)
+		return (1); /* somewhat arbitrary - it's what AT&T ksh does */
+
+	/* Already commented? */
+	if (buf[0] == '#') {
+		bool saw_nl = false;
+
+		for (j = 0, i = 1; i < len; i++) {
+			if (!saw_nl || buf[i] != '#')
+				buf[j++] = buf[i];
+			saw_nl = buf[i] == '\n';
+		}
+		*lenp = j;
+		return (0);
+	} else {
+		int n = 1;
+
+		/* See if there's room for the #s - 1 per \n */
+		for (i = 0; i < len; i++)
+			if (buf[i] == '\n')
+				n++;
+		if (len + n >= bsize)
+			return (-1);
+		/* Now add them... */
+		for (i = len, j = len + n; --i >= 0; ) {
+			if (buf[i] == '\n')
+				buf[--j] = '#';
+			buf[--j] = buf[i];
+		}
+		buf[0] = '#';
+		*lenp += n;
+		return (1);
+	}
+}
+
+/****************************************************
+ * Common file/command completion code for vi/emacs *
+ ****************************************************/
+
+static void
+x_print_expansions(int nwords, char * const *words, bool is_command)
+{
+	bool use_copy = false;
+	int prefix_len;
+	XPtrV l = { NULL, NULL, NULL };
+
+	/* Check if all matches are in the same directory (in this
+	 * case, we want to omit the directory name)
+	 */
+	if (!is_command &&
+	    (prefix_len = x_longest_prefix(nwords, words)) > 0) {
+		int i;
+
+		/* Special case for 1 match (prefix is whole word) */
+		if (nwords == 1)
+			prefix_len = x_basename(words[0], NULL);
+		/* Any (non-trailing) slashes in non-common word suffixes? */
+		for (i = 0; i < nwords; i++)
+			if (x_basename(words[i] + prefix_len, NULL) >
+			    prefix_len)
+				break;
+		/* All in same directory? */
+		if (i == nwords) {
+			while (prefix_len > 0 && words[0][prefix_len - 1] != '/')
+				prefix_len--;
+			use_copy = true;
+			XPinit(l, nwords + 1);
+			for (i = 0; i < nwords; i++)
+				XPput(l, words[i] + prefix_len);
+			XPput(l, NULL);
+		}
+	}
+	/*
+	 * Enumerate expansions
+	 */
+	x_putc('\r');
+	x_putc('\n');
+	pr_list(use_copy ? (char **)XPptrv(l) : words);
+
+	if (use_copy)
+		XPfree(l); /* not x_free_words() */
+}
+
+/**
+ * Do file globbing:
+ *	- appends * to (copy of) str if no globbing chars found
+ *	- does expansion, checks for no match, etc.
+ *	- sets *wordsp to array of matching strings
+ *	- returns number of matching strings
+ */
+static int
+x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp)
+{
+	char *toglob, **words;
+	int nwords, i, idx;
+	bool escaping;
+	XPtrV w;
+	struct source *s, *sold;
+
+	if (slen < 0)
+		return (0);
+
+	toglob = add_glob(str, slen);
+
+	/* remove all escaping backward slashes */
+	escaping = false;
+	for (i = 0, idx = 0; toglob[i]; i++) {
+		if (toglob[i] == '\\' && !escaping) {
+			escaping = true;
+			continue;
+		}
+		/* specially escape escaped [ or $ or ` for globbing */
+		if (escaping && (toglob[i] == '[' ||
+		    toglob[i] == '$' || toglob[i] == '`'))
+			toglob[idx++] = QCHAR;
+
+		toglob[idx] = toglob[i];
+		idx++;
+		if (escaping)
+			escaping = false;
+	}
+	toglob[idx] = '\0';
+
+	/*
+	 * Convert "foo*" (toglob) to an array of strings (words)
+	 */
+	sold = source;
+	s = pushs(SWSTR, ATEMP);
+	s->start = s->str = toglob;
+	source = s;
+	if (yylex(ONEWORD | LQCHAR) != LWORD) {
+		source = sold;
+		internal_warningf("fileglob: substitute error");
+		return (0);
+	}
+	source = sold;
+	XPinit(w, 32);
+	expand(yylval.cp, &w, DOGLOB | DOTILDE | DOMARKDIRS);
+	XPput(w, NULL);
+	words = (char **)XPclose(w);
+
+	for (nwords = 0; words[nwords]; nwords++)
+		;
+	if (nwords == 1) {
+		struct stat statb;
+
+		/* Check if globbing failed (returned glob pattern),
+		 * but be careful (E.g. toglob == "ab*" when the file
+		 * "ab*" exists is not an error).
+		 * Also, check for empty result - happens if we tried
+		 * to glob something which evaluated to an empty
+		 * string (e.g., "$FOO" when there is no FOO, etc).
+		 */
+		if ((strcmp(words[0], toglob) == 0 &&
+		    stat(words[0], &statb) < 0) ||
+		    words[0][0] == '\0') {
+			x_free_words(nwords, words);
+			words = NULL;
+			nwords = 0;
+		}
+	}
+	afree(toglob, ATEMP);
+
+	if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL)
+		x_free_words(nwords, words);
+
+	return (nwords);
+}
+
+/* Data structure used in x_command_glob() */
+struct path_order_info {
+	char *word;
+	int base;
+	int path_order;
+};
+
+/* Compare routine used in x_command_glob() */
+static int
+path_order_cmp(const void *aa, const void *bb)
+{
+	const struct path_order_info *a = (const struct path_order_info *)aa;
+	const struct path_order_info *b = (const struct path_order_info *)bb;
+	int t;
+
+	t = strcmp(a->word + a->base, b->word + b->base);
+	return (t ? t : a->path_order - b->path_order);
+}
+
+static int
+x_command_glob(int flags, const char *str, int slen, char ***wordsp)
+{
+	char *toglob, *pat, *fpath;
+	int nwords;
+	XPtrV w;
+	struct block *l;
+
+	if (slen < 0)
+		return (0);
+
+	toglob = add_glob(str, slen);
+
+	/* Convert "foo*" (toglob) to a pattern for future use */
+	pat = evalstr(toglob, DOPAT | DOTILDE);
+	afree(toglob, ATEMP);
+
+	XPinit(w, 32);
+
+	glob_table(pat, &w, &keywords);
+	glob_table(pat, &w, &aliases);
+	glob_table(pat, &w, &builtins);
+	for (l = e->loc; l; l = l->next)
+		glob_table(pat, &w, &l->funs);
+
+	glob_path(flags, pat, &w, path);
+	if ((fpath = str_val(global("FPATH"))) != null)
+		glob_path(flags, pat, &w, fpath);
+
+	nwords = XPsize(w);
+
+	if (!nwords) {
+		*wordsp = NULL;
+		XPfree(w);
+		return (0);
+	}
+	/* Sort entries */
+	if (flags & XCF_FULLPATH) {
+		/* Sort by basename, then path order */
+		struct path_order_info *info, *last_info = NULL;
+		char **words = (char **)XPptrv(w);
+		int i, path_order = 0;
+
+		info = (struct path_order_info *)
+		    alloc(nwords * sizeof(struct path_order_info), ATEMP);
+		for (i = 0; i < nwords; i++) {
+			info[i].word = words[i];
+			info[i].base = x_basename(words[i], NULL);
+			if (!last_info || info[i].base != last_info->base ||
+			    strncmp(words[i], last_info->word, info[i].base) != 0) {
+				last_info = &info[i];
+				path_order++;
+			}
+			info[i].path_order = path_order;
+		}
+		qsort(info, nwords, sizeof(struct path_order_info),
+		    path_order_cmp);
+		for (i = 0; i < nwords; i++)
+			words[i] = info[i].word;
+		afree(info, ATEMP);
+	} else {
+		/* Sort and remove duplicate entries */
+		char **words = (char **)XPptrv(w);
+		int i, j;
+
+		qsort(words, nwords, sizeof(void *), xstrcmp);
+		for (i = j = 0; i < nwords - 1; i++) {
+			if (strcmp(words[i], words[i + 1]))
+				words[j++] = words[i];
+			else
+				afree(words[i], ATEMP);
+		}
+		words[j++] = words[i];
+		nwords = j;
+		w.cur = (void **)&words[j];
+	}
+
+	XPput(w, NULL);
+	*wordsp = (char **)XPclose(w);
+
+	return (nwords);
+}
+
+#define IS_WORDC(c)	(!ctype(c, C_LEX1) && (c) != '\'' && (c) != '"' && \
+			    (c) != '`' && (c) != '=' && (c) != ':')
+
+static int
+x_locate_word(const char *buf, int buflen, int pos, int *startp,
+    bool *is_commandp)
+{
+	int start, end;
+
+	/* Bad call? Probably should report error */
+	if (pos < 0 || pos > buflen) {
+		*startp = pos;
+		*is_commandp = false;
+		return (0);
+	}
+	/* The case where pos == buflen happens to take care of itself... */
+
+	start = pos;
+	/* Keep going backwards to start of word (has effect of allowing
+	 * one blank after the end of a word)
+	 */
+	for (; (start > 0 && IS_WORDC(buf[start - 1])) ||
+	    (start > 1 && buf[start - 2] == '\\'); start--)
+		;
+	/* Go forwards to end of word */
+	for (end = start; end < buflen && IS_WORDC(buf[end]); end++) {
+		if (buf[end] == '\\' && (end + 1) < buflen)
+			end++;
+	}
+
+	if (is_commandp) {
+		bool iscmd;
+		int p = start - 1;
+
+		/* Figure out if this is a command */
+		while (p >= 0 && ksh_isspace(buf[p]))
+			p--;
+		iscmd = p < 0 || vstrchr(";|&()`", buf[p]);
+		if (iscmd) {
+			/* If command has a /, path, etc. is not searched;
+			 * only current directory is searched which is just
+			 * like file globbing.
+			 */
+			for (p = start; p < end; p++)
+				if (buf[p] == '/')
+					break;
+			iscmd = p == end;
+		}
+		*is_commandp = iscmd;
+	}
+	*startp = start;
+
+	return (end - start);
+}
+
+static int
+x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp,
+    int *endp, char ***wordsp, bool *is_commandp)
+{
+	int len, nwords;
+	char **words = NULL;
+	bool is_command;
+
+	len = x_locate_word(buf, buflen, pos, startp, &is_command);
+	if (!(flags & XCF_COMMAND))
+		is_command = false;
+	/* Don't do command globing on zero length strings - it takes too
+	 * long and isn't very useful. File globs are more likely to be
+	 * useful, so allow these.
+	 */
+	if (len == 0 && is_command)
+		return (0);
+
+	nwords = is_command ?
+	    x_command_glob(flags, buf + *startp, len, &words) :
+	    x_file_glob(flags, buf + *startp, len, &words);
+	if (nwords == 0) {
+		*wordsp = NULL;
+		return (0);
+	}
+	if (is_commandp)
+		*is_commandp = is_command;
+	*wordsp = words;
+	*endp = *startp + len;
+
+	return (nwords);
+}
+
+/* Given a string, copy it and possibly add a '*' to the end.
+ * The new string is returned.
+ */
+static char *
+add_glob(const char *str, int slen)
+{
+	char *toglob, *s;
+	bool saw_slash = false;
+
+	if (slen < 0)
+		return (NULL);
+
+	/* for clang's static analyser, the nonnull attribute isn't enough */
+	mkssert(str != NULL);
+
+	strndupx(toglob, str, slen + 1, ATEMP); /* + 1 for "*" */
+	toglob[slen] = '\0';
+
+	/*
+	 * If the pathname contains a wildcard (an unquoted '*',
+	 * '?', or '[') or parameter expansion ('$'), or a ~username
+	 * with no trailing slash, then it is globbed based on that
+	 * value (i.e., without the appended '*').
+	 */
+	for (s = toglob; *s; s++) {
+		if (*s == '\\' && s[1])
+			s++;
+		else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' ||
+		    (s[1] == '(' /*)*/ && /* *s in '*','?' already checked */
+		    (*s == '+' || *s == '@' || *s == '!')))
+			break;
+		else if (*s == '/')
+			saw_slash = true;
+	}
+	if (!*s && (*toglob != '~' || saw_slash)) {
+		toglob[slen] = '*';
+		toglob[slen + 1] = '\0';
+	}
+	return (toglob);
+}
+
+/*
+ * Find longest common prefix
+ */
+static int
+x_longest_prefix(int nwords, char * const * words)
+{
+	int i, j, prefix_len;
+	char *p;
+
+	if (nwords <= 0)
+		return (0);
+
+	prefix_len = strlen(words[0]);
+	for (i = 1; i < nwords; i++)
+		for (j = 0, p = words[i]; j < prefix_len; j++)
+			if (p[j] != words[0][j]) {
+				prefix_len = j;
+				break;
+			}
+	return (prefix_len);
+}
+
+static void
+x_free_words(int nwords, char **words)
+{
+	while (nwords)
+		afree(words[--nwords], ATEMP);
+	afree(words, ATEMP);
+}
+
+/* Return the offset of the basename of string s (which ends at se - need not
+ * be null terminated). Trailing slashes are ignored. If s is just a slash,
+ * then the offset is 0 (actually, length - 1).
+ *	s		Return
+ *	/etc		1
+ *	/etc/		1
+ *	/etc//		1
+ *	/etc/fo		5
+ *	foo		0
+ *	///		2
+ *			0
+ */
+static int
+x_basename(const char *s, const char *se)
+{
+	const char *p;
+
+	if (se == NULL)
+		se = s + strlen(s);
+	if (s == se)
+		return (0);
+
+	/* Skip trailing slashes */
+	for (p = se - 1; p > s && *p == '/'; p--)
+		;
+	for (; p > s && *p != '/'; p--)
+		;
+	if (*p == '/' && p + 1 < se)
+		p++;
+
+	return (p - s);
+}
+
+/*
+ * Apply pattern matching to a table: all table entries that match a pattern
+ * are added to wp.
+ */
+static void
+glob_table(const char *pat, XPtrV *wp, struct table *tp)
+{
+	struct tstate ts;
+	struct tbl *te;
+
+	ktwalk(&ts, tp);
+	while ((te = ktnext(&ts)))
+		if (gmatchx(te->name, pat, false)) {
+			char *cp;
+
+			strdupx(cp, te->name, ATEMP);
+			XPput(*wp, cp);
+		}
+}
+
+static void
+glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
+{
+	const char *sp, *p;
+	char *xp, **words;
+	int staterr, pathlen, patlen, oldsize, newsize, i, j;
+	XString xs;
+
+	patlen = strlen(pat) + 1;
+	sp = lpath;
+	Xinit(xs, xp, patlen + 128, ATEMP);
+	while (sp) {
+		xp = Xstring(xs, xp);
+		if (!(p = cstrchr(sp, ':')))
+			p = sp + strlen(sp);
+		pathlen = p - sp;
+		if (pathlen) {
+			/* Copy sp into xp, stuffing any MAGIC characters
+			 * on the way
+			 */
+			const char *s = sp;
+
+			XcheckN(xs, xp, pathlen * 2);
+			while (s < p) {
+				if (ISMAGIC(*s))
+					*xp++ = MAGIC;
+				*xp++ = *s++;
+			}
+			*xp++ = '/';
+			pathlen++;
+		}
+		sp = p;
+		XcheckN(xs, xp, patlen);
+		memcpy(xp, pat, patlen);
+
+		oldsize = XPsize(*wp);
+		glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */
+		newsize = XPsize(*wp);
+
+		/* Check that each match is executable... */
+		words = (char **)XPptrv(*wp);
+		for (i = j = oldsize; i < newsize; i++) {
+			staterr = 0;
+			if ((search_access(words[i], X_OK, &staterr) >= 0) ||
+			    (staterr == EISDIR)) {
+				words[j] = words[i];
+				if (!(flags & XCF_FULLPATH))
+					memmove(words[j], words[j] + pathlen,
+					    strlen(words[j] + pathlen) + 1);
+				j++;
+			} else
+				afree(words[i], ATEMP);
+		}
+		wp->cur = (void **)&words[j];
+
+		if (!*sp++)
+			break;
+	}
+	Xfree(xs, xp);
+}
+
+/*
+ * if argument string contains any special characters, they will
+ * be escaped and the result will be put into edit buffer by
+ * keybinding-specific function
+ */
+static int
+x_escape(const char *s, size_t len, int (*putbuf_func)(const char *, size_t))
+{
+	size_t add = 0, wlen = len;
+	const char *ifs = str_val(local("IFS", 0));
+	int rval = 0;
+
+	while (wlen - add > 0)
+		if (vstrchr("\"#$&'()*:;<=>?[\\`{|}", s[add]) ||
+		    vstrchr(ifs, s[add])) {
+			if (putbuf_func(s, add) != 0) {
+				rval = -1;
+				break;
+			}
+			putbuf_func(s[add] == '\n' ? "'" : "\\", 1);
+			putbuf_func(&s[add], 1);
+			if (s[add] == '\n')
+				putbuf_func("'", 1);
+
+			add++;
+			wlen -= add;
+			s += add;
+			add = 0;
+		} else
+			++add;
+	if (wlen > 0 && rval == 0)
+		rval = putbuf_func(s, wlen);
+
+	return (rval);
+}
+
+
+/* +++ emacs editing mode +++ */
+
+static	Area	aedit;
+#define	AEDIT	&aedit		/* area for kill ring and macro defns */
+
+/* values returned by keyboard functions */
+#define	KSTD	0
+#define	KEOL	1		/* ^M, ^J */
+#define	KINTR	2		/* ^G, ^C */
+
+struct x_ftab {
+	int (*xf_func)(int c);
+	const char *xf_name;
+	short xf_flags;
+};
+
+struct x_defbindings {
+	unsigned char xdb_func;	/* XFUNC_* */
+	unsigned char xdb_tab;
+	unsigned char xdb_char;
+};
+
+#define XF_ARG		1	/* command takes number prefix */
+#define	XF_NOBIND	2	/* not allowed to bind to function */
+#define	XF_PREFIX	4	/* function sets prefix */
+
+/* Separator for completion */
+#define	is_cfs(c)	((c) == ' ' || (c) == '\t' || (c) == '"' || (c) == '\'')
+/* Separator for motion */
+#define	is_mfs(c)	(!(ksh_isalnux(c) || (c) == '$' || ((c) & 0x80)))
+
+#define X_NTABS		3			/* normal, meta1, meta2 */
+#define X_TABSZ		256			/* size of keydef tables etc */
+
+/* Arguments for do_complete()
+ * 0 = enumerate	M-=	complete as much as possible and then list
+ * 1 = complete		M-Esc
+ * 2 = list		M-?
+ */
+typedef enum {
+	CT_LIST,	/* list the possible completions */
+	CT_COMPLETE,	/* complete to longest prefix */
+	CT_COMPLIST	/* complete and then list (if non-exact) */
+} Comp_type;
+
+/*
+ * The following are used for my horizontal scrolling stuff
+ */
+static char *xbuf;		/* beg input buffer */
+static char *xend;		/* end input buffer */
+static char *xcp;		/* current position */
+static char *xep;		/* current end */
+static char *xbp;		/* start of visible portion of input buffer */
+static char *xlp;		/* last char visible on screen */
+static int x_adj_ok;
+/*
+ * we use x_adj_done so that functions can tell
+ * whether x_adjust() has been called while they are active.
+ */
+static int x_adj_done;
+
+static int x_col;
+static int x_displen;
+static int x_arg;		/* general purpose arg */
+static int x_arg_defaulted;	/* x_arg not explicitly set; defaulted to 1 */
+
+static int xlp_valid;
+
+static char **x_histp;		/* history position */
+static int x_nextcmd;		/* for newline-and-next */
+static char *xmp;		/* mark pointer */
+static unsigned char x_last_command;
+static unsigned char (*x_tab)[X_TABSZ];	/* key definition */
+#ifndef MKSH_SMALL
+static char *(*x_atab)[X_TABSZ];	/* macro definitions */
+#endif
+static unsigned char x_bound[(X_TABSZ * X_NTABS + 7) / 8];
+#define KILLSIZE	20
+static char *killstack[KILLSIZE];
+static int killsp, killtp;
+static int x_curprefix;
+#ifndef MKSH_SMALL
+static char *macroptr = NULL;	/* bind key macro active? */
+#endif
+#if !MKSH_S_NOVI
+static int cur_col;		/* current column on line */
+static int pwidth;		/* width of prompt */
+static int prompt_trunc;	/* how much of prompt to truncate */
+static int winwidth;		/* width of window */
+static char *wbuf[2];		/* window buffers */
+static int wbuf_len;		/* length of window buffers (x_cols - 3) */
+static int win;			/* window buffer in use */
+static char morec;		/* more character at right of window */
+static int lastref;		/* argument to last refresh() */
+static int holdlen;		/* length of holdbuf */
+#endif
+static int prompt_redraw;	/* 0 if newline forced after prompt */
+
+static int x_ins(const char *);
+static void x_delete(int, int);
+static int x_bword(void);
+static int x_fword(int);
+static void x_goto(char *);
+static void x_bs3(char **);
+static int x_size_str(char *);
+static int x_size2(char *, char **);
+static void x_zots(char *);
+static void x_zotc2(int);
+static void x_zotc3(char **);
+static void x_load_hist(char **);
+static int x_search(char *, int, int);
+#ifndef MKSH_SMALL
+static int x_search_dir(int);
+#endif
+static int x_match(char *, char *);
+static void x_redraw(int);
+static void x_push(int);
+static char *x_mapin(const char *, Area *)
+    MKSH_A_NONNULL((nonnull (1)));
+static char *x_mapout(int);
+static void x_mapout2(int, char **);
+static void x_print(int, int);
+static void x_adjust(void);
+static void x_e_ungetc(int);
+static int x_e_getc(void);
+static void x_e_putc2(int);
+static void x_e_putc3(const char **);
+static void x_e_puts(const char *);
+#ifndef MKSH_SMALL
+static int x_fold_case(int);
+#endif
+static char *x_lastcp(void);
+static void do_complete(int, Comp_type);
+
+static int unget_char = -1;
+
+static int x_do_ins(const char *, size_t);
+static void bind_if_not_bound(int, int, int);
+
+enum emacs_funcs {
+#define EMACSFN_ENUMS
+#include "emacsfn.h"
+	XFUNC_MAX
+};
+
+#define EMACSFN_DEFNS
+#include "emacsfn.h"
+
+static const struct x_ftab x_ftab[] = {
+#define EMACSFN_ITEMS
+#include "emacsfn.h"
+	{ 0, NULL, 0 }
+};
+
+static struct x_defbindings const x_defbindings[] = {
+	{ XFUNC_del_back,		0, CTRL('?')	},
+	{ XFUNC_del_bword,		1, CTRL('?')	},
+	{ XFUNC_eot_del,		0, CTRL('D')	},
+	{ XFUNC_del_back,		0, CTRL('H')	},
+	{ XFUNC_del_bword,		1, CTRL('H')	},
+	{ XFUNC_del_bword,		1,	'h'	},
+	{ XFUNC_mv_bword,		1,	'b'	},
+	{ XFUNC_mv_fword,		1,	'f'	},
+	{ XFUNC_del_fword,		1,	'd'	},
+	{ XFUNC_mv_back,		0, CTRL('B')	},
+	{ XFUNC_mv_forw,		0, CTRL('F')	},
+	{ XFUNC_search_char_forw,	0, CTRL(']')	},
+	{ XFUNC_search_char_back,	1, CTRL(']')	},
+	{ XFUNC_newline,		0, CTRL('M')	},
+	{ XFUNC_newline,		0, CTRL('J')	},
+	{ XFUNC_end_of_text,		0, CTRL('_')	},
+	{ XFUNC_abort,			0, CTRL('G')	},
+	{ XFUNC_prev_com,		0, CTRL('P')	},
+	{ XFUNC_next_com,		0, CTRL('N')	},
+	{ XFUNC_nl_next_com,		0, CTRL('O')	},
+	{ XFUNC_search_hist,		0, CTRL('R')	},
+	{ XFUNC_beg_hist,		1,	'<'	},
+	{ XFUNC_end_hist,		1,	'>'	},
+	{ XFUNC_goto_hist,		1,	'g'	},
+	{ XFUNC_mv_end,			0, CTRL('E')	},
+	{ XFUNC_mv_begin,		0, CTRL('A')	},
+	{ XFUNC_draw_line,		0, CTRL('L')	},
+	{ XFUNC_cls,			1, CTRL('L')	},
+	{ XFUNC_meta1,			0, CTRL('[')	},
+	{ XFUNC_meta2,			0, CTRL('X')	},
+	{ XFUNC_kill,			0, CTRL('K')	},
+	{ XFUNC_yank,			0, CTRL('Y')	},
+	{ XFUNC_meta_yank,		1,	'y'	},
+	{ XFUNC_literal,		0, CTRL('^')	},
+	{ XFUNC_comment,		1,	'#'	},
+	{ XFUNC_transpose,		0, CTRL('T')	},
+	{ XFUNC_complete,		1, CTRL('[')	},
+	{ XFUNC_comp_list,		0, CTRL('I')	},
+	{ XFUNC_comp_list,		1,	'='	},
+	{ XFUNC_enumerate,		1,	'?'	},
+	{ XFUNC_expand,			1,	'*'	},
+	{ XFUNC_comp_file,		1, CTRL('X')	},
+	{ XFUNC_comp_comm,		2, CTRL('[')	},
+	{ XFUNC_list_comm,		2,	'?'	},
+	{ XFUNC_list_file,		2, CTRL('Y')	},
+	{ XFUNC_set_mark,		1,	' '	},
+	{ XFUNC_kill_region,		0, CTRL('W')	},
+	{ XFUNC_xchg_point_mark,	2, CTRL('X')	},
+	{ XFUNC_literal,		0, CTRL('V')	},
+	{ XFUNC_version,		1, CTRL('V')	},
+	{ XFUNC_prev_histword,		1,	'.'	},
+	{ XFUNC_prev_histword,		1,	'_'	},
+	{ XFUNC_set_arg,		1,	'0'	},
+	{ XFUNC_set_arg,		1,	'1'	},
+	{ XFUNC_set_arg,		1,	'2'	},
+	{ XFUNC_set_arg,		1,	'3'	},
+	{ XFUNC_set_arg,		1,	'4'	},
+	{ XFUNC_set_arg,		1,	'5'	},
+	{ XFUNC_set_arg,		1,	'6'	},
+	{ XFUNC_set_arg,		1,	'7'	},
+	{ XFUNC_set_arg,		1,	'8'	},
+	{ XFUNC_set_arg,		1,	'9'	},
+#ifndef MKSH_SMALL
+	{ XFUNC_fold_upper,		1,	'U'	},
+	{ XFUNC_fold_upper,		1,	'u'	},
+	{ XFUNC_fold_lower,		1,	'L'	},
+	{ XFUNC_fold_lower,		1,	'l'	},
+	{ XFUNC_fold_capitalise,	1,	'C'	},
+	{ XFUNC_fold_capitalise,	1,	'c'	},
+#endif
+	/* These for ansi arrow keys: arguablely shouldn't be here by
+	 * default, but its simpler/faster/smaller than using termcap
+	 * entries.
+	 */
+	{ XFUNC_meta2,			1,	'['	},
+	{ XFUNC_meta2,			1,	'O'	},
+	{ XFUNC_prev_com,		2,	'A'	},
+	{ XFUNC_next_com,		2,	'B'	},
+	{ XFUNC_mv_forw,		2,	'C'	},
+	{ XFUNC_mv_back,		2,	'D'	},
+#ifndef MKSH_SMALL
+	{ XFUNC_vt_hack,		2,	'1'	},
+	{ XFUNC_mv_begin | 0x80,	2,	'7'	},
+	{ XFUNC_mv_begin,		2,	'H'	},
+	{ XFUNC_mv_end | 0x80,		2,	'4'	},
+	{ XFUNC_mv_end | 0x80,		2,	'8'	},
+	{ XFUNC_mv_end,			2,	'F'	},
+	{ XFUNC_del_char | 0x80,	2,	'3'	},
+	{ XFUNC_search_hist_up | 0x80,	2,	'5'	},
+	{ XFUNC_search_hist_dn | 0x80,	2,	'6'	},
+	/* more non-standard ones */
+	{ XFUNC_edit_line,		2,	'e'	}
+#endif
+};
+
+#ifdef MKSH_SMALL
+static void x_modified(void);
+static void
+x_modified(void)
+{
+	if (!modified) {
+		x_histp = histptr + 1;
+		modified = 1;
+	}
+}
+#define XFUNC_VALUE(f) (f)
+#else
+#define x_modified() do {			\
+	if (!modified) {			\
+		x_histp = histptr + 1;		\
+		modified = 1;			\
+	}					\
+} while (/* CONSTCOND */ 0)
+#define XFUNC_VALUE(f) (f & 0x7F)
+#endif
+
+static int
+x_e_getmbc(char *sbuf)
+{
+	int c, pos = 0;
+	unsigned char *buf = (unsigned char *)sbuf;
+
+	memset(buf, 0, 4);
+	buf[pos++] = c = x_e_getc();
+	if (c == -1)
+		return (-1);
+	if (UTFMODE) {
+		if ((buf[0] >= 0xC2) && (buf[0] < 0xF0)) {
+			c = x_e_getc();
+			if (c == -1)
+				return (-1);
+			if ((c & 0xC0) != 0x80) {
+				x_e_ungetc(c);
+				return (1);
+			}
+			buf[pos++] = c;
+		}
+		if ((buf[0] >= 0xE0) && (buf[0] < 0xF0)) {
+			/* XXX x_e_ungetc is one-octet only */
+			buf[pos++] = c = x_e_getc();
+			if (c == -1)
+				return (-1);
+		}
+	}
+	return (pos);
+}
+
+static void
+x_init_prompt(void)
+{
+	x_col = promptlen(prompt);
+	x_adj_ok = 1;
+	prompt_redraw = 1;
+	if (x_col >= xx_cols)
+		x_col %= xx_cols;
+	x_displen = xx_cols - 2 - x_col;
+	x_adj_done = 0;
+
+	pprompt(prompt, 0);
+	if (x_displen < 1) {
+		x_col = 0;
+		x_displen = xx_cols - 2;
+		x_e_putc2('\n');
+		prompt_redraw = 0;
+	}
+}
+
+static int
+x_emacs(char *buf, size_t len)
+{
+	int c, i;
+	unsigned char f;
+
+	xbp = xbuf = buf; xend = buf + len;
+	xlp = xcp = xep = buf;
+	*xcp = 0;
+	xlp_valid = true;
+	xmp = NULL;
+	x_curprefix = 0;
+	x_histp = histptr + 1;
+	x_last_command = XFUNC_error;
+
+	xx_cols = x_cols;
+	x_init_prompt();
+
+	if (x_nextcmd >= 0) {
+		int off = source->line - x_nextcmd;
+		if (histptr - history >= off)
+			x_load_hist(histptr - off);
+		x_nextcmd = -1;
+	}
+	editmode = 1;
+	while (1) {
+		x_flush();
+		if ((c = x_e_getc()) < 0)
+			return (0);
+
+		f = x_curprefix == -1 ? XFUNC_insert :
+		    x_tab[x_curprefix][c];
+#ifndef MKSH_SMALL
+		if (f & 0x80) {
+			f &= 0x7F;
+			if ((i = x_e_getc()) != '~')
+				x_e_ungetc(i);
+		}
+
+		/* avoid bind key macro recursion */
+		if (macroptr && f == XFUNC_ins_string)
+			f = XFUNC_insert;
+#endif
+
+		if (!(x_ftab[f].xf_flags & XF_PREFIX) &&
+		    x_last_command != XFUNC_set_arg) {
+			x_arg = 1;
+			x_arg_defaulted = 1;
+		}
+		i = c | (x_curprefix << 8);
+		x_curprefix = 0;
+		switch ((*x_ftab[f].xf_func)(i)) {
+		case KSTD:
+			if (!(x_ftab[f].xf_flags & XF_PREFIX))
+				x_last_command = f;
+			break;
+		case KEOL:
+			i = xep - xbuf;
+			return (i);
+		case KINTR:	/* special case for interrupt */
+			trapsig(SIGINT);
+			x_mode(false);
+			unwind(LSHELL);
+		}
+		/* ad-hoc hack for fixing the cursor position */
+		x_goto(xcp);
+	}
+}
+
+static int
+x_insert(int c)
+{
+	static int left = 0, pos, save_arg;
+	static char str[4];
+
+	/*
+	 * Should allow tab and control chars.
+	 */
+	if (c == 0) {
+ invmbs:
+		left = 0;
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	if (UTFMODE) {
+		if (((c & 0xC0) == 0x80) && left) {
+			str[pos++] = c;
+			if (!--left) {
+				str[pos] = '\0';
+				x_arg = save_arg;
+				while (x_arg--)
+					x_ins(str);
+			}
+			return (KSTD);
+		}
+		if (left) {
+			if (x_curprefix == -1) {
+				/* flush invalid multibyte */
+				str[pos] = '\0';
+				while (save_arg--)
+					x_ins(str);
+			}
+		}
+		if ((c >= 0xC2) && (c < 0xE0))
+			left = 1;
+		else if ((c >= 0xE0) && (c < 0xF0))
+			left = 2;
+		else if (c > 0x7F)
+			goto invmbs;
+		else
+			left = 0;
+		if (left) {
+			save_arg = x_arg;
+			pos = 1;
+			str[0] = c;
+			return (KSTD);
+		}
+	}
+	left = 0;
+	str[0] = c;
+	str[1] = '\0';
+	while (x_arg--)
+		x_ins(str);
+	return (KSTD);
+}
+
+#ifndef MKSH_SMALL
+static int
+x_ins_string(int c)
+{
+	macroptr = x_atab[c >> 8][c & 255];
+	/*
+	 * we no longer need to bother checking if macroptr is
+	 * not NULL but first char is NUL; x_e_getc() does it
+	 */
+	return (KSTD);
+}
+#endif
+
+static int
+x_do_ins(const char *cp, size_t len)
+{
+	if (xep + len >= xend) {
+		x_e_putc2(7);
+		return (-1);
+	}
+	memmove(xcp + len, xcp, xep - xcp + 1);
+	memmove(xcp, cp, len);
+	xcp += len;
+	xep += len;
+	x_modified();
+	return (0);
+}
+
+static int
+x_ins(const char *s)
+{
+	char *cp = xcp;
+	int adj = x_adj_done;
+
+	if (x_do_ins(s, strlen(s)) < 0)
+		return (-1);
+	/*
+	 * x_zots() may result in a call to x_adjust()
+	 * we want xcp to reflect the new position.
+	 */
+	xlp_valid = false;
+	x_lastcp();
+	x_adj_ok = (xcp >= xlp);
+	x_zots(cp);
+	if (adj == x_adj_done) {	/* has x_adjust() been called? */
+		/* no */
+		cp = xlp;
+		while (cp > xcp)
+			x_bs3(&cp);
+	}
+	if (xlp == xep - 1)
+		x_redraw(xx_cols);
+	x_adj_ok = 1;
+	return (0);
+}
+
+static int
+x_del_back(int c MKSH_A_UNUSED)
+{
+	int i = 0;
+
+	if (xcp == xbuf) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	do {
+		x_goto(xcp - 1);
+	} while ((++i < x_arg) && (xcp != xbuf));
+	x_delete(i, false);
+	return (KSTD);
+}
+
+static int
+x_del_char(int c MKSH_A_UNUSED)
+{
+	char *cp, *cp2;
+	int i = 0;
+
+	cp = xcp;
+	while (i < x_arg) {
+		utf_ptradjx(cp, cp2);
+		if (cp2 > xep)
+			break;
+		cp = cp2;
+		i++;
+	}
+
+	if (!i) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	x_delete(i, false);
+	return (KSTD);
+}
+
+/* Delete nc chars to the right of the cursor (including cursor position) */
+static void
+x_delete(int nc, int push)
+{
+	int i, nb, nw;
+	char *cp;
+
+	if (nc == 0)
+		return;
+
+	nw = 0;
+	cp = xcp;
+	for (i = 0; i < nc; ++i) {
+		char *cp2;
+		int j;
+
+		j = x_size2(cp, &cp2);
+		if (cp2 > xep)
+			break;
+		cp = cp2;
+		nw += j;
+	}
+	nb = cp - xcp;
+	/* nc = i; */
+
+	if (xmp != NULL && xmp > xcp) {
+		if (xcp + nb > xmp)
+			xmp = xcp;
+		else
+			xmp -= nb;
+	}
+	/*
+	 * This lets us yank a word we have deleted.
+	 */
+	if (push)
+		x_push(nb);
+
+	xep -= nb;
+	memmove(xcp, xcp + nb, xep - xcp + 1);	/* Copies the NUL */
+	x_adj_ok = 0;			/* don't redraw */
+	xlp_valid = false;
+	x_zots(xcp);
+	/*
+	 * if we are already filling the line,
+	 * there is no need to ' ','\b'.
+	 * But if we must, make sure we do the minimum.
+	 */
+	if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) {
+		nw = i = (nw < i) ? nw : i;
+		while (i--)
+			x_e_putc2(' ');
+		if (x_col == xx_cols - 2) {
+			x_e_putc2((xep > xlp) ? '>' : (xbp > xbuf) ? '<' : ' ');
+			++nw;
+		}
+		while (nw--)
+			x_e_putc2('\b');
+	}
+	/*x_goto(xcp);*/
+	x_adj_ok = 1;
+	xlp_valid = false;
+	cp = x_lastcp();
+	while (cp > xcp)
+		x_bs3(&cp);
+
+	x_modified();
+	return;
+}
+
+static int
+x_del_bword(int c MKSH_A_UNUSED)
+{
+	x_delete(x_bword(), true);
+	return (KSTD);
+}
+
+static int
+x_mv_bword(int c MKSH_A_UNUSED)
+{
+	x_bword();
+	return (KSTD);
+}
+
+static int
+x_mv_fword(int c MKSH_A_UNUSED)
+{
+	x_fword(1);
+	return (KSTD);
+}
+
+static int
+x_del_fword(int c MKSH_A_UNUSED)
+{
+	x_delete(x_fword(0), true);
+	return (KSTD);
+}
+
+static int
+x_bword(void)
+{
+	int nc = 0, nb = 0;
+	char *cp = xcp;
+
+	if (cp == xbuf) {
+		x_e_putc2(7);
+		return (0);
+	}
+	while (x_arg--) {
+		while (cp != xbuf && is_mfs(cp[-1])) {
+			cp--;
+			nb++;
+		}
+		while (cp != xbuf && !is_mfs(cp[-1])) {
+			cp--;
+			nb++;
+		}
+	}
+	x_goto(cp);
+	for (cp = xcp; cp < (xcp + nb); ++nc)
+		cp += utf_ptradj(cp);
+	return (nc);
+}
+
+static int
+x_fword(int move)
+{
+	int nc = 0;
+	char *cp = xcp, *cp2;
+
+	if (cp == xep) {
+		x_e_putc2(7);
+		return (0);
+	}
+	while (x_arg--) {
+		while (cp != xep && is_mfs(*cp))
+			cp++;
+		while (cp != xep && !is_mfs(*cp))
+			cp++;
+	}
+	for (cp2 = xcp; cp2 < cp; ++nc)
+		cp2 += utf_ptradj(cp2);
+	if (move)
+		x_goto(cp);
+	return (nc);
+}
+
+static void
+x_goto(char *cp)
+{
+	if (UTFMODE)
+		while ((cp > xbuf) && ((*cp & 0xC0) == 0x80))
+			--cp;
+	if (cp < xbp || cp >= utf_skipcols(xbp, x_displen)) {
+		/* we are heading off screen */
+		xcp = cp;
+		x_adjust();
+	} else if (cp < xcp) {		/* move back */
+		while (cp < xcp)
+			x_bs3(&xcp);
+	} else if (cp > xcp) {		/* move forward */
+		while (cp > xcp)
+			x_zotc3(&xcp);
+	}
+}
+
+static void
+x_bs3(char **p)
+{
+	int i;
+
+	(*p)--;
+	if (UTFMODE)
+		while (((unsigned char)**p & 0xC0) == 0x80)
+			(*p)--;
+
+	i = x_size2(*p, NULL);
+	while (i--)
+		x_e_putc2('\b');
+}
+
+static int
+x_size_str(char *cp)
+{
+	int size = 0;
+	while (*cp)
+		size += x_size2(cp, &cp);
+	return (size);
+}
+
+static int
+x_size2(char *cp, char **dcp)
+{
+	int c = *(unsigned char *)cp;
+
+	if (UTFMODE && (c > 0x7F))
+		return (utf_widthadj(cp, (const char **)dcp));
+	if (dcp)
+		*dcp = cp + 1;
+	if (c == '\t')
+		return (4);	/* Kludge, tabs are always four spaces. */
+	if (c < ' ' || c == 0x7f)
+		return (2);	/* control unsigned char */
+	return (1);
+}
+
+static void
+x_zots(char *str)
+{
+	int adj = x_adj_done;
+
+	x_lastcp();
+	while (*str && str < xlp && adj == x_adj_done)
+		x_zotc3(&str);
+}
+
+static void
+x_zotc2(int c)
+{
+	if (c == '\t') {
+		/* Kludge, tabs are always four spaces. */
+		x_e_puts("    ");
+	} else if (c < ' ' || c == 0x7f) {
+		x_e_putc2('^');
+		x_e_putc2(UNCTRL(c));
+	} else
+		x_e_putc2(c);
+}
+
+static void
+x_zotc3(char **cp)
+{
+	unsigned char c = **(unsigned char **)cp;
+
+	if (c == '\t') {
+		/* Kludge, tabs are always four spaces. */
+		x_e_puts("    ");
+		(*cp)++;
+	} else if (c < ' ' || c == 0x7f) {
+		x_e_putc2('^');
+		x_e_putc2(UNCTRL(c));
+		(*cp)++;
+	} else
+		x_e_putc3((const char **)cp);
+}
+
+static int
+x_mv_back(int c MKSH_A_UNUSED)
+{
+	if (xcp == xbuf) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	while (x_arg--) {
+		x_goto(xcp - 1);
+		if (xcp == xbuf)
+			break;
+	}
+	return (KSTD);
+}
+
+static int
+x_mv_forw(int c MKSH_A_UNUSED)
+{
+	char *cp = xcp, *cp2;
+
+	if (xcp == xep) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	while (x_arg--) {
+		utf_ptradjx(cp, cp2);
+		if (cp2 > xep)
+			break;
+		cp = cp2;
+	}
+	x_goto(cp);
+	return (KSTD);
+}
+
+static int
+x_search_char_forw(int c MKSH_A_UNUSED)
+{
+	char *cp = xcp;
+	char tmp[4];
+
+	*xep = '\0';
+	if (x_e_getmbc(tmp) < 0) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	while (x_arg--) {
+		if ((cp = (cp == xep) ? NULL : strstr(cp + 1, tmp)) == NULL &&
+		    (cp = strstr(xbuf, tmp)) == NULL) {
+			x_e_putc2(7);
+			return (KSTD);
+		}
+	}
+	x_goto(cp);
+	return (KSTD);
+}
+
+static int
+x_search_char_back(int c MKSH_A_UNUSED)
+{
+	char *cp = xcp, *p, tmp[4];
+	bool b;
+
+	if (x_e_getmbc(tmp) < 0) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	for (; x_arg--; cp = p)
+		for (p = cp; ; ) {
+			if (p-- == xbuf)
+				p = xep;
+			if (p == cp) {
+				x_e_putc2(7);
+				return (KSTD);
+			}
+			if ((tmp[1] && ((p+1) > xep)) ||
+			    (tmp[2] && ((p+2) > xep)))
+				continue;
+			b = true;
+			if (*p != tmp[0])
+				b = false;
+			if (b && tmp[1] && p[1] != tmp[1])
+				b = false;
+			if (b && tmp[2] && p[2] != tmp[2])
+				b = false;
+			if (b)
+				break;
+		}
+	x_goto(cp);
+	return (KSTD);
+}
+
+static int
+x_newline(int c MKSH_A_UNUSED)
+{
+	x_e_putc2('\r');
+	x_e_putc2('\n');
+	x_flush();
+	*xep++ = '\n';
+	return (KEOL);
+}
+
+static int
+x_end_of_text(int c MKSH_A_UNUSED)
+{
+	x_zotc2(edchars.eof);
+	x_putc('\r');
+	x_putc('\n');
+	x_flush();
+	return (KEOL);
+}
+
+static int
+x_beg_hist(int c MKSH_A_UNUSED)
+{
+	x_load_hist(history);
+	return (KSTD);
+}
+
+static int
+x_end_hist(int c MKSH_A_UNUSED)
+{
+	x_load_hist(histptr);
+	return (KSTD);
+}
+
+static int
+x_prev_com(int c MKSH_A_UNUSED)
+{
+	x_load_hist(x_histp - x_arg);
+	return (KSTD);
+}
+
+static int
+x_next_com(int c MKSH_A_UNUSED)
+{
+	x_load_hist(x_histp + x_arg);
+	return (KSTD);
+}
+
+/* Goto a particular history number obtained from argument.
+ * If no argument is given history 1 is probably not what you
+ * want so we'll simply go to the oldest one.
+ */
+static int
+x_goto_hist(int c MKSH_A_UNUSED)
+{
+	if (x_arg_defaulted)
+		x_load_hist(history);
+	else
+		x_load_hist(histptr + x_arg - source->line);
+	return (KSTD);
+}
+
+static void
+x_load_hist(char **hp)
+{
+	int oldsize;
+	char *sp = NULL;
+
+	if (hp == histptr + 1) {
+		sp = holdbuf;
+		modified = 0;
+	} else if (hp < history || hp > histptr) {
+		x_e_putc2(7);
+		return;
+	}
+	if (sp == NULL)
+		sp = *hp;
+	x_histp = hp;
+	oldsize = x_size_str(xbuf);
+	if (modified)
+		strlcpy(holdbuf, xbuf, sizeof(holdbuf));
+	strlcpy(xbuf, sp, xend - xbuf);
+	xbp = xbuf;
+	xep = xcp = xbuf + strlen(xbuf);
+	xlp_valid = false;
+	if (xep <= x_lastcp()) {
+		x_redraw(oldsize);
+	}
+	x_goto(xep);
+	modified = 0;
+}
+
+static int
+x_nl_next_com(int c MKSH_A_UNUSED)
+{
+	x_nextcmd = source->line - (histptr - x_histp) + 1;
+	return (x_newline('\n'));
+}
+
+static int
+x_eot_del(int c)
+{
+	if (xep == xbuf && x_arg_defaulted)
+		return (x_end_of_text(c));
+	else
+		return (x_del_char(c));
+}
+
+/* reverse incremental history search */
+static int
+x_search_hist(int c)
+{
+	int offset = -1;	/* offset of match in xbuf, else -1 */
+	char pat[256 + 1];	/* pattern buffer */
+	char *p = pat;
+	unsigned char f;
+
+	*p = '\0';
+	while (1) {
+		if (offset < 0) {
+			x_e_puts("\nI-search: ");
+			x_e_puts(pat);
+		}
+		x_flush();
+		if ((c = x_e_getc()) < 0)
+			return (KSTD);
+		f = x_tab[0][c];
+		if (c == CTRL('[')) {
+			if ((f & 0x7F) == XFUNC_meta1) {
+				if ((c = x_e_getc()) < 0)
+					return (KSTD);
+				f = x_tab[1][c] & 0x7F;
+				if (f == XFUNC_meta1 || f == XFUNC_meta2)
+					x_meta1(CTRL('['));
+				x_e_ungetc(c);
+			}
+			break;
+		}
+#ifndef MKSH_SMALL
+		if (f & 0x80) {
+			f &= 0x7F;
+			if ((c = x_e_getc()) != '~')
+				x_e_ungetc(c);
+		}
+#endif
+		if (f == XFUNC_search_hist)
+			offset = x_search(pat, 0, offset);
+		else if (f == XFUNC_del_back) {
+			if (p == pat) {
+				offset = -1;
+				break;
+			}
+			if (p > pat)
+				*--p = '\0';
+			if (p == pat)
+				offset = -1;
+			else
+				offset = x_search(pat, 1, offset);
+			continue;
+		} else if (f == XFUNC_insert) {
+			/* add char to pattern */
+			/* overflow check... */
+			if (p >= &pat[sizeof(pat) - 1]) {
+				x_e_putc2(7);
+				continue;
+			}
+			*p++ = c, *p = '\0';
+			if (offset >= 0) {
+				/* already have partial match */
+				offset = x_match(xbuf, pat);
+				if (offset >= 0) {
+					x_goto(xbuf + offset + (p - pat) -
+					    (*pat == '^'));
+					continue;
+				}
+			}
+			offset = x_search(pat, 0, offset);
+		} else if (f == XFUNC_abort) {
+			if (offset >= 0)
+				x_load_hist(histptr + 1);
+			break;
+		} else { /* other command */
+			x_e_ungetc(c);
+			break;
+		}
+	}
+	if (offset < 0)
+		x_redraw(-1);
+	return (KSTD);
+}
+
+/* search backward from current line */
+static int
+x_search(char *pat, int sameline, int offset)
+{
+	char **hp;
+	int i;
+
+	for (hp = x_histp - (sameline ? 0 : 1); hp >= history; --hp) {
+		i = x_match(*hp, pat);
+		if (i >= 0) {
+			if (offset < 0)
+				x_e_putc2('\n');
+			x_load_hist(hp);
+			x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
+			return (i);
+		}
+	}
+	x_e_putc2(7);
+	x_histp = histptr;
+	return (-1);
+}
+
+#ifndef MKSH_SMALL
+/* anchored search up from current line */
+static int
+x_search_hist_up(int c MKSH_A_UNUSED)
+{
+	return (x_search_dir(-1));
+}
+
+/* anchored search down from current line */
+static int
+x_search_hist_dn(int c MKSH_A_UNUSED)
+{
+	return (x_search_dir(1));
+}
+
+/* anchored search in the indicated direction */
+static int
+x_search_dir(int search_dir /* should've been bool */)
+{
+	char **hp = x_histp + search_dir;
+	size_t curs = xcp - xbuf;
+
+	while (histptr >= hp && hp >= history) {
+		if (strncmp(xbuf, *hp, curs) == 0) {
+			x_load_hist(hp);
+			x_goto(xbuf + curs);
+			break;
+		}
+		hp += search_dir;
+	}
+	return (KSTD);
+}
+#endif
+
+/* return position of first match of pattern in string, else -1 */
+static int
+x_match(char *str, char *pat)
+{
+	if (*pat == '^') {
+		return ((strncmp(str, pat + 1, strlen(pat + 1)) == 0) ? 0 : -1);
+	} else {
+		char *q = strstr(str, pat);
+		return ((q == NULL) ? -1 : q - str);
+	}
+}
+
+static int
+x_del_line(int c MKSH_A_UNUSED)
+{
+	int i, j;
+
+	*xep = 0;
+	i = xep - xbuf;
+	j = x_size_str(xbuf);
+	xcp = xbuf;
+	x_push(i);
+	xlp = xbp = xep = xbuf;
+	xlp_valid = true;
+	*xcp = 0;
+	xmp = NULL;
+	x_redraw(j);
+	x_modified();
+	return (KSTD);
+}
+
+static int
+x_mv_end(int c MKSH_A_UNUSED)
+{
+	x_goto(xep);
+	return (KSTD);
+}
+
+static int
+x_mv_begin(int c MKSH_A_UNUSED)
+{
+	x_goto(xbuf);
+	return (KSTD);
+}
+
+static int
+x_draw_line(int c MKSH_A_UNUSED)
+{
+	x_redraw(-1);
+	return (KSTD);
+}
+
+static int
+x_e_rebuildline(const char *clrstr)
+{
+	shf_puts(clrstr, shl_out);
+	x_adjust();
+	return (KSTD);
+}
+
+static int
+x_cls(int c MKSH_A_UNUSED)
+{
+	return (x_e_rebuildline(MKSH_CLS_STRING));
+}
+
+/* Redraw (part of) the line. If limit is < 0, the everything is redrawn
+ * on a NEW line, otherwise limit is the screen column up to which needs
+ * redrawing.
+ */
+static void
+x_redraw(int limit)
+{
+	int i, j, x_trunc = 0;
+	char *cp;
+
+	x_adj_ok = 0;
+	if (limit == -1)
+		x_e_putc2('\n');
+	else
+		x_e_putc2('\r');
+	x_flush();
+	if (xbp == xbuf) {
+		x_col = promptlen(prompt);
+		if (x_col >= xx_cols)
+			x_trunc = (x_col / xx_cols) * xx_cols;
+		if (prompt_redraw)
+			pprompt(prompt, x_trunc);
+	}
+	if (x_col >= xx_cols)
+		x_col %= xx_cols;
+	x_displen = xx_cols - 2 - x_col;
+	if (x_displen < 1) {
+		x_col = 0;
+		x_displen = xx_cols - 2;
+	}
+	xlp_valid = false;
+	x_lastcp();
+	x_zots(xbp);
+	if (xbp != xbuf || xep > xlp)
+		limit = xx_cols;
+	if (limit >= 0) {
+		if (xep > xlp)
+			i = 0;			/* we fill the line */
+		else {
+			char *cpl = xbp;
+
+			i = limit;
+			while (cpl < xlp)
+				i -= x_size2(cpl, &cpl);
+		}
+
+		j = 0;
+		while ((j < i) || (x_col < (xx_cols - 2))) {
+			if (!(x_col < (xx_cols - 2)))
+				break;
+			x_e_putc2(' ');
+			j++;
+		}
+		i = ' ';
+		if (xep > xlp) {		/* more off screen */
+			if (xbp > xbuf)
+				i = '*';
+			else
+				i = '>';
+		} else if (xbp > xbuf)
+			i = '<';
+		x_e_putc2(i);
+		j++;
+		while (j--)
+			x_e_putc2('\b');
+	}
+	cp = xlp;
+	while (cp > xcp)
+		x_bs3(&cp);
+	x_adj_ok = 1;
+	return;
+}
+
+static int
+x_transpose(int c MKSH_A_UNUSED)
+{
+	unsigned int tmpa, tmpb;
+
+	/* What transpose is meant to do seems to be up for debate. This
+	 * is a general summary of the options; the text is abcd with the
+	 * upper case character or underscore indicating the cursor position:
+	 *	Who			Before	After	Before	After
+	 *	AT&T ksh in emacs mode:	abCd	abdC	abcd_	(bell)
+	 *	AT&T ksh in gmacs mode:	abCd	baCd	abcd_	abdc_
+	 *	gnu emacs:		abCd	acbD	abcd_	abdc_
+	 * Pdksh currently goes with GNU behavior since I believe this is the
+	 * most common version of emacs, unless in gmacs mode, in which case
+	 * it does the AT&T ksh gmacs mode.
+	 * This should really be broken up into 3 functions so users can bind
+	 * to the one they want.
+	 */
+	if (xcp == xbuf) {
+		x_e_putc2(7);
+		return (KSTD);
+	} else if (xcp == xep || Flag(FGMACS)) {
+		if (xcp - xbuf == 1) {
+			x_e_putc2(7);
+			return (KSTD);
+		}
+		/* Gosling/Unipress emacs style: Swap two characters before the
+		 * cursor, do not change cursor position
+		 */
+		x_bs3(&xcp);
+		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
+			x_e_putc2(7);
+			return (KSTD);
+		}
+		x_bs3(&xcp);
+		if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
+			x_e_putc2(7);
+			return (KSTD);
+		}
+		utf_wctomb(xcp, tmpa);
+		x_zotc3(&xcp);
+		utf_wctomb(xcp, tmpb);
+		x_zotc3(&xcp);
+	} else {
+		/* GNU emacs style: Swap the characters before and under the
+		 * cursor, move cursor position along one.
+		 */
+		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
+			x_e_putc2(7);
+			return (KSTD);
+		}
+		x_bs3(&xcp);
+		if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
+			x_e_putc2(7);
+			return (KSTD);
+		}
+		utf_wctomb(xcp, tmpa);
+		x_zotc3(&xcp);
+		utf_wctomb(xcp, tmpb);
+		x_zotc3(&xcp);
+	}
+	x_modified();
+	return (KSTD);
+}
+
+static int
+x_literal(int c MKSH_A_UNUSED)
+{
+	x_curprefix = -1;
+	return (KSTD);
+}
+
+static int
+x_meta1(int c MKSH_A_UNUSED)
+{
+	x_curprefix = 1;
+	return (KSTD);
+}
+
+static int
+x_meta2(int c MKSH_A_UNUSED)
+{
+	x_curprefix = 2;
+	return (KSTD);
+}
+
+static int
+x_kill(int c MKSH_A_UNUSED)
+{
+	int col = xcp - xbuf;
+	int lastcol = xep - xbuf;
+	int ndel;
+
+	if (x_arg_defaulted)
+		x_arg = lastcol;
+	else if (x_arg > lastcol)
+		x_arg = lastcol;
+	ndel = x_arg - col;
+	if (ndel < 0) {
+		x_goto(xbuf + x_arg);
+		ndel = -ndel;
+	}
+	x_delete(ndel, true);
+	return (KSTD);
+}
+
+static void
+x_push(int nchars)
+{
+	char *cp;
+
+	strndupx(cp, xcp, nchars, AEDIT);
+	if (killstack[killsp])
+		afree(killstack[killsp], AEDIT);
+	killstack[killsp] = cp;
+	killsp = (killsp + 1) % KILLSIZE;
+}
+
+static int
+x_yank(int c MKSH_A_UNUSED)
+{
+	if (killsp == 0)
+		killtp = KILLSIZE;
+	else
+		killtp = killsp;
+	killtp--;
+	if (killstack[killtp] == 0) {
+		x_e_puts("\nnothing to yank");
+		x_redraw(-1);
+		return (KSTD);
+	}
+	xmp = xcp;
+	x_ins(killstack[killtp]);
+	return (KSTD);
+}
+
+static int
+x_meta_yank(int c MKSH_A_UNUSED)
+{
+	int len;
+
+	if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) ||
+	    killstack[killtp] == 0) {
+		killtp = killsp;
+		x_e_puts("\nyank something first");
+		x_redraw(-1);
+		return (KSTD);
+	}
+	len = strlen(killstack[killtp]);
+	x_goto(xcp - len);
+	x_delete(len, false);
+	do {
+		if (killtp == 0)
+			killtp = KILLSIZE - 1;
+		else
+			killtp--;
+	} while (killstack[killtp] == 0);
+	x_ins(killstack[killtp]);
+	return (KSTD);
+}
+
+static int
+x_abort(int c MKSH_A_UNUSED)
+{
+	/* x_zotc(c); */
+	xlp = xep = xcp = xbp = xbuf;
+	xlp_valid = true;
+	*xcp = 0;
+	x_modified();
+	return (KINTR);
+}
+
+static int
+x_error(int c MKSH_A_UNUSED)
+{
+	x_e_putc2(7);
+	return (KSTD);
+}
+
+#ifndef MKSH_SMALL
+/* special VT100 style key sequence hack */
+static int
+x_vt_hack(int c)
+{
+	/* we only support PF2-'1' for now */
+	if (c != (2 << 8 | '1'))
+		return (x_error(c));
+
+	/* what's the next character? */
+	switch ((c = x_e_getc())) {
+	case '~':
+		x_arg = 1;
+		x_arg_defaulted = 1;
+		return (x_mv_begin(0));
+	case ';':
+		/* "interesting" sequence detected */
+		break;
+	default:
+		goto unwind_err;
+	}
+
+	/* XXX x_e_ungetc is one-octet only */
+	if ((c = x_e_getc()) != '5' && c != '3')
+		goto unwind_err;
+
+	/*-
+	 * At this point, we have read the following octets so far:
+	 * - ESC+[ or ESC+O or Ctrl-X (Præfix 2)
+	 * - 1 (vt_hack)
+	 * - ;
+	 * - 5 (Ctrl key combiner) or 3 (Alt key combiner)
+	 * We can now accept one more octet designating the key.
+	 */
+
+	switch ((c = x_e_getc())) {
+	case 'C':
+		return (x_mv_fword(c));
+	case 'D':
+		return (x_mv_bword(c));
+	}
+
+ unwind_err:
+	x_e_ungetc(c);
+	return (x_error(c));
+}
+#endif
+
+static char *
+x_mapin(const char *cp, Area *ap)
+{
+	char *news, *op;
+
+	/* for clang's static analyser, the nonnull attribute isn't enough */
+	mkssert(cp != NULL);
+
+	strdupx(news, cp, ap);
+	op = news;
+	while (*cp) {
+		/* XXX -- should handle \^ escape? */
+		if (*cp == '^') {
+			cp++;
+			if (*cp >= '?')	/* includes '?'; ASCII */
+				*op++ = CTRL(*cp);
+			else {
+				*op++ = '^';
+				cp--;
+			}
+		} else
+			*op++ = *cp;
+		cp++;
+	}
+	*op = '\0';
+
+	return (news);
+}
+
+static void
+x_mapout2(int c, char **buf)
+{
+	char *p = *buf;
+
+	if (c < ' ' || c == 0x7f) {
+		*p++ = '^';
+		*p++ = UNCTRL(c);
+	} else
+		*p++ = c;
+	*p = 0;
+	*buf = p;
+}
+
+static char *
+x_mapout(int c)
+{
+	static char buf[8];
+	char *bp = buf;
+
+	x_mapout2(c, &bp);
+	return (buf);
+}
+
+static void
+x_print(int prefix, int key)
+{
+	int f = x_tab[prefix][key];
+
+	if (prefix)
+		/* prefix == 1 || prefix == 2 */
+		shf_puts(x_mapout(prefix == 1 ?
+		    CTRL('[') : CTRL('X')), shl_stdout);
+#ifdef MKSH_SMALL
+	shprintf("%s = ", x_mapout(key));
+#else
+	shprintf("%s%s = ", x_mapout(key), (f & 0x80) ? "~" : "");
+	if (XFUNC_VALUE(f) != XFUNC_ins_string)
+#endif
+		shprintf("%s\n", x_ftab[XFUNC_VALUE(f)].xf_name);
+#ifndef MKSH_SMALL
+	else
+		shprintf("'%s'\n", x_atab[prefix][key]);
+#endif
+}
+
+int
+x_bind(const char *a1, const char *a2,
+#ifndef MKSH_SMALL
+    bool macro,			/* bind -m */
+#endif
+    bool list)			/* bind -l */
+{
+	unsigned char f;
+	int prefix, key;
+	char *m1, *m2;
+#ifndef MKSH_SMALL
+	char *sp = NULL;
+	bool hastilde;
+#endif
+
+	if (x_tab == NULL) {
+		bi_errorf("cannot bind, not a tty");
+		return (1);
+	}
+	/* List function names */
+	if (list) {
+		for (f = 0; f < NELEM(x_ftab); f++)
+			if (x_ftab[f].xf_name &&
+			    !(x_ftab[f].xf_flags & XF_NOBIND))
+				shprintf("%s\n", x_ftab[f].xf_name);
+		return (0);
+	}
+	if (a1 == NULL) {
+		for (prefix = 0; prefix < X_NTABS; prefix++)
+			for (key = 0; key < X_TABSZ; key++) {
+				f = XFUNC_VALUE(x_tab[prefix][key]);
+				if (f == XFUNC_insert || f == XFUNC_error
+#ifndef MKSH_SMALL
+				    || (macro && f != XFUNC_ins_string)
+#endif
+				    )
+					continue;
+				x_print(prefix, key);
+			}
+		return (0);
+	}
+	m2 = m1 = x_mapin(a1, ATEMP);
+	prefix = 0;
+	for (;; m1++) {
+		key = (unsigned char)*m1;
+		f = XFUNC_VALUE(x_tab[prefix][key]);
+		if (f == XFUNC_meta1)
+			prefix = 1;
+		else if (f == XFUNC_meta2)
+			prefix = 2;
+		else
+			break;
+	}
+	if (*++m1
+#ifndef MKSH_SMALL
+	    && ((*m1 != '~') || *(m1 + 1))
+#endif
+	    ) {
+		char msg[256] = "key sequence '";
+		const char *c = a1;
+		m1 = msg + strlen(msg);
+		while (*c && m1 < (msg + sizeof(msg) - 3))
+			x_mapout2(*c++, &m1);
+		bi_errorf("%s' too long", msg);
+		return (1);
+	}
+#ifndef MKSH_SMALL
+	hastilde = *m1;
+#endif
+	afree(m2, ATEMP);
+
+	if (a2 == NULL) {
+		x_print(prefix, key);
+		return (0);
+	}
+	if (*a2 == 0) {
+		f = XFUNC_insert;
+#ifndef MKSH_SMALL
+	} else if (macro) {
+		f = XFUNC_ins_string;
+		sp = x_mapin(a2, AEDIT);
+#endif
+	} else {
+		for (f = 0; f < NELEM(x_ftab); f++)
+			if (x_ftab[f].xf_name &&
+			    strcmp(x_ftab[f].xf_name, a2) == 0)
+				break;
+		if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
+			bi_errorf("%s: no such function", a2);
+			return (1);
+		}
+	}
+
+#ifndef MKSH_SMALL
+	if (XFUNC_VALUE(x_tab[prefix][key]) == XFUNC_ins_string &&
+	    x_atab[prefix][key])
+		afree(x_atab[prefix][key], AEDIT);
+#endif
+	x_tab[prefix][key] = f
+#ifndef MKSH_SMALL
+	    | (hastilde ? 0x80 : 0)
+#endif
+	    ;
+#ifndef MKSH_SMALL
+	x_atab[prefix][key] = sp;
+#endif
+
+	/* Track what the user has bound so x_mode(true) won't toast things */
+	if (f == XFUNC_insert)
+		x_bound[(prefix * X_TABSZ + key) / 8] &=
+		    ~(1 << ((prefix * X_TABSZ + key) % 8));
+	else
+		x_bound[(prefix * X_TABSZ + key) / 8] |=
+		    (1 << ((prefix * X_TABSZ + key) % 8));
+
+	return (0);
+}
+
+static void
+x_init_emacs(void)
+{
+	int i, j;
+
+	ainit(AEDIT);
+	x_nextcmd = -1;
+
+	x_tab = alloc(X_NTABS * sizeof(*x_tab), AEDIT);
+	for (j = 0; j < X_TABSZ; j++)
+		x_tab[0][j] = XFUNC_insert;
+	for (i = 1; i < X_NTABS; i++)
+		for (j = 0; j < X_TABSZ; j++)
+			x_tab[i][j] = XFUNC_error;
+	for (i = 0; i < (int)NELEM(x_defbindings); i++)
+		x_tab[x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char]
+		    = x_defbindings[i].xdb_func;
+
+#ifndef MKSH_SMALL
+	x_atab = alloc(X_NTABS * sizeof(*x_atab), AEDIT);
+	for (i = 1; i < X_NTABS; i++)
+		for (j = 0; j < X_TABSZ; j++)
+			x_atab[i][j] = NULL;
+#endif
+}
+
+static void
+bind_if_not_bound(int p, int k, int func)
+{
+	/* Has user already bound this key? If so, don't override it */
+	if (x_bound[((p) * X_TABSZ + (k)) / 8] &
+	    (1 << (((p) * X_TABSZ + (k)) % 8)))
+		return;
+
+	x_tab[p][k] = func;
+}
+
+static int
+x_set_mark(int c MKSH_A_UNUSED)
+{
+	xmp = xcp;
+	return (KSTD);
+}
+
+static int
+x_kill_region(int c MKSH_A_UNUSED)
+{
+	int rsize;
+	char *xr;
+
+	if (xmp == NULL) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	if (xmp > xcp) {
+		rsize = xmp - xcp;
+		xr = xcp;
+	} else {
+		rsize = xcp - xmp;
+		xr = xmp;
+	}
+	x_goto(xr);
+	x_delete(rsize, true);
+	xmp = xr;
+	return (KSTD);
+}
+
+static int
+x_xchg_point_mark(int c MKSH_A_UNUSED)
+{
+	char *tmp;
+
+	if (xmp == NULL) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	tmp = xmp;
+	xmp = xcp;
+	x_goto(tmp);
+	return (KSTD);
+}
+
+static int
+x_noop(int c MKSH_A_UNUSED)
+{
+	return (KSTD);
+}
+
+/*
+ *	File/command name completion routines
+ */
+static int
+x_comp_comm(int c MKSH_A_UNUSED)
+{
+	do_complete(XCF_COMMAND, CT_COMPLETE);
+	return (KSTD);
+}
+
+static int
+x_list_comm(int c MKSH_A_UNUSED)
+{
+	do_complete(XCF_COMMAND, CT_LIST);
+	return (KSTD);
+}
+
+static int
+x_complete(int c MKSH_A_UNUSED)
+{
+	do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
+	return (KSTD);
+}
+
+static int
+x_enumerate(int c MKSH_A_UNUSED)
+{
+	do_complete(XCF_COMMAND_FILE, CT_LIST);
+	return (KSTD);
+}
+
+static int
+x_comp_file(int c MKSH_A_UNUSED)
+{
+	do_complete(XCF_FILE, CT_COMPLETE);
+	return (KSTD);
+}
+
+static int
+x_list_file(int c MKSH_A_UNUSED)
+{
+	do_complete(XCF_FILE, CT_LIST);
+	return (KSTD);
+}
+
+static int
+x_comp_list(int c MKSH_A_UNUSED)
+{
+	do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
+	return (KSTD);
+}
+
+static int
+x_expand(int c MKSH_A_UNUSED)
+{
+	char **words;
+	int start, end, nwords, i;
+	bool is_command;
+
+	nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf,
+	    &start, &end, &words, &is_command);
+
+	if (nwords == 0) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	x_goto(xbuf + start);
+	x_delete(end - start, false);
+	for (i = 0; i < nwords;) {
+		if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 ||
+		    (++i < nwords && x_ins(" ") < 0)) {
+			x_e_putc2(7);
+			return (KSTD);
+		}
+	}
+	x_adjust();
+
+	return (KSTD);
+}
+
+/* type == 0 for list, 1 for complete and 2 for complete-list */
+static void
+do_complete(int flags,	/* XCF_{COMMAND,FILE,COMMAND_FILE} */
+    Comp_type type)
+{
+	char **words;
+	int start, end, nlen, olen, nwords;
+	bool is_command, completed = false;
+
+	nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
+	    &start, &end, &words, &is_command);
+	/* no match */
+	if (nwords == 0) {
+		x_e_putc2(7);
+		return;
+	}
+	if (type == CT_LIST) {
+		x_print_expansions(nwords, words, is_command);
+		x_redraw(0);
+		x_free_words(nwords, words);
+		return;
+	}
+	olen = end - start;
+	nlen = x_longest_prefix(nwords, words);
+	/* complete */
+	if (nwords == 1 || nlen > olen) {
+		x_goto(xbuf + start);
+		x_delete(olen, false);
+		x_escape(words[0], nlen, x_do_ins);
+		x_adjust();
+		completed = true;
+	}
+	/* add space if single non-dir match */
+	if (nwords == 1 && words[0][nlen - 1] != '/') {
+		x_ins(" ");
+		completed = true;
+	}
+	if (type == CT_COMPLIST && !completed) {
+		x_print_expansions(nwords, words, is_command);
+		completed = true;
+	}
+	if (completed)
+		x_redraw(0);
+
+	x_free_words(nwords, words);
+}
+
+/* NAME:
+ *	x_adjust - redraw the line adjusting starting point etc.
+ *
+ * DESCRIPTION:
+ *	This function is called when we have exceeded the bounds
+ *	of the edit window. It increments x_adj_done so that
+ *	functions like x_ins and x_delete know that we have been
+ *	called and can skip the x_bs() stuff which has already
+ *	been done by x_redraw.
+ *
+ * RETURN VALUE:
+ *	None
+ */
+static void
+x_adjust(void)
+{
+	x_adj_done++;			/* flag the fact that we were called. */
+	/*
+	 * we had a problem if the prompt length > xx_cols / 2
+	 */
+	if ((xbp = xcp - (x_displen / 2)) < xbuf)
+		xbp = xbuf;
+	if (UTFMODE)
+		while ((xbp > xbuf) && ((*xbp & 0xC0) == 0x80))
+			--xbp;
+	xlp_valid = false;
+	x_redraw(xx_cols);
+	x_flush();
+}
+
+static void
+x_e_ungetc(int c)
+{
+	unget_char = c < 0 ? -1 : (c & 255);
+}
+
+static int
+x_e_getc(void)
+{
+	int c;
+
+	if (unget_char >= 0) {
+		c = unget_char;
+		unget_char = -1;
+		return (c);
+	}
+
+#ifndef MKSH_SMALL
+	if (macroptr) {
+		if ((c = (unsigned char)*macroptr++))
+			return (c);
+		macroptr = NULL;
+	}
+#endif
+
+	return (x_getc());
+}
+
+static void
+x_e_putc2(int c)
+{
+	int width = 1;
+
+	if (c == '\r' || c == '\n')
+		x_col = 0;
+	if (x_col < xx_cols) {
+		if (UTFMODE && (c > 0x7F)) {
+			char utf_tmp[3];
+			size_t x;
+
+			if (c < 0xA0)
+				c = 0xFFFD;
+			x = utf_wctomb(utf_tmp, c);
+			x_putc(utf_tmp[0]);
+			if (x > 1)
+				x_putc(utf_tmp[1]);
+			if (x > 2)
+				x_putc(utf_tmp[2]);
+			width = utf_wcwidth(c);
+		} else
+			x_putc(c);
+		switch (c) {
+		case 7:
+			break;
+		case '\r':
+		case '\n':
+			break;
+		case '\b':
+			x_col--;
+			break;
+		default:
+			x_col += width;
+			break;
+		}
+	}
+	if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
+		x_adjust();
+}
+
+static void
+x_e_putc3(const char **cp)
+{
+	int width = 1, c = **(const unsigned char **)cp;
+
+	if (c == '\r' || c == '\n')
+		x_col = 0;
+	if (x_col < xx_cols) {
+		if (UTFMODE && (c > 0x7F)) {
+			char *cp2;
+
+			width = utf_widthadj(*cp, (const char **)&cp2);
+			while (*cp < cp2)
+				x_putcf(*(*cp)++);
+		} else {
+			(*cp)++;
+			x_putc(c);
+		}
+		switch (c) {
+		case 7:
+			break;
+		case '\r':
+		case '\n':
+			break;
+		case '\b':
+			x_col--;
+			break;
+		default:
+			x_col += width;
+			break;
+		}
+	}
+	if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
+		x_adjust();
+}
+
+static void
+x_e_puts(const char *s)
+{
+	int adj = x_adj_done;
+
+	while (*s && adj == x_adj_done)
+		x_e_putc3(&s);
+}
+
+/* NAME:
+ *	x_set_arg - set an arg value for next function
+ *
+ * DESCRIPTION:
+ *	This is a simple implementation of M-[0-9].
+ *
+ * RETURN VALUE:
+ *	KSTD
+ */
+static int
+x_set_arg(int c)
+{
+	int n = 0, first = 1;
+
+	c &= 255;	/* strip command prefix */
+	for (; c >= 0 && ksh_isdigit(c); c = x_e_getc(), first = 0)
+		n = n * 10 + (c - '0');
+	if (c < 0 || first) {
+		x_e_putc2(7);
+		x_arg = 1;
+		x_arg_defaulted = 1;
+	} else {
+		x_e_ungetc(c);
+		x_arg = n;
+		x_arg_defaulted = 0;
+	}
+	return (KSTD);
+}
+
+/* Comment or uncomment the current line. */
+static int
+x_comment(int c MKSH_A_UNUSED)
+{
+	int oldsize = x_size_str(xbuf);
+	int len = xep - xbuf;
+	int ret = x_do_comment(xbuf, xend - xbuf, &len);
+
+	if (ret < 0)
+		x_e_putc2(7);
+	else {
+		x_modified();
+		xep = xbuf + len;
+		*xep = '\0';
+		xcp = xbp = xbuf;
+		x_redraw(oldsize);
+		if (ret > 0)
+			return (x_newline('\n'));
+	}
+	return (KSTD);
+}
+
+static int
+x_version(int c MKSH_A_UNUSED)
+{
+	char *o_xbuf = xbuf, *o_xend = xend;
+	char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
+	int vlen, lim = x_lastcp() - xbp;
+	char *v;
+
+	strdupx(v, KSH_VERSION, ATEMP);
+
+	xbuf = xbp = xcp = v;
+	xend = xep = v + (vlen = strlen(v));
+	x_redraw(lim);
+	x_flush();
+
+	c = x_e_getc();
+	xbuf = o_xbuf;
+	xend = o_xend;
+	xbp = o_xbp;
+	xep = o_xep;
+	xcp = o_xcp;
+	x_redraw(vlen);
+
+	if (c < 0)
+		return (KSTD);
+	/* This is what AT&T ksh seems to do... Very bizarre */
+	if (c != ' ')
+		x_e_ungetc(c);
+
+	afree(v, ATEMP);
+	return (KSTD);
+}
+
+#ifndef MKSH_SMALL
+static int
+x_edit_line(int c MKSH_A_UNUSED)
+{
+	if (x_arg_defaulted) {
+		if (xep == xbuf) {
+			x_e_putc2(7);
+			return (KSTD);
+		}
+		if (modified) {
+			*xep = '\0';
+			histsave(&source->line, xbuf, true, true);
+			x_arg = 0;
+		} else
+			x_arg = source->line - (histptr - x_histp);
+	}
+	if (x_arg)
+		shf_snprintf(xbuf, xend - xbuf, "%s %d",
+		    "fc -e ${VISUAL:-${EDITOR:-vi}} --", x_arg);
+	else
+		strlcpy(xbuf, "fc -e ${VISUAL:-${EDITOR:-vi}} --", xend - xbuf);
+	xep = xbuf + strlen(xbuf);
+	return (x_newline('\n'));
+}
+#endif
+
+/* NAME:
+ *	x_prev_histword - recover word from prev command
+ *
+ * DESCRIPTION:
+ *	This function recovers the last word from the previous
+ *	command and inserts it into the current edit line. If a
+ *	numeric arg is supplied then the n'th word from the
+ *	start of the previous command is used.
+ *	As a side effect, trashes the mark in order to achieve
+ *	being called in a repeatable fashion.
+ *
+ *	Bound to M-.
+ *
+ * RETURN VALUE:
+ *	KSTD
+ */
+static int
+x_prev_histword(int c MKSH_A_UNUSED)
+{
+	char *rcp, *cp;
+	char **xhp;
+	int m;
+
+	if (xmp && modified > 1)
+		x_kill_region(0);
+	m = modified ? modified : 1;
+	xhp = histptr - (m - 1);
+	if ((xhp < history) || !(cp = *xhp)) {
+		x_e_putc2(7);
+		x_modified();
+		return (KSTD);
+	}
+	x_set_mark(0);
+	if (x_arg_defaulted) {
+		rcp = &cp[strlen(cp) - 1];
+		/*
+		 * ignore white-space after the last word
+		 */
+		while (rcp > cp && is_cfs(*rcp))
+			rcp--;
+		while (rcp > cp && !is_cfs(*rcp))
+			rcp--;
+		if (is_cfs(*rcp))
+			rcp++;
+		x_ins(rcp);
+	} else {
+		char ch;
+
+		rcp = cp;
+		/*
+		 * ignore white-space at start of line
+		 */
+		while (*rcp && is_cfs(*rcp))
+			rcp++;
+		while (x_arg-- > 1) {
+			while (*rcp && !is_cfs(*rcp))
+				rcp++;
+			while (*rcp && is_cfs(*rcp))
+				rcp++;
+		}
+		cp = rcp;
+		while (*rcp && !is_cfs(*rcp))
+			rcp++;
+		ch = *rcp;
+		*rcp = '\0';
+		x_ins(cp);
+		*rcp = ch;
+	}
+	modified = m + 1;
+	return (KSTD);
+}
+
+#ifndef MKSH_SMALL
+/* Uppercase N(1) words */
+static int
+x_fold_upper(int c MKSH_A_UNUSED)
+{
+	return (x_fold_case('U'));
+}
+
+/* Lowercase N(1) words */
+static int
+x_fold_lower(int c MKSH_A_UNUSED)
+{
+	return (x_fold_case('L'));
+}
+
+/* Lowercase N(1) words */
+static int
+x_fold_capitalise(int c MKSH_A_UNUSED)
+{
+	return (x_fold_case('C'));
+}
+
+/* NAME:
+ *	x_fold_case - convert word to UPPER/lower/Capital case
+ *
+ * DESCRIPTION:
+ *	This function is used to implement M-U,M-u,M-L,M-l,M-C and M-c
+ *	to UPPER case, lower case or Capitalise words.
+ *
+ * RETURN VALUE:
+ *	None
+ */
+static int
+x_fold_case(int c)
+{
+	char *cp = xcp;
+
+	if (cp == xep) {
+		x_e_putc2(7);
+		return (KSTD);
+	}
+	while (x_arg--) {
+		/*
+		 * first skip over any white-space
+		 */
+		while (cp != xep && is_mfs(*cp))
+			cp++;
+		/*
+		 * do the first char on its own since it may be
+		 * a different action than for the rest.
+		 */
+		if (cp != xep) {
+			if (c == 'L')		/* lowercase */
+				*cp = ksh_tolower(*cp);
+			else			/* uppercase, capitalise */
+				*cp = ksh_toupper(*cp);
+			cp++;
+		}
+		/*
+		 * now for the rest of the word
+		 */
+		while (cp != xep && !is_mfs(*cp)) {
+			if (c == 'U')		/* uppercase */
+				*cp = ksh_toupper(*cp);
+			else			/* lowercase, capitalise */
+				*cp = ksh_tolower(*cp);
+			cp++;
+		}
+	}
+	x_goto(cp);
+	x_modified();
+	return (KSTD);
+}
+#endif
+
+/* NAME:
+ *	x_lastcp - last visible char
+ *
+ * SYNOPSIS:
+ *	x_lastcp()
+ *
+ * DESCRIPTION:
+ *	This function returns a pointer to that char in the
+ *	edit buffer that will be the last displayed on the
+ *	screen. The sequence:
+ *
+ *	cp = x_lastcp();
+ *	while (cp > xcp)
+ *		x_bs3(&cp);
+ *
+ *	Will position the cursor correctly on the screen.
+ *
+ * RETURN VALUE:
+ *	cp or NULL
+ */
+static char *
+x_lastcp(void)
+{
+	if (!xlp_valid) {
+		int i = 0, j;
+		char *xlp2;
+
+		xlp = xbp;
+		while (xlp < xep) {
+			j = x_size2(xlp, &xlp2);
+			if ((i + j) > x_displen)
+				break;
+			i += j;
+			xlp = xlp2;
+		}
+	}
+	xlp_valid = true;
+	return (xlp);
+}
+
+static bool
+x_mode(bool onoff)
+{
+	static bool x_cur_mode;
+	bool prev;
+
+	if (x_cur_mode == onoff)
+		return (x_cur_mode);
+	prev = x_cur_mode;
+	x_cur_mode = onoff;
+
+	if (onoff) {
+		struct termios cb;
+
+		cb = tty_state;
+
+		edchars.erase = cb.c_cc[VERASE];
+		edchars.kill = cb.c_cc[VKILL];
+		edchars.intr = cb.c_cc[VINTR];
+		edchars.quit = cb.c_cc[VQUIT];
+		edchars.eof = cb.c_cc[VEOF];
+#ifdef VWERASE
+		edchars.werase = cb.c_cc[VWERASE];
+#endif
+		cb.c_iflag &= ~(INLCR | ICRNL);
+		cb.c_lflag &= ~(ISIG | ICANON | ECHO);
+#if defined(VLNEXT) && defined(_POSIX_VDISABLE)
+		/* osf/1 processes lnext when ~icanon */
+		cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
+#endif
+		/* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */
+#if defined(VDISCARD) && defined(_POSIX_VDISABLE)
+		cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
+#endif
+		cb.c_cc[VTIME] = 0;
+		cb.c_cc[VMIN] = 1;
+
+		tcsetattr(tty_fd, TCSADRAIN, &cb);
+
+#ifdef _POSIX_VDISABLE
+		/* Convert unset values to internal 'unset' value */
+		if (edchars.erase == _POSIX_VDISABLE)
+			edchars.erase = -1;
+		if (edchars.kill == _POSIX_VDISABLE)
+			edchars.kill = -1;
+		if (edchars.intr == _POSIX_VDISABLE)
+			edchars.intr = -1;
+		if (edchars.quit == _POSIX_VDISABLE)
+			edchars.quit = -1;
+		if (edchars.eof == _POSIX_VDISABLE)
+			edchars.eof = -1;
+		if (edchars.werase == _POSIX_VDISABLE)
+			edchars.werase = -1;
+#endif
+
+		if (edchars.erase >= 0) {
+			bind_if_not_bound(0, edchars.erase, XFUNC_del_back);
+			bind_if_not_bound(1, edchars.erase, XFUNC_del_bword);
+		}
+		if (edchars.kill >= 0)
+			bind_if_not_bound(0, edchars.kill, XFUNC_del_line);
+		if (edchars.werase >= 0)
+			bind_if_not_bound(0, edchars.werase, XFUNC_del_bword);
+		if (edchars.intr >= 0)
+			bind_if_not_bound(0, edchars.intr, XFUNC_abort);
+		if (edchars.quit >= 0)
+			bind_if_not_bound(0, edchars.quit, XFUNC_noop);
+	} else
+		tcsetattr(tty_fd, TCSADRAIN, &tty_state);
+
+	return (prev);
+}
+
+#if !MKSH_S_NOVI
+/* +++ vi editing mode +++ */
+
+#define Ctrl(c)		(c&0x1f)
+
+struct edstate {
+	char *cbuf;
+	int winleft;
+	int cbufsize;
+	int linelen;
+	int cursor;
+};
+
+static int vi_hook(int);
+static int nextstate(int);
+static int vi_insert(int);
+static int vi_cmd(int, const char *);
+static int domove(int, const char *, int);
+static int redo_insert(int);
+static void yank_range(int, int);
+static int bracktype(int);
+static void save_cbuf(void);
+static void restore_cbuf(void);
+static int putbuf(const char *, int, int);
+static void del_range(int, int);
+static int findch(int, int, int, int);
+static int forwword(int);
+static int backword(int);
+static int endword(int);
+static int Forwword(int);
+static int Backword(int);
+static int Endword(int);
+static int grabhist(int, int);
+static int grabsearch(int, int, int, char *);
+static void redraw_line(int);
+static void refresh(int);
+static int outofwin(void);
+static void rewindow(void);
+static int newcol(int, int);
+static void display(char *, char *, int);
+static void ed_mov_opt(int, char *);
+static int expand_word(int);
+static int complete_word(int, int);
+static int print_expansions(struct edstate *, int);
+#define char_len(c)	((c) < ' ' || (c) == 0x7F ? 2 : 1)
+static void x_vi_zotc(int);
+static void vi_error(void);
+static void vi_macro_reset(void);
+static int x_vi_putbuf(const char *, size_t);
+
+#define C_	0x1		/* a valid command that isn't a M_, E_, U_ */
+#define M_	0x2		/* movement command (h, l, etc.) */
+#define E_	0x4		/* extended command (c, d, y) */
+#define X_	0x8		/* long command (@, f, F, t, T, etc.) */
+#define U_	0x10		/* an UN-undoable command (that isn't a M_) */
+#define B_	0x20		/* bad command (^@) */
+#define Z_	0x40		/* repeat count defaults to 0 (not 1) */
+#define S_	0x80		/* search (/, ?) */
+
+#define is_bad(c)	(classify[(c)&0x7f]&B_)
+#define is_cmd(c)	(classify[(c)&0x7f]&(M_|E_|C_|U_))
+#define is_move(c)	(classify[(c)&0x7f]&M_)
+#define is_extend(c)	(classify[(c)&0x7f]&E_)
+#define is_long(c)	(classify[(c)&0x7f]&X_)
+#define is_undoable(c)	(!(classify[(c)&0x7f]&U_))
+#define is_srch(c)	(classify[(c)&0x7f]&S_)
+#define is_zerocount(c)	(classify[(c)&0x7f]&Z_)
+
+static const unsigned char classify[128] = {
+/*	 0	1	2	3	4	5	6	7	*/
+/* 0	^@	^A	^B	^C	^D	^E	^F	^G	*/
+	B_,	0,	0,	0,	0,	C_|U_,	C_|Z_,	0,
+/* 1	^H	^I	^J	^K	^L	^M	^N	^O	*/
+	M_,	C_|Z_,	0,	0,	C_|U_,	0,	C_,	0,
+/* 2	^P	^Q	^R	^S	^T	^U	^V	^W	*/
+	C_,	0,	C_|U_,	0,	0,	0,	C_,	0,
+/* 3	^X	^Y	^Z	^[	^\	^]	^^	^_	*/
+	C_,	0,	0,	C_|Z_,	0,	0,	0,	0,
+/* 4	<space>	!	"	#	$	%	&	'	*/
+	M_,	0,	0,	C_,	M_,	M_,	0,	0,
+/* 5	(	)	*	+	,	-	.	/	*/
+	0,	0,	C_,	C_,	M_,	C_,	0,	C_|S_,
+/* 6	0	1	2	3	4	5	6	7	*/
+	M_,	0,	0,	0,	0,	0,	0,	0,
+/* 7	8	9	:	;	<	=	>	?	*/
+	0,	0,	0,	M_,	0,	C_,	0,	C_|S_,
+/* 8	@	A	B	C	D	E	F	G	*/
+	C_|X_,	C_,	M_,	C_,	C_,	M_,	M_|X_,	C_|U_|Z_,
+/* 9	H	I	J	K	L	M	N	O	*/
+	0,	C_,	0,	0,	0,	0,	C_|U_,	0,
+/* A	P	Q	R	S	T	U	V	W	*/
+	C_,	0,	C_,	C_,	M_|X_,	C_,	0,	M_,
+/* B	X	Y	Z	[	\	]	^	_	*/
+	C_,	C_|U_,	0,	0,	C_|Z_,	0,	M_,	C_|Z_,
+/* C	`	a	b	c	d	e	f	g	*/
+	0,	C_,	M_,	E_,	E_,	M_,	M_|X_,	C_|Z_,
+/* D	h	i	j	k	l	m	n	o	*/
+	M_,	C_,	C_|U_,	C_|U_,	M_,	0,	C_|U_,	0,
+/* E	p	q	r	s	t	u	v	w	*/
+	C_,	0,	X_,	C_,	M_|X_,	C_|U_,	C_|U_|Z_, M_,
+/* F	x	y	z	{	|	}	~	^?	*/
+	C_,	E_|U_,	0,	0,	M_|Z_,	0,	C_,	0
+};
+
+#define MAXVICMD	3
+#define SRCHLEN		40
+
+#define INSERT		1
+#define REPLACE		2
+
+#define VNORMAL		0		/* command, insert or replace mode */
+#define VARG1		1		/* digit prefix (first, eg, 5l) */
+#define VEXTCMD		2		/* cmd + movement (eg, cl) */
+#define VARG2		3		/* digit prefix (second, eg, 2c3l) */
+#define VXCH		4		/* f, F, t, T, @ */
+#define VFAIL		5		/* bad command */
+#define VCMD		6		/* single char command (eg, X) */
+#define VREDO		7		/* . */
+#define VLIT		8		/* ^V */
+#define VSEARCH		9		/* /, ? */
+#define VVERSION	10		/* <ESC> ^V */
+
+static char		undocbuf[LINE];
+
+static struct edstate	*save_edstate(struct edstate *old);
+static void		restore_edstate(struct edstate *old, struct edstate *news);
+static void		free_edstate(struct edstate *old);
+
+static struct edstate	ebuf;
+static struct edstate	undobuf = { undocbuf, 0, LINE, 0, 0 };
+
+static struct edstate	*es;			/* current editor state */
+static struct edstate	*undo;
+
+static char ibuf[LINE];			/* input buffer */
+static int first_insert;		/* set when starting in insert mode */
+static int saved_inslen;		/* saved inslen for first insert */
+static int inslen;			/* length of input buffer */
+static int srchlen;			/* length of current search pattern */
+static char ybuf[LINE];			/* yank buffer */
+static int yanklen;			/* length of yank buffer */
+static int fsavecmd = ' ';		/* last find command */
+static int fsavech;			/* character to find */
+static char lastcmd[MAXVICMD];		/* last non-move command */
+static int lastac;			/* argcnt for lastcmd */
+static int lastsearch = ' ';		/* last search command */
+static char srchpat[SRCHLEN];		/* last search pattern */
+static int insert;			/* non-zero in insert mode */
+static int hnum;			/* position in history */
+static int ohnum;			/* history line copied (after mod) */
+static int hlast;			/* 1 past last position in history */
+static int state;
+
+/* Information for keeping track of macros that are being expanded.
+ * The format of buf is the alias contents followed by a NUL byte followed
+ * by the name (letter) of the alias. The end of the buffer is marked by
+ * a double NUL. The name of the alias is stored so recursive macros can
+ * be detected.
+ */
+struct macro_state {
+	unsigned char *p;	/* current position in buf */
+	unsigned char *buf;	/* pointer to macro(s) being expanded */
+	int len;		/* how much data in buffer */
+};
+static struct macro_state macro;
+
+enum expand_mode {
+	NONE, EXPAND, COMPLETE, PRINT
+};
+static enum expand_mode expanded = NONE;	/* last input was expanded */
+
+static int
+x_vi(char *buf, size_t len)
+{
+	int c;
+
+	state = VNORMAL;
+	ohnum = hnum = hlast = histnum(-1) + 1;
+	insert = INSERT;
+	saved_inslen = inslen;
+	first_insert = 1;
+	inslen = 0;
+	vi_macro_reset();
+
+	es = &ebuf;
+	es->cbuf = buf;
+	undo = &undobuf;
+	undo->cbufsize = es->cbufsize = len > LINE ? LINE : len;
+
+	es->linelen = undo->linelen = 0;
+	es->cursor = undo->cursor = 0;
+	es->winleft = undo->winleft = 0;
+
+	cur_col = promptlen(prompt);
+	prompt_trunc = (cur_col / x_cols) * x_cols;
+	cur_col -= prompt_trunc;
+
+	pprompt(prompt, 0);
+	if (cur_col > x_cols - 3 - MIN_EDIT_SPACE) {
+		prompt_redraw = cur_col = 0;
+		x_putc('\n');
+	} else
+		prompt_redraw = 1;
+	pwidth = cur_col;
+
+	if (!wbuf_len || wbuf_len != x_cols - 3) {
+		wbuf_len = x_cols - 3;
+		wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
+		wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
+	}
+	(void)memset(wbuf[0], ' ', wbuf_len);
+	(void)memset(wbuf[1], ' ', wbuf_len);
+	winwidth = x_cols - pwidth - 3;
+	win = 0;
+	morec = ' ';
+	lastref = 1;
+	holdlen = 0;
+
+	editmode = 2;
+	x_flush();
+	while (1) {
+		if (macro.p) {
+			c = *macro.p++;
+			/* end of current macro? */
+			if (!c) {
+				/* more macros left to finish? */
+				if (*macro.p++)
+					continue;
+				/* must be the end of all the macros */
+				vi_macro_reset();
+				c = x_getc();
+			}
+		} else
+			c = x_getc();
+
+		if (c == -1)
+			break;
+		if (state != VLIT) {
+			if (c == edchars.intr || c == edchars.quit) {
+				/* pretend we got an interrupt */
+				x_vi_zotc(c);
+				x_flush();
+				trapsig(c == edchars.intr ? SIGINT : SIGQUIT);
+				x_mode(false);
+				unwind(LSHELL);
+			} else if (c == edchars.eof && state != VVERSION) {
+				if (es->linelen == 0) {
+					x_vi_zotc(edchars.eof);
+					c = -1;
+					break;
+				}
+				continue;
+			}
+		}
+		if (vi_hook(c))
+			break;
+		x_flush();
+	}
+
+	x_putc('\r');
+	x_putc('\n');
+	x_flush();
+
+	if (c == -1 || (ssize_t)len <= es->linelen)
+		return (-1);
+
+	if (es->cbuf != buf)
+		memmove(buf, es->cbuf, es->linelen);
+
+	buf[es->linelen++] = '\n';
+
+	return (es->linelen);
+}
+
+static int
+vi_hook(int ch)
+{
+	static char curcmd[MAXVICMD], locpat[SRCHLEN];
+	static int cmdlen, argc1, argc2;
+
+	switch (state) {
+
+	case VNORMAL:
+		if (insert != 0) {
+			if (ch == Ctrl('v')) {
+				state = VLIT;
+				ch = '^';
+			}
+			switch (vi_insert(ch)) {
+			case -1:
+				vi_error();
+				state = VNORMAL;
+				break;
+			case 0:
+				if (state == VLIT) {
+					es->cursor--;
+					refresh(0);
+				} else
+					refresh(insert != 0);
+				break;
+			case 1:
+				return (1);
+			}
+		} else {
+			if (ch == '\r' || ch == '\n')
+				return (1);
+			cmdlen = 0;
+			argc1 = 0;
+			if (ch >= '1' && ch <= '9') {
+				argc1 = ch - '0';
+				state = VARG1;
+			} else {
+				curcmd[cmdlen++] = ch;
+				state = nextstate(ch);
+				if (state == VSEARCH) {
+					save_cbuf();
+					es->cursor = 0;
+					es->linelen = 0;
+					if (ch == '/') {
+						if (putbuf("/", 1, 0) != 0)
+							return (-1);
+					} else if (putbuf("?", 1, 0) != 0)
+						return (-1);
+					refresh(0);
+				}
+				if (state == VVERSION) {
+					save_cbuf();
+					es->cursor = 0;
+					es->linelen = 0;
+					putbuf(KSH_VERSION,
+					    strlen(KSH_VERSION), 0);
+					refresh(0);
+				}
+			}
+		}
+		break;
+
+	case VLIT:
+		if (is_bad(ch)) {
+			del_range(es->cursor, es->cursor + 1);
+			vi_error();
+		} else
+			es->cbuf[es->cursor++] = ch;
+		refresh(1);
+		state = VNORMAL;
+		break;
+
+	case VVERSION:
+		restore_cbuf();
+		state = VNORMAL;
+		refresh(0);
+		break;
+
+	case VARG1:
+		if (ksh_isdigit(ch))
+			argc1 = argc1 * 10 + ch - '0';
+		else {
+			curcmd[cmdlen++] = ch;
+			state = nextstate(ch);
+		}
+		break;
+
+	case VEXTCMD:
+		argc2 = 0;
+		if (ch >= '1' && ch <= '9') {
+			argc2 = ch - '0';
+			state = VARG2;
+			return (0);
+		} else {
+			curcmd[cmdlen++] = ch;
+			if (ch == curcmd[0])
+				state = VCMD;
+			else if (is_move(ch))
+				state = nextstate(ch);
+			else
+				state = VFAIL;
+		}
+		break;
+
+	case VARG2:
+		if (ksh_isdigit(ch))
+			argc2 = argc2 * 10 + ch - '0';
+		else {
+			if (argc1 == 0)
+				argc1 = argc2;
+			else
+				argc1 *= argc2;
+			curcmd[cmdlen++] = ch;
+			if (ch == curcmd[0])
+				state = VCMD;
+			else if (is_move(ch))
+				state = nextstate(ch);
+			else
+				state = VFAIL;
+		}
+		break;
+
+	case VXCH:
+		if (ch == Ctrl('['))
+			state = VNORMAL;
+		else {
+			curcmd[cmdlen++] = ch;
+			state = VCMD;
+		}
+		break;
+
+	case VSEARCH:
+		if (ch == '\r' || ch == '\n' /*|| ch == Ctrl('[')*/ ) {
+			restore_cbuf();
+			/* Repeat last search? */
+			if (srchlen == 0) {
+				if (!srchpat[0]) {
+					vi_error();
+					state = VNORMAL;
+					refresh(0);
+					return (0);
+				}
+			} else {
+				locpat[srchlen] = '\0';
+				memcpy(srchpat, locpat, srchlen + 1);
+			}
+			state = VCMD;
+		} else if (ch == edchars.erase || ch == Ctrl('h')) {
+			if (srchlen != 0) {
+				srchlen--;
+				es->linelen -= char_len((unsigned char)locpat[srchlen]);
+				es->cursor = es->linelen;
+				refresh(0);
+				return (0);
+			}
+			restore_cbuf();
+			state = VNORMAL;
+			refresh(0);
+		} else if (ch == edchars.kill) {
+			srchlen = 0;
+			es->linelen = 1;
+			es->cursor = 1;
+			refresh(0);
+			return (0);
+		} else if (ch == edchars.werase) {
+			int i, n = srchlen;
+			struct edstate new_es, *save_es;
+
+			new_es.cursor = n;
+			new_es.cbuf = locpat;
+
+			save_es = es;
+			es = &new_es;
+			n = backword(1);
+			es = save_es;
+
+			for (i = srchlen; --i >= n; )
+				es->linelen -= char_len((unsigned char)locpat[i]);
+			srchlen = n;
+			es->cursor = es->linelen;
+			refresh(0);
+			return (0);
+		} else {
+			if (srchlen == SRCHLEN - 1)
+				vi_error();
+			else {
+				locpat[srchlen++] = ch;
+				if (ch < ' ' || ch == 0x7f) {
+					if (es->linelen + 2 > es->cbufsize)
+						vi_error();
+					es->cbuf[es->linelen++] = '^';
+					es->cbuf[es->linelen++] = ch ^ '@';
+				} else {
+					if (es->linelen >= es->cbufsize)
+						vi_error();
+					es->cbuf[es->linelen++] = ch;
+				}
+				es->cursor = es->linelen;
+				refresh(0);
+			}
+			return (0);
+		}
+		break;
+	}
+
+	switch (state) {
+	case VCMD:
+		state = VNORMAL;
+		switch (vi_cmd(argc1, curcmd)) {
+		case -1:
+			vi_error();
+			refresh(0);
+			break;
+		case 0:
+			if (insert != 0)
+				inslen = 0;
+			refresh(insert != 0);
+			break;
+		case 1:
+			refresh(0);
+			return (1);
+		case 2:
+			/* back from a 'v' command - don't redraw the screen */
+			return (1);
+		}
+		break;
+
+	case VREDO:
+		state = VNORMAL;
+		if (argc1 != 0)
+			lastac = argc1;
+		switch (vi_cmd(lastac, lastcmd)) {
+		case -1:
+			vi_error();
+			refresh(0);
+			break;
+		case 0:
+			if (insert != 0) {
+				if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
+				    lastcmd[0] == 'C') {
+					if (redo_insert(1) != 0)
+						vi_error();
+				} else {
+					if (redo_insert(lastac) != 0)
+						vi_error();
+				}
+			}
+			refresh(0);
+			break;
+		case 1:
+			refresh(0);
+			return (1);
+		case 2:
+			/* back from a 'v' command - can't happen */
+			break;
+		}
+		break;
+
+	case VFAIL:
+		state = VNORMAL;
+		vi_error();
+		break;
+	}
+	return (0);
+}
+
+static int
+nextstate(int ch)
+{
+	if (is_extend(ch))
+		return (VEXTCMD);
+	else if (is_srch(ch))
+		return (VSEARCH);
+	else if (is_long(ch))
+		return (VXCH);
+	else if (ch == '.')
+		return (VREDO);
+	else if (ch == Ctrl('v'))
+		return (VVERSION);
+	else if (is_cmd(ch))
+		return (VCMD);
+	else
+		return (VFAIL);
+}
+
+static int
+vi_insert(int ch)
+{
+	int tcursor;
+
+	if (ch == edchars.erase || ch == Ctrl('h')) {
+		if (insert == REPLACE) {
+			if (es->cursor == undo->cursor) {
+				vi_error();
+				return (0);
+			}
+			if (inslen > 0)
+				inslen--;
+			es->cursor--;
+			if (es->cursor >= undo->linelen)
+				es->linelen--;
+			else
+				es->cbuf[es->cursor] = undo->cbuf[es->cursor];
+		} else {
+			if (es->cursor == 0)
+				return (0);
+			if (inslen > 0)
+				inslen--;
+			es->cursor--;
+			es->linelen--;
+			memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor + 1],
+			    es->linelen - es->cursor + 1);
+		}
+		expanded = NONE;
+		return (0);
+	}
+	if (ch == edchars.kill) {
+		if (es->cursor != 0) {
+			inslen = 0;
+			memmove(es->cbuf, &es->cbuf[es->cursor],
+			    es->linelen - es->cursor);
+			es->linelen -= es->cursor;
+			es->cursor = 0;
+		}
+		expanded = NONE;
+		return (0);
+	}
+	if (ch == edchars.werase) {
+		if (es->cursor != 0) {
+			tcursor = backword(1);
+			memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor],
+			    es->linelen - es->cursor);
+			es->linelen -= es->cursor - tcursor;
+			if (inslen < es->cursor - tcursor)
+				inslen = 0;
+			else
+				inslen -= es->cursor - tcursor;
+			es->cursor = tcursor;
+		}
+		expanded = NONE;
+		return (0);
+	}
+	/* If any chars are entered before escape, trash the saved insert
+	 * buffer (if user inserts & deletes char, ibuf gets trashed and
+	 * we don't want to use it)
+	 */
+	if (first_insert && ch != Ctrl('['))
+		saved_inslen = 0;
+	switch (ch) {
+	case '\0':
+		return (-1);
+
+	case '\r':
+	case '\n':
+		return (1);
+
+	case Ctrl('['):
+		expanded = NONE;
+		if (first_insert) {
+			first_insert = 0;
+			if (inslen == 0) {
+				inslen = saved_inslen;
+				return (redo_insert(0));
+			}
+			lastcmd[0] = 'a';
+			lastac = 1;
+		}
+		if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
+		    lastcmd[0] == 'C')
+			return (redo_insert(0));
+		else
+			return (redo_insert(lastac - 1));
+
+	/* { Begin nonstandard vi commands */
+	case Ctrl('x'):
+		expand_word(0);
+		break;
+
+	case Ctrl('f'):
+		complete_word(0, 0);
+		break;
+
+	case Ctrl('e'):
+		print_expansions(es, 0);
+		break;
+
+	case Ctrl('i'):
+		if (Flag(FVITABCOMPLETE)) {
+			complete_word(0, 0);
+			break;
+		}
+		/* FALLTHROUGH */
+	/* End nonstandard vi commands } */
+
+	default:
+		if (es->linelen >= es->cbufsize - 1)
+			return (-1);
+		ibuf[inslen++] = ch;
+		if (insert == INSERT) {
+			memmove(&es->cbuf[es->cursor + 1], &es->cbuf[es->cursor],
+			    es->linelen - es->cursor);
+			es->linelen++;
+		}
+		es->cbuf[es->cursor++] = ch;
+		if (insert == REPLACE && es->cursor > es->linelen)
+			es->linelen++;
+		expanded = NONE;
+	}
+	return (0);
+}
+
+static int
+vi_cmd(int argcnt, const char *cmd)
+{
+	int ncursor;
+	int cur, c1, c2, c3 = 0;
+	int any;
+	struct edstate *t;
+
+	if (argcnt == 0 && !is_zerocount(*cmd))
+		argcnt = 1;
+
+	if (is_move(*cmd)) {
+		if ((cur = domove(argcnt, cmd, 0)) >= 0) {
+			if (cur == es->linelen && cur != 0)
+				cur--;
+			es->cursor = cur;
+		} else
+			return (-1);
+	} else {
+		/* Don't save state in middle of macro.. */
+		if (is_undoable(*cmd) && !macro.p) {
+			undo->winleft = es->winleft;
+			memmove(undo->cbuf, es->cbuf, es->linelen);
+			undo->linelen = es->linelen;
+			undo->cursor = es->cursor;
+			lastac = argcnt;
+			memmove(lastcmd, cmd, MAXVICMD);
+		}
+		switch (*cmd) {
+
+		case Ctrl('l'):
+		case Ctrl('r'):
+			redraw_line(1);
+			break;
+
+		case '@':
+			{
+				static char alias[] = "_\0";
+				struct tbl *ap;
+				int olen, nlen;
+				char *p, *nbuf;
+
+				/* lookup letter in alias list... */
+				alias[1] = cmd[1];
+				ap = ktsearch(&aliases, alias, hash(alias));
+				if (!cmd[1] || !ap || !(ap->flag & ISSET))
+					return (-1);
+				/* check if this is a recursive call... */
+				if ((p = (char *)macro.p))
+					while ((p = strnul(p)) && p[1])
+						if (*++p == cmd[1])
+							return (-1);
+				/* insert alias into macro buffer */
+				nlen = strlen(ap->val.s) + 1;
+				olen = !macro.p ? 2 :
+				    macro.len - (macro.p - macro.buf);
+				nbuf = alloc(nlen + 1 + olen, APERM);
+				memcpy(nbuf, ap->val.s, nlen);
+				nbuf[nlen++] = cmd[1];
+				if (macro.p) {
+					memcpy(nbuf + nlen, macro.p, olen);
+					afree(macro.buf, APERM);
+					nlen += olen;
+				} else {
+					nbuf[nlen++] = '\0';
+					nbuf[nlen++] = '\0';
+				}
+				macro.p = macro.buf = (unsigned char *)nbuf;
+				macro.len = nlen;
+			}
+			break;
+
+		case 'a':
+			modified = 1;
+			hnum = hlast;
+			if (es->linelen != 0)
+				es->cursor++;
+			insert = INSERT;
+			break;
+
+		case 'A':
+			modified = 1;
+			hnum = hlast;
+			del_range(0, 0);
+			es->cursor = es->linelen;
+			insert = INSERT;
+			break;
+
+		case 'S':
+			es->cursor = domove(1, "^", 1);
+			del_range(es->cursor, es->linelen);
+			modified = 1;
+			hnum = hlast;
+			insert = INSERT;
+			break;
+
+		case 'Y':
+			cmd = "y$";
+			/* ahhhhhh... */
+		case 'c':
+		case 'd':
+		case 'y':
+			if (*cmd == cmd[1]) {
+				c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
+				c2 = es->linelen;
+			} else if (!is_move(cmd[1]))
+				return (-1);
+			else {
+				if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
+					return (-1);
+				if (*cmd == 'c' &&
+				    (cmd[1] == 'w' || cmd[1] == 'W') &&
+				    !ksh_isspace(es->cbuf[es->cursor])) {
+					do {
+						--ncursor;
+					} while (ksh_isspace(es->cbuf[ncursor]));
+					ncursor++;
+				}
+				if (ncursor > es->cursor) {
+					c1 = es->cursor;
+					c2 = ncursor;
+				} else {
+					c1 = ncursor;
+					c2 = es->cursor;
+					if (cmd[1] == '%')
+						c2++;
+				}
+			}
+			if (*cmd != 'c' && c1 != c2)
+				yank_range(c1, c2);
+			if (*cmd != 'y') {
+				del_range(c1, c2);
+				es->cursor = c1;
+			}
+			if (*cmd == 'c') {
+				modified = 1;
+				hnum = hlast;
+				insert = INSERT;
+			}
+			break;
+
+		case 'p':
+			modified = 1;
+			hnum = hlast;
+			if (es->linelen != 0)
+				es->cursor++;
+			while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
+				;
+			if (es->cursor != 0)
+				es->cursor--;
+			if (argcnt != 0)
+				return (-1);
+			break;
+
+		case 'P':
+			modified = 1;
+			hnum = hlast;
+			any = 0;
+			while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
+				any = 1;
+			if (any && es->cursor != 0)
+				es->cursor--;
+			if (argcnt != 0)
+				return (-1);
+			break;
+
+		case 'C':
+			modified = 1;
+			hnum = hlast;
+			del_range(es->cursor, es->linelen);
+			insert = INSERT;
+			break;
+
+		case 'D':
+			yank_range(es->cursor, es->linelen);
+			del_range(es->cursor, es->linelen);
+			if (es->cursor != 0)
+				es->cursor--;
+			break;
+
+		case 'g':
+			if (!argcnt)
+				argcnt = hlast;
+			/* FALLTHROUGH */
+		case 'G':
+			if (!argcnt)
+				argcnt = 1;
+			else
+				argcnt = hlast - (source->line - argcnt);
+			if (grabhist(modified, argcnt - 1) < 0)
+				return (-1);
+			else {
+				modified = 0;
+				hnum = argcnt - 1;
+			}
+			break;
+
+		case 'i':
+			modified = 1;
+			hnum = hlast;
+			insert = INSERT;
+			break;
+
+		case 'I':
+			modified = 1;
+			hnum = hlast;
+			es->cursor = domove(1, "^", 1);
+			insert = INSERT;
+			break;
+
+		case 'j':
+		case '+':
+		case Ctrl('n'):
+			if (grabhist(modified, hnum + argcnt) < 0)
+				return (-1);
+			else {
+				modified = 0;
+				hnum += argcnt;
+			}
+			break;
+
+		case 'k':
+		case '-':
+		case Ctrl('p'):
+			if (grabhist(modified, hnum - argcnt) < 0)
+				return (-1);
+			else {
+				modified = 0;
+				hnum -= argcnt;
+			}
+			break;
+
+		case 'r':
+			if (es->linelen == 0)
+				return (-1);
+			modified = 1;
+			hnum = hlast;
+			if (cmd[1] == 0)
+				vi_error();
+			else {
+				int n;
+
+				if (es->cursor + argcnt > es->linelen)
+					return (-1);
+				for (n = 0; n < argcnt; ++n)
+					es->cbuf[es->cursor + n] = cmd[1];
+				es->cursor += n - 1;
+			}
+			break;
+
+		case 'R':
+			modified = 1;
+			hnum = hlast;
+			insert = REPLACE;
+			break;
+
+		case 's':
+			if (es->linelen == 0)
+				return (-1);
+			modified = 1;
+			hnum = hlast;
+			if (es->cursor + argcnt > es->linelen)
+				argcnt = es->linelen - es->cursor;
+			del_range(es->cursor, es->cursor + argcnt);
+			insert = INSERT;
+			break;
+
+		case 'v':
+			if (!argcnt) {
+				if (es->linelen == 0)
+					return (-1);
+				if (modified) {
+					es->cbuf[es->linelen] = '\0';
+					histsave(&source->line, es->cbuf, true,
+					    true);
+				} else
+					argcnt = source->line + 1 -
+					    (hlast - hnum);
+			}
+			if (argcnt)
+				shf_snprintf(es->cbuf, es->cbufsize, "%s %d",
+				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
+				    argcnt);
+			else
+				strlcpy(es->cbuf,
+				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
+				    es->cbufsize);
+			es->linelen = strlen(es->cbuf);
+			return (2);
+
+		case 'x':
+			if (es->linelen == 0)
+				return (-1);
+			modified = 1;
+			hnum = hlast;
+			if (es->cursor + argcnt > es->linelen)
+				argcnt = es->linelen - es->cursor;
+			yank_range(es->cursor, es->cursor + argcnt);
+			del_range(es->cursor, es->cursor + argcnt);
+			break;
+
+		case 'X':
+			if (es->cursor > 0) {
+				modified = 1;
+				hnum = hlast;
+				if (es->cursor < argcnt)
+					argcnt = es->cursor;
+				yank_range(es->cursor - argcnt, es->cursor);
+				del_range(es->cursor - argcnt, es->cursor);
+				es->cursor -= argcnt;
+			} else
+				return (-1);
+			break;
+
+		case 'u':
+			t = es;
+			es = undo;
+			undo = t;
+			break;
+
+		case 'U':
+			if (!modified)
+				return (-1);
+			if (grabhist(modified, ohnum) < 0)
+				return (-1);
+			modified = 0;
+			hnum = ohnum;
+			break;
+
+		case '?':
+			if (hnum == hlast)
+				hnum = -1;
+			/* ahhh */
+		case '/':
+			c3 = 1;
+			srchlen = 0;
+			lastsearch = *cmd;
+			/* FALLTHROUGH */
+		case 'n':
+		case 'N':
+			if (lastsearch == ' ')
+				return (-1);
+			if (lastsearch == '?')
+				c1 = 1;
+			else
+				c1 = 0;
+			if (*cmd == 'N')
+				c1 = !c1;
+			if ((c2 = grabsearch(modified, hnum,
+			    c1, srchpat)) < 0) {
+				if (c3) {
+					restore_cbuf();
+					refresh(0);
+				}
+				return (-1);
+			} else {
+				modified = 0;
+				hnum = c2;
+				ohnum = hnum;
+			}
+			break;
+		case '_':
+			{
+				int inspace;
+				char *p, *sp;
+
+				if (histnum(-1) < 0)
+					return (-1);
+				p = *histpos();
+#define issp(c)		(ksh_isspace(c) || (c) == '\n')
+				if (argcnt) {
+					while (*p && issp(*p))
+						p++;
+					while (*p && --argcnt) {
+						while (*p && !issp(*p))
+							p++;
+						while (*p && issp(*p))
+							p++;
+					}
+					if (!*p)
+						return (-1);
+					sp = p;
+				} else {
+					sp = p;
+					inspace = 0;
+					while (*p) {
+						if (issp(*p))
+							inspace = 1;
+						else if (inspace) {
+							inspace = 0;
+							sp = p;
+						}
+						p++;
+					}
+					p = sp;
+				}
+				modified = 1;
+				hnum = hlast;
+				if (es->cursor != es->linelen)
+					es->cursor++;
+				while (*p && !issp(*p)) {
+					argcnt++;
+					p++;
+				}
+				if (putbuf(" ", 1, 0) != 0)
+					argcnt = -1;
+				else if (putbuf(sp, argcnt, 0) != 0)
+					argcnt = -1;
+				if (argcnt < 0) {
+					if (es->cursor != 0)
+						es->cursor--;
+					return (-1);
+				}
+				insert = INSERT;
+			}
+			break;
+
+		case '~':
+			{
+				char *p;
+				int i;
+
+				if (es->linelen == 0)
+					return (-1);
+				for (i = 0; i < argcnt; i++) {
+					p = &es->cbuf[es->cursor];
+					if (ksh_islower(*p)) {
+						modified = 1;
+						hnum = hlast;
+						*p = ksh_toupper(*p);
+					} else if (ksh_isupper(*p)) {
+						modified = 1;
+						hnum = hlast;
+						*p = ksh_tolower(*p);
+					}
+					if (es->cursor < es->linelen - 1)
+						es->cursor++;
+				}
+				break;
+			}
+
+		case '#':
+			{
+				int ret = x_do_comment(es->cbuf, es->cbufsize,
+				    &es->linelen);
+				if (ret >= 0)
+					es->cursor = 0;
+				return (ret);
+			}
+
+		case '=':			/* AT&T ksh */
+		case Ctrl('e'):			/* Nonstandard vi/ksh */
+			print_expansions(es, 1);
+			break;
+
+
+		case Ctrl('i'):			/* Nonstandard vi/ksh */
+			if (!Flag(FVITABCOMPLETE))
+				return (-1);
+			complete_word(1, argcnt);
+			break;
+
+		case Ctrl('['):			/* some annoying AT&T kshs */
+			if (!Flag(FVIESCCOMPLETE))
+				return (-1);
+		case '\\':			/* AT&T ksh */
+		case Ctrl('f'):			/* Nonstandard vi/ksh */
+			complete_word(1, argcnt);
+			break;
+
+
+		case '*':			/* AT&T ksh */
+		case Ctrl('x'):			/* Nonstandard vi/ksh */
+			expand_word(1);
+			break;
+		}
+		if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
+			es->cursor--;
+	}
+	return (0);
+}
+
+static int
+domove(int argcnt, const char *cmd, int sub)
+{
+	int bcount, i = 0, t;
+	int ncursor = 0;
+
+	switch (*cmd) {
+	case 'b':
+		if (!sub && es->cursor == 0)
+			return (-1);
+		ncursor = backword(argcnt);
+		break;
+
+	case 'B':
+		if (!sub && es->cursor == 0)
+			return (-1);
+		ncursor = Backword(argcnt);
+		break;
+
+	case 'e':
+		if (!sub && es->cursor + 1 >= es->linelen)
+			return (-1);
+		ncursor = endword(argcnt);
+		if (sub && ncursor < es->linelen)
+			ncursor++;
+		break;
+
+	case 'E':
+		if (!sub && es->cursor + 1 >= es->linelen)
+			return (-1);
+		ncursor = Endword(argcnt);
+		if (sub && ncursor < es->linelen)
+			ncursor++;
+		break;
+
+	case 'f':
+	case 'F':
+	case 't':
+	case 'T':
+		fsavecmd = *cmd;
+		fsavech = cmd[1];
+		/* drop through */
+
+	case ',':
+	case ';':
+		if (fsavecmd == ' ')
+			return (-1);
+		i = fsavecmd == 'f' || fsavecmd == 'F';
+		t = fsavecmd > 'a';
+		if (*cmd == ',')
+			t = !t;
+		if ((ncursor = findch(fsavech, argcnt, t, i)) < 0)
+			return (-1);
+		if (sub && t)
+			ncursor++;
+		break;
+
+	case 'h':
+	case Ctrl('h'):
+		if (!sub && es->cursor == 0)
+			return (-1);
+		ncursor = es->cursor - argcnt;
+		if (ncursor < 0)
+			ncursor = 0;
+		break;
+
+	case ' ':
+	case 'l':
+		if (!sub && es->cursor + 1 >= es->linelen)
+			return (-1);
+		if (es->linelen != 0) {
+			ncursor = es->cursor + argcnt;
+			if (ncursor > es->linelen)
+				ncursor = es->linelen;
+		}
+		break;
+
+	case 'w':
+		if (!sub && es->cursor + 1 >= es->linelen)
+			return (-1);
+		ncursor = forwword(argcnt);
+		break;
+
+	case 'W':
+		if (!sub && es->cursor + 1 >= es->linelen)
+			return (-1);
+		ncursor = Forwword(argcnt);
+		break;
+
+	case '0':
+		ncursor = 0;
+		break;
+
+	case '^':
+		ncursor = 0;
+		while (ncursor < es->linelen - 1 &&
+		    ksh_isspace(es->cbuf[ncursor]))
+			ncursor++;
+		break;
+
+	case '|':
+		ncursor = argcnt;
+		if (ncursor > es->linelen)
+			ncursor = es->linelen;
+		if (ncursor)
+			ncursor--;
+		break;
+
+	case '$':
+		if (es->linelen != 0)
+			ncursor = es->linelen;
+		else
+			ncursor = 0;
+		break;
+
+	case '%':
+		ncursor = es->cursor;
+		while (ncursor < es->linelen &&
+		    (i = bracktype(es->cbuf[ncursor])) == 0)
+			ncursor++;
+		if (ncursor == es->linelen)
+			return (-1);
+		bcount = 1;
+		do {
+			if (i > 0) {
+				if (++ncursor >= es->linelen)
+					return (-1);
+			} else {
+				if (--ncursor < 0)
+					return (-1);
+			}
+			t = bracktype(es->cbuf[ncursor]);
+			if (t == i)
+				bcount++;
+			else if (t == -i)
+				bcount--;
+		} while (bcount != 0);
+		if (sub && i > 0)
+			ncursor++;
+		break;
+
+	default:
+		return (-1);
+	}
+	return (ncursor);
+}
+
+static int
+redo_insert(int count)
+{
+	while (count-- > 0)
+		if (putbuf(ibuf, inslen, insert == REPLACE) != 0)
+			return (-1);
+	if (es->cursor > 0)
+		es->cursor--;
+	insert = 0;
+	return (0);
+}
+
+static void
+yank_range(int a, int b)
+{
+	yanklen = b - a;
+	if (yanklen != 0)
+		memmove(ybuf, &es->cbuf[a], yanklen);
+}
+
+static int
+bracktype(int ch)
+{
+	switch (ch) {
+
+	case '(':
+		return (1);
+
+	case '[':
+		return (2);
+
+	case '{':
+		return (3);
+
+	case ')':
+		return (-1);
+
+	case ']':
+		return (-2);
+
+	case '}':
+		return (-3);
+
+	default:
+		return (0);
+	}
+}
+
+/*
+ *	Non user interface editor routines below here
+ */
+
+static void
+save_cbuf(void)
+{
+	memmove(holdbuf, es->cbuf, es->linelen);
+	holdlen = es->linelen;
+	holdbuf[holdlen] = '\0';
+}
+
+static void
+restore_cbuf(void)
+{
+	es->cursor = 0;
+	es->linelen = holdlen;
+	memmove(es->cbuf, holdbuf, holdlen);
+}
+
+/* return a new edstate */
+static struct edstate *
+save_edstate(struct edstate *old)
+{
+	struct edstate *news;
+
+	news = alloc(sizeof(struct edstate), APERM);
+	news->cbuf = alloc(old->cbufsize, APERM);
+	memcpy(news->cbuf, old->cbuf, old->linelen);
+	news->cbufsize = old->cbufsize;
+	news->linelen = old->linelen;
+	news->cursor = old->cursor;
+	news->winleft = old->winleft;
+	return (news);
+}
+
+static void
+restore_edstate(struct edstate *news, struct edstate *old)
+{
+	memcpy(news->cbuf, old->cbuf, old->linelen);
+	news->linelen = old->linelen;
+	news->cursor = old->cursor;
+	news->winleft = old->winleft;
+	free_edstate(old);
+}
+
+static void
+free_edstate(struct edstate *old)
+{
+	afree(old->cbuf, APERM);
+	afree(old, APERM);
+}
+
+/*
+ * this is used for calling x_escape() in complete_word()
+ */
+static int
+x_vi_putbuf(const char *s, size_t len)
+{
+	return (putbuf(s, len, 0));
+}
+
+static int
+putbuf(const char *buf, int len, int repl)
+{
+	if (len == 0)
+		return (0);
+	if (repl) {
+		if (es->cursor + len >= es->cbufsize)
+			return (-1);
+		if (es->cursor + len > es->linelen)
+			es->linelen = es->cursor + len;
+	} else {
+		if (es->linelen + len >= es->cbufsize)
+			return (-1);
+		memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
+		    es->linelen - es->cursor);
+		es->linelen += len;
+	}
+	memmove(&es->cbuf[es->cursor], buf, len);
+	es->cursor += len;
+	return (0);
+}
+
+static void
+del_range(int a, int b)
+{
+	if (es->linelen != b)
+		memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
+	es->linelen -= b - a;
+}
+
+static int
+findch(int ch, int cnt, int forw, int incl)
+{
+	int ncursor;
+
+	if (es->linelen == 0)
+		return (-1);
+	ncursor = es->cursor;
+	while (cnt--) {
+		do {
+			if (forw) {
+				if (++ncursor == es->linelen)
+					return (-1);
+			} else {
+				if (--ncursor < 0)
+					return (-1);
+			}
+		} while (es->cbuf[ncursor] != ch);
+	}
+	if (!incl) {
+		if (forw)
+			ncursor--;
+		else
+			ncursor++;
+	}
+	return (ncursor);
+}
+
+static int
+forwword(int argcnt)
+{
+	int ncursor;
+
+	ncursor = es->cursor;
+	while (ncursor < es->linelen && argcnt--) {
+		if (ksh_isalnux(es->cbuf[ncursor]))
+			while (ksh_isalnux(es->cbuf[ncursor]) &&
+			    ncursor < es->linelen)
+				ncursor++;
+		else if (!ksh_isspace(es->cbuf[ncursor]))
+			while (!ksh_isalnux(es->cbuf[ncursor]) &&
+			    !ksh_isspace(es->cbuf[ncursor]) &&
+			    ncursor < es->linelen)
+				ncursor++;
+		while (ksh_isspace(es->cbuf[ncursor]) &&
+		    ncursor < es->linelen)
+			ncursor++;
+	}
+	return (ncursor);
+}
+
+static int
+backword(int argcnt)
+{
+	int ncursor;
+
+	ncursor = es->cursor;
+	while (ncursor > 0 && argcnt--) {
+		while (--ncursor > 0 && ksh_isspace(es->cbuf[ncursor]))
+			;
+		if (ncursor > 0) {
+			if (ksh_isalnux(es->cbuf[ncursor]))
+				while (--ncursor >= 0 &&
+				    ksh_isalnux(es->cbuf[ncursor]))
+					;
+			else
+				while (--ncursor >= 0 &&
+				    !ksh_isalnux(es->cbuf[ncursor]) &&
+				    !ksh_isspace(es->cbuf[ncursor]))
+					;
+			ncursor++;
+		}
+	}
+	return (ncursor);
+}
+
+static int
+endword(int argcnt)
+{
+	int ncursor;
+
+	ncursor = es->cursor;
+	while (ncursor < es->linelen && argcnt--) {
+		while (++ncursor < es->linelen - 1 &&
+		    ksh_isspace(es->cbuf[ncursor]))
+			;
+		if (ncursor < es->linelen - 1) {
+			if (ksh_isalnux(es->cbuf[ncursor]))
+				while (++ncursor < es->linelen &&
+				    ksh_isalnux(es->cbuf[ncursor]))
+					;
+			else
+				while (++ncursor < es->linelen &&
+				    !ksh_isalnux(es->cbuf[ncursor]) &&
+				    !ksh_isspace(es->cbuf[ncursor]))
+					;
+			ncursor--;
+		}
+	}
+	return (ncursor);
+}
+
+static int
+Forwword(int argcnt)
+{
+	int ncursor;
+
+	ncursor = es->cursor;
+	while (ncursor < es->linelen && argcnt--) {
+		while (!ksh_isspace(es->cbuf[ncursor]) &&
+		    ncursor < es->linelen)
+			ncursor++;
+		while (ksh_isspace(es->cbuf[ncursor]) &&
+		    ncursor < es->linelen)
+			ncursor++;
+	}
+	return (ncursor);
+}
+
+static int
+Backword(int argcnt)
+{
+	int ncursor;
+
+	ncursor = es->cursor;
+	while (ncursor > 0 && argcnt--) {
+		while (--ncursor >= 0 && ksh_isspace(es->cbuf[ncursor]))
+			;
+		while (ncursor >= 0 && !ksh_isspace(es->cbuf[ncursor]))
+			ncursor--;
+		ncursor++;
+	}
+	return (ncursor);
+}
+
+static int
+Endword(int argcnt)
+{
+	int ncursor;
+
+	ncursor = es->cursor;
+	while (ncursor < es->linelen - 1 && argcnt--) {
+		while (++ncursor < es->linelen - 1 &&
+		    ksh_isspace(es->cbuf[ncursor]))
+			;
+		if (ncursor < es->linelen - 1) {
+			while (++ncursor < es->linelen &&
+			    !ksh_isspace(es->cbuf[ncursor]))
+				;
+			ncursor--;
+		}
+	}
+	return (ncursor);
+}
+
+static int
+grabhist(int save, int n)
+{
+	char *hptr;
+
+	if (n < 0 || n > hlast)
+		return (-1);
+	if (n == hlast) {
+		restore_cbuf();
+		ohnum = n;
+		return (0);
+	}
+	(void)histnum(n);
+	if ((hptr = *histpos()) == NULL) {
+		internal_warningf("grabhist: bad history array");
+		return (-1);
+	}
+	if (save)
+		save_cbuf();
+	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
+		es->linelen = es->cbufsize - 1;
+	memmove(es->cbuf, hptr, es->linelen);
+	es->cursor = 0;
+	ohnum = n;
+	return (0);
+}
+
+static int
+grabsearch(int save, int start, int fwd, char *pat)
+{
+	char *hptr;
+	int hist;
+	int anchored;
+
+	if ((start == 0 && fwd == 0) || (start >= hlast - 1 && fwd == 1))
+		return (-1);
+	if (fwd)
+		start++;
+	else
+		start--;
+	anchored = *pat == '^' ? (++pat, 1) : 0;
+	if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
+		/* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
+		/* XXX should strcmp be strncmp? */
+		if (start != 0 && fwd && strcmp(holdbuf, pat) >= 0) {
+			restore_cbuf();
+			return (0);
+		} else
+			return (-1);
+	}
+	if (save)
+		save_cbuf();
+	histnum(hist);
+	hptr = *histpos();
+	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
+		es->linelen = es->cbufsize - 1;
+	memmove(es->cbuf, hptr, es->linelen);
+	es->cursor = 0;
+	return (hist);
+}
+
+static void
+redraw_line(int newl)
+{
+	(void)memset(wbuf[win], ' ', wbuf_len);
+	if (newl) {
+		x_putc('\r');
+		x_putc('\n');
+	}
+	if (prompt_redraw)
+		pprompt(prompt, prompt_trunc);
+	cur_col = pwidth;
+	morec = ' ';
+}
+
+static void
+refresh(int leftside)
+{
+	if (leftside < 0)
+		leftside = lastref;
+	else
+		lastref = leftside;
+	if (outofwin())
+		rewindow();
+	display(wbuf[1 - win], wbuf[win], leftside);
+	win = 1 - win;
+}
+
+static int
+outofwin(void)
+{
+	int cur, col;
+
+	if (es->cursor < es->winleft)
+		return (1);
+	col = 0;
+	cur = es->winleft;
+	while (cur < es->cursor)
+		col = newcol((unsigned char)es->cbuf[cur++], col);
+	if (col >= winwidth)
+		return (1);
+	return (0);
+}
+
+static void
+rewindow(void)
+{
+	int tcur, tcol;
+	int holdcur1, holdcol1;
+	int holdcur2, holdcol2;
+
+	holdcur1 = holdcur2 = tcur = 0;
+	holdcol1 = holdcol2 = tcol = 0;
+	while (tcur < es->cursor) {
+		if (tcol - holdcol2 > winwidth / 2) {
+			holdcur1 = holdcur2;
+			holdcol1 = holdcol2;
+			holdcur2 = tcur;
+			holdcol2 = tcol;
+		}
+		tcol = newcol((unsigned char)es->cbuf[tcur++], tcol);
+	}
+	while (tcol - holdcol1 > winwidth / 2)
+		holdcol1 = newcol((unsigned char)es->cbuf[holdcur1++],
+		    holdcol1);
+	es->winleft = holdcur1;
+}
+
+static int
+newcol(int ch, int col)
+{
+	if (ch == '\t')
+		return ((col | 7) + 1);
+	return (col + char_len(ch));
+}
+
+static void
+display(char *wb1, char *wb2, int leftside)
+{
+	unsigned char ch;
+	char *twb1, *twb2, mc;
+	int cur, col, cnt;
+	int ncol = 0;
+	int moreright;
+
+	col = 0;
+	cur = es->winleft;
+	moreright = 0;
+	twb1 = wb1;
+	while (col < winwidth && cur < es->linelen) {
+		if (cur == es->cursor && leftside)
+			ncol = col + pwidth;
+		if ((ch = es->cbuf[cur]) == '\t')
+			do {
+				*twb1++ = ' ';
+			} while (++col < winwidth && (col & 7) != 0);
+		else if (col < winwidth) {
+			if (ch < ' ' || ch == 0x7f) {
+				*twb1++ = '^';
+				if (++col < winwidth) {
+					*twb1++ = ch ^ '@';
+					col++;
+				}
+			} else {
+				*twb1++ = ch;
+				col++;
+			}
+		}
+		if (cur == es->cursor && !leftside)
+			ncol = col + pwidth - 1;
+		cur++;
+	}
+	if (cur == es->cursor)
+		ncol = col + pwidth;
+	if (col < winwidth) {
+		while (col < winwidth) {
+			*twb1++ = ' ';
+			col++;
+		}
+	} else
+		moreright++;
+	*twb1 = ' ';
+
+	col = pwidth;
+	cnt = winwidth;
+	twb1 = wb1;
+	twb2 = wb2;
+	while (cnt--) {
+		if (*twb1 != *twb2) {
+			if (cur_col != col)
+				ed_mov_opt(col, wb1);
+			x_putc(*twb1);
+			cur_col++;
+		}
+		twb1++;
+		twb2++;
+		col++;
+	}
+	if (es->winleft > 0 && moreright)
+		/* POSIX says to use * for this but that is a globbing
+		 * character and may confuse people; + is more innocuous
+		 */
+		mc = '+';
+	else if (es->winleft > 0)
+		mc = '<';
+	else if (moreright)
+		mc = '>';
+	else
+		mc = ' ';
+	if (mc != morec) {
+		ed_mov_opt(pwidth + winwidth + 1, wb1);
+		x_putc(mc);
+		cur_col++;
+		morec = mc;
+	}
+	if (cur_col != ncol)
+		ed_mov_opt(ncol, wb1);
+}
+
+static void
+ed_mov_opt(int col, char *wb)
+{
+	if (col < cur_col) {
+		if (col + 1 < cur_col - col) {
+			x_putc('\r');
+			if (prompt_redraw)
+				pprompt(prompt, prompt_trunc);
+			cur_col = pwidth;
+			while (cur_col++ < col)
+				x_putcf(*wb++);
+		} else {
+			while (cur_col-- > col)
+				x_putc('\b');
+		}
+	} else {
+		wb = &wb[cur_col - pwidth];
+		while (cur_col++ < col)
+			x_putcf(*wb++);
+	}
+	cur_col = col;
+}
+
+
+/* replace word with all expansions (ie, expand word*) */
+static int
+expand_word(int cmd)
+{
+	static struct edstate *buf;
+	int rval = 0;
+	int nwords;
+	int start, end;
+	char **words;
+	int i;
+
+	/* Undo previous expansion */
+	if (cmd == 0 && expanded == EXPAND && buf) {
+		restore_edstate(es, buf);
+		buf = 0;
+		expanded = NONE;
+		return (0);
+	}
+	if (buf) {
+		free_edstate(buf);
+		buf = 0;
+	}
+
+	nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
+	    es->cbuf, es->linelen, es->cursor,
+	    &start, &end, &words, NULL);
+	if (nwords == 0) {
+		vi_error();
+		return (-1);
+	}
+
+	buf = save_edstate(es);
+	expanded = EXPAND;
+	del_range(start, end);
+	es->cursor = start;
+	for (i = 0; i < nwords; ) {
+		if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
+			rval = -1;
+			break;
+		}
+		if (++i < nwords && putbuf(" ", 1, 0) != 0) {
+			rval = -1;
+			break;
+		}
+	}
+	i = buf->cursor - end;
+	if (rval == 0 && i > 0)
+		es->cursor += i;
+	modified = 1;
+	hnum = hlast;
+	insert = INSERT;
+	lastac = 0;
+	refresh(0);
+	return (rval);
+}
+
+static int
+complete_word(int cmd, int count)
+{
+	static struct edstate *buf;
+	int rval, nwords, start, end, match_len;
+	char **words;
+	char *match;
+	bool is_command, is_unique;
+
+	/* Undo previous completion */
+	if (cmd == 0 && expanded == COMPLETE && buf) {
+		print_expansions(buf, 0);
+		expanded = PRINT;
+		return (0);
+	}
+	if (cmd == 0 && expanded == PRINT && buf) {
+		restore_edstate(es, buf);
+		buf = 0;
+		expanded = NONE;
+		return (0);
+	}
+	if (buf) {
+		free_edstate(buf);
+		buf = 0;
+	}
+
+	/* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
+	 * was done this way.
+	 */
+	nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
+	    es->cbuf, es->linelen, es->cursor,
+	    &start, &end, &words, &is_command);
+	if (nwords == 0) {
+		vi_error();
+		return (-1);
+	}
+	if (count) {
+		int i;
+
+		count--;
+		if (count >= nwords) {
+			vi_error();
+			x_print_expansions(nwords, words, is_command);
+			x_free_words(nwords, words);
+			redraw_line(0);
+			return (-1);
+		}
+		/*
+		 * Expand the count'th word to its basename
+		 */
+		if (is_command) {
+			match = words[count] +
+			    x_basename(words[count], NULL);
+			/* If more than one possible match, use full path */
+			for (i = 0; i < nwords; i++)
+				if (i != count &&
+				    strcmp(words[i] + x_basename(words[i],
+				    NULL), match) == 0) {
+					match = words[count];
+					break;
+				}
+		} else
+			match = words[count];
+		match_len = strlen(match);
+		is_unique = true;
+		/* expanded = PRINT;	next call undo */
+	} else {
+		match = words[0];
+		match_len = x_longest_prefix(nwords, words);
+		expanded = COMPLETE;	/* next call will list completions */
+		is_unique = nwords == 1;
+	}
+
+	buf = save_edstate(es);
+	del_range(start, end);
+	es->cursor = start;
+
+	/* escape all shell-sensitive characters and put the result into
+	 * command buffer */
+	rval = x_escape(match, match_len, x_vi_putbuf);
+
+	if (rval == 0 && is_unique) {
+		/* If exact match, don't undo. Allows directory completions
+		 * to be used (ie, complete the next portion of the path).
+		 */
+		expanded = NONE;
+
+		/* If not a directory, add a space to the end... */
+		if (match_len > 0 && match[match_len - 1] != '/')
+			rval = putbuf(" ", 1, 0);
+	}
+	x_free_words(nwords, words);
+
+	modified = 1;
+	hnum = hlast;
+	insert = INSERT;
+	lastac = 0;	 /* prevent this from being redone... */
+	refresh(0);
+
+	return (rval);
+}
+
+static int
+print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED)
+{
+	int start, end, nwords;
+	char **words;
+	bool is_command;
+
+	nwords = x_cf_glob(XCF_COMMAND_FILE | XCF_FULLPATH,
+	    est->cbuf, est->linelen, est->cursor,
+	    &start, &end, &words, &is_command);
+	if (nwords == 0) {
+		vi_error();
+		return (-1);
+	}
+	x_print_expansions(nwords, words, is_command);
+	x_free_words(nwords, words);
+	redraw_line(0);
+	return (0);
+}
+
+/* Similar to x_zotc(emacs.c), but no tab weirdness */
+static void
+x_vi_zotc(int c)
+{
+	if (c < ' ' || c == 0x7f) {
+		x_putc('^');
+		c ^= '@';
+	}
+	x_putc(c);
+}
+
+static void
+vi_error(void)
+{
+	/* Beem out of any macros as soon as an error occurs */
+	vi_macro_reset();
+	x_putc(7);
+	x_flush();
+}
+
+static void
+vi_macro_reset(void)
+{
+	if (macro.p) {
+		afree(macro.buf, APERM);
+		memset((char *)&macro, 0, sizeof(macro));
+	}
+}
+#endif /* !MKSH_S_NOVI */
diff --git a/mksh/src/emacsfn.h b/mksh/src/emacsfn.h
new file mode 100644
index 0000000..1333399
--- /dev/null
+++ b/mksh/src/emacsfn.h
@@ -0,0 +1,89 @@
+#if defined(EMACSFN_DEFNS)
+__RCSID("$MirOS: src/bin/mksh/emacsfn.h,v 1.5 2010/07/17 22:09:33 tg Exp $");
+#define FN(cname,sname,flags)	static int x_##cname(int);
+#elif defined(EMACSFN_ENUMS)
+#define FN(cname,sname,flags)	XFUNC_##cname,
+#define F0(cname,sname,flags)	XFUNC_##cname = 0,
+#elif defined(EMACSFN_ITEMS)
+#define FN(cname,sname,flags)	{ x_##cname, sname, flags },
+#endif
+
+#ifndef F0
+#define F0 FN
+#endif
+
+F0(abort, "abort", 0)
+FN(beg_hist, "beginning-of-history", 0)
+FN(cls, "clear-screen", 0)
+FN(comment, "comment", 0)
+FN(comp_comm, "complete-command", 0)
+FN(comp_file, "complete-file", 0)
+FN(comp_list, "complete-list", 0)
+FN(complete, "complete", 0)
+FN(del_back, "delete-char-backward", XF_ARG)
+FN(del_bword, "delete-word-backward", XF_ARG)
+FN(del_char, "delete-char-forward", XF_ARG)
+FN(del_fword, "delete-word-forward", XF_ARG)
+FN(del_line, "kill-line", 0)
+FN(draw_line, "redraw", 0)
+#ifndef MKSH_SMALL
+FN(edit_line, "edit-line", XF_ARG)
+#endif
+FN(end_hist, "end-of-history", 0)
+FN(end_of_text, "eot", 0)
+FN(enumerate, "list", 0)
+FN(eot_del, "eot-or-delete", XF_ARG)
+FN(error, "error", 0)
+FN(expand, "expand-file", 0)
+#ifndef MKSH_SMALL
+FN(fold_capitalise, "capitalize-word", XF_ARG)
+FN(fold_lower, "downcase-word", XF_ARG)
+FN(fold_upper, "upcase-word", XF_ARG)
+#endif
+FN(goto_hist, "goto-history", XF_ARG)
+#ifndef MKSH_SMALL
+FN(ins_string, "macro-string", XF_NOBIND)
+#endif
+FN(insert, "auto-insert", XF_ARG)
+FN(kill, "kill-to-eol", XF_ARG)
+FN(kill_region, "kill-region", 0)
+FN(list_comm, "list-command", 0)
+FN(list_file, "list-file", 0)
+FN(literal, "quote", 0)
+FN(meta1, "prefix-1", XF_PREFIX)
+FN(meta2, "prefix-2", XF_PREFIX)
+FN(meta_yank, "yank-pop", 0)
+FN(mv_back, "backward-char", XF_ARG)
+FN(mv_begin, "beginning-of-line", 0)
+FN(mv_bword, "backward-word", XF_ARG)
+FN(mv_end, "end-of-line", 0)
+FN(mv_forw, "forward-char", XF_ARG)
+FN(mv_fword, "forward-word", XF_ARG)
+FN(newline, "newline", 0)
+FN(next_com, "down-history", XF_ARG)
+FN(nl_next_com, "newline-and-next", 0)
+FN(noop, "no-op", 0)
+FN(prev_com, "up-history", XF_ARG)
+FN(prev_histword, "prev-hist-word", XF_ARG)
+FN(search_char_back, "search-character-backward", XF_ARG)
+FN(search_char_forw, "search-character-forward", XF_ARG)
+FN(search_hist, "search-history", 0)
+#ifndef MKSH_SMALL
+FN(search_hist_dn, "search-history-down", 0)
+FN(search_hist_up, "search-history-up", 0)
+#endif
+FN(set_arg, "set-arg", XF_NOBIND)
+FN(set_mark, "set-mark-command", 0)
+FN(transpose, "transpose-chars", 0)
+FN(version, "version", 0)
+#ifndef MKSH_SMALL
+FN(vt_hack, "vt100-hack", XF_ARG)
+#endif
+FN(xchg_point_mark, "exchange-point-and-mark", 0)
+FN(yank, "yank", 0)
+
+#undef FN
+#undef F0
+#undef EMACSFN_DEFNS
+#undef EMACSFN_ENUMS
+#undef EMACSFN_ITEMS
diff --git a/mksh/src/eval.c b/mksh/src/eval.c
new file mode 100644
index 0000000..c22e346
--- /dev/null
+++ b/mksh/src/eval.c
@@ -0,0 +1,1580 @@
+/*	$OpenBSD: eval.c,v 1.35 2010/03/24 08:27:26 fgsch Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.90 2010/07/17 22:09:33 tg Exp $");
+
+/*
+ * string expansion
+ *
+ * first pass: quoting, IFS separation, ~, ${}, $() and $(()) substitution.
+ * second pass: alternation ({,}), filename expansion (*?[]).
+ */
+
+/* expansion generator state */
+typedef struct Expand {
+	/* int type; */			/* see expand() */
+	const char *str;		/* string */
+	union {
+		const char **strv;	/* string[] */
+		struct shf *shf;	/* file */
+	} u;				/* source */
+	struct tbl *var;		/* variable in ${var..} */
+	short split;			/* split "$@" / call waitlast $() */
+} Expand;
+
+#define	XBASE		0	/* scanning original */
+#define	XSUB		1	/* expanding ${} string */
+#define	XARGSEP		2	/* ifs0 between "$*" */
+#define	XARG		3	/* expanding $*, $@ */
+#define	XCOM		4	/* expanding $() */
+#define XNULLSUB	5	/* "$@" when $# is 0 (don't generate word) */
+#define XSUBMID		6	/* middle of expanding ${} */
+
+/* States used for field splitting */
+#define IFS_WORD	0	/* word has chars (or quotes) */
+#define IFS_WS		1	/* have seen IFS white-space */
+#define IFS_NWS		2	/* have seen IFS non-white-space */
+
+static int varsub(Expand *, const char *, const char *, int *, int *);
+static int comsub(Expand *, const char *);
+static char *trimsub(char *, char *, int);
+static void glob(char *, XPtrV *, int);
+static void globit(XString *, char **, char *, XPtrV *, int);
+static const char *maybe_expand_tilde(const char *, XString *, char **, int);
+static char *tilde(char *);
+#ifndef MKSH_NOPWNAM
+static char *homedir(char *);
+#endif
+static void alt_expand(XPtrV *, char *, char *, char *, int);
+static size_t utflen(const char *);
+static void utfincptr(const char *, mksh_ari_t *);
+
+/* UTFMODE functions */
+static size_t
+utflen(const char *s)
+{
+	size_t n;
+
+	if (UTFMODE) {
+		n = 0;
+		while (*s) {
+			s += utf_ptradj(s);
+			++n;
+		}
+	} else
+		n = strlen(s);
+	return (n);
+}
+
+static void
+utfincptr(const char *s, mksh_ari_t *lp)
+{
+	const char *cp = s;
+
+	while ((*lp)--)
+		cp += utf_ptradj(cp);
+	*lp = cp - s;
+}
+
+/* compile and expand word */
+char *
+substitute(const char *cp, int f)
+{
+	struct source *s, *sold;
+
+	sold = source;
+	s = pushs(SWSTR, ATEMP);
+	s->start = s->str = cp;
+	source = s;
+	if (yylex(ONEWORD) != LWORD)
+		internal_errorf("substitute");
+	source = sold;
+	afree(s, ATEMP);
+	return (evalstr(yylval.cp, f));
+}
+
+/*
+ * expand arg-list
+ */
+char **
+eval(const char **ap, int f)
+{
+	XPtrV w;
+
+	if (*ap == NULL) {
+		union mksh_ccphack vap;
+
+		vap.ro = ap;
+		return (vap.rw);
+	}
+	XPinit(w, 32);
+	XPput(w, NULL);		/* space for shell name */
+	while (*ap != NULL)
+		expand(*ap++, &w, f);
+	XPput(w, NULL);
+	return ((char **)XPclose(w) + 1);
+}
+
+/*
+ * expand string
+ */
+char *
+evalstr(const char *cp, int f)
+{
+	XPtrV w;
+	char *dp = null;
+
+	XPinit(w, 1);
+	expand(cp, &w, f);
+	if (XPsize(w))
+		dp = *XPptrv(w);
+	XPfree(w);
+	return (dp);
+}
+
+/*
+ * expand string - return only one component
+ * used from iosetup to expand redirection files
+ */
+char *
+evalonestr(const char *cp, int f)
+{
+	XPtrV w;
+	char *rv;
+
+	XPinit(w, 1);
+	expand(cp, &w, f);
+	switch (XPsize(w)) {
+	case 0:
+		rv = null;
+		break;
+	case 1:
+		rv = (char *) *XPptrv(w);
+		break;
+	default:
+		rv = evalstr(cp, f&~DOGLOB);
+		break;
+	}
+	XPfree(w);
+	return (rv);
+}
+
+/* for nested substitution: ${var:=$var2} */
+typedef struct SubType {
+	struct tbl *var;	/* variable for ${var..} */
+	struct SubType *prev;	/* old type */
+	struct SubType *next;	/* poped type (to avoid re-allocating) */
+	short	stype;		/* [=+-?%#] action after expanded word */
+	short	base;		/* begin position of expanded word */
+	short	f;		/* saved value of f (DOPAT, etc) */
+	uint8_t	quotep;		/* saved value of quote (for ${..[%#]..}) */
+	uint8_t	quotew;		/* saved value of quote (for ${..[+-=]..}) */
+} SubType;
+
+void
+expand(const char *cp,	/* input word */
+    XPtrV *wp,		/* output words */
+    int f)		/* DO* flags */
+{
+	int c = 0;
+	int type;		/* expansion type */
+	int quote = 0;		/* quoted */
+	XString ds;		/* destination string */
+	char *dp;		/* destination */
+	const char *sp;		/* source */
+	int fdo, word;		/* second pass flags; have word */
+	int doblank;		/* field splitting of parameter/command subst */
+	Expand x = {		/* expansion variables */
+		NULL, { NULL }, NULL, 0
+	};
+	SubType st_head, *st;
+	int newlines = 0; /* For trailing newlines in COMSUB */
+	int saw_eq, tilde_ok;
+	int make_magic;
+	size_t len;
+
+	if (cp == NULL)
+		internal_errorf("expand(NULL)");
+	/* for alias, readonly, set, typeset commands */
+	if ((f & DOVACHECK) && is_wdvarassign(cp)) {
+		f &= ~(DOVACHECK|DOBLANK|DOGLOB|DOTILDE);
+		f |= DOASNTILDE;
+	}
+	if (Flag(FNOGLOB))
+		f &= ~DOGLOB;
+	if (Flag(FMARKDIRS))
+		f |= DOMARKDIRS;
+	if (Flag(FBRACEEXPAND) && (f & DOGLOB))
+		f |= DOBRACE_;
+
+	Xinit(ds, dp, 128, ATEMP);	/* init dest. string */
+	type = XBASE;
+	sp = cp;
+	fdo = 0;
+	saw_eq = 0;
+	tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; /* must be 1/0 */
+	doblank = 0;
+	make_magic = 0;
+	word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
+	/* clang doesn't know OSUBST comes before CSUBST */
+	memset(&st_head, 0, sizeof(st_head));
+	st = &st_head;
+
+	while (1) {
+		Xcheck(ds, dp);
+
+		switch (type) {
+		case XBASE:	/* original prefixed string */
+			c = *sp++;
+			switch (c) {
+			case EOS:
+				c = 0;
+				break;
+			case CHAR:
+				c = *sp++;
+				break;
+			case QCHAR:
+				quote |= 2; /* temporary quote */
+				c = *sp++;
+				break;
+			case OQUOTE:
+				word = IFS_WORD;
+				tilde_ok = 0;
+				quote = 1;
+				continue;
+			case CQUOTE:
+				quote = st->quotew;
+				continue;
+			case COMSUB:
+				tilde_ok = 0;
+				if (f & DONTRUNCOMMAND) {
+					word = IFS_WORD;
+					*dp++ = '$'; *dp++ = '(';
+					while (*sp != '\0') {
+						Xcheck(ds, dp);
+						*dp++ = *sp++;
+					}
+					*dp++ = ')';
+				} else {
+					type = comsub(&x, sp);
+					if (type == XCOM && (f&DOBLANK))
+						doblank++;
+					sp = strnul(sp) + 1;
+					newlines = 0;
+				}
+				continue;
+			case EXPRSUB:
+				word = IFS_WORD;
+				tilde_ok = 0;
+				if (f & DONTRUNCOMMAND) {
+					*dp++ = '$'; *dp++ = '('; *dp++ = '(';
+					while (*sp != '\0') {
+						Xcheck(ds, dp);
+						*dp++ = *sp++;
+					}
+					*dp++ = ')'; *dp++ = ')';
+				} else {
+					struct tbl v;
+					char *p;
+
+					v.flag = DEFINED|ISSET|INTEGER;
+					v.type = 10; /* not default */
+					v.name[0] = '\0';
+					v_evaluate(&v, substitute(sp, 0),
+					    KSH_UNWIND_ERROR, true);
+					sp = strnul(sp) + 1;
+					for (p = str_val(&v); *p; ) {
+						Xcheck(ds, dp);
+						*dp++ = *p++;
+					}
+				}
+				continue;
+			case OSUBST: {	/* ${{#}var{:}[=+-?#%]word} */
+			/* format is:
+			 *	OSUBST [{x] plain-variable-part \0
+			 *	    compiled-word-part CSUBST [}x]
+			 * This is where all syntax checking gets done...
+			 */
+				const char *varname = ++sp; /* skip the { or x (}) */
+				int stype;
+				int slen = 0;
+
+				sp = cstrchr(sp, '\0') + 1; /* skip variable */
+				type = varsub(&x, varname, sp, &stype, &slen);
+				if (type < 0) {
+					char *beg, *end, *str;
+
+ unwind_substsyn:
+					sp = varname - 2; /* restore sp */
+					end = (beg = wdcopy(sp, ATEMP)) +
+					    (wdscan(sp, CSUBST) - sp);
+					/* ({) the } or x is already skipped */
+					if (end < wdscan(beg, EOS))
+						*end = EOS;
+					str = snptreef(NULL, 64, "%S", beg);
+					afree(beg, ATEMP);
+					errorf("%s: bad substitution", str);
+				}
+				if (f & DOBLANK)
+					doblank++;
+				tilde_ok = 0;
+				if (type == XBASE) {	/* expand? */
+					if (!st->next) {
+						SubType *newst;
+
+						newst = alloc(sizeof(SubType), ATEMP);
+						newst->next = NULL;
+						newst->prev = st;
+						st->next = newst;
+					}
+					st = st->next;
+					st->stype = stype;
+					st->base = Xsavepos(ds, dp);
+					st->f = f;
+					st->var = x.var;
+					st->quotew = st->quotep = quote;
+					/* skip qualifier(s) */
+					if (stype)
+						sp += slen;
+					switch (stype & 0x7f) {
+					case '0': {
+						char *beg, *mid, *end, *stg;
+						mksh_ari_t from = 0, num = -1, flen, finc = 0;
+
+						beg = wdcopy(sp, ATEMP);
+						mid = beg + (wdscan(sp, ADELIM) - sp);
+						stg = beg + (wdscan(sp, CSUBST) - sp);
+						if (mid >= stg)
+							goto unwind_substsyn;
+						mid[-2] = EOS;
+						if (mid[-1] == /*{*/'}') {
+							sp += mid - beg - 1;
+							end = NULL;
+						} else {
+							end = mid +
+							    (wdscan(mid, ADELIM) - mid);
+							if (end >= stg)
+								goto unwind_substsyn;
+							end[-2] = EOS;
+							sp += end - beg - 1;
+						}
+						evaluate(substitute(stg = wdstrip(beg, false, false), 0),
+						    &from, KSH_UNWIND_ERROR, true);
+						afree(stg, ATEMP);
+						if (end) {
+							evaluate(substitute(stg = wdstrip(mid, false, false), 0),
+							    &num, KSH_UNWIND_ERROR, true);
+							afree(stg, ATEMP);
+						}
+						afree(beg, ATEMP);
+						beg = str_val(st->var);
+						flen = utflen(beg);
+						if (from < 0) {
+							if (-from < flen)
+								finc = flen + from;
+						} else
+							finc = from < flen ? from : flen;
+						if (UTFMODE)
+							utfincptr(beg, &finc);
+						beg += finc;
+						flen = utflen(beg);
+						if (num < 0 || num > flen)
+							num = flen;
+						if (UTFMODE)
+							utfincptr(beg, &num);
+						strndupx(x.str, beg, num, ATEMP);
+						goto do_CSUBST;
+					}
+					case '/': {
+						char *s, *p, *d, *sbeg, *end;
+						char *pat, *rrep;
+						char *tpat0, *tpat1, *tpat2;
+
+						s = wdcopy(sp, ATEMP);
+						p = s + (wdscan(sp, ADELIM) - sp);
+						d = s + (wdscan(sp, CSUBST) - sp);
+						if (p >= d)
+							goto unwind_substsyn;
+						p[-2] = EOS;
+						if (p[-1] == /*{*/'}')
+							d = NULL;
+						else
+							d[-2] = EOS;
+						sp += (d ? d : p) - s - 1;
+						tpat0 = wdstrip(s, true, true);
+						pat = substitute(tpat0, 0);
+						if (d) {
+							d = wdstrip(p, true, false);
+							rrep = substitute(d, 0);
+							afree(d, ATEMP);
+						} else
+							rrep = null;
+						afree(s, ATEMP);
+						s = d = pat;
+						while (*s)
+							if (*s != '\\' ||
+							    s[1] == '%' ||
+							    s[1] == '#' ||
+							    s[1] == '\0' ||
+				/* XXX really? */	    s[1] == '\\' ||
+							    s[1] == '/')
+								*d++ = *s++;
+							else
+								s++;
+						*d = '\0';
+						afree(tpat0, ATEMP);
+
+						/* reject empty pattern */
+						if (!*pat || gmatchx("", pat, false))
+							goto no_repl;
+
+						/* prepare string on which to work */
+						strdupx(s, str_val(st->var), ATEMP);
+						sbeg = s;
+
+						/* first see if we have any match at all */
+						tpat0 = pat;
+						if (*pat == '#') {
+							/* anchor at the beginning */
+							tpat1 = shf_smprintf("%s%c*", ++tpat0, MAGIC);
+							tpat2 = tpat1;
+						} else if (*pat == '%') {
+							/* anchor at the end */
+							tpat1 = shf_smprintf("%c*%s", MAGIC, ++tpat0);
+							tpat2 = tpat0;
+						} else {
+							/* float */
+							tpat1 = shf_smprintf("%c*%s%c*", MAGIC, pat, MAGIC);
+							tpat2 = tpat1 + 2;
+						}
+ again_repl:
+						/* this would not be necessary if gmatchx would return
+						 * the start and end values of a match found, like re*
+						 */
+						if (!gmatchx(sbeg, tpat1, false))
+							goto end_repl;
+						end = strnul(s);
+						/* now anchor the beginning of the match */
+						if (*pat != '#')
+							while (sbeg <= end) {
+								if (gmatchx(sbeg, tpat2, false))
+									break;
+								else
+									sbeg++;
+							}
+						/* now anchor the end of the match */
+						p = end;
+						if (*pat != '%')
+							while (p >= sbeg) {
+								bool gotmatch;
+
+								c = *p; *p = '\0';
+								gotmatch = gmatchx(sbeg, tpat0, false);
+								*p = c;
+								if (gotmatch)
+									break;
+								p--;
+							}
+						strndupx(end, s, sbeg - s, ATEMP);
+						d = shf_smprintf("%s%s%s", end, rrep, p);
+						afree(end, ATEMP);
+						sbeg = d + (sbeg - s) + strlen(rrep);
+						afree(s, ATEMP);
+						s = d;
+						if (stype & 0x80)
+							goto again_repl;
+ end_repl:
+						afree(tpat1, ATEMP);
+						x.str = s;
+ no_repl:
+						afree(pat, ATEMP);
+						if (rrep != null)
+							afree(rrep, ATEMP);
+						goto do_CSUBST;
+					}
+					case '#':
+					case '%':
+						/* ! DOBLANK,DOBRACE_,DOTILDE */
+						f = DOPAT | (f&DONTRUNCOMMAND) |
+						    DOTEMP_;
+						st->quotew = quote = 0;
+						/* Prepend open pattern (so |
+						 * in a trim will work as
+						 * expected)
+						 */
+						*dp++ = MAGIC;
+						*dp++ = (char)('@' | 0x80);
+						break;
+					case '=':
+						/* Enabling tilde expansion
+						 * after :s here is
+						 * non-standard ksh, but is
+						 * consistent with rules for
+						 * other assignments. Not
+						 * sure what POSIX thinks of
+						 * this.
+						 * Not doing tilde expansion
+						 * for integer variables is a
+						 * non-POSIX thing - makes
+						 * sense though, since ~ is
+						 * a arithmetic operator.
+						 */
+						if (!(x.var->flag & INTEGER))
+							f |= DOASNTILDE|DOTILDE;
+						f |= DOTEMP_;
+						/* These will be done after the
+						 * value has been assigned.
+						 */
+						f &= ~(DOBLANK|DOGLOB|DOBRACE_);
+						tilde_ok = 1;
+						break;
+					case '?':
+						f &= ~DOBLANK;
+						f |= DOTEMP_;
+						/* FALLTHROUGH */
+					default:
+						/* Enable tilde expansion */
+						tilde_ok = 1;
+						f |= DOTILDE;
+					}
+				} else
+					/* skip word */
+					sp += wdscan(sp, CSUBST) - sp;
+				continue;
+			}
+			case CSUBST: /* only get here if expanding word */
+ do_CSUBST:
+				sp++; /* ({) skip the } or x */
+				tilde_ok = 0;	/* in case of ${unset:-} */
+				*dp = '\0';
+				quote = st->quotep;
+				f = st->f;
+				if (f&DOBLANK)
+					doblank--;
+				switch (st->stype&0x7f) {
+				case '#':
+				case '%':
+					/* Append end-pattern */
+					*dp++ = MAGIC; *dp++ = ')'; *dp = '\0';
+					dp = Xrestpos(ds, dp, st->base);
+					/* Must use st->var since calling
+					 * global would break things
+					 * like x[i+=1].
+					 */
+					x.str = trimsub(str_val(st->var),
+						dp, st->stype);
+					if (x.str[0] != '\0' || st->quotep)
+						type = XSUB;
+					else
+						type = XNULLSUB;
+					if (f&DOBLANK)
+						doblank++;
+					st = st->prev;
+					continue;
+				case '=':
+					/* Restore our position and substitute
+					 * the value of st->var (may not be
+					 * the assigned value in the presence
+					 * of integer/right-adj/etc attributes).
+					 */
+					dp = Xrestpos(ds, dp, st->base);
+					/* Must use st->var since calling
+					 * global would cause with things
+					 * like x[i+=1] to be evaluated twice.
+					 */
+					/* Note: not exported by FEXPORT
+					 * in AT&T ksh.
+					 */
+					/* XXX POSIX says readonly is only
+					 * fatal for special builtins (setstr
+					 * does readonly check).
+					 */
+					len = strlen(dp) + 1;
+					setstr(st->var,
+					    debunk(alloc(len, ATEMP),
+					    dp, len), KSH_UNWIND_ERROR);
+					x.str = str_val(st->var);
+					type = XSUB;
+					if (f&DOBLANK)
+						doblank++;
+					st = st->prev;
+					continue;
+				case '?': {
+					char *s = Xrestpos(ds, dp, st->base);
+
+					errorf("%s: %s", st->var->name,
+					    dp == s ?
+					    "parameter null or not set" :
+					    (debunk(s, s, strlen(s) + 1), s));
+				}
+				case '0':
+				case '/':
+					dp = Xrestpos(ds, dp, st->base);
+					type = XSUB;
+					if (f&DOBLANK)
+						doblank++;
+					st = st->prev;
+					continue;
+				}
+				st = st->prev;
+				type = XBASE;
+				continue;
+
+			case OPAT: /* open pattern: *(foo|bar) */
+				/* Next char is the type of pattern */
+				make_magic = 1;
+				c = *sp++ + 0x80;
+				break;
+
+			case SPAT: /* pattern separator (|) */
+				make_magic = 1;
+				c = '|';
+				break;
+
+			case CPAT: /* close pattern */
+				make_magic = 1;
+				c = /*(*/ ')';
+				break;
+			}
+			break;
+
+		case XNULLSUB:
+			/* Special case for "$@" (and "${foo[@]}") - no
+			 * word is generated if $# is 0 (unless there is
+			 * other stuff inside the quotes).
+			 */
+			type = XBASE;
+			if (f&DOBLANK) {
+				doblank--;
+				/* not really correct: x=; "$x$@" should
+				 * generate a null argument and
+				 * set A; "${@:+}" shouldn't.
+				 */
+				if (dp == Xstring(ds, dp))
+					word = IFS_WS;
+			}
+			continue;
+
+		case XSUB:
+		case XSUBMID:
+			if ((c = *x.str++) == 0) {
+				type = XBASE;
+				if (f&DOBLANK)
+					doblank--;
+				continue;
+			}
+			break;
+
+		case XARGSEP:
+			type = XARG;
+			quote = 1;
+		case XARG:
+			if ((c = *x.str++) == '\0') {
+				/* force null words to be created so
+				 * set -- '' 2 ''; foo "$@" will do
+				 * the right thing
+				 */
+				if (quote && x.split)
+					word = IFS_WORD;
+				if ((x.str = *x.u.strv++) == NULL) {
+					type = XBASE;
+					if (f&DOBLANK)
+						doblank--;
+					continue;
+				}
+				c = ifs0;
+				if (c == 0) {
+					if (quote && !x.split)
+						continue;
+					c = ' ';
+				}
+				if (quote && x.split) {
+					/* terminate word for "$@" */
+					type = XARGSEP;
+					quote = 0;
+				}
+			}
+			break;
+
+		case XCOM:
+			if (newlines) {		/* Spit out saved NLs */
+				c = '\n';
+				--newlines;
+			} else {
+				while ((c = shf_getc(x.u.shf)) == 0 || c == '\n')
+					if (c == '\n')
+						/* Save newlines */
+						newlines++;
+				if (newlines && c != EOF) {
+					shf_ungetc(c, x.u.shf);
+					c = '\n';
+					--newlines;
+				}
+			}
+			if (c == EOF) {
+				newlines = 0;
+				shf_close(x.u.shf);
+				if (x.split)
+					subst_exstat = waitlast();
+				type = XBASE;
+				if (f&DOBLANK)
+					doblank--;
+				continue;
+			}
+			break;
+		}
+
+		/* check for end of word or IFS separation */
+		if (c == 0 || (!quote && (f & DOBLANK) && doblank &&
+		    !make_magic && ctype(c, C_IFS))) {
+			/* How words are broken up:
+			 *			|	value of c
+			 *	word		|	ws	nws	0
+			 *	-----------------------------------
+			 *	IFS_WORD		w/WS	w/NWS	w
+			 *	IFS_WS			-/WS	w/NWS	-
+			 *	IFS_NWS			-/NWS	w/NWS	w
+			 * (w means generate a word)
+			 * Note that IFS_NWS/0 generates a word (AT&T ksh
+			 * doesn't do this, but POSIX does).
+			 */
+			if (word == IFS_WORD ||
+			    (!ctype(c, C_IFSWS) && c && word == IFS_NWS)) {
+				char *p;
+
+				*dp++ = '\0';
+				p = Xclose(ds, dp);
+				if (fdo & DOBRACE_)
+					/* also does globbing */
+					alt_expand(wp, p, p,
+					    p + Xlength(ds, (dp - 1)),
+					    fdo | (f & DOMARKDIRS));
+				else if (fdo & DOGLOB)
+					glob(p, wp, f & DOMARKDIRS);
+				else if ((f & DOPAT) || !(fdo & DOMAGIC_))
+					XPput(*wp, p);
+				else
+					XPput(*wp, debunk(p, p, strlen(p) + 1));
+				fdo = 0;
+				saw_eq = 0;
+				tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0;
+				if (c != 0)
+					Xinit(ds, dp, 128, ATEMP);
+			}
+			if (c == 0)
+				return;
+			if (word != IFS_NWS)
+				word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS;
+		} else {
+			if (type == XSUB) {
+				if (word == IFS_NWS &&
+				    Xlength(ds, dp) == 0) {
+					char *p;
+
+					*(p = alloc(1, ATEMP)) = '\0';
+					XPput(*wp, p);
+				}
+				type = XSUBMID;
+			}
+
+			/* age tilde_ok info - ~ code tests second bit */
+			tilde_ok <<= 1;
+			/* mark any special second pass chars */
+			if (!quote)
+				switch (c) {
+				case '[':
+				case NOT:
+				case '-':
+				case ']':
+					/* For character classes - doesn't hurt
+					 * to have magic !,-,]s outside of
+					 * [...] expressions.
+					 */
+					if (f & (DOPAT | DOGLOB)) {
+						fdo |= DOMAGIC_;
+						if (c == '[')
+							fdo |= f & DOGLOB;
+						*dp++ = MAGIC;
+					}
+					break;
+				case '*':
+				case '?':
+					if (f & (DOPAT | DOGLOB)) {
+						fdo |= DOMAGIC_ | (f & DOGLOB);
+						*dp++ = MAGIC;
+					}
+					break;
+				case OBRACE:
+				case ',':
+				case CBRACE:
+					if ((f & DOBRACE_) && (c == OBRACE ||
+					    (fdo & DOBRACE_))) {
+						fdo |= DOBRACE_|DOMAGIC_;
+						*dp++ = MAGIC;
+					}
+					break;
+				case '=':
+					/* Note first unquoted = for ~ */
+					if (!(f & DOTEMP_) && !saw_eq &&
+					    (Flag(FBRACEEXPAND) ||
+					    (f & DOASNTILDE))) {
+						saw_eq = 1;
+						tilde_ok = 1;
+					}
+					break;
+				case ':': /* : */
+					/* Note unquoted : for ~ */
+					if (!(f & DOTEMP_) && (f & DOASNTILDE))
+						tilde_ok = 1;
+					break;
+				case '~':
+					/* tilde_ok is reset whenever
+					 * any of ' " $( $(( ${ } are seen.
+					 * Note that tilde_ok must be preserved
+					 * through the sequence ${A=a=}~
+					 */
+					if (type == XBASE &&
+					    (f & (DOTILDE|DOASNTILDE)) &&
+					    (tilde_ok & 2)) {
+						const char *p;
+						char *dp_x;
+
+						dp_x = dp;
+						p = maybe_expand_tilde(sp,
+						    &ds, &dp_x,
+						    f & DOASNTILDE);
+						if (p) {
+							if (dp != dp_x)
+								word = IFS_WORD;
+							dp = dp_x;
+							sp = p;
+							continue;
+						}
+					}
+					break;
+				}
+			else
+				quote &= ~2; /* undo temporary */
+
+			if (make_magic) {
+				make_magic = 0;
+				fdo |= DOMAGIC_ | (f & DOGLOB);
+				*dp++ = MAGIC;
+			} else if (ISMAGIC(c)) {
+				fdo |= DOMAGIC_;
+				*dp++ = MAGIC;
+			}
+			*dp++ = c; /* save output char */
+			word = IFS_WORD;
+		}
+	}
+}
+
+/*
+ * Prepare to generate the string returned by ${} substitution.
+ */
+static int
+varsub(Expand *xp, const char *sp, const char *word,
+    int *stypep,	/* becomes qualifier type */
+    int *slenp)		/* " " len (=, :=, etc.) valid iff *stypep != 0 */
+{
+	int c;
+	int state;	/* next state: XBASE, XARG, XSUB, XNULLSUB */
+	int stype;	/* substitution type */
+	int slen;
+	const char *p;
+	struct tbl *vp;
+	bool zero_ok = false;
+
+	if ((stype = sp[0]) == '\0')	/* Bad variable name */
+		return (-1);
+
+	xp->var = NULL;
+
+	/*-
+	 * ${#var}, string length (-U: characters, +U: octets) or array size
+	 * ${%var}, string width (-U: screen columns, +U: octets)
+	 */
+	c = sp[1];
+	if (stype == '%' && c == '\0')
+		return (-1);
+	if ((stype == '#' || stype == '%') && c != '\0') {
+		/* Can't have any modifiers for ${#...} or ${%...} */
+		if (*word != CSUBST)
+			return (-1);
+		sp++;
+		/* Check for size of array */
+		if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') &&
+		    p[2] == ']') {
+			int n = 0;
+
+			if (stype != '#')
+				return (-1);
+			vp = global(arrayname(sp));
+			if (vp->flag & (ISSET|ARRAY))
+				zero_ok = true;
+			for (; vp; vp = vp->u.array)
+				if (vp->flag & ISSET)
+					n++;
+			c = n;
+		} else if (c == '*' || c == '@') {
+			if (stype != '#')
+				return (-1);
+			c = e->loc->argc;
+		} else {
+			p = str_val(global(sp));
+			zero_ok = p != null;
+			if (stype == '#')
+				c = utflen(p);
+			else {
+				/* partial utf_mbswidth reimplementation */
+				const char *s = p;
+				unsigned int wc;
+				size_t len;
+				int cw;
+
+				c = 0;
+				while (*s) {
+					if (!UTFMODE || (len = utf_mbtowc(&wc,
+					    s)) == (size_t)-1)
+						/* not UTFMODE or not UTF-8 */
+						wc = (unsigned char)(*s++);
+					else
+						/* UTFMODE and UTF-8 */
+						s += len;
+					/* wc == char or wchar at s++ */
+					if ((cw = utf_wcwidth(wc)) == -1) {
+						/* 646, 8859-1, 10646 C0/C1 */
+						c = -1;
+						break;
+					}
+					c += cw;
+				}
+			}
+		}
+		if (Flag(FNOUNSET) && c == 0 && !zero_ok)
+			errorf("%s: parameter not set", sp);
+		*stypep = 0; /* unqualified variable/string substitution */
+		xp->str = shf_smprintf("%d", c);
+		return (XSUB);
+	}
+
+	/* Check for qualifiers in word part */
+	stype = 0;
+	c = word[slen = 0] == CHAR ? word[1] : 0;
+	if (c == ':') {
+		slen += 2;
+		stype = 0x80;
+		c = word[slen + 0] == CHAR ? word[slen + 1] : 0;
+	}
+	if (!stype && c == '/') {
+		slen += 2;
+		stype = c;
+		if (word[slen] == ADELIM) {
+			slen += 2;
+			stype |= 0x80;
+		}
+	} else if (stype == 0x80 && (c == ' ' || c == '0')) {
+		stype |= '0';
+	} else if (ctype(c, C_SUBOP1)) {
+		slen += 2;
+		stype |= c;
+	} else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */
+		slen += 2;
+		stype = c;
+		if (word[slen + 0] == CHAR && c == word[slen + 1]) {
+			stype |= 0x80;
+			slen += 2;
+		}
+	} else if (stype)	/* : is not ok */
+		return (-1);
+	if (!stype && *word != CSUBST)
+		return (-1);
+	*stypep = stype;
+	*slenp = slen;
+
+	c = sp[0];
+	if (c == '*' || c == '@') {
+		switch (stype & 0x7f) {
+		case '=':	/* can't assign to a vector */
+		case '%':	/* can't trim a vector (yet) */
+		case '#':
+		case '0':
+		case '/':
+			return (-1);
+		}
+		if (e->loc->argc == 0) {
+			xp->str = null;
+			xp->var = global(sp);
+			state = c == '@' ? XNULLSUB : XSUB;
+		} else {
+			xp->u.strv = (const char **)e->loc->argv + 1;
+			xp->str = *xp->u.strv++;
+			xp->split = c == '@'; /* $@ */
+			state = XARG;
+		}
+		zero_ok = true;	/* POSIX 2009? */
+	} else {
+		if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') &&
+		    p[2] == ']') {
+			XPtrV wv;
+
+			switch (stype & 0x7f) {
+			case '=':	/* can't assign to a vector */
+			case '%':	/* can't trim a vector (yet) */
+			case '#':
+			case '?':
+			case '0':
+			case '/':
+				return (-1);
+			}
+			XPinit(wv, 32);
+			if ((c = sp[0]) == '!')
+				++sp;
+			vp = global(arrayname(sp));
+			for (; vp; vp = vp->u.array) {
+				if (!(vp->flag&ISSET))
+					continue;
+				XPput(wv, c == '!' ? shf_smprintf("%lu",
+				    arrayindex(vp)) :
+				    str_val(vp));
+			}
+			if (XPsize(wv) == 0) {
+				xp->str = null;
+				state = p[1] == '@' ? XNULLSUB : XSUB;
+				XPfree(wv);
+			} else {
+				XPput(wv, 0);
+				xp->u.strv = (const char **)XPptrv(wv);
+				xp->str = *xp->u.strv++;
+				xp->split = p[1] == '@'; /* ${foo[@]} */
+				state = XARG;
+			}
+		} else {
+			/* Can't assign things like $! or $1 */
+			if ((stype & 0x7f) == '=' &&
+			    ctype(*sp, C_VAR1 | C_DIGIT))
+				return (-1);
+			if (*sp == '!' && sp[1]) {
+				++sp;
+				xp->var = global(sp);
+				if (cstrchr(sp, '[')) {
+					if (xp->var->flag & ISSET)
+						xp->str = shf_smprintf("%lu",
+						    arrayindex(xp->var));
+					else
+						xp->str = null;
+				} else if (xp->var->flag & ISSET)
+					xp->str = xp->var->name;
+				else
+					xp->str = "0";	/* ksh93 compat */
+			} else {
+				xp->var = global(sp);
+				xp->str = str_val(xp->var);
+			}
+			state = XSUB;
+		}
+	}
+
+	c = stype&0x7f;
+	/* test the compiler's code generator */
+	if (ctype(c, C_SUBOP2) || stype == (0x80 | '0') || c == '/' ||
+	    (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */
+	    c == '=' || c == '-' || c == '?' : c == '+'))
+		state = XBASE;	/* expand word instead of variable value */
+	if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
+	    (ctype(c, C_SUBOP2) || (state != XBASE && c != '+')))
+		errorf("%s: parameter not set", sp);
+	return (state);
+}
+
+/*
+ * Run the command in $(...) and read its output.
+ */
+static int
+comsub(Expand *xp, const char *cp)
+{
+	Source *s, *sold;
+	struct op *t;
+	struct shf *shf;
+
+	s = pushs(SSTRING, ATEMP);
+	s->start = s->str = cp;
+	sold = source;
+	t = compile(s);
+	afree(s, ATEMP);
+	source = sold;
+
+	if (t == NULL)
+		return (XBASE);
+
+	if (t != NULL && t->type == TCOM && /* $(<file) */
+	    *t->args == NULL && *t->vars == NULL && t->ioact != NULL) {
+		struct ioword *io = *t->ioact;
+		char *name;
+
+		if ((io->flag&IOTYPE) != IOREAD)
+			errorf("funny $() command: %s",
+			    snptreef(NULL, 32, "%R", io));
+		shf = shf_open(name = evalstr(io->name, DOTILDE), O_RDONLY, 0,
+			SHF_MAPHI|SHF_CLEXEC);
+		if (shf == NULL)
+			errorf("%s: cannot open $() input", name);
+		xp->split = 0;	/* no waitlast() */
+	} else {
+		int ofd1, pv[2];
+		openpipe(pv);
+		shf = shf_fdopen(pv[0], SHF_RD, NULL);
+		ofd1 = savefd(1);
+		if (pv[1] != 1) {
+			ksh_dup2(pv[1], 1, false);
+			close(pv[1]);
+		}
+		execute(t, XFORK|XXCOM|XPIPEO, NULL);
+		restfd(1, ofd1);
+		startlast();
+		xp->split = 1;	/* waitlast() */
+	}
+
+	xp->u.shf = shf;
+	return (XCOM);
+}
+
+/*
+ * perform #pattern and %pattern substitution in ${}
+ */
+
+static char *
+trimsub(char *str, char *pat, int how)
+{
+	char *end = strnul(str);
+	char *p, c;
+
+	switch (how & 0xFF) {
+	case '#':		/* shortest at beginning */
+		for (p = str; p <= end; p += utf_ptradj(p)) {
+			c = *p; *p = '\0';
+			if (gmatchx(str, pat, false)) {
+				*p = c;
+				return (p);
+			}
+			*p = c;
+		}
+		break;
+	case '#'|0x80:		/* longest match at beginning */
+		for (p = end; p >= str; p--) {
+			c = *p; *p = '\0';
+			if (gmatchx(str, pat, false)) {
+				*p = c;
+				return (p);
+			}
+			*p = c;
+		}
+		break;
+	case '%':		/* shortest match at end */
+		p = end;
+		while (p >= str) {
+			if (gmatchx(p, pat, false))
+				goto trimsub_match;
+			if (UTFMODE) {
+				char *op = p;
+				while ((p-- > str) && ((*p & 0xC0) == 0x80))
+					;
+				if ((p < str) || (p + utf_ptradj(p) != op))
+					p = op - 1;
+			} else
+				--p;
+		}
+		break;
+	case '%'|0x80:		/* longest match at end */
+		for (p = str; p <= end; p++)
+			if (gmatchx(p, pat, false)) {
+ trimsub_match:
+				strndupx(end, str, p - str, ATEMP);
+				return (end);
+			}
+		break;
+	}
+
+	return (str);		/* no match, return string */
+}
+
+/*
+ * glob
+ * Name derived from V6's /etc/glob, the program that expanded filenames.
+ */
+
+/* XXX cp not const 'cause slashes are temporarily replaced with NULs... */
+static void
+glob(char *cp, XPtrV *wp, int markdirs)
+{
+	int oldsize = XPsize(*wp);
+
+	if (glob_str(cp, wp, markdirs) == 0)
+		XPput(*wp, debunk(cp, cp, strlen(cp) + 1));
+	else
+		qsort(XPptrv(*wp) + oldsize, XPsize(*wp) - oldsize,
+		    sizeof(void *), xstrcmp);
+}
+
+#define GF_NONE		0
+#define GF_EXCHECK	BIT(0)		/* do existence check on file */
+#define GF_GLOBBED	BIT(1)		/* some globbing has been done */
+#define GF_MARKDIR	BIT(2)		/* add trailing / to directories */
+
+/* Apply file globbing to cp and store the matching files in wp. Returns
+ * the number of matches found.
+ */
+int
+glob_str(char *cp, XPtrV *wp, int markdirs)
+{
+	int oldsize = XPsize(*wp);
+	XString xs;
+	char *xp;
+
+	Xinit(xs, xp, 256, ATEMP);
+	globit(&xs, &xp, cp, wp, markdirs ? GF_MARKDIR : GF_NONE);
+	Xfree(xs, xp);
+
+	return (XPsize(*wp) - oldsize);
+}
+
+static void
+globit(XString *xs,	/* dest string */
+    char **xpp,		/* ptr to dest end */
+    char *sp,		/* source path */
+    XPtrV *wp,		/* output list */
+    int check)		/* GF_* flags */
+{
+	char *np;		/* next source component */
+	char *xp = *xpp;
+	char *se;
+	char odirsep;
+
+	/* This to allow long expansions to be interrupted */
+	intrcheck();
+
+	if (sp == NULL) {	/* end of source path */
+		/* We only need to check if the file exists if a pattern
+		 * is followed by a non-pattern (eg, foo*x/bar; no check
+		 * is needed for foo* since the match must exist) or if
+		 * any patterns were expanded and the markdirs option is set.
+		 * Symlinks make things a bit tricky...
+		 */
+		if ((check & GF_EXCHECK) ||
+		    ((check & GF_MARKDIR) && (check & GF_GLOBBED))) {
+#define stat_check()	(stat_done ? stat_done : \
+			    (stat_done = stat(Xstring(*xs, xp), &statb) < 0 \
+				? -1 : 1))
+			struct stat lstatb, statb;
+			int stat_done = 0;	 /* -1: failed, 1 ok */
+
+			if (lstat(Xstring(*xs, xp), &lstatb) < 0)
+				return;
+			/* special case for systems which strip trailing
+			 * slashes from regular files (eg, /etc/passwd/).
+			 * SunOS 4.1.3 does this...
+			 */
+			if ((check & GF_EXCHECK) && xp > Xstring(*xs, xp) &&
+			    xp[-1] == '/' && !S_ISDIR(lstatb.st_mode) &&
+			    (!S_ISLNK(lstatb.st_mode) ||
+			    stat_check() < 0 || !S_ISDIR(statb.st_mode)))
+				return;
+			/* Possibly tack on a trailing / if there isn't already
+			 * one and if the file is a directory or a symlink to a
+			 * directory
+			 */
+			if (((check & GF_MARKDIR) && (check & GF_GLOBBED)) &&
+			    xp > Xstring(*xs, xp) && xp[-1] != '/' &&
+			    (S_ISDIR(lstatb.st_mode) ||
+			    (S_ISLNK(lstatb.st_mode) && stat_check() > 0 &&
+			    S_ISDIR(statb.st_mode)))) {
+				*xp++ = '/';
+				*xp = '\0';
+			}
+		}
+		strndupx(np, Xstring(*xs, xp), Xlength(*xs, xp), ATEMP);
+		XPput(*wp, np);
+		return;
+	}
+
+	if (xp > Xstring(*xs, xp))
+		*xp++ = '/';
+	while (*sp == '/') {
+		Xcheck(*xs, xp);
+		*xp++ = *sp++;
+	}
+	np = strchr(sp, '/');
+	if (np != NULL) {
+		se = np;
+		odirsep = *np;	/* don't assume '/', can be multiple kinds */
+		*np++ = '\0';
+	} else {
+		odirsep = '\0'; /* keep gcc quiet */
+		se = sp + strlen(sp);
+	}
+
+
+	/* Check if sp needs globbing - done to avoid pattern checks for strings
+	 * containing MAGIC characters, open [s without the matching close ],
+	 * etc. (otherwise opendir() will be called which may fail because the
+	 * directory isn't readable - if no globbing is needed, only execute
+	 * permission should be required (as per POSIX)).
+	 */
+	if (!has_globbing(sp, se)) {
+		XcheckN(*xs, xp, se - sp + 1);
+		debunk(xp, sp, Xnleft(*xs, xp));
+		xp += strlen(xp);
+		*xpp = xp;
+		globit(xs, xpp, np, wp, check);
+	} else {
+		DIR *dirp;
+		struct dirent *d;
+		char *name;
+		int len;
+		int prefix_len;
+
+		/* xp = *xpp;	copy_non_glob() may have re-alloc'd xs */
+		*xp = '\0';
+		prefix_len = Xlength(*xs, xp);
+		dirp = opendir(prefix_len ? Xstring(*xs, xp) : ".");
+		if (dirp == NULL)
+			goto Nodir;
+		while ((d = readdir(dirp)) != NULL) {
+			name = d->d_name;
+			if (name[0] == '.' &&
+			    (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
+				continue; /* always ignore . and .. */
+			if ((*name == '.' && *sp != '.') ||
+			    !gmatchx(name, sp, true))
+				continue;
+
+			len = strlen(d->d_name) + 1;
+			XcheckN(*xs, xp, len);
+			memcpy(xp, name, len);
+			*xpp = xp + len - 1;
+			globit(xs, xpp, np, wp,
+				(check & GF_MARKDIR) | GF_GLOBBED
+				| (np ? GF_EXCHECK : GF_NONE));
+			xp = Xstring(*xs, xp) + prefix_len;
+		}
+		closedir(dirp);
+ Nodir:
+		;
+	}
+
+	if (np != NULL)
+		*--np = odirsep;
+}
+
+/* remove MAGIC from string */
+char *
+debunk(char *dp, const char *sp, size_t dlen)
+{
+	char *d;
+	const char *s;
+
+	if ((s = cstrchr(sp, MAGIC))) {
+		if (s - sp >= (ssize_t)dlen)
+			return (dp);
+		memmove(dp, sp, s - sp);
+		for (d = dp + (s - sp); *s && (d - dp < (ssize_t)dlen); s++)
+			if (!ISMAGIC(*s) || !(*++s & 0x80) ||
+			    !vstrchr("*+?@! ", *s & 0x7f))
+				*d++ = *s;
+			else {
+				/* extended pattern operators: *+?@! */
+				if ((*s & 0x7f) != ' ')
+					*d++ = *s & 0x7f;
+				if (d - dp < (ssize_t)dlen)
+					*d++ = '(';
+			}
+		*d = '\0';
+	} else if (dp != sp)
+		strlcpy(dp, sp, dlen);
+	return (dp);
+}
+
+/* Check if p is an unquoted name, possibly followed by a / or :. If so
+ * puts the expanded version in *dcp,dp and returns a pointer in p just
+ * past the name, otherwise returns 0.
+ */
+static const char *
+maybe_expand_tilde(const char *p, XString *dsp, char **dpp, int isassign)
+{
+	XString ts;
+	char *dp = *dpp;
+	char *tp;
+	const char *r;
+
+	Xinit(ts, tp, 16, ATEMP);
+	/* : only for DOASNTILDE form */
+	while (p[0] == CHAR && p[1] != '/' && (!isassign || p[1] != ':'))
+	{
+		Xcheck(ts, tp);
+		*tp++ = p[1];
+		p += 2;
+	}
+	*tp = '\0';
+	r = (p[0] == EOS || p[0] == CHAR || p[0] == CSUBST) ?
+	    tilde(Xstring(ts, tp)) : NULL;
+	Xfree(ts, tp);
+	if (r) {
+		while (*r) {
+			Xcheck(*dsp, dp);
+			if (ISMAGIC(*r))
+				*dp++ = MAGIC;
+			*dp++ = *r++;
+		}
+		*dpp = dp;
+		r = p;
+	}
+	return (r);
+}
+
+/*
+ * tilde expansion
+ *
+ * based on a version by Arnold Robbins
+ */
+
+static char *
+tilde(char *cp)
+{
+	char *dp = null;
+
+	if (cp[0] == '\0')
+		dp = str_val(global("HOME"));
+	else if (cp[0] == '+' && cp[1] == '\0')
+		dp = str_val(global("PWD"));
+	else if (cp[0] == '-' && cp[1] == '\0')
+		dp = str_val(global("OLDPWD"));
+#ifndef MKSH_NOPWNAM
+	else
+		dp = homedir(cp);
+#endif
+	/* If HOME, PWD or OLDPWD are not set, don't expand ~ */
+	return (dp == null ? NULL : dp);
+}
+
+#ifndef MKSH_NOPWNAM
+/*
+ * map userid to user's home directory.
+ * note that 4.3's getpw adds more than 6K to the shell,
+ * and the YP version probably adds much more.
+ * we might consider our own version of getpwnam() to keep the size down.
+ */
+static char *
+homedir(char *name)
+{
+	struct tbl *ap;
+
+	ap = ktenter(&homedirs, name, hash(name));
+	if (!(ap->flag & ISSET)) {
+		struct passwd *pw;
+
+		pw = getpwnam(name);
+		if (pw == NULL)
+			return (NULL);
+		strdupx(ap->val.s, pw->pw_dir, APERM);
+		ap->flag |= DEFINED|ISSET|ALLOC;
+	}
+	return (ap->val.s);
+}
+#endif
+
+static void
+alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo)
+{
+	int count = 0;
+	char *brace_start, *brace_end, *comma = NULL;
+	char *field_start;
+	char *p;
+
+	/* search for open brace */
+	for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != OBRACE; p += 2)
+		;
+	brace_start = p;
+
+	/* find matching close brace, if any */
+	if (p) {
+		comma = NULL;
+		count = 1;
+		for (p += 2; *p && count; p++) {
+			if (ISMAGIC(*p)) {
+				if (*++p == OBRACE)
+					count++;
+				else if (*p == CBRACE)
+					--count;
+				else if (*p == ',' && count == 1)
+					comma = p;
+			}
+		}
+	}
+	/* no valid expansions... */
+	if (!p || count != 0) {
+		/* Note that given a{{b,c} we do not expand anything (this is
+		 * what AT&T ksh does. This may be changed to do the {b,c}
+		 * expansion. }
+		 */
+		if (fdo & DOGLOB)
+			glob(start, wp, fdo & DOMARKDIRS);
+		else
+			XPput(*wp, debunk(start, start, end - start));
+		return;
+	}
+	brace_end = p;
+	if (!comma) {
+		alt_expand(wp, start, brace_end, end, fdo);
+		return;
+	}
+
+	/* expand expression */
+	field_start = brace_start + 2;
+	count = 1;
+	for (p = brace_start + 2; p != brace_end; p++) {
+		if (ISMAGIC(*p)) {
+			if (*++p == OBRACE)
+				count++;
+			else if ((*p == CBRACE && --count == 0) ||
+			    (*p == ',' && count == 1)) {
+				char *news;
+				int l1, l2, l3;
+
+				l1 = brace_start - start;
+				l2 = (p - 1) - field_start;
+				l3 = end - brace_end;
+				news = alloc(l1 + l2 + l3 + 1, ATEMP);
+				memcpy(news, start, l1);
+				memcpy(news + l1, field_start, l2);
+				memcpy(news + l1 + l2, brace_end, l3);
+				news[l1 + l2 + l3] = '\0';
+				alt_expand(wp, news, news + l1,
+				    news + l1 + l2 + l3, fdo);
+				field_start = p + 1;
+			}
+		}
+	}
+	return;
+}
diff --git a/mksh/src/exec.c b/mksh/src/exec.c
new file mode 100644
index 0000000..391321a
--- /dev/null
+++ b/mksh/src/exec.c
@@ -0,0 +1,1518 @@
+/*	$OpenBSD: exec.c,v 1.49 2009/01/29 23:27:26 jaredy Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.75 2010/07/17 22:09:34 tg Exp $");
+
+#ifndef MKSH_DEFAULT_EXECSHELL
+#define MKSH_DEFAULT_EXECSHELL	"/bin/sh"
+#endif
+
+static int comexec(struct op *, struct tbl *volatile, const char **,
+    int volatile, volatile int *);
+static void scriptexec(struct op *, const char **) MKSH_A_NORETURN;
+static int call_builtin(struct tbl *, const char **);
+static int iosetup(struct ioword *, struct tbl *);
+static int herein(const char *, int);
+static const char *do_selectargs(const char **, bool);
+static Test_op dbteste_isa(Test_env *, Test_meta);
+static const char *dbteste_getopnd(Test_env *, Test_op, bool);
+static void dbteste_error(Test_env *, int, const char *);
+
+/*
+ * execute command tree
+ */
+int
+execute(struct op *volatile t,
+    volatile int flags,		/* if XEXEC don't fork */
+    volatile int * volatile xerrok)
+{
+	int i;
+	volatile int rv = 0, dummy = 0;
+	int pv[2];
+	const char ** volatile ap;
+	char ** volatile up;
+	const char *s, *cp;
+	struct ioword **iowp;
+	struct tbl *tp = NULL;
+
+	if (t == NULL)
+		return (0);
+
+	/* Caller doesn't care if XERROK should propagate. */
+	if (xerrok == NULL)
+		xerrok = &dummy;
+
+	if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE)
+		/* run in sub-process */
+		return (exchild(t, flags & ~XTIME, xerrok, -1));
+
+	newenv(E_EXEC);
+	if (trap)
+		runtraps(0);
+
+	if (t->type == TCOM) {
+		/* Clear subst_exstat before argument expansion. Used by
+		 * null commands (see comexec() and c_eval()) and by c_set().
+		 */
+		subst_exstat = 0;
+
+		current_lineno = t->lineno;	/* for $LINENO */
+
+		/* POSIX says expand command words first, then redirections,
+		 * and assignments last..
+		 */
+		up = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE);
+		if (flags & XTIME)
+			/* Allow option parsing (bizarre, but POSIX) */
+			timex_hook(t, &up);
+		ap = (const char **)up;
+		if (Flag(FXTRACE) && ap[0]) {
+			shf_fprintf(shl_out, "%s",
+				substitute(str_val(global("PS4")), 0));
+			for (i = 0; ap[i]; i++)
+				shf_fprintf(shl_out, "%s%c", ap[i],
+				    ap[i + 1] ? ' ' : '\n');
+			shf_flush(shl_out);
+		}
+		if (ap[0])
+			tp = findcom(ap[0], FC_BI|FC_FUNC);
+	}
+	flags &= ~XTIME;
+
+	if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) {
+		e->savefd = alloc(NUFILE * sizeof(short), ATEMP);
+		/* initialise to not redirected */
+		memset(e->savefd, 0, NUFILE * sizeof(short));
+	}
+
+	/* do redirection, to be restored in quitenv() */
+	if (t->ioact != NULL)
+		for (iowp = t->ioact; *iowp != NULL; iowp++) {
+			if (iosetup(*iowp, tp) < 0) {
+				exstat = rv = 1;
+				/* Redirection failures for special commands
+				 * cause (non-interactive) shell to exit.
+				 */
+				if (tp && tp->type == CSHELL &&
+				    (tp->flag & SPEC_BI))
+					errorfz();
+				/* Deal with FERREXIT, quitenv(), etc. */
+				goto Break;
+			}
+		}
+
+	switch (t->type) {
+	case TCOM:
+		rv = comexec(t, tp, (const char **)ap, flags, xerrok);
+		break;
+
+	case TPAREN:
+		rv = execute(t->left, flags | XFORK, xerrok);
+		break;
+
+	case TPIPE:
+		flags |= XFORK;
+		flags &= ~XEXEC;
+		e->savefd[0] = savefd(0);
+		e->savefd[1] = savefd(1);
+		while (t->type == TPIPE) {
+			openpipe(pv);
+			ksh_dup2(pv[1], 1, false); /* stdout of curr */
+			/**
+			 * Let exchild() close pv[0] in child
+			 * (if this isn't done, commands like
+			 *	(: ; cat /etc/termcap) | sleep 1
+			 * will hang forever).
+			 */
+			exchild(t->left, flags | XPIPEO | XCCLOSE,
+			    NULL, pv[0]);
+			ksh_dup2(pv[0], 0, false); /* stdin of next */
+			closepipe(pv);
+			flags |= XPIPEI;
+			t = t->right;
+		}
+		restfd(1, e->savefd[1]); /* stdout of last */
+		e->savefd[1] = 0; /* no need to re-restore this */
+		/* Let exchild() close 0 in parent, after fork, before wait */
+		i = exchild(t, flags | XPCLOSE, xerrok, 0);
+		if (!(flags&XBGND) && !(flags&XXCOM))
+			rv = i;
+		break;
+
+	case TLIST:
+		while (t->type == TLIST) {
+			execute(t->left, flags & XERROK, NULL);
+			t = t->right;
+		}
+		rv = execute(t, flags & XERROK, xerrok);
+		break;
+
+	case TCOPROC: {
+		sigset_t omask;
+
+		/* Block sigchild as we are using things changed in the
+		 * signal handler
+		 */
+		sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+		e->type = E_ERRH;
+		i = sigsetjmp(e->jbuf, 0);
+		if (i) {
+			sigprocmask(SIG_SETMASK, &omask, NULL);
+			quitenv(NULL);
+			unwind(i);
+			/* NOTREACHED */
+		}
+		/* Already have a (live) co-process? */
+		if (coproc.job && coproc.write >= 0)
+			errorf("coprocess already exists");
+
+		/* Can we re-use the existing co-process pipe? */
+		coproc_cleanup(true);
+
+		/* do this before opening pipes, in case these fail */
+		e->savefd[0] = savefd(0);
+		e->savefd[1] = savefd(1);
+
+		openpipe(pv);
+		if (pv[0] != 0) {
+			ksh_dup2(pv[0], 0, false);
+			close(pv[0]);
+		}
+		coproc.write = pv[1];
+		coproc.job = NULL;
+
+		if (coproc.readw >= 0)
+			ksh_dup2(coproc.readw, 1, false);
+		else {
+			openpipe(pv);
+			coproc.read = pv[0];
+			ksh_dup2(pv[1], 1, false);
+			coproc.readw = pv[1];	 /* closed before first read */
+			coproc.njobs = 0;
+			/* create new coprocess id */
+			++coproc.id;
+		}
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		e->type = E_EXEC; /* no more need for error handler */
+
+		/* exchild() closes coproc.* in child after fork,
+		 * will also increment coproc.njobs when the
+		 * job is actually created.
+		 */
+		flags &= ~XEXEC;
+		exchild(t->left, flags | XBGND | XFORK | XCOPROC | XCCLOSE,
+		    NULL, coproc.readw);
+		break;
+	}
+
+	case TASYNC:
+		/* XXX non-optimal, I think - "(foo &)", forks for (),
+		 * forks again for async... parent should optimise
+		 * this to "foo &"...
+		 */
+		rv = execute(t->left, (flags&~XEXEC)|XBGND|XFORK, xerrok);
+		break;
+
+	case TOR:
+	case TAND:
+		rv = execute(t->left, XERROK, xerrok);
+		if ((rv == 0) == (t->type == TAND))
+			rv = execute(t->right, XERROK, xerrok);
+		flags |= XERROK;
+		if (xerrok)
+			*xerrok = 1;
+		break;
+
+	case TBANG:
+		rv = !execute(t->right, XERROK, xerrok);
+		flags |= XERROK;
+		if (xerrok)
+			*xerrok = 1;
+		break;
+
+	case TDBRACKET: {
+		Test_env te;
+
+		te.flags = TEF_DBRACKET;
+		te.pos.wp = t->args;
+		te.isa = dbteste_isa;
+		te.getopnd = dbteste_getopnd;
+		te.eval = test_eval;
+		te.error = dbteste_error;
+
+		rv = test_parse(&te);
+		break;
+	}
+
+	case TFOR:
+	case TSELECT: {
+		volatile bool is_first = true;
+		ap = (t->vars == NULL) ? e->loc->argv + 1 :
+		    (const char **)eval((const char **)t->vars,
+		    DOBLANK | DOGLOB | DOTILDE);
+		e->type = E_LOOP;
+		while (1) {
+			i = sigsetjmp(e->jbuf, 0);
+			if (!i)
+				break;
+			if ((e->flags&EF_BRKCONT_PASS) ||
+			    (i != LBREAK && i != LCONTIN)) {
+				quitenv(NULL);
+				unwind(i);
+			} else if (i == LBREAK) {
+				rv = 0;
+				goto Break;
+			}
+		}
+		rv = 0; /* in case of a continue */
+		if (t->type == TFOR) {
+			while (*ap != NULL) {
+				setstr(global(t->str), *ap++, KSH_UNWIND_ERROR);
+				rv = execute(t->left, flags & XERROK, xerrok);
+			}
+		} else { /* TSELECT */
+			for (;;) {
+				if (!(cp = do_selectargs(ap, is_first))) {
+					rv = 1;
+					break;
+				}
+				is_first = false;
+				setstr(global(t->str), cp, KSH_UNWIND_ERROR);
+				execute(t->left, flags & XERROK, xerrok);
+			}
+		}
+		break;
+	}
+
+	case TWHILE:
+	case TUNTIL:
+		e->type = E_LOOP;
+		while (1) {
+			i = sigsetjmp(e->jbuf, 0);
+			if (!i)
+				break;
+			if ((e->flags&EF_BRKCONT_PASS) ||
+			    (i != LBREAK && i != LCONTIN)) {
+				quitenv(NULL);
+				unwind(i);
+			} else if (i == LBREAK) {
+				rv = 0;
+				goto Break;
+			}
+		}
+		rv = 0; /* in case of a continue */
+		while ((execute(t->left, XERROK, NULL) == 0) ==
+		    (t->type == TWHILE))
+			rv = execute(t->right, flags & XERROK, xerrok);
+		break;
+
+	case TIF:
+	case TELIF:
+		if (t->right == NULL)
+			break;	/* should be error */
+		rv = execute(t->left, XERROK, NULL) == 0 ?
+		    execute(t->right->left, flags & XERROK, xerrok) :
+		    execute(t->right->right, flags & XERROK, xerrok);
+		break;
+
+	case TCASE:
+		cp = evalstr(t->str, DOTILDE);
+		for (t = t->left; t != NULL && t->type == TPAT; t = t->right)
+		    for (ap = (const char **)t->vars; *ap; ap++)
+			if ((s = evalstr(*ap, DOTILDE|DOPAT)) &&
+			    gmatchx(cp, s, false))
+				goto Found;
+		break;
+ Found:
+		rv = execute(t->left, flags & XERROK, xerrok);
+		break;
+
+	case TBRACE:
+		rv = execute(t->left, flags & XERROK, xerrok);
+		break;
+
+	case TFUNCT:
+		rv = define(t->str, t);
+		break;
+
+	case TTIME:
+		/* Clear XEXEC so nested execute() call doesn't exit
+		 * (allows "ls -l | time grep foo").
+		 */
+		rv = timex(t, flags & ~XEXEC, xerrok);
+		break;
+
+	case TEXEC:		/* an eval'd TCOM */
+		s = t->args[0];
+		up = makenv();
+		restoresigs();
+		cleanup_proc_env();
+		{
+			union mksh_ccphack cargs;
+
+			cargs.ro = t->args;
+			execve(t->str, cargs.rw, up);
+			rv = errno;
+		}
+		if (rv == ENOEXEC)
+			scriptexec(t, (const char **)up);
+		else
+			errorf("%s: %s", s, strerror(rv));
+	}
+ Break:
+	exstat = rv;
+
+	quitenv(NULL);		/* restores IO */
+	if ((flags&XEXEC))
+		unwind(LEXIT);	/* exit child */
+	if (rv != 0 && !(flags & XERROK) &&
+	    (xerrok == NULL || !*xerrok)) {
+		trapsig(SIGERR_);
+		if (Flag(FERREXIT))
+			unwind(LERROR);
+	}
+	return (rv);
+}
+
+/*
+ * execute simple command
+ */
+
+static int
+comexec(struct op *t, struct tbl *volatile tp, const char **ap,
+    volatile int flags, volatile int *xerrok)
+{
+	int i;
+	volatile int rv = 0;
+	const char *cp;
+	const char **lastp;
+	static struct op texec; /* Must be static (XXX but why?) */
+	int type_flags;
+	int keepasn_ok;
+	int fcflags = FC_BI|FC_FUNC|FC_PATH;
+	bool bourne_function_call = false;
+	struct block *l_expand, *l_assign;
+
+	/* snag the last argument for $_ XXX not the same as AT&T ksh,
+	 * which only seems to set $_ after a newline (but not in
+	 * functions/dot scripts, but in interactive and script) -
+	 * perhaps save last arg here and set it in shell()?.
+	 */
+	if (Flag(FTALKING) && *(lastp = ap)) {
+		while (*++lastp)
+			;
+		/* setstr() can't fail here */
+		setstr(typeset("_", LOCAL, 0, INTEGER, 0), *--lastp,
+		    KSH_RETURN_ERROR);
+	}
+
+	/* Deal with the shell builtins builtin, exec and command since
+	 * they can be followed by other commands. This must be done before
+	 * we know if we should create a local block which must be done
+	 * before we can do a path search (in case the assignments change
+	 * PATH).
+	 * Odd cases:
+	 *	FOO=bar exec >/dev/null		FOO is kept but not exported
+	 *	FOO=bar exec foobar		FOO is exported
+	 *	FOO=bar command exec >/dev/null	FOO is neither kept nor exported
+	 *	FOO=bar command			FOO is neither kept nor exported
+	 *	PATH=... foobar			use new PATH in foobar search
+	 */
+	keepasn_ok = 1;
+	while (tp && tp->type == CSHELL) {
+		fcflags = FC_BI|FC_FUNC|FC_PATH;/* undo effects of command */
+		if (tp->val.f == c_builtin) {
+			if ((cp = *++ap) == NULL) {
+				tp = NULL;
+				break;
+			}
+			tp = findcom(cp, FC_BI);
+			if (tp == NULL)
+				errorf("builtin: %s: not a builtin", cp);
+			continue;
+		} else if (tp->val.f == c_exec) {
+			if (ap[1] == NULL)
+				break;
+			ap++;
+			flags |= XEXEC;
+		} else if (tp->val.f == c_command) {
+			int optc, saw_p = 0;
+
+			/* Ugly dealing with options in two places (here and
+			 * in c_command(), but such is life)
+			 */
+			ksh_getopt_reset(&builtin_opt, 0);
+			while ((optc = ksh_getopt(ap, &builtin_opt, ":p")) == 'p')
+				saw_p = 1;
+			if (optc != EOF)
+				break;	/* command -vV or something */
+			/* don't look for functions */
+			fcflags = FC_BI|FC_PATH;
+			if (saw_p) {
+				if (Flag(FRESTRICTED)) {
+					warningf(true,
+					    "command -p: restricted");
+					rv = 1;
+					goto Leave;
+				}
+				fcflags |= FC_DEFPATH;
+			}
+			ap += builtin_opt.optind;
+			/* POSIX says special builtins lose their status
+			 * if accessed using command.
+			 */
+			keepasn_ok = 0;
+			if (!ap[0]) {
+				/* ensure command with no args exits with 0 */
+				subst_exstat = 0;
+				break;
+			}
+		} else
+			break;
+		tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC));
+	}
+	l_expand = e->loc;
+	if (keepasn_ok && (!ap[0] || (tp && (tp->flag & KEEPASN))))
+		type_flags = 0;
+	else {
+		/* create new variable/function block */
+		newblock();
+		/* ksh functions don't keep assignments, POSIX functions do. */
+		if (keepasn_ok && tp && tp->type == CFUNC &&
+		    !(tp->flag & FKSH)) {
+			bourne_function_call = true;
+			type_flags = EXPORT;
+		} else
+			type_flags = LOCAL|LOCAL_COPY|EXPORT;
+	}
+	l_assign = e->loc;
+	if (Flag(FEXPORT))
+		type_flags |= EXPORT;
+	for (i = 0; t->vars[i]; i++) {
+		/* do NOT lookup in the new var/fn block just created */
+		e->loc = l_expand;
+		cp = evalstr(t->vars[i], DOASNTILDE);
+		e->loc = l_assign;
+		/* but assign in there as usual */
+
+		if (Flag(FXTRACE)) {
+			if (i == 0)
+				shf_fprintf(shl_out, "%s",
+					substitute(str_val(global("PS4")), 0));
+			shf_fprintf(shl_out, "%s%c", cp,
+			    t->vars[i + 1] ? ' ' : '\n');
+			if (!t->vars[i + 1])
+				shf_flush(shl_out);
+		}
+		typeset(cp, type_flags, 0, 0, 0);
+		if (bourne_function_call && !(type_flags & EXPORT))
+			typeset(cp, LOCAL|LOCAL_COPY|EXPORT, 0, 0, 0);
+	}
+
+	if ((cp = *ap) == NULL) {
+		rv = subst_exstat;
+		goto Leave;
+	} else if (!tp) {
+		if (Flag(FRESTRICTED) && vstrchr(cp, '/')) {
+			warningf(true, "%s: restricted", cp);
+			rv = 1;
+			goto Leave;
+		}
+		tp = findcom(cp, fcflags);
+	}
+
+	switch (tp->type) {
+	case CSHELL:			/* shell built-in */
+		rv = call_builtin(tp, (const char **)ap);
+		break;
+
+	case CFUNC: {			/* function call */
+		volatile unsigned char old_xflag;
+		volatile Tflag old_inuse;
+		const char *volatile old_kshname;
+
+		if (!(tp->flag & ISSET)) {
+			struct tbl *ftp;
+
+			if (!tp->u.fpath) {
+				if (tp->u2.errno_) {
+					warningf(true,
+					    "%s: can't find function "
+					    "definition file - %s",
+					    cp, strerror(tp->u2.errno_));
+					rv = 126;
+				} else {
+					warningf(true,
+					    "%s: can't find function "
+					    "definition file", cp);
+					rv = 127;
+				}
+				break;
+			}
+			if (include(tp->u.fpath, 0, NULL, 0) < 0) {
+				rv = errno;
+				warningf(true,
+				    "%s: can't open function definition file %s - %s",
+				    cp, tp->u.fpath, strerror(rv));
+				rv = 127;
+				break;
+			}
+			if (!(ftp = findfunc(cp, hash(cp), false)) ||
+			    !(ftp->flag & ISSET)) {
+				warningf(true,
+				    "%s: function not defined by %s",
+				    cp, tp->u.fpath);
+				rv = 127;
+				break;
+			}
+			tp = ftp;
+		}
+
+		/* ksh functions set $0 to function name, POSIX functions leave
+		 * $0 unchanged.
+		 */
+		old_kshname = kshname;
+		if (tp->flag & FKSH)
+			kshname = ap[0];
+		else
+			ap[0] = kshname;
+		e->loc->argv = ap;
+		for (i = 0; *ap++ != NULL; i++)
+			;
+		e->loc->argc = i - 1;
+		/* ksh-style functions handle getopts sanely,
+		 * Bourne/POSIX functions are insane...
+		 */
+		if (tp->flag & FKSH) {
+			e->loc->flags |= BF_DOGETOPTS;
+			e->loc->getopts_state = user_opt;
+			getopts_reset(1);
+		}
+
+		old_xflag = Flag(FXTRACE);
+		Flag(FXTRACE) = tp->flag & TRACE ? 1 : 0;
+
+		old_inuse = tp->flag & FINUSE;
+		tp->flag |= FINUSE;
+
+		e->type = E_FUNC;
+		i = sigsetjmp(e->jbuf, 0);
+		if (i == 0) {
+			/* seems odd to pass XERROK here, but AT&T ksh does */
+			exstat = execute(tp->val.t, flags & XERROK, xerrok);
+			i = LRETURN;
+		}
+		kshname = old_kshname;
+		Flag(FXTRACE) = old_xflag;
+		tp->flag = (tp->flag & ~FINUSE) | old_inuse;
+		/* Were we deleted while executing? If so, free the execution
+		 * tree. todo: Unfortunately, the table entry is never re-used
+		 * until the lookup table is expanded.
+		 */
+		if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) {
+			if (tp->flag & ALLOC) {
+				tp->flag &= ~ALLOC;
+				tfree(tp->val.t, tp->areap);
+			}
+			tp->flag = 0;
+		}
+		switch (i) {
+		case LRETURN:
+		case LERROR:
+			rv = exstat;
+			break;
+		case LINTR:
+		case LEXIT:
+		case LLEAVE:
+		case LSHELL:
+			quitenv(NULL);
+			unwind(i);
+			/* NOTREACHED */
+		default:
+			quitenv(NULL);
+			internal_errorf("CFUNC %d", i);
+		}
+		break;
+	}
+
+	case CEXEC:		/* executable command */
+	case CTALIAS:		/* tracked alias */
+		if (!(tp->flag&ISSET)) {
+			/* errno_ will be set if the named command was found
+			 * but could not be executed (permissions, no execute
+			 * bit, directory, etc). Print out a (hopefully)
+			 * useful error message and set the exit status to 126.
+			 */
+			if (tp->u2.errno_) {
+				warningf(true, "%s: cannot execute - %s", cp,
+				    strerror(tp->u2.errno_));
+				rv = 126;	/* POSIX */
+			} else {
+				warningf(true, "%s: not found", cp);
+				rv = 127;
+			}
+			break;
+		}
+
+		/* set $_ to programme's full path */
+		/* setstr() can't fail here */
+		setstr(typeset("_", LOCAL|EXPORT, 0, INTEGER, 0),
+		    tp->val.s, KSH_RETURN_ERROR);
+
+		if (flags&XEXEC) {
+			j_exit();
+			if (!(flags&XBGND)
+#ifndef MKSH_UNEMPLOYED
+			    || Flag(FMONITOR)
+#endif
+			    ) {
+				setexecsig(&sigtraps[SIGINT], SS_RESTORE_ORIG);
+				setexecsig(&sigtraps[SIGQUIT], SS_RESTORE_ORIG);
+			}
+		}
+
+		/* to fork we set up a TEXEC node and call execute */
+		texec.type = TEXEC;
+		texec.left = t;	/* for tprint */
+		texec.str = tp->val.s;
+		texec.args = ap;
+		rv = exchild(&texec, flags, xerrok, -1);
+		break;
+	}
+ Leave:
+	if (flags & XEXEC) {
+		exstat = rv;
+		unwind(LLEAVE);
+	}
+	return (rv);
+}
+
+static void
+scriptexec(struct op *tp, const char **ap)
+{
+	const char *sh;
+#ifndef MKSH_SMALL
+	unsigned char *cp;
+	char buf[64];		/* 64 == MAXINTERP in MirBSD <sys/param.h> */
+	int fd;
+#endif
+	union mksh_ccphack args, cap;
+
+	sh = str_val(global("EXECSHELL"));
+	if (sh && *sh)
+		sh = search(sh, path, X_OK, NULL);
+	if (!sh || !*sh)
+		sh = MKSH_DEFAULT_EXECSHELL;
+
+	*tp->args-- = tp->str;
+
+#ifndef MKSH_SMALL
+	if ((fd = open(tp->str, O_RDONLY)) >= 0) {
+		/* read first MAXINTERP octets from file */
+		if (read(fd, buf, sizeof(buf)) <= 0)
+			/* read error -> no good */
+			buf[0] = '\0';
+		close(fd);
+		/* scan for newline (or CR) or NUL _before_ end of buffer */
+		cp = (unsigned char *)buf;
+		while ((char *)cp < (buf + sizeof(buf)))
+			if (*cp == '\0' || *cp == '\n' || *cp == '\r') {
+				*cp = '\0';
+				break;
+			} else
+				++cp;
+		/* if the shebang line is longer than MAXINTERP, bail out */
+		if ((char *)cp >= (buf + sizeof(buf)))
+			goto noshebang;
+		/* skip UTF-8 Byte Order Mark, if present */
+		cp = (unsigned char *)buf;
+		if ((cp[0] == 0xEF) && (cp[1] == 0xBB) && (cp[2] == 0xBF))
+			cp += 3;
+		/* bail out if read error (above) or no shebang */
+		if ((cp[0] != '#') || (cp[1] != '!'))
+			goto noshebang;
+		cp += 2;
+		/* skip whitespace before shell name */
+		while (*cp == ' ' || *cp == '\t')
+			++cp;
+		/* just whitespace on the line? */
+		if (*cp == '\0')
+			goto noshebang;
+		/* no, we actually found an interpreter name */
+		sh = (char *)cp;
+		/* look for end of shell/interpreter name */
+		while (*cp != ' ' && *cp != '\t' && *cp != '\0')
+			++cp;
+		/* any arguments? */
+		if (*cp) {
+			*cp++ = '\0';
+			/* skip spaces before arguments */
+			while (*cp == ' ' || *cp == '\t')
+				++cp;
+			/* pass it all in ONE argument (historic reasons) */
+			if (*cp)
+				*tp->args-- = (char *)cp;
+		}
+ noshebang:
+		fd = buf[0] << 8 | buf[1];
+		if ((fd == /* OMAGIC */ 0407) ||
+		    (fd == /* NMAGIC */ 0410) ||
+		    (fd == /* ZMAGIC */ 0413) ||
+		    (fd == /* QMAGIC */ 0314) ||
+		    (fd == /* ECOFF_I386 */ 0x4C01) ||
+		    (fd == /* ECOFF_M68K */ 0x0150 || fd == 0x5001) ||
+		    (fd == /* ECOFF_SH */   0x0500 || fd == 0x0005) ||
+		    (fd == 0x7F45 && buf[2] == 'L' && buf[3] == 'F') ||
+		    (fd == /* "MZ" */ 0x4D5A) ||
+		    (fd == /* gzip */ 0x1F8B))
+			errorf("%s: not executable: magic %04X", tp->str, fd);
+	}
+#endif
+	args.ro = tp->args;
+	*args.ro = sh;
+
+	cap.ro = ap;
+	execve(args.rw[0], args.rw, cap.rw);
+
+	/* report both the programme that was run and the bogus interpreter */
+	errorf("%s: %s: %s", tp->str, sh, strerror(errno));
+}
+
+int
+shcomexec(const char **wp)
+{
+	struct tbl *tp;
+
+	tp = ktsearch(&builtins, *wp, hash(*wp));
+	if (tp == NULL)
+		internal_errorf("shcomexec: %s", *wp);
+	return (call_builtin(tp, wp));
+}
+
+/*
+ * Search function tables for a function. If create set, a table entry
+ * is created if none is found.
+ */
+struct tbl *
+findfunc(const char *name, uint32_t h, bool create)
+{
+	struct block *l;
+	struct tbl *tp = NULL;
+
+	for (l = e->loc; l; l = l->next) {
+		tp = ktsearch(&l->funs, name, h);
+		if (tp)
+			break;
+		if (!l->next && create) {
+			tp = ktenter(&l->funs, name, h);
+			tp->flag = DEFINED;
+			tp->type = CFUNC;
+			tp->val.t = NULL;
+			break;
+		}
+	}
+	return (tp);
+}
+
+/*
+ * define function. Returns 1 if function is being undefined (t == 0) and
+ * function did not exist, returns 0 otherwise.
+ */
+int
+define(const char *name, struct op *t)
+{
+	struct tbl *tp;
+	bool was_set = false;
+
+	while (1) {
+		tp = findfunc(name, hash(name), true);
+
+		if (tp->flag & ISSET)
+			was_set = true;
+		/* If this function is currently being executed, we zap this
+		 * table entry so findfunc() won't see it
+		 */
+		if (tp->flag & FINUSE) {
+			tp->name[0] = '\0';
+			tp->flag &= ~DEFINED; /* ensure it won't be found */
+			tp->flag |= FDELETE;
+		} else
+			break;
+	}
+
+	if (tp->flag & ALLOC) {
+		tp->flag &= ~(ISSET|ALLOC);
+		tfree(tp->val.t, tp->areap);
+	}
+
+	if (t == NULL) {		/* undefine */
+		ktdelete(tp);
+		return (was_set ? 0 : 1);
+	}
+
+	tp->val.t = tcopy(t->left, tp->areap);
+	tp->flag |= (ISSET|ALLOC);
+	if (t->u.ksh_func)
+		tp->flag |= FKSH;
+
+	return (0);
+}
+
+/*
+ * add builtin
+ */
+void
+builtin(const char *name, int (*func) (const char **))
+{
+	struct tbl *tp;
+	Tflag flag;
+
+	/* see if any flags should be set for this builtin */
+	for (flag = 0; ; name++) {
+		if (*name == '=')	/* command does variable assignment */
+			flag |= KEEPASN;
+		else if (*name == '*')	/* POSIX special builtin */
+			flag |= SPEC_BI;
+		else if (*name == '+')	/* POSIX regular builtin */
+			flag |= REG_BI;
+		else
+			break;
+	}
+
+	tp = ktenter(&builtins, name, hash(name));
+	tp->flag = DEFINED | flag;
+	tp->type = CSHELL;
+	tp->val.f = func;
+}
+
+/*
+ * find command
+ * either function, hashed command, or built-in (in that order)
+ */
+struct tbl *
+findcom(const char *name, int flags)
+{
+	static struct tbl temp;
+	uint32_t h = hash(name);
+	struct tbl *tp = NULL, *tbi;
+	unsigned char insert = Flag(FTRACKALL);	/* insert if not found */
+	char *fpath;			/* for function autoloading */
+	union mksh_cchack npath;
+
+	if (vstrchr(name, '/')) {
+		insert = 0;
+		/* prevent FPATH search below */
+		flags &= ~FC_FUNC;
+		goto Search;
+	}
+	tbi = (flags & FC_BI) ? ktsearch(&builtins, name, h) : NULL;
+	/* POSIX says special builtins first, then functions, then
+	 * POSIX regular builtins, then search path...
+	 */
+	if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI))
+		tp = tbi;
+	if (!tp && (flags & FC_FUNC)) {
+		tp = findfunc(name, h, false);
+		if (tp && !(tp->flag & ISSET)) {
+			if ((fpath = str_val(global("FPATH"))) == null) {
+				tp->u.fpath = NULL;
+				tp->u2.errno_ = 0;
+			} else
+				tp->u.fpath = search(name, fpath, R_OK,
+				    &tp->u2.errno_);
+		}
+	}
+	if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI))
+		tp = tbi;
+	if (!tp && (flags & FC_UNREGBI) && tbi)
+		tp = tbi;
+	if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) {
+		tp = ktsearch(&taliases, name, h);
+		if (tp && (tp->flag & ISSET) && access(tp->val.s, X_OK) != 0) {
+			if (tp->flag & ALLOC) {
+				tp->flag &= ~ALLOC;
+				afree(tp->val.s, APERM);
+			}
+			tp->flag &= ~ISSET;
+		}
+	}
+
+ Search:
+	if ((!tp || (tp->type == CTALIAS && !(tp->flag&ISSET))) &&
+	    (flags & FC_PATH)) {
+		if (!tp) {
+			if (insert && !(flags & FC_DEFPATH)) {
+				tp = ktenter(&taliases, name, h);
+				tp->type = CTALIAS;
+			} else {
+				tp = &temp;
+				tp->type = CEXEC;
+			}
+			tp->flag = DEFINED;	/* make ~ISSET */
+		}
+		npath.ro = search(name, flags & FC_DEFPATH ? def_path : path,
+		    X_OK, &tp->u2.errno_);
+		if (npath.ro) {
+			strdupx(tp->val.s, npath.ro, APERM);
+			if (npath.ro != name)
+				afree(npath.rw, ATEMP);
+			tp->flag |= ISSET|ALLOC;
+		} else if ((flags & FC_FUNC) &&
+		    (fpath = str_val(global("FPATH"))) != null &&
+		    (npath.ro = search(name, fpath, R_OK,
+		    &tp->u2.errno_)) != NULL) {
+			/* An undocumented feature of AT&T ksh is that it
+			 * searches FPATH if a command is not found, even
+			 * if the command hasn't been set up as an autoloaded
+			 * function (ie, no typeset -uf).
+			 */
+			tp = &temp;
+			tp->type = CFUNC;
+			tp->flag = DEFINED; /* make ~ISSET */
+			tp->u.fpath = npath.ro;
+		}
+	}
+	return (tp);
+}
+
+/*
+ * flush executable commands with relative paths
+ */
+void
+flushcom(int all)	/* just relative or all */
+{
+	struct tbl *tp;
+	struct tstate ts;
+
+	for (ktwalk(&ts, &taliases); (tp = ktnext(&ts)) != NULL; )
+		if ((tp->flag&ISSET) && (all || tp->val.s[0] != '/')) {
+			if (tp->flag&ALLOC) {
+				tp->flag &= ~(ALLOC|ISSET);
+				afree(tp->val.s, APERM);
+			}
+			tp->flag &= ~ISSET;
+		}
+}
+
+/* Check if path is something we want to find. Returns -1 for failure. */
+int
+search_access(const char *lpath, int mode,
+    int *errnop)	/* set if candidate found, but not suitable */
+{
+	int ret, err = 0;
+	struct stat statb;
+
+	if (stat(lpath, &statb) < 0)
+		return (-1);
+	ret = access(lpath, mode);
+	if (ret < 0)
+		err = errno; /* File exists, but we can't access it */
+	else if (mode == X_OK && (!S_ISREG(statb.st_mode) ||
+	    !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) {
+		/* This 'cause access() says root can execute everything */
+		ret = -1;
+		err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES;
+	}
+	if (err && errnop && !*errnop)
+		*errnop = err;
+	return (ret);
+}
+
+/*
+ * search for command with PATH
+ */
+const char *
+search(const char *name, const char *lpath,
+    int mode,		/* R_OK or X_OK */
+    int *errnop)	/* set if candidate found, but not suitable */
+{
+	const char *sp, *p;
+	char *xp;
+	XString xs;
+	int namelen;
+
+	if (errnop)
+		*errnop = 0;
+	if (vstrchr(name, '/')) {
+		if (search_access(name, mode, errnop) == 0)
+			return (name);
+		return (NULL);
+	}
+
+	namelen = strlen(name) + 1;
+	Xinit(xs, xp, 128, ATEMP);
+
+	sp = lpath;
+	while (sp != NULL) {
+		xp = Xstring(xs, xp);
+		if (!(p = cstrchr(sp, ':')))
+			p = sp + strlen(sp);
+		if (p != sp) {
+			XcheckN(xs, xp, p - sp);
+			memcpy(xp, sp, p - sp);
+			xp += p - sp;
+			*xp++ = '/';
+		}
+		sp = p;
+		XcheckN(xs, xp, namelen);
+		memcpy(xp, name, namelen);
+		if (search_access(Xstring(xs, xp), mode, errnop) == 0)
+			return (Xclose(xs, xp + namelen));
+		if (*sp++ == '\0')
+			sp = NULL;
+	}
+	Xfree(xs, xp);
+	return (NULL);
+}
+
+static int
+call_builtin(struct tbl *tp, const char **wp)
+{
+	int rv;
+
+	builtin_argv0 = wp[0];
+	builtin_flag = tp->flag;
+	shf_reopen(1, SHF_WR, shl_stdout);
+	shl_stdout_ok = 1;
+	ksh_getopt_reset(&builtin_opt, GF_ERROR);
+	rv = (*tp->val.f)(wp);
+	shf_flush(shl_stdout);
+	shl_stdout_ok = 0;
+	builtin_flag = 0;
+	builtin_argv0 = NULL;
+	return (rv);
+}
+
+/*
+ * set up redirection, saving old fds in e->savefd
+ */
+static int
+iosetup(struct ioword *iop, struct tbl *tp)
+{
+	int u = -1;
+	char *cp = iop->name;
+	int iotype = iop->flag & IOTYPE;
+	int do_open = 1, do_close = 0, flags = 0;
+	struct ioword iotmp;
+	struct stat statb;
+
+	if (iotype != IOHERE)
+		cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0));
+
+	/* Used for tracing and error messages to print expanded cp */
+	iotmp = *iop;
+	iotmp.name = (iotype == IOHERE) ? NULL : cp;
+	iotmp.flag |= IONAMEXP;
+
+	if (Flag(FXTRACE))
+		shellf("%s%s\n",
+		    substitute(str_val(global("PS4")), 0),
+		    snptreef(NULL, 32, "%R", &iotmp));
+
+	switch (iotype) {
+	case IOREAD:
+		flags = O_RDONLY;
+		break;
+
+	case IOCAT:
+		flags = O_WRONLY | O_APPEND | O_CREAT;
+		break;
+
+	case IOWRITE:
+		flags = O_WRONLY | O_CREAT | O_TRUNC;
+		/* The stat() is here to allow redirections to
+		 * things like /dev/null without error.
+		 */
+		if (Flag(FNOCLOBBER) && !(iop->flag & IOCLOB) &&
+		    (stat(cp, &statb) < 0 || S_ISREG(statb.st_mode)))
+			flags |= O_EXCL;
+		break;
+
+	case IORDWR:
+		flags = O_RDWR | O_CREAT;
+		break;
+
+	case IOHERE:
+		do_open = 0;
+		/* herein() returns -2 if error has been printed */
+		u = herein(iop->heredoc, iop->flag & IOEVAL);
+		/* cp may have wrong name */
+		break;
+
+	case IODUP: {
+		const char *emsg;
+
+		do_open = 0;
+		if (*cp == '-' && !cp[1]) {
+			u = 1009;	 /* prevent error return below */
+			do_close = 1;
+		} else if ((u = check_fd(cp,
+		    X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK),
+		    &emsg)) < 0) {
+			warningf(true, "%s: %s",
+			    snptreef(NULL, 32, "%R", &iotmp), emsg);
+			return (-1);
+		}
+		if (u == iop->unit)
+			return (0);		/* "dup from" == "dup to" */
+		break;
+	}
+	}
+
+	if (do_open) {
+		if (Flag(FRESTRICTED) && (flags & O_CREAT)) {
+			warningf(true, "%s: restricted", cp);
+			return (-1);
+		}
+		u = open(cp, flags, 0666);
+	}
+	if (u < 0) {
+		/* herein() may already have printed message */
+		if (u == -1) {
+			u = errno;
+			warningf(true, "cannot %s %s: %s",
+			    iotype == IODUP ? "dup" :
+			    (iotype == IOREAD || iotype == IOHERE) ?
+			    "open" : "create", cp, strerror(u));
+		}
+		return (-1);
+	}
+	/* Do not save if it has already been redirected (i.e. "cat >x >y"). */
+	if (e->savefd[iop->unit] == 0) {
+		/* If these are the same, it means unit was previously closed */
+		if (u == iop->unit)
+			e->savefd[iop->unit] = -1;
+		else
+			/* c_exec() assumes e->savefd[fd] set for any
+			 * redirections. Ask savefd() not to close iop->unit;
+			 * this allows error messages to be seen if iop->unit
+			 * is 2; also means we can't lose the fd (eg, both
+			 * dup2 below and dup2 in restfd() failing).
+			 */
+			e->savefd[iop->unit] = savefd(iop->unit);
+	}
+
+	if (do_close)
+		close(iop->unit);
+	else if (u != iop->unit) {
+		if (ksh_dup2(u, iop->unit, true) < 0) {
+			int ev;
+
+			ev = errno;
+			warningf(true,
+			    "could not finish (dup) redirection %s: %s",
+			    snptreef(NULL, 32, "%R", &iotmp),
+			    strerror(ev));
+			if (iotype != IODUP)
+				close(u);
+			return (-1);
+		}
+		if (iotype != IODUP)
+			close(u);
+		/* Touching any co-process fd in an empty exec
+		 * causes the shell to close its copies
+		 */
+		else if (tp && tp->type == CSHELL && tp->val.f == c_exec) {
+			if (iop->flag & IORDUP)	/* possible exec <&p */
+				coproc_read_close(u);
+			else			/* possible exec >&p */
+				coproc_write_close(u);
+		}
+	}
+	if (u == 2) /* Clear any write errors */
+		shf_reopen(2, SHF_WR, shl_out);
+	return (0);
+}
+
+/*
+ * open here document temp file.
+ * if unquoted here, expand here temp file into second temp file.
+ */
+static int
+herein(const char *content, int sub)
+{
+	volatile int fd = -1;
+	struct source *s, *volatile osource;
+	struct shf *volatile shf;
+	struct temp *h;
+	int i;
+
+	/* ksh -c 'cat << EOF' can cause this... */
+	if (content == NULL) {
+		warningf(true, "here document missing");
+		return (-2); /* special to iosetup(): don't print error */
+	}
+
+	/* Create temp file to hold content (done before newenv so temp
+	 * doesn't get removed too soon).
+	 */
+	h = maketemp(ATEMP, TT_HEREDOC_EXP, &e->temps);
+	if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) {
+		fd = errno;
+		warningf(true, "can't %s temporary file %s: %s",
+		    !shf ? "create" : "open",
+		    h->name, strerror(fd));
+		if (shf)
+			shf_close(shf);
+		return (-2 /* special to iosetup(): don't print error */);
+	}
+
+	osource = source;
+	newenv(E_ERRH);
+	i = sigsetjmp(e->jbuf, 0);
+	if (i) {
+		source = osource;
+		quitenv(shf);
+		close(fd);
+		return (-2); /* special to iosetup(): don't print error */
+	}
+	if (sub) {
+		/* Do substitutions on the content of heredoc */
+		s = pushs(SSTRING, ATEMP);
+		s->start = s->str = content;
+		source = s;
+		if (yylex(ONEWORD|HEREDOC) != LWORD)
+			internal_errorf("herein: yylex");
+		source = osource;
+		shf_puts(evalstr(yylval.cp, 0), shf);
+	} else
+		shf_puts(content, shf);
+
+	quitenv(NULL);
+
+	if (shf_close(shf) == EOF) {
+		i = errno;
+		close(fd);
+		fd = errno;
+		warningf(true, "error writing %s: %s, %s", h->name,
+		    strerror(i), strerror(fd));
+		return (-2);	/* special to iosetup(): don't print error */
+	}
+
+	return (fd);
+}
+
+/*
+ *	ksh special - the select command processing section
+ *	print the args in column form - assuming that we can
+ */
+static const char *
+do_selectargs(const char **ap, bool print_menu)
+{
+	static const char *read_args[] = {
+		"read", "-r", "REPLY", NULL
+	};
+	char *s;
+	int i, argct;
+
+	for (argct = 0; ap[argct]; argct++)
+		;
+	while (1) {
+		/* Menu is printed if
+		 *	- this is the first time around the select loop
+		 *	- the user enters a blank line
+		 *	- the REPLY parameter is empty
+		 */
+		if (print_menu || !*str_val(global("REPLY")))
+			pr_menu(ap);
+		shellf("%s", str_val(global("PS3")));
+		if (call_builtin(findcom("read", FC_BI), read_args))
+			return (NULL);
+		s = str_val(global("REPLY"));
+		if (*s) {
+			getn(s, &i);
+			return ((i >= 1 && i <= argct) ? ap[i - 1] : null);
+		}
+		print_menu = 1;
+	}
+}
+
+struct select_menu_info {
+	const char * const *args;
+	int num_width;
+};
+
+static char *select_fmt_entry(char *, int, int, const void *);
+
+/* format a single select menu item */
+static char *
+select_fmt_entry(char *buf, int buflen, int i, const void *arg)
+{
+	const struct select_menu_info *smi =
+	    (const struct select_menu_info *)arg;
+
+	shf_snprintf(buf, buflen, "%*d) %s",
+	    smi->num_width, i + 1, smi->args[i]);
+	return (buf);
+}
+
+/*
+ *	print a select style menu
+ */
+int
+pr_menu(const char * const *ap)
+{
+	struct select_menu_info smi;
+	const char * const *pp;
+	int acols = 0, aocts = 0, i, n;
+
+	/*
+	 * width/column calculations were done once and saved, but this
+	 * means select can't be used recursively so we re-calculate
+	 * each time (could save in a structure that is returned, but
+	 * it's probably not worth the bother)
+	 */
+
+	/*
+	 * get dimensions of the list
+	 */
+	for (n = 0, pp = ap; *pp; n++, pp++) {
+		i = strlen(*pp);
+		if (i > aocts)
+			aocts = i;
+		i = utf_mbswidth(*pp);
+		if (i > acols)
+			acols = i;
+	}
+
+	/*
+	 * we will print an index of the form "%d) " in front of
+	 * each entry, so get the maximum width of this
+	 */
+	for (i = n, smi.num_width = 1; i >= 10; i /= 10)
+		smi.num_width++;
+
+	smi.args = ap;
+	print_columns(shl_out, n, select_fmt_entry, (void *)&smi,
+	    smi.num_width + 2 + aocts, smi.num_width + 2 + acols,
+	    true);
+
+	return (n);
+}
+
+/* XXX: horrible kludge to fit within the framework */
+static char *plain_fmt_entry(char *, int, int, const void *);
+
+static char *
+plain_fmt_entry(char *buf, int buflen, int i, const void *arg)
+{
+	shf_snprintf(buf, buflen, "%s", ((const char * const *)arg)[i]);
+	return (buf);
+}
+
+int
+pr_list(char * const *ap)
+{
+	int acols = 0, aocts = 0, i, n;
+	char * const *pp;
+
+	for (n = 0, pp = ap; *pp; n++, pp++) {
+		i = strlen(*pp);
+		if (i > aocts)
+			aocts = i;
+		i = utf_mbswidth(*pp);
+		if (i > acols)
+			acols = i;
+	}
+
+	print_columns(shl_out, n, plain_fmt_entry, (const void *)ap,
+	    aocts, acols, false);
+
+	return (n);
+}
+
+/*
+ *	[[ ... ]] evaluation routines
+ */
+
+/*
+ * Test if the current token is a whatever. Accepts the current token if
+ * it is. Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static Test_op
+dbteste_isa(Test_env *te, Test_meta meta)
+{
+	Test_op ret = TO_NONOP;
+	int uqword;
+	const char *p;
+
+	if (!*te->pos.wp)
+		return (meta == TM_END ? TO_NONNULL : TO_NONOP);
+
+	/* unquoted word? */
+	for (p = *te->pos.wp; *p == CHAR; p += 2)
+		;
+	uqword = *p == EOS;
+
+	if (meta == TM_UNOP || meta == TM_BINOP) {
+		if (uqword) {
+			char buf[8];	/* longer than the longest operator */
+			char *q = buf;
+			for (p = *te->pos.wp;
+			    *p == CHAR && q < &buf[sizeof(buf) - 1]; p += 2)
+				*q++ = p[1];
+			*q = '\0';
+			ret = test_isop(meta, buf);
+		}
+	} else if (meta == TM_END)
+		ret = TO_NONOP;
+	else
+		ret = (uqword && !strcmp(*te->pos.wp,
+		    dbtest_tokens[(int)meta])) ? TO_NONNULL : TO_NONOP;
+
+	/* Accept the token? */
+	if (ret != TO_NONOP)
+		te->pos.wp++;
+
+	return (ret);
+}
+
+static const char *
+dbteste_getopnd(Test_env *te, Test_op op, bool do_eval)
+{
+	const char *s = *te->pos.wp;
+
+	if (!s)
+		return (NULL);
+
+	te->pos.wp++;
+
+	if (!do_eval)
+		return (null);
+
+	if (op == TO_STEQL || op == TO_STNEQ)
+		s = evalstr(s, DOTILDE | DOPAT);
+	else
+		s = evalstr(s, DOTILDE);
+
+	return (s);
+}
+
+static void
+dbteste_error(Test_env *te, int offset, const char *msg)
+{
+	te->flags |= TEF_ERROR;
+	internal_warningf("dbteste_error: %s (offset %d)", msg, offset);
+}
diff --git a/mksh/src/expr.c b/mksh/src/expr.c
new file mode 100644
index 0000000..6c5710c
--- /dev/null
+++ b/mksh/src/expr.c
@@ -0,0 +1,895 @@
+/*	$OpenBSD: expr.c,v 1.21 2009/06/01 19:00:57 deraadt Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.44 2010/08/14 21:35:13 tg Exp $");
+
+/* The order of these enums is constrained by the order of opinfo[] */
+enum token {
+	/* some (long) unary operators */
+	O_PLUSPLUS = 0, O_MINUSMINUS,
+	/* binary operators */
+	O_EQ, O_NE,
+	/* assignments are assumed to be in range O_ASN .. O_BORASN */
+	O_ASN, O_TIMESASN, O_DIVASN, O_MODASN, O_PLUSASN, O_MINUSASN,
+	O_LSHIFTASN, O_RSHIFTASN, O_BANDASN, O_BXORASN, O_BORASN,
+	O_LSHIFT, O_RSHIFT,
+	O_LE, O_GE, O_LT, O_GT,
+	O_LAND,
+	O_LOR,
+	O_TIMES, O_DIV, O_MOD,
+	O_PLUS, O_MINUS,
+	O_BAND,
+	O_BXOR,
+	O_BOR,
+	O_TERN,
+	O_COMMA,
+	/* things after this aren't used as binary operators */
+	/* unary that are not also binaries */
+	O_BNOT, O_LNOT,
+	/* misc */
+	OPEN_PAREN, CLOSE_PAREN, CTERN,
+	/* things that don't appear in the opinfo[] table */
+	VAR, LIT, END, BAD
+};
+#define IS_BINOP(op) (((int)op) >= (int)O_EQ && ((int)op) <= (int)O_COMMA)
+#define IS_ASSIGNOP(op)	((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN)
+
+/* precisions; used to be enum prec but we do arithmetics on it */
+#define P_PRIMARY	0	/* VAR, LIT, (), ~ ! - + */
+#define P_MULT		1	/* * / % */
+#define P_ADD		2	/* + - */
+#define P_SHIFT		3	/* << >> */
+#define P_RELATION	4	/* < <= > >= */
+#define P_EQUALITY	5	/* == != */
+#define P_BAND		6	/* & */
+#define P_BXOR		7	/* ^ */
+#define P_BOR		8	/* | */
+#define P_LAND		9	/* && */
+#define P_LOR		10	/* || */
+#define P_TERN		11	/* ?: */
+#define P_ASSIGN	12	/* = *= /= %= += -= <<= >>= &= ^= |= */
+#define P_COMMA		13	/* , */
+#define MAX_PREC	P_COMMA
+
+struct opinfo {
+	char		name[4];
+	int		len;	/* name length */
+	int		prec;	/* precedence: lower is higher */
+};
+
+/* Tokens in this table must be ordered so the longest are first
+ * (eg, += before +). If you change something, change the order
+ * of enum token too.
+ */
+static const struct opinfo opinfo[] = {
+	{ "++",	 2, P_PRIMARY },	/* before + */
+	{ "--",	 2, P_PRIMARY },	/* before - */
+	{ "==",	 2, P_EQUALITY },	/* before = */
+	{ "!=",	 2, P_EQUALITY },	/* before ! */
+	{ "=",	 1, P_ASSIGN },		/* keep assigns in a block */
+	{ "*=",	 2, P_ASSIGN },
+	{ "/=",	 2, P_ASSIGN },
+	{ "%=",	 2, P_ASSIGN },
+	{ "+=",	 2, P_ASSIGN },
+	{ "-=",	 2, P_ASSIGN },
+	{ "<<=", 3, P_ASSIGN },
+	{ ">>=", 3, P_ASSIGN },
+	{ "&=",	 2, P_ASSIGN },
+	{ "^=",	 2, P_ASSIGN },
+	{ "|=",	 2, P_ASSIGN },
+	{ "<<",	 2, P_SHIFT },
+	{ ">>",	 2, P_SHIFT },
+	{ "<=",	 2, P_RELATION },
+	{ ">=",	 2, P_RELATION },
+	{ "<",	 1, P_RELATION },
+	{ ">",	 1, P_RELATION },
+	{ "&&",	 2, P_LAND },
+	{ "||",	 2, P_LOR },
+	{ "*",	 1, P_MULT },
+	{ "/",	 1, P_MULT },
+	{ "%",	 1, P_MULT },
+	{ "+",	 1, P_ADD },
+	{ "-",	 1, P_ADD },
+	{ "&",	 1, P_BAND },
+	{ "^",	 1, P_BXOR },
+	{ "|",	 1, P_BOR },
+	{ "?",	 1, P_TERN },
+	{ ",",	 1, P_COMMA },
+	{ "~",	 1, P_PRIMARY },
+	{ "!",	 1, P_PRIMARY },
+	{ "(",	 1, P_PRIMARY },
+	{ ")",	 1, P_PRIMARY },
+	{ ":",	 1, P_PRIMARY },
+	{ "",	 0, P_PRIMARY }
+};
+
+typedef struct expr_state Expr_state;
+struct expr_state {
+	const char *expression;		/* expression being evaluated */
+	const char *tokp;		/* lexical position */
+	struct tbl *val;		/* value from token() */
+	struct tbl *evaling;		/* variable that is being recursively
+					 * expanded (EXPRINEVAL flag set) */
+	int noassign;			/* don't do assigns (for ?:,&&,||) */
+	enum token tok;			/* token from token() */
+	bool arith;			/* evaluating an $(()) expression? */
+	bool natural;			/* unsigned arithmetic calculation */
+};
+
+#define bivui(x, op, y)	(es->natural ?			\
+	(mksh_ari_t)((x)->val.u op (y)->val.u) :	\
+	(mksh_ari_t)((x)->val.i op (y)->val.i)		\
+)
+#define chvui(x, op)	do {			\
+	if (es->natural)			\
+		(x)->val.u = op (x)->val.u;	\
+	else					\
+		(x)->val.i = op (x)->val.i;	\
+} while (/* CONSTCOND */ 0)
+#define stvui(x, n)	do {			\
+	if (es->natural)			\
+		(x)->val.u = (n);		\
+	else					\
+		(x)->val.i = (n);		\
+} while (/* CONSTCOND */ 0)
+
+enum error_type {
+	ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE,
+	ET_LVALUE, ET_RDONLY, ET_STR
+};
+
+static void evalerr(Expr_state *, enum error_type, const char *)
+    MKSH_A_NORETURN;
+static struct tbl *evalexpr(Expr_state *, int);
+static void exprtoken(Expr_state *);
+static struct tbl *do_ppmm(Expr_state *, enum token, struct tbl *, bool);
+static void assign_check(Expr_state *, enum token, struct tbl *);
+static struct tbl *tempvar(void);
+static struct tbl *intvar(Expr_state *, struct tbl *);
+
+/*
+ * parse and evaluate expression
+ */
+int
+evaluate(const char *expr, mksh_ari_t *rval, int error_ok, bool arith)
+{
+	struct tbl v;
+	int ret;
+
+	v.flag = DEFINED|INTEGER;
+	v.type = 0;
+	ret = v_evaluate(&v, expr, error_ok, arith);
+	*rval = v.val.i;
+	return (ret);
+}
+
+/*
+ * parse and evaluate expression, storing result in vp.
+ */
+int
+v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok,
+    bool arith)
+{
+	struct tbl *v;
+	Expr_state curstate;
+	Expr_state * const es = &curstate;
+	int i;
+
+	/* save state to allow recursive calls */
+	curstate.expression = curstate.tokp = expr;
+	curstate.noassign = 0;
+	curstate.arith = arith;
+	curstate.evaling = NULL;
+	curstate.natural = false;
+
+	newenv(E_ERRH);
+	i = sigsetjmp(e->jbuf, 0);
+	if (i) {
+		/* Clear EXPRINEVAL in of any variables we were playing with */
+		if (curstate.evaling)
+			curstate.evaling->flag &= ~EXPRINEVAL;
+		quitenv(NULL);
+		if (i == LAEXPR) {
+			if (error_ok == KSH_RETURN_ERROR)
+				return (0);
+			errorfz();
+		}
+		unwind(i);
+		/* NOTREACHED */
+	}
+
+	exprtoken(es);
+	if (es->tok == END) {
+		es->tok = LIT;
+		es->val = tempvar();
+	}
+	v = intvar(es, evalexpr(es, MAX_PREC));
+
+	if (es->tok != END)
+		evalerr(es, ET_UNEXPECTED, NULL);
+
+	if (es->arith && es->natural)
+		vp->flag |= INT_U;
+	if (vp->flag & INTEGER)
+		setint_v(vp, v, es->arith);
+	else
+		/* can fail if readonly */
+		setstr(vp, str_val(v), error_ok);
+
+	quitenv(NULL);
+
+	return (1);
+}
+
+static void
+evalerr(Expr_state *es, enum error_type type, const char *str)
+{
+	char tbuf[2];
+	const char *s;
+
+	es->arith = false;
+	switch (type) {
+	case ET_UNEXPECTED:
+		switch (es->tok) {
+		case VAR:
+			s = es->val->name;
+			break;
+		case LIT:
+			s = str_val(es->val);
+			break;
+		case END:
+			s = "end of expression";
+			break;
+		case BAD:
+			tbuf[0] = *es->tokp;
+			tbuf[1] = '\0';
+			s = tbuf;
+			break;
+		default:
+			s = opinfo[(int)es->tok].name;
+		}
+		warningf(true, "%s: unexpected '%s'", es->expression, s);
+		break;
+
+	case ET_BADLIT:
+		warningf(true, "%s: bad number '%s'", es->expression, str);
+		break;
+
+	case ET_RECURSIVE:
+		warningf(true, "%s: expression recurses on parameter '%s'",
+		    es->expression, str);
+		break;
+
+	case ET_LVALUE:
+		warningf(true, "%s: %s requires lvalue",
+		    es->expression, str);
+		break;
+
+	case ET_RDONLY:
+		warningf(true, "%s: %s applied to read only variable",
+		    es->expression, str);
+		break;
+
+	default: /* keep gcc happy */
+	case ET_STR:
+		warningf(true, "%s: %s", es->expression, str);
+		break;
+	}
+	unwind(LAEXPR);
+}
+
+static struct tbl *
+evalexpr(Expr_state *es, int prec)
+{
+	struct tbl *vl, *vr = NULL, *vasn;
+	enum token op;
+	mksh_ari_t res = 0;
+
+	if (prec == P_PRIMARY) {
+		op = es->tok;
+		if (op == O_BNOT || op == O_LNOT || op == O_MINUS ||
+		    op == O_PLUS) {
+			exprtoken(es);
+			vl = intvar(es, evalexpr(es, P_PRIMARY));
+			if (op == O_BNOT)
+				chvui(vl, ~);
+			else if (op == O_LNOT)
+				chvui(vl, !);
+			else if (op == O_MINUS)
+				chvui(vl, -);
+			/* op == O_PLUS is a no-op */
+		} else if (op == OPEN_PAREN) {
+			exprtoken(es);
+			vl = evalexpr(es, MAX_PREC);
+			if (es->tok != CLOSE_PAREN)
+				evalerr(es, ET_STR, "missing )");
+			exprtoken(es);
+		} else if (op == O_PLUSPLUS || op == O_MINUSMINUS) {
+			exprtoken(es);
+			vl = do_ppmm(es, op, es->val, true);
+			exprtoken(es);
+		} else if (op == VAR || op == LIT) {
+			vl = es->val;
+			exprtoken(es);
+		} else {
+			evalerr(es, ET_UNEXPECTED, NULL);
+			/* NOTREACHED */
+		}
+		if (es->tok == O_PLUSPLUS || es->tok == O_MINUSMINUS) {
+			vl = do_ppmm(es, es->tok, vl, false);
+			exprtoken(es);
+		}
+		return (vl);
+	}
+	vl = evalexpr(es, prec - 1);
+	for (op = es->tok; IS_BINOP(op) && opinfo[(int)op].prec == prec;
+	    op = es->tok) {
+		exprtoken(es);
+		vasn = vl;
+		if (op != O_ASN) /* vl may not have a value yet */
+			vl = intvar(es, vl);
+		if (IS_ASSIGNOP(op)) {
+			assign_check(es, op, vasn);
+			vr = intvar(es, evalexpr(es, P_ASSIGN));
+		} else if (op != O_TERN && op != O_LAND && op != O_LOR)
+			vr = intvar(es, evalexpr(es, prec - 1));
+		if ((op == O_DIV || op == O_MOD || op == O_DIVASN ||
+		    op == O_MODASN) && vr->val.i == 0) {
+			if (es->noassign)
+				vr->val.i = 1;
+			else
+				evalerr(es, ET_STR, "zero divisor");
+		}
+		switch ((int)op) {
+		case O_TIMES:
+		case O_TIMESASN:
+			res = bivui(vl, *, vr);
+			break;
+		case O_DIV:
+		case O_DIVASN:
+			res = bivui(vl, /, vr);
+			break;
+		case O_MOD:
+		case O_MODASN:
+			res = bivui(vl, %, vr);
+			break;
+		case O_PLUS:
+		case O_PLUSASN:
+			res = bivui(vl, +, vr);
+			break;
+		case O_MINUS:
+		case O_MINUSASN:
+			res = bivui(vl, -, vr);
+			break;
+		case O_LSHIFT:
+		case O_LSHIFTASN:
+			res = bivui(vl, <<, vr);
+			break;
+		case O_RSHIFT:
+		case O_RSHIFTASN:
+			res = bivui(vl, >>, vr);
+			break;
+		case O_LT:
+			res = bivui(vl, <, vr);
+			break;
+		case O_LE:
+			res = bivui(vl, <=, vr);
+			break;
+		case O_GT:
+			res = bivui(vl, >, vr);
+			break;
+		case O_GE:
+			res = bivui(vl, >=, vr);
+			break;
+		case O_EQ:
+			res = bivui(vl, ==, vr);
+			break;
+		case O_NE:
+			res = bivui(vl, !=, vr);
+			break;
+		case O_BAND:
+		case O_BANDASN:
+			res = bivui(vl, &, vr);
+			break;
+		case O_BXOR:
+		case O_BXORASN:
+			res = bivui(vl, ^, vr);
+			break;
+		case O_BOR:
+		case O_BORASN:
+			res = bivui(vl, |, vr);
+			break;
+		case O_LAND:
+			if (!vl->val.i)
+				es->noassign++;
+			vr = intvar(es, evalexpr(es, prec - 1));
+			res = bivui(vl, &&, vr);
+			if (!vl->val.i)
+				es->noassign--;
+			break;
+		case O_LOR:
+			if (vl->val.i)
+				es->noassign++;
+			vr = intvar(es, evalexpr(es, prec - 1));
+			res = bivui(vl, ||, vr);
+			if (vl->val.i)
+				es->noassign--;
+			break;
+		case O_TERN:
+			{
+				bool ev = vl->val.i != 0;
+
+				if (!ev)
+					es->noassign++;
+				vl = evalexpr(es, MAX_PREC);
+				if (!ev)
+					es->noassign--;
+				if (es->tok != CTERN)
+					evalerr(es, ET_STR, "missing :");
+				exprtoken(es);
+				if (ev)
+					es->noassign++;
+				vr = evalexpr(es, P_TERN);
+				if (ev)
+					es->noassign--;
+				vl = ev ? vl : vr;
+			}
+			break;
+		case O_ASN:
+			res = vr->val.i;
+			break;
+		case O_COMMA:
+			res = vr->val.i;
+			break;
+		}
+		if (IS_ASSIGNOP(op)) {
+			stvui(vr, res);
+			if (!es->noassign) {
+				if (vasn->flag & INTEGER)
+					setint_v(vasn, vr, es->arith);
+				else
+					setint(vasn, res);
+			}
+			vl = vr;
+		} else if (op != O_TERN)
+			stvui(vl, res);
+	}
+	return (vl);
+}
+
+static void
+exprtoken(Expr_state *es)
+{
+	const char *cp = es->tokp;
+	int c;
+	char *tvar;
+
+	/* skip white space */
+ skip_spaces:
+	while ((c = *cp), ksh_isspace(c))
+		++cp;
+	if (es->tokp == es->expression && c == '#') {
+		/* expression begins with # */
+		es->natural = true;	/* switch to unsigned */
+		++cp;
+		goto skip_spaces;
+	}
+	es->tokp = cp;
+
+	if (c == '\0')
+		es->tok = END;
+	else if (ksh_isalphx(c)) {
+		for (; ksh_isalnux(c); c = *cp)
+			cp++;
+		if (c == '[') {
+			int len;
+
+			len = array_ref_len(cp);
+			if (len == 0)
+				evalerr(es, ET_STR, "missing ]");
+			cp += len;
+		} else if (c == '(' /*)*/ ) {
+			/* todo: add math functions (all take single argument):
+			 * abs acos asin atan cos cosh exp int log sin sinh sqrt
+			 * tan tanh
+			 */
+			;
+		}
+		if (es->noassign) {
+			es->val = tempvar();
+			es->val->flag |= EXPRLVALUE;
+		} else {
+			strndupx(tvar, es->tokp, cp - es->tokp, ATEMP);
+			es->val = global(tvar);
+			afree(tvar, ATEMP);
+		}
+		es->tok = VAR;
+	} else if (c == '1' && cp[1] == '#') {
+		cp += 2;
+		cp += utf_ptradj(cp);
+		strndupx(tvar, es->tokp, cp - es->tokp, ATEMP);
+		goto process_tvar;
+#ifndef MKSH_SMALL
+	} else if (c == '\'') {
+		++cp;
+		cp += utf_ptradj(cp);
+		if (*cp++ != '\'')
+			evalerr(es, ET_STR,
+			    "multi-character character constant");
+		/* 'x' -> 1#x (x = one multibyte character) */
+		c = cp - es->tokp;
+		tvar = alloc(c + /* NUL */ 1, ATEMP);
+		tvar[0] = '1';
+		tvar[1] = '#';
+		memcpy(tvar + 2, es->tokp + 1, c - 2);
+		tvar[c] = '\0';
+		goto process_tvar;
+#endif
+	} else if (ksh_isdigit(c)) {
+		while (c != '_' && (ksh_isalnux(c) || c == '#'))
+			c = *cp++;
+		strndupx(tvar, es->tokp, --cp - es->tokp, ATEMP);
+ process_tvar:
+		es->val = tempvar();
+		es->val->flag &= ~INTEGER;
+		es->val->type = 0;
+		es->val->val.s = tvar;
+		if (setint_v(es->val, es->val, es->arith) == NULL)
+			evalerr(es, ET_BADLIT, tvar);
+		afree(tvar, ATEMP);
+		es->tok = LIT;
+	} else {
+		int i, n0;
+
+		for (i = 0; (n0 = opinfo[i].name[0]); i++)
+			if (c == n0 && strncmp(cp, opinfo[i].name,
+			    (size_t)opinfo[i].len) == 0) {
+				es->tok = (enum token)i;
+				cp += opinfo[i].len;
+				break;
+			}
+		if (!n0)
+			es->tok = BAD;
+	}
+	es->tokp = cp;
+}
+
+/* Do a ++ or -- operation */
+static struct tbl *
+do_ppmm(Expr_state *es, enum token op, struct tbl *vasn, bool is_prefix)
+{
+	struct tbl *vl;
+	mksh_ari_t oval;
+
+	assign_check(es, op, vasn);
+
+	vl = intvar(es, vasn);
+	oval = vl->val.i;
+	if (op == O_PLUSPLUS) {
+		if (es->natural)
+			++vl->val.u;
+		else
+			++vl->val.i;
+	} else {
+		if (es->natural)
+			--vl->val.u;
+		else
+			--vl->val.i;
+	}
+	if (vasn->flag & INTEGER)
+		setint_v(vasn, vl, es->arith);
+	else
+		setint(vasn, vl->val.i);
+	if (!is_prefix)		/* undo the inc/dec */
+		vl->val.i = oval;
+
+	return (vl);
+}
+
+static void
+assign_check(Expr_state *es, enum token op, struct tbl *vasn)
+{
+	if (es->tok == END ||
+	    (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE)))
+		evalerr(es, ET_LVALUE, opinfo[(int)op].name);
+	else if (vasn->flag & RDONLY)
+		evalerr(es, ET_RDONLY, opinfo[(int)op].name);
+}
+
+static struct tbl *
+tempvar(void)
+{
+	struct tbl *vp;
+
+	vp = alloc(sizeof(struct tbl), ATEMP);
+	vp->flag = ISSET|INTEGER;
+	vp->type = 0;
+	vp->areap = ATEMP;
+	vp->ua.hval = 0;
+	vp->val.i = 0;
+	vp->name[0] = '\0';
+	return (vp);
+}
+
+/* cast (string) variable to temporary integer variable */
+static struct tbl *
+intvar(Expr_state *es, struct tbl *vp)
+{
+	struct tbl *vq;
+
+	/* try to avoid replacing a temp var with another temp var */
+	if (vp->name[0] == '\0' &&
+	    (vp->flag & (ISSET|INTEGER|EXPRLVALUE)) == (ISSET|INTEGER))
+		return (vp);
+
+	vq = tempvar();
+	if (setint_v(vq, vp, es->arith) == NULL) {
+		if (vp->flag & EXPRINEVAL)
+			evalerr(es, ET_RECURSIVE, vp->name);
+		es->evaling = vp;
+		vp->flag |= EXPRINEVAL;
+		v_evaluate(vq, str_val(vp), KSH_UNWIND_ERROR, es->arith);
+		vp->flag &= ~EXPRINEVAL;
+		es->evaling = NULL;
+	}
+	return (vq);
+}
+
+
+/*
+ * UTF-8 support code: high-level functions
+ */
+
+int
+utf_widthadj(const char *src, const char **dst)
+{
+	size_t len;
+	unsigned int wc;
+	int width;
+
+	if (!UTFMODE || (len = utf_mbtowc(&wc, src)) == (size_t)-1 ||
+	    wc == 0)
+		len = width = 1;
+	else if ((width = utf_wcwidth(wc)) < 0)
+		/* XXX use 2 for x_zotc3 here? */
+		width = 1;
+
+	if (dst)
+		*dst = src + len;
+	return (width);
+}
+
+int
+utf_mbswidth(const char *s)
+{
+	size_t len;
+	unsigned int wc;
+	int width = 0, cw;
+
+	if (!UTFMODE)
+		return (strlen(s));
+
+	while (*s)
+		if (((len = utf_mbtowc(&wc, s)) == (size_t)-1) ||
+		    ((cw = utf_wcwidth(wc)) == -1)) {
+			s++;
+			width += 1;
+		} else {
+			s += len;
+			width += cw;
+		}
+	return (width);
+}
+
+const char *
+utf_skipcols(const char *p, int cols)
+{
+	int c = 0;
+
+	while (c < cols) {
+		if (!*p)
+			return (p + cols - c);
+		c += utf_widthadj(p, &p);
+	}
+	return (p);
+}
+
+size_t
+utf_ptradj(const char *src)
+{
+	register size_t n;
+
+	if (!UTFMODE ||
+	    *(const unsigned char *)(src) < 0xC2 ||
+	    (n = utf_mbtowc(NULL, src)) == (size_t)-1)
+		n = 1;
+	return (n);
+}
+
+/*
+ * UTF-8 support code: low-level functions
+ */
+
+/* CESU-8 multibyte and wide character conversion crafted for mksh */
+
+size_t
+utf_mbtowc(unsigned int *dst, const char *src)
+{
+	const unsigned char *s = (const unsigned char *)src;
+	unsigned int c, wc;
+
+	if ((wc = *s++) < 0x80) {
+ out:
+		if (dst != NULL)
+			*dst = wc;
+		return (wc ? ((const char *)s - src) : 0);
+	}
+	if (wc < 0xC2 || wc >= 0xF0)
+		/* < 0xC0: spurious second byte */
+		/* < 0xC2: non-minimalistic mapping error in 2-byte seqs */
+		/* > 0xEF: beyond BMP */
+		goto ilseq;
+
+	if (wc < 0xE0) {
+		wc = (wc & 0x1F) << 6;
+		if (((c = *s++) & 0xC0) != 0x80)
+			goto ilseq;
+		wc |= c & 0x3F;
+		goto out;
+	}
+
+	wc = (wc & 0x0F) << 12;
+
+	if (((c = *s++) & 0xC0) != 0x80)
+		goto ilseq;
+	wc |= (c & 0x3F) << 6;
+
+	if (((c = *s++) & 0xC0) != 0x80)
+		goto ilseq;
+	wc |= c & 0x3F;
+
+	/* Check for non-minimalistic mapping error in 3-byte seqs */
+	if (wc >= 0x0800 && wc <= 0xFFFD)
+		goto out;
+ ilseq:
+	return ((size_t)(-1));
+}
+
+size_t
+utf_wctomb(char *dst, unsigned int wc)
+{
+	unsigned char *d;
+
+	if (wc < 0x80) {
+		*dst = wc;
+		return (1);
+	}
+
+	d = (unsigned char *)dst;
+	if (wc < 0x0800)
+		*d++ = (wc >> 6) | 0xC0;
+	else {
+		*d++ = ((wc = wc > 0xFFFD ? 0xFFFD : wc) >> 12) | 0xE0;
+		*d++ = ((wc >> 6) & 0x3F) | 0x80;
+	}
+	*d++ = (wc & 0x3F) | 0x80;
+	return ((char *)d - dst);
+}
+
+
+#ifndef MKSH_mirbsd_wcwidth
+/* --- begin of wcwidth.c excerpt --- */
+/*-
+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ */
+
+__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.8 2008/09/20 12:01:18 tg Exp $");
+
+int
+utf_wcwidth(unsigned int c)
+{
+	static const struct cbset {
+		unsigned short first;
+		unsigned short last;
+	} comb[] = {
+		{ 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
+		{ 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+		{ 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
+		{ 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
+		{ 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+		{ 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
+		{ 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
+		{ 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
+		{ 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
+		{ 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
+		{ 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
+		{ 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
+		{ 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
+		{ 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
+		{ 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
+		{ 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
+		{ 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+		{ 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
+		{ 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
+		{ 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
+		{ 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+		{ 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+		{ 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+		{ 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+		{ 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+		{ 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+		{ 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+		{ 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+		{ 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+		{ 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
+		{ 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
+		{ 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
+		{ 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
+		{ 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
+		{ 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
+		{ 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+		{ 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
+		{ 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
+		{ 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
+		{ 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
+		{ 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
+		{ 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
+		{ 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }
+	};
+	size_t min = 0, mid, max = NELEM(comb) - 1;
+
+	/* test for 8-bit control characters */
+	if (c < 32 || (c >= 0x7f && c < 0xa0))
+		return (c ? -1 : 0);
+
+	/* binary search in table of non-spacing characters */
+	if (c >= comb[0].first && c <= comb[max].last)
+		while (max >= min) {
+			mid = (min + max) / 2;
+			if (c > comb[mid].last)
+				min = mid + 1;
+			else if (c < comb[mid].first)
+				max = mid - 1;
+			else
+				return (0);
+		}
+
+	/* if we arrive here, c is not a combining or C0/C1 control char */
+	return ((c >= 0x1100 && (
+	    c <= 0x115f || /* Hangul Jamo init. consonants */
+	    c == 0x2329 || c == 0x232a ||
+	    (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
+	    (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
+	    (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
+	    (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
+	    (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
+	    (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
+	    (c >= 0xffe0 && c <= 0xffe6))) ? 2 : 1);
+}
+/* --- end of wcwidth.c excerpt --- */
+#endif
diff --git a/mksh/src/funcs.c b/mksh/src/funcs.c
new file mode 100644
index 0000000..9d9c03a
--- /dev/null
+++ b/mksh/src/funcs.c
@@ -0,0 +1,3429 @@
+/*	$OpenBSD: c_ksh.c,v 1.33 2009/02/07 14:03:24 kili Exp $	*/
+/*	$OpenBSD: c_sh.c,v 1.41 2010/03/27 09:10:01 jmc Exp $	*/
+/*	$OpenBSD: c_test.c,v 1.18 2009/03/01 20:11:06 otto Exp $	*/
+/*	$OpenBSD: c_ulimit.c,v 1.17 2008/03/21 12:51:19 millert Exp $	*/
+
+/*-
+ * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.157 2010/08/24 14:42:01 tg Exp $");
+
+#if HAVE_KILLPG
+/*
+ * use killpg if < -1 since -1 does special things
+ * for some non-killpg-endowed kills
+ */
+#define mksh_kill(p,s)	((p) < -1 ? killpg(-(p), (s)) : kill((p), (s)))
+#else
+/* cross fingers and hope kill is killpg-endowed */
+#define mksh_kill	kill
+#endif
+
+/* XXX conditions correct? */
+#if !defined(RLIM_INFINITY) && !defined(MKSH_NO_LIMITS)
+#define MKSH_NO_LIMITS
+#endif
+
+#ifdef MKSH_NO_LIMITS
+#define c_ulimit c_label
+#endif
+
+extern uint8_t set_refflag;
+
+/* A leading = means assignments before command are kept;
+ * a leading * means a POSIX special builtin;
+ * a leading + means a POSIX regular builtin
+ * (* and + should not be combined).
+ */
+const struct builtin mkshbuiltins[] = {
+	{"*=.", c_dot},
+	{"*=:", c_label},
+	{"[", c_test},
+	{"*=break", c_brkcont},
+	{"=builtin", c_builtin},
+	{"*=continue", c_brkcont},
+	{"*=eval", c_eval},
+	{"*=exec", c_exec},
+	{"*=exit", c_exitreturn},
+	{"+false", c_label},
+	{"*=return", c_exitreturn},
+	{"*=set", c_set},
+	{"*=shift", c_shift},
+	{"=times", c_times},
+	{"*=trap", c_trap},
+	{"+=wait", c_wait},
+	{"+read", c_read},
+	{"test", c_test},
+	{"+true", c_label},
+	{"ulimit", c_ulimit},
+	{"+umask", c_umask},
+	{"*=unset", c_unset},
+	{"+alias", c_alias},	/* no =: AT&T manual wrong */
+	{"+cd", c_cd},
+	{"chdir", c_cd},	/* dash compatibility hack */
+	{"+command", c_command},
+	{"echo", c_print},
+	{"*=export", c_typeset},
+	{"+fc", c_fc},
+	{"+getopts", c_getopts},
+	{"+jobs", c_jobs},
+	{"+kill", c_kill},
+	{"let", c_let},
+	{"print", c_print},
+#ifdef MKSH_PRINTF_BUILTIN
+	{"printf", c_printf},
+#endif
+	{"pwd", c_pwd},
+	{"*=readonly", c_typeset},
+	{T__typeset, c_typeset},
+	{"+unalias", c_unalias},
+	{"whence", c_whence},
+#ifndef MKSH_UNEMPLOYED
+	{"+bg", c_fgbg},
+	{"+fg", c_fgbg},
+#endif
+	{"bind", c_bind},
+#if HAVE_MKNOD
+	{"mknod", c_mknod},
+#endif
+	{"realpath", c_realpath},
+	{"rename", c_rename},
+	{NULL, (int (*)(const char **))NULL}
+};
+
+struct kill_info {
+	int num_width;
+	int name_width;
+};
+
+static const struct t_op {
+	char op_text[4];
+	Test_op op_num;
+} u_ops[] = {
+	{"-a",	TO_FILAXST },
+	{"-b",	TO_FILBDEV },
+	{"-c",	TO_FILCDEV },
+	{"-d",	TO_FILID },
+	{"-e",	TO_FILEXST },
+	{"-f",	TO_FILREG },
+	{"-G",	TO_FILGID },
+	{"-g",	TO_FILSETG },
+	{"-h",	TO_FILSYM },
+	{"-H",	TO_FILCDF },
+	{"-k",	TO_FILSTCK },
+	{"-L",	TO_FILSYM },
+	{"-n",	TO_STNZE },
+	{"-O",	TO_FILUID },
+	{"-o",	TO_OPTION },
+	{"-p",	TO_FILFIFO },
+	{"-r",	TO_FILRD },
+	{"-s",	TO_FILGZ },
+	{"-S",	TO_FILSOCK },
+	{"-t",	TO_FILTT },
+	{"-u",	TO_FILSETU },
+	{"-w",	TO_FILWR },
+	{"-x",	TO_FILEX },
+	{"-z",	TO_STZER },
+	{"",	TO_NONOP }
+};
+static const struct t_op b_ops[] = {
+	{"=",	TO_STEQL },
+	{"==",	TO_STEQL },
+	{"!=",	TO_STNEQ },
+	{"<",	TO_STLT },
+	{">",	TO_STGT },
+	{"-eq",	TO_INTEQ },
+	{"-ne",	TO_INTNE },
+	{"-gt",	TO_INTGT },
+	{"-ge",	TO_INTGE },
+	{"-lt",	TO_INTLT },
+	{"-le",	TO_INTLE },
+	{"-ef",	TO_FILEQ },
+	{"-nt",	TO_FILNT },
+	{"-ot",	TO_FILOT },
+	{"",	TO_NONOP }
+};
+
+static int test_eaccess(const char *, int);
+static int test_oexpr(Test_env *, bool);
+static int test_aexpr(Test_env *, bool);
+static int test_nexpr(Test_env *, bool);
+static int test_primary(Test_env *, bool);
+static Test_op ptest_isa(Test_env *, Test_meta);
+static const char *ptest_getopnd(Test_env *, Test_op, bool);
+static void ptest_error(Test_env *, int, const char *);
+static char *kill_fmt_entry(char *, int, int, const void *);
+static void p_time(struct shf *, bool, long, int, int,
+    const char *, const char *)
+    MKSH_A_NONNULL((nonnull (6, 7)));
+static char *do_realpath(const char *);
+
+static char *
+do_realpath(const char *upath)
+{
+	char *xp, *ip, *tp, *ipath, *ldest = NULL;
+	XString xs;
+	ptrdiff_t pos;
+	size_t len;
+	int symlinks = 32;	/* max. recursion depth */
+	int llen;
+	struct stat sb;
+#ifdef NO_PATH_MAX
+	size_t ldestlen = 0;
+#define pathlen sb.st_size
+#define pathcnd (ldestlen < (pathlen + 1))
+#else
+#define pathlen PATH_MAX
+#define pathcnd (!ldest)
+#endif
+
+	if (upath[0] == '/') {
+		/* upath is an absolute pathname */
+		strdupx(ipath, upath, ATEMP);
+	} else {
+		/* upath is a relative pathname, prepend cwd */
+		if ((tp = ksh_get_wd(NULL)) == NULL || tp[0] != '/')
+			return (NULL);
+		ipath = shf_smprintf("%s/%s", tp, upath);
+		afree(tp, ATEMP);
+	}
+
+	Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP);
+
+	while (*ip) {
+		/* skip slashes in input */
+		while (*ip == '/')
+			++ip;
+		if (!*ip)
+			break;
+
+		/* get next pathname component from input */
+		tp = ip;
+		while (*ip && *ip != '/')
+			++ip;
+		len = ip - tp;
+
+		/* check input for "." and ".." */
+		if (tp[0] == '.') {
+			if (len == 1)
+				/* just continue with the next one */
+				continue;
+			else if (len == 2 && tp[1] == '.') {
+				/* strip off last pathname component */
+				while (xp > Xstring(xs, xp))
+					if (*--xp == '/')
+						break;
+				/* then continue with the next one */
+				continue;
+			}
+		}
+
+		/* store output position away, then append slash to output */
+		pos = Xsavepos(xs, xp);
+		/* 1 for the '/' and len + 1 for tp and the NUL from below */
+		XcheckN(xs, xp, 1 + len + 1);
+		Xput(xs, xp, '/');
+
+		/* append next pathname component to output */
+		memcpy(xp, tp, len);
+		xp += len;
+		*xp = '\0';
+
+		/* lstat the current output, see if it's a symlink */
+		if (lstat(Xstring(xs, xp), &sb)) {
+			/* lstat failed */
+			if (errno == ENOENT) {
+				/* because the pathname does not exist */
+				while (*ip == '/')
+					/* skip any trailing slashes */
+					++ip;
+				/* no more components left? */
+				if (!*ip)
+					/* we can still return successfully */
+					break;
+				/* more components left? fall through */
+			}
+			/* not ENOENT or not at the end of ipath */
+			goto notfound;
+		}
+
+		/* check if we encountered a symlink? */
+		if (S_ISLNK(sb.st_mode)) {
+			/* reached maximum recursion depth? */
+			if (!symlinks--) {
+				/* yep, prevent infinite loops */
+				errno = ELOOP;
+				goto notfound;
+			}
+
+			/* get symlink(7) target */
+			if (pathcnd)
+				ldest = aresize(ldest, pathlen + 1, ATEMP);
+			llen = readlink(Xstring(xs, xp), ldest, pathlen);
+			if (llen < 0)
+				/* oops... */
+				goto notfound;
+			ldest[llen] = '\0';
+
+			/*
+			 * restart if symlink target is an absolute path,
+			 * otherwise continue with currently resolved prefix
+			 */
+			xp = (ldest[0] == '/') ? Xstring(xs, xp) :
+			    Xrestpos(xs, xp, pos);
+			tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip);
+			afree(ipath, ATEMP);
+			ip = ipath = tp;
+		}
+		/* otherwise (no symlink) merely go on */
+	}
+
+	/*
+	 * either found the target and successfully resolved it,
+	 * or found its parent directory and may create it
+	 */
+	if (Xlength(xs, xp) == 0)
+		/*
+		 * if the resolved pathname is "", make it "/",
+		 * otherwise do not add a trailing slash
+		 */
+		Xput(xs, xp, '/');
+	Xput(xs, xp, '\0');
+
+	/*
+	 * if source path had a trailing slash, check if target path
+	 * is not a non-directory existing file
+	 */
+	if (ip > ipath && ip[-1] == '/') {
+		if (stat(Xstring(xs, xp), &sb)) {
+			if (errno != ENOENT)
+				goto notfound;
+		} else if (!S_ISDIR(sb.st_mode)) {
+			errno = ENOTDIR;
+			goto notfound;
+		}
+		/* target now either does not exist or is a directory */
+	}
+
+	/* return target path */
+	if (ldest != NULL)
+		afree(ldest, ATEMP);
+	afree(ipath, ATEMP);
+	return (Xclose(xs, xp));
+
+ notfound:
+	llen = errno;	/* save; free(3) might trash it */
+	if (ldest != NULL)
+		afree(ldest, ATEMP);
+	afree(ipath, ATEMP);
+	Xfree(xs, xp);
+	errno = llen;
+	return (NULL);
+
+#undef pathlen
+#undef pathcnd
+}
+
+int
+c_cd(const char **wp)
+{
+	int optc, rv, phys_path;
+	bool physical = Flag(FPHYSICAL) ? true : false;
+	int cdnode;			/* was a node from cdpath added in? */
+	bool printpath = false;		/* print where we cd'd? */
+	struct tbl *pwd_s, *oldpwd_s;
+	XString xs;
+	char *dir, *allocd = NULL, *tryp, *pwd, *cdpath;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
+		switch (optc) {
+		case 'L':
+			physical = false;
+			break;
+		case 'P':
+			physical = true;
+			break;
+		case '?':
+			return (1);
+		}
+	wp += builtin_opt.optind;
+
+	if (Flag(FRESTRICTED)) {
+		bi_errorf("restricted shell - can't cd");
+		return (1);
+	}
+
+	pwd_s = global("PWD");
+	oldpwd_s = global("OLDPWD");
+
+	if (!wp[0]) {
+		/* No arguments - go home */
+		if ((dir = str_val(global("HOME"))) == null) {
+			bi_errorf("no home directory (HOME not set)");
+			return (1);
+		}
+	} else if (!wp[1]) {
+		/* One argument: - or dir */
+		strdupx(allocd, wp[0], ATEMP);
+		if (ksh_isdash((dir = allocd))) {
+			afree(allocd, ATEMP);
+			allocd = NULL;
+			dir = str_val(oldpwd_s);
+			if (dir == null) {
+				bi_errorf("no OLDPWD");
+				return (1);
+			}
+			printpath = true;
+		}
+	} else if (!wp[2]) {
+		/* Two arguments - substitute arg1 in PWD for arg2 */
+		int ilen, olen, nlen, elen;
+		char *cp;
+
+		if (!current_wd[0]) {
+			bi_errorf("don't know current directory");
+			return (1);
+		}
+		/* substitute arg1 for arg2 in current path.
+		 * if the first substitution fails because the cd fails
+		 * we could try to find another substitution. For now
+		 * we don't
+		 */
+		if ((cp = strstr(current_wd, wp[0])) == NULL) {
+			bi_errorf("bad substitution");
+			return (1);
+		}
+		ilen = cp - current_wd;
+		olen = strlen(wp[0]);
+		nlen = strlen(wp[1]);
+		elen = strlen(current_wd + ilen + olen) + 1;
+		dir = allocd = alloc(ilen + nlen + elen, ATEMP);
+		memcpy(dir, current_wd, ilen);
+		memcpy(dir + ilen, wp[1], nlen);
+		memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
+		printpath = true;
+	} else {
+		bi_errorf("too many arguments");
+		return (1);
+	}
+
+#ifdef NO_PATH_MAX
+	/* only a first guess; make_path will enlarge xs if necessary */
+	XinitN(xs, 1024, ATEMP);
+#else
+	XinitN(xs, PATH_MAX, ATEMP);
+#endif
+
+	cdpath = str_val(global("CDPATH"));
+	do {
+		cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
+		if (physical)
+			rv = chdir(tryp = Xstring(xs, xp) + phys_path);
+		else {
+			simplify_path(Xstring(xs, xp));
+			rv = chdir(tryp = Xstring(xs, xp));
+		}
+	} while (rv < 0 && cdpath != NULL);
+
+	if (rv < 0) {
+		if (cdnode)
+			bi_errorf("%s: bad directory", dir);
+		else
+			bi_errorf("%s - %s", tryp, strerror(errno));
+		afree(allocd, ATEMP);
+		return (1);
+	}
+
+	/* allocd (above) => dir, which is no longer used */
+	afree(allocd, ATEMP);
+	allocd = NULL;
+
+	/* Clear out tracked aliases with relative paths */
+	flushcom(0);
+
+	/* Set OLDPWD (note: unsetting OLDPWD does not disable this
+	 * setting in AT&T ksh)
+	 */
+	if (current_wd[0])
+		/* Ignore failure (happens if readonly or integer) */
+		setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
+
+	if (Xstring(xs, xp)[0] != '/') {
+		pwd = NULL;
+	} else if (!physical || !(pwd = allocd = do_realpath(Xstring(xs, xp))))
+		pwd = Xstring(xs, xp);
+
+	/* Set PWD */
+	if (pwd) {
+		char *ptmp = pwd;
+
+		set_current_wd(ptmp);
+		/* Ignore failure (happens if readonly or integer) */
+		setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
+	} else {
+		set_current_wd(null);
+		pwd = Xstring(xs, xp);
+		/* XXX unset $PWD? */
+	}
+	if (printpath || cdnode)
+		shprintf("%s\n", pwd);
+
+	afree(allocd, ATEMP);
+	return (0);
+}
+
+int
+c_pwd(const char **wp)
+{
+	int optc;
+	bool physical = Flag(FPHYSICAL) ? true : false;
+	char *p, *allocd = NULL;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
+		switch (optc) {
+		case 'L':
+			physical = false;
+			break;
+		case 'P':
+			physical = true;
+			break;
+		case '?':
+			return (1);
+		}
+	wp += builtin_opt.optind;
+
+	if (wp[0]) {
+		bi_errorf("too many arguments");
+		return (1);
+	}
+	p = current_wd[0] ? (physical ? allocd = do_realpath(current_wd) :
+	    current_wd) : NULL;
+	if (p && access(p, R_OK) < 0)
+		p = NULL;
+	if (!p && !(p = allocd = ksh_get_wd(NULL))) {
+		bi_errorf("can't get current directory - %s", strerror(errno));
+		return (1);
+	}
+	shprintf("%s\n", p);
+	afree(allocd, ATEMP);
+	return (0);
+}
+
+static const char *s_ptr;
+static int s_get(void);
+static void s_put(int);
+
+int
+c_print(const char **wp)
+{
+#define PO_NL		BIT(0)	/* print newline */
+#define PO_EXPAND	BIT(1)	/* expand backslash sequences */
+#define PO_PMINUSMINUS	BIT(2)	/* print a -- argument */
+#define PO_HIST		BIT(3)	/* print to history instead of stdout */
+#define PO_COPROC	BIT(4)	/* printing to coprocess: block SIGPIPE */
+	int fd = 1, c;
+	int flags = PO_EXPAND|PO_NL;
+	const char *s, *emsg;
+	XString xs;
+	char *xp;
+
+	if (wp[0][0] == 'e') {
+		/* echo builtin */
+		wp++;
+		if (Flag(FPOSIX) || Flag(FSH)) {
+			/* Debian Policy 10.4 compliant "echo" builtin */
+			if (*wp && !strcmp(*wp, "-n")) {
+				/* we recognise "-n" only as the first arg */
+				flags = 0;
+				wp++;
+			} else
+				/* otherwise, we print everything as-is */
+				flags = PO_NL;
+		} else {
+			int nflags = flags;
+
+			/**
+			 * a compromise between sysV and BSD echo commands:
+			 * escape sequences are enabled by default, and -n,
+			 * -e and -E are recognised if they appear in argu-
+			 * ments with no illegal options (ie, echo -nq will
+			 * print -nq).
+			 * Different from sysV echo since options are reco-
+			 * gnised, different from BSD echo since escape se-
+			 * quences are enabled by default.
+			 */
+
+			while ((s = *wp) && *s == '-' && s[1]) {
+				while (*++s)
+					if (*s == 'n')
+						nflags &= ~PO_NL;
+					else if (*s == 'e')
+						nflags |= PO_EXPAND;
+					else if (*s == 'E')
+						nflags &= ~PO_EXPAND;
+					else
+						/*
+						 * bad option: don't use
+						 * nflags, print argument
+						 */
+						break;
+
+				if (*s)
+					break;
+				wp++;
+				flags = nflags;
+			}
+		}
+	} else {
+		int optc;
+		const char *opts = "Rnprsu,";
+
+		while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1)
+			switch (optc) {
+			case 'R': /* fake BSD echo command */
+				flags |= PO_PMINUSMINUS;
+				flags &= ~PO_EXPAND;
+				opts = "ne";
+				break;
+			case 'e':
+				flags |= PO_EXPAND;
+				break;
+			case 'n':
+				flags &= ~PO_NL;
+				break;
+			case 'p':
+				if ((fd = coproc_getfd(W_OK, &emsg)) < 0) {
+					bi_errorf("-p: %s", emsg);
+					return (1);
+				}
+				break;
+			case 'r':
+				flags &= ~PO_EXPAND;
+				break;
+			case 's':
+				flags |= PO_HIST;
+				break;
+			case 'u':
+				if (!*(s = builtin_opt.optarg))
+					fd = 0;
+				else if ((fd = check_fd(s, W_OK, &emsg)) < 0) {
+					bi_errorf("-u: %s: %s", s, emsg);
+					return (1);
+				}
+				break;
+			case '?':
+				return (1);
+			}
+
+		if (!(builtin_opt.info & GI_MINUSMINUS)) {
+			/* treat a lone - like -- */
+			if (wp[builtin_opt.optind] &&
+			    ksh_isdash(wp[builtin_opt.optind]))
+				builtin_opt.optind++;
+		} else if (flags & PO_PMINUSMINUS)
+			builtin_opt.optind--;
+		wp += builtin_opt.optind;
+	}
+
+	Xinit(xs, xp, 128, ATEMP);
+
+	while (*wp != NULL) {
+		s = *wp;
+		while ((c = *s++) != '\0') {
+			Xcheck(xs, xp);
+			if ((flags & PO_EXPAND) && c == '\\') {
+				s_ptr = s;
+				c = unbksl(false, s_get, s_put);
+				s = s_ptr;
+				if (c == -1) {
+					/* rejected by generic function */
+					switch ((c = *s++)) {
+					case 'c':
+						flags &= ~PO_NL;
+						/* AT&T brain damage */
+						continue;
+					case '\0':
+						s--;
+						c = '\\';
+						break;
+					default:
+						Xput(xs, xp, '\\');
+					}
+				} else if ((unsigned int)c > 0xFF) {
+					/* generic function returned Unicode */
+					char ts[4];
+
+					c = utf_wctomb(ts, c - 0x100);
+					ts[c] = 0;
+					for (c = 0; ts[c]; ++c)
+						Xput(xs, xp, ts[c]);
+					continue;
+				}
+			}
+			Xput(xs, xp, c);
+		}
+		if (*++wp != NULL)
+			Xput(xs, xp, ' ');
+	}
+	if (flags & PO_NL)
+		Xput(xs, xp, '\n');
+
+	if (flags & PO_HIST) {
+		Xput(xs, xp, '\0');
+		histsave(&source->line, Xstring(xs, xp), true, false);
+		Xfree(xs, xp);
+	} else {
+		int len = Xlength(xs, xp);
+		int opipe = 0;
+
+		/* Ensure we aren't killed by a SIGPIPE while writing to
+		 * a coprocess. AT&T ksh doesn't seem to do this (seems
+		 * to just check that the co-process is alive which is
+		 * not enough).
+		 */
+		if (coproc.write >= 0 && coproc.write == fd) {
+			flags |= PO_COPROC;
+			opipe = block_pipe();
+		}
+		for (s = Xstring(xs, xp); len > 0; ) {
+			if ((c = write(fd, s, len)) < 0) {
+				if (flags & PO_COPROC)
+					restore_pipe(opipe);
+				if (errno == EINTR) {
+					/* allow user to ^C out */
+					intrcheck();
+					if (flags & PO_COPROC)
+						opipe = block_pipe();
+					continue;
+				}
+				return (1);
+			}
+			s += c;
+			len -= c;
+		}
+		if (flags & PO_COPROC)
+			restore_pipe(opipe);
+	}
+
+	return (0);
+}
+
+static int
+s_get(void)
+{
+	return (*s_ptr++);
+}
+
+static void
+s_put(int c MKSH_A_UNUSED)
+{
+	--s_ptr;
+}
+
+int
+c_whence(const char **wp)
+{
+	struct tbl *tp;
+	const char *id;
+	bool pflag = false, vflag = false, Vflag = false;
+	int rv = 0, optc, fcflags;
+	bool iam_whence = wp[0][0] == 'w';
+	const char *opts = iam_whence ? "pv" : "pvV";
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1)
+		switch (optc) {
+		case 'p':
+			pflag = true;
+			break;
+		case 'v':
+			vflag = true;
+			break;
+		case 'V':
+			Vflag = true;
+			break;
+		case '?':
+			return (1);
+		}
+	wp += builtin_opt.optind;
+
+	fcflags = FC_BI | FC_PATH | FC_FUNC;
+	if (!iam_whence) {
+		/* Note that -p on its own is deal with in comexec() */
+		if (pflag)
+			fcflags |= FC_DEFPATH;
+		/* Convert command options to whence options - note that
+		 * command -pV uses a different path search than whence -v
+		 * or whence -pv. This should be considered a feature.
+		 */
+		vflag = Vflag;
+	}
+	if (pflag)
+		fcflags &= ~(FC_BI | FC_FUNC);
+
+	while ((vflag || rv == 0) && (id = *wp++) != NULL) {
+		uint32_t h = 0;
+
+		tp = NULL;
+		if ((iam_whence || vflag) && !pflag)
+			tp = ktsearch(&keywords, id, h = hash(id));
+		if (!tp && !pflag) {
+			tp = ktsearch(&aliases, id, h ? h : hash(id));
+			if (tp && !(tp->flag & ISSET))
+				tp = NULL;
+		}
+		if (!tp)
+			tp = findcom(id, fcflags);
+		if (vflag || (tp->type != CALIAS && tp->type != CEXEC &&
+		    tp->type != CTALIAS))
+			shf_puts(id, shl_stdout);
+		switch (tp->type) {
+		case CKEYWD:
+			if (vflag)
+				shf_puts(" is a reserved word", shl_stdout);
+			break;
+		case CALIAS:
+			if (vflag)
+				shprintf(" is an %salias for ",
+				    (tp->flag & EXPORT) ? "exported " : null);
+			if (!iam_whence && !vflag)
+				shprintf("alias %s=", id);
+			print_value_quoted(tp->val.s);
+			break;
+		case CFUNC:
+			if (vflag) {
+				shf_puts(" is a", shl_stdout);
+				if (tp->flag & EXPORT)
+					shf_puts("n exported", shl_stdout);
+				if (tp->flag & TRACE)
+					shf_puts(" traced", shl_stdout);
+				if (!(tp->flag & ISSET)) {
+					shf_puts(" undefined", shl_stdout);
+					if (tp->u.fpath)
+						shprintf(" (autoload from %s)",
+						    tp->u.fpath);
+				}
+				shf_puts(" function", shl_stdout);
+			}
+			break;
+		case CSHELL:
+			if (vflag)
+				shprintf(" is a%s shell builtin",
+				    (tp->flag & SPEC_BI) ? " special" : null);
+			break;
+		case CTALIAS:
+		case CEXEC:
+			if (tp->flag & ISSET) {
+				if (vflag) {
+					shf_puts(" is ", shl_stdout);
+					if (tp->type == CTALIAS)
+						shprintf("a tracked %salias for ",
+						    (tp->flag & EXPORT) ?
+						    "exported " : null);
+				}
+				shf_puts(tp->val.s, shl_stdout);
+			} else {
+				if (vflag)
+					shf_puts(" not found", shl_stdout);
+				rv = 1;
+			}
+			break;
+		default:
+			shprintf("%s is *GOK*", id);
+			break;
+		}
+		if (vflag || !rv)
+			shf_putc('\n', shl_stdout);
+	}
+	return (rv);
+}
+
+/* Deal with command -vV - command -p dealt with in comexec() */
+int
+c_command(const char **wp)
+{
+	/* Let c_whence do the work. Note that c_command() must be
+	 * a distinct function from c_whence() (tested in comexec()).
+	 */
+	return (c_whence(wp));
+}
+
+/* typeset, export, and readonly */
+int
+c_typeset(const char **wp)
+{
+	struct block *l;
+	struct tbl *vp, **p;
+	Tflag fset = 0, fclr = 0, flag;
+	int thing = 0, field, base, optc;
+	const char *opts;
+	const char *fieldstr, *basestr;
+	bool localv = false, func = false, pflag = false, istset = true;
+
+	switch (**wp) {
+	case 'e':		/* export */
+		fset |= EXPORT;
+		istset = false;
+		break;
+	case 'r':		/* readonly */
+		fset |= RDONLY;
+		istset = false;
+		break;
+	case 's':		/* set */
+		/* called with 'typeset -' */
+		break;
+	case 't':		/* typeset */
+		localv = true;
+		break;
+	}
+
+	/* see comment below regarding possible opions */
+	opts = istset ? "L#R#UZ#afi#lnprtux" : "p";
+
+	fieldstr = basestr = NULL;
+	builtin_opt.flags |= GF_PLUSOPT;
+	/* AT&T ksh seems to have 0-9 as options which are multiplied
+	 * to get a number that is used with -L, -R, -Z or -i (eg, -1R2
+	 * sets right justify in a field of 12). This allows options
+	 * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
+	 * does not allow the number to be specified as a separate argument
+	 * Here, the number must follow the RLZi option, but is optional
+	 * (see the # kludge in ksh_getopt()).
+	 */
+	while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1) {
+		flag = 0;
+		switch (optc) {
+		case 'L':
+			flag = LJUST;
+			fieldstr = builtin_opt.optarg;
+			break;
+		case 'R':
+			flag = RJUST;
+			fieldstr = builtin_opt.optarg;
+			break;
+		case 'U':
+			/* AT&T ksh uses u, but this conflicts with
+			 * upper/lower case. If this option is changed,
+			 * need to change the -U below as well
+			 */
+			flag = INT_U;
+			break;
+		case 'Z':
+			flag = ZEROFIL;
+			fieldstr = builtin_opt.optarg;
+			break;
+		case 'a':
+			/*
+			 * this is supposed to set (-a) or unset (+a) the
+			 * indexed array attribute; it does nothing on an
+			 * existing regular string or indexed array though
+			 */
+			break;
+		case 'f':
+			func = true;
+			break;
+		case 'i':
+			flag = INTEGER;
+			basestr = builtin_opt.optarg;
+			break;
+		case 'l':
+			flag = LCASEV;
+			break;
+		case 'n':
+			set_refflag = (builtin_opt.info & GI_PLUS) ? 2 : 1;
+			break;
+		case 'p':
+			/* export, readonly: POSIX -p flag */
+			/* typeset: show values as well */
+			pflag = true;
+			if (istset)
+				continue;
+			break;
+		case 'r':
+			flag = RDONLY;
+			break;
+		case 't':
+			flag = TRACE;
+			break;
+		case 'u':
+			flag = UCASEV_AL;	/* upper case / autoload */
+			break;
+		case 'x':
+			flag = EXPORT;
+			break;
+		case '?':
+			return (1);
+		}
+		if (builtin_opt.info & GI_PLUS) {
+			fclr |= flag;
+			fset &= ~flag;
+			thing = '+';
+		} else {
+			fset |= flag;
+			fclr &= ~flag;
+			thing = '-';
+		}
+	}
+
+	field = 0;
+	if (fieldstr && !bi_getn(fieldstr, &field))
+		return (1);
+	base = 0;
+	if (basestr && !bi_getn(basestr, &base))
+		return (1);
+
+	if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] &&
+	    (wp[builtin_opt.optind][0] == '-' ||
+	    wp[builtin_opt.optind][0] == '+') &&
+	    wp[builtin_opt.optind][1] == '\0') {
+		thing = wp[builtin_opt.optind][0];
+		builtin_opt.optind++;
+	}
+
+	if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) || set_refflag)) {
+		bi_errorf("only -t, -u and -x options may be used with -f");
+		set_refflag = 0;
+		return (1);
+	}
+	if (wp[builtin_opt.optind]) {
+		/* Take care of exclusions.
+		 * At this point, flags in fset are cleared in fclr and vice
+		 * versa. This property should be preserved.
+		 */
+		if (fset & LCASEV)	/* LCASEV has priority over UCASEV_AL */
+			fset &= ~UCASEV_AL;
+		if (fset & LJUST)	/* LJUST has priority over RJUST */
+			fset &= ~RJUST;
+		if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */
+			fset |= RJUST;
+			fclr &= ~RJUST;
+		}
+		/* Setting these attributes clears the others, unless they
+		 * are also set in this command
+		 */
+		if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV |
+		    INTEGER | INT_U | INT_L)) || set_refflag)
+			fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
+			    LCASEV | INTEGER | INT_U | INT_L);
+	}
+
+	/* set variables and attributes */
+	if (wp[builtin_opt.optind]) {
+		int i, rv = 0;
+		struct tbl *f;
+
+		if (localv && !func)
+			fset |= LOCAL;
+		for (i = builtin_opt.optind; wp[i]; i++) {
+			if (func) {
+				f = findfunc(wp[i], hash(wp[i]),
+				    (fset&UCASEV_AL) ? true : false);
+				if (!f) {
+					/* AT&T ksh does ++rv: bogus */
+					rv = 1;
+					continue;
+				}
+				if (fset | fclr) {
+					f->flag |= fset;
+					f->flag &= ~fclr;
+				} else
+					fptreef(shl_stdout, 0,
+					    f->flag & FKSH ?
+					    "function %s %T\n" :
+					    "%s() %T\n", wp[i], f->val.t);
+			} else if (!typeset(wp[i], fset, fclr, field, base)) {
+				bi_errorf("%s: not identifier", wp[i]);
+				set_refflag = 0;
+				return (1);
+			}
+		}
+		set_refflag = 0;
+		return (rv);
+	}
+
+	/* list variables and attributes */
+	flag = fset | fclr; /* no difference at this point.. */
+	if (func) {
+		for (l = e->loc; l; l = l->next) {
+			for (p = ktsort(&l->funs); (vp = *p++); ) {
+				if (flag && (vp->flag & flag) == 0)
+					continue;
+				if (thing == '-')
+					fptreef(shl_stdout, 0, vp->flag & FKSH ?
+					    "function %s %T\n" : "%s() %T\n",
+					    vp->name, vp->val.t);
+				else
+					shprintf("%s\n", vp->name);
+			}
+		}
+	} else {
+		for (l = e->loc; l; l = l->next) {
+			for (p = ktsort(&l->vars); (vp = *p++); ) {
+				struct tbl *tvp;
+				bool any_set = false;
+				/*
+				 * See if the parameter is set (for arrays, if any
+				 * element is set).
+				 */
+				for (tvp = vp; tvp; tvp = tvp->u.array)
+					if (tvp->flag & ISSET) {
+						any_set = true;
+						break;
+					}
+
+				/*
+				 * Check attributes - note that all array elements
+				 * have (should have?) the same attributes, so checking
+				 * the first is sufficient.
+				 *
+				 * Report an unset param only if the user has
+				 * explicitly given it some attribute (like export);
+				 * otherwise, after "echo $FOO", we would report FOO...
+				 */
+				if (!any_set && !(vp->flag & USERATTRIB))
+					continue;
+				if (flag && (vp->flag & flag) == 0)
+					continue;
+				for (; vp; vp = vp->u.array) {
+					/* Ignore array elements that aren't
+					 * set unless there are no set elements,
+					 * in which case the first is reported on */
+					if ((vp->flag&ARRAY) && any_set &&
+					    !(vp->flag & ISSET))
+						continue;
+					/* no arguments */
+					if (thing == 0 && flag == 0) {
+						/* AT&T ksh prints things
+						 * like export, integer,
+						 * leftadj, zerofill, etc.,
+						 * but POSIX says must
+						 * be suitable for re-entry...
+						 */
+						shf_puts("typeset ", shl_stdout);
+						if (((vp->flag&(ARRAY|ASSOC))==ASSOC))
+							shf_puts("-n ", shl_stdout);
+						if ((vp->flag&INTEGER))
+							shf_puts("-i ", shl_stdout);
+						if ((vp->flag&EXPORT))
+							shf_puts("-x ", shl_stdout);
+						if ((vp->flag&RDONLY))
+							shf_puts("-r ", shl_stdout);
+						if ((vp->flag&TRACE))
+							shf_puts("-t ", shl_stdout);
+						if ((vp->flag&LJUST))
+							shprintf("-L%d ", vp->u2.field);
+						if ((vp->flag&RJUST))
+							shprintf("-R%d ", vp->u2.field);
+						if ((vp->flag&ZEROFIL))
+							shf_puts("-Z ", shl_stdout);
+						if ((vp->flag&LCASEV))
+							shf_puts("-l ", shl_stdout);
+						if ((vp->flag&UCASEV_AL))
+							shf_puts("-u ", shl_stdout);
+						if ((vp->flag&INT_U))
+							shf_puts("-U ", shl_stdout);
+						shf_puts(vp->name, shl_stdout);
+						if (pflag) {
+							char *s = str_val(vp);
+
+							shf_putc('=', shl_stdout);
+							/* AT&T ksh can't have
+							 * justified integers.. */
+							if ((vp->flag &
+							    (INTEGER|LJUST|RJUST)) ==
+							    INTEGER)
+								shf_puts(s, shl_stdout);
+							else
+								print_value_quoted(s);
+						}
+						shf_putc('\n', shl_stdout);
+						if (vp->flag & ARRAY)
+							break;
+					} else {
+						if (pflag)
+							shf_puts(istset ?
+							    "typeset " :
+							    (flag & EXPORT) ?
+							    "export " :
+							    "readonly ",
+							    shl_stdout);
+						if ((vp->flag&ARRAY) && any_set)
+							shprintf("%s[%lu]",
+							    vp->name,
+							    arrayindex(vp));
+						else
+							shf_puts(vp->name, shl_stdout);
+						if (thing == '-' && (vp->flag&ISSET)) {
+							char *s = str_val(vp);
+
+							shf_putc('=', shl_stdout);
+							/* AT&T ksh can't have
+							 * justified integers.. */
+							if ((vp->flag &
+							    (INTEGER|LJUST|RJUST)) ==
+							    INTEGER)
+								shf_puts(s, shl_stdout);
+							else
+								print_value_quoted(s);
+						}
+						shf_putc('\n', shl_stdout);
+					}
+					/* Only report first 'element' of an array with
+					 * no set elements.
+					 */
+					if (!any_set)
+						break;
+				}
+			}
+		}
+	}
+	return (0);
+}
+
+int
+c_alias(const char **wp)
+{
+	struct table *t = &aliases;
+	int rv = 0, prefix = 0;
+	bool rflag = false, tflag, Uflag = false, pflag = false;
+	Tflag xflag = 0;
+	int optc;
+
+	builtin_opt.flags |= GF_PLUSOPT;
+	while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != -1) {
+		prefix = builtin_opt.info & GI_PLUS ? '+' : '-';
+		switch (optc) {
+		case 'd':
+#ifdef MKSH_NOPWNAM
+			t = NULL;	/* fix "alias -dt" */
+#else
+			t = &homedirs;
+#endif
+			break;
+		case 'p':
+			pflag = true;
+			break;
+		case 'r':
+			rflag = true;
+			break;
+		case 't':
+			t = &taliases;
+			break;
+		case 'U':
+			/*
+			 * kludge for tracked alias initialization
+			 * (don't do a path search, just make an entry)
+			 */
+			Uflag = true;
+			break;
+		case 'x':
+			xflag = EXPORT;
+			break;
+		case '?':
+			return (1);
+		}
+	}
+#ifdef MKSH_NOPWNAM
+	if (t == NULL)
+		return (0);
+#endif
+	wp += builtin_opt.optind;
+
+	if (!(builtin_opt.info & GI_MINUSMINUS) && *wp &&
+	    (wp[0][0] == '-' || wp[0][0] == '+') && wp[0][1] == '\0') {
+		prefix = wp[0][0];
+		wp++;
+	}
+
+	tflag = t == &taliases;
+
+	/* "hash -r" means reset all the tracked aliases.. */
+	if (rflag) {
+		static const char *args[] = {
+			"unalias", "-ta", NULL
+		};
+
+		if (!tflag || *wp) {
+			shf_puts("alias: -r flag can only be used with -t"
+			    " and without arguments\n", shl_stdout);
+			return (1);
+		}
+		ksh_getopt_reset(&builtin_opt, GF_ERROR);
+		return (c_unalias(args));
+	}
+
+	if (*wp == NULL) {
+		struct tbl *ap, **p;
+
+		for (p = ktsort(t); (ap = *p++) != NULL; )
+			if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) {
+				if (pflag)
+					shf_puts("alias ", shl_stdout);
+				shf_puts(ap->name, shl_stdout);
+				if (prefix != '+') {
+					shf_putc('=', shl_stdout);
+					print_value_quoted(ap->val.s);
+				}
+				shf_putc('\n', shl_stdout);
+			}
+	}
+
+	for (; *wp != NULL; wp++) {
+		const char *alias = *wp, *val, *newval;
+		char *xalias = NULL;
+		struct tbl *ap;
+		uint32_t h;
+
+		if ((val = cstrchr(alias, '='))) {
+			strndupx(xalias, alias, val++ - alias, ATEMP);
+			alias = xalias;
+		}
+		h = hash(alias);
+		if (val == NULL && !tflag && !xflag) {
+			ap = ktsearch(t, alias, h);
+			if (ap != NULL && (ap->flag&ISSET)) {
+				if (pflag)
+					shf_puts("alias ", shl_stdout);
+				shf_puts(ap->name, shl_stdout);
+				if (prefix != '+') {
+					shf_putc('=', shl_stdout);
+					print_value_quoted(ap->val.s);
+				}
+				shf_putc('\n', shl_stdout);
+			} else {
+				shprintf("%s alias not found\n", alias);
+				rv = 1;
+			}
+			continue;
+		}
+		ap = ktenter(t, alias, h);
+		ap->type = tflag ? CTALIAS : CALIAS;
+		/* Are we setting the value or just some flags? */
+		if ((val && !tflag) || (!val && tflag && !Uflag)) {
+			if (ap->flag&ALLOC) {
+				ap->flag &= ~(ALLOC|ISSET);
+				afree(ap->val.s, APERM);
+			}
+			/* ignore values for -t (AT&T ksh does this) */
+			newval = tflag ? search(alias, path, X_OK, NULL) : val;
+			if (newval) {
+				strdupx(ap->val.s, newval, APERM);
+				ap->flag |= ALLOC|ISSET;
+			} else
+				ap->flag &= ~ISSET;
+		}
+		ap->flag |= DEFINED;
+		if (prefix == '+')
+			ap->flag &= ~xflag;
+		else
+			ap->flag |= xflag;
+		afree(xalias, ATEMP);
+	}
+
+	return (rv);
+}
+
+int
+c_unalias(const char **wp)
+{
+	struct table *t = &aliases;
+	struct tbl *ap;
+	int optc, rv = 0;
+	bool all = false;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != -1)
+		switch (optc) {
+		case 'a':
+			all = true;
+			break;
+		case 'd':
+#ifdef MKSH_NOPWNAM
+			t = NULL;	/* fix "unalias -dt" */
+#else
+			t = &homedirs;
+#endif
+			break;
+		case 't':
+			t = &taliases;
+			break;
+		case '?':
+			return (1);
+		}
+#ifdef MKSH_NOPWNAM
+	if (t == NULL)
+		return (0);
+#endif
+	wp += builtin_opt.optind;
+
+	for (; *wp != NULL; wp++) {
+		ap = ktsearch(t, *wp, hash(*wp));
+		if (ap == NULL) {
+			rv = 1;	/* POSIX */
+			continue;
+		}
+		if (ap->flag&ALLOC) {
+			ap->flag &= ~(ALLOC|ISSET);
+			afree(ap->val.s, APERM);
+		}
+		ap->flag &= ~(DEFINED|ISSET|EXPORT);
+	}
+
+	if (all) {
+		struct tstate ts;
+
+		for (ktwalk(&ts, t); (ap = ktnext(&ts)); ) {
+			if (ap->flag&ALLOC) {
+				ap->flag &= ~(ALLOC|ISSET);
+				afree(ap->val.s, APERM);
+			}
+			ap->flag &= ~(DEFINED|ISSET|EXPORT);
+		}
+	}
+
+	return (rv);
+}
+
+int
+c_let(const char **wp)
+{
+	int rv = 1;
+	mksh_ari_t val;
+
+	if (wp[1] == NULL) /* AT&T ksh does this */
+		bi_errorf("no arguments");
+	else
+		for (wp++; *wp; wp++)
+			if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) {
+				rv = 2;	/* distinguish error from zero result */
+				break;
+			} else
+				rv = val == 0;
+	return (rv);
+}
+
+int
+c_jobs(const char **wp)
+{
+	int optc, flag = 0, nflag = 0, rv = 0;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != -1)
+		switch (optc) {
+		case 'l':
+			flag = 1;
+			break;
+		case 'p':
+			flag = 2;
+			break;
+		case 'n':
+			nflag = 1;
+			break;
+		case 'z':	/* debugging: print zombies */
+			nflag = -1;
+			break;
+		case '?':
+			return (1);
+		}
+	wp += builtin_opt.optind;
+	if (!*wp) {
+		if (j_jobs(NULL, flag, nflag))
+			rv = 1;
+	} else {
+		for (; *wp; wp++)
+			if (j_jobs(*wp, flag, nflag))
+				rv = 1;
+	}
+	return (rv);
+}
+
+#ifndef MKSH_UNEMPLOYED
+int
+c_fgbg(const char **wp)
+{
+	bool bg = strcmp(*wp, "bg") == 0;
+	int rv = 0;
+
+	if (!Flag(FMONITOR)) {
+		bi_errorf("job control not enabled");
+		return (1);
+	}
+	if (ksh_getopt(wp, &builtin_opt, null) == '?')
+		return (1);
+	wp += builtin_opt.optind;
+	if (*wp)
+		for (; *wp; wp++)
+			rv = j_resume(*wp, bg);
+	else
+		rv = j_resume("%%", bg);
+	return (bg ? 0 : rv);
+}
+#endif
+
+/* format a single kill item */
+static char *
+kill_fmt_entry(char *buf, int buflen, int i, const void *arg)
+{
+	const struct kill_info *ki = (const struct kill_info *)arg;
+
+	i++;
+	shf_snprintf(buf, buflen, "%*d %*s %s",
+	    ki->num_width, i,
+	    ki->name_width, sigtraps[i].name,
+	    sigtraps[i].mess);
+	return (buf);
+}
+
+int
+c_kill(const char **wp)
+{
+	Trap *t = NULL;
+	const char *p;
+	bool lflag = false;
+	int i, n, rv, sig;
+
+	/* assume old style options if -digits or -UPPERCASE */
+	if ((p = wp[1]) && *p == '-' && (ksh_isdigit(p[1]) ||
+	    ksh_isupper(p[1]))) {
+		if (!(t = gettrap(p + 1, true))) {
+			bi_errorf("bad signal '%s'", p + 1);
+			return (1);
+		}
+		i = (wp[2] && strcmp(wp[2], "--") == 0) ? 3 : 2;
+	} else {
+		int optc;
+
+		while ((optc = ksh_getopt(wp, &builtin_opt, "ls:")) != -1)
+			switch (optc) {
+			case 'l':
+				lflag = true;
+				break;
+			case 's':
+				if (!(t = gettrap(builtin_opt.optarg, true))) {
+					bi_errorf("bad signal '%s'",
+					    builtin_opt.optarg);
+					return (1);
+				}
+				break;
+			case '?':
+				return (1);
+			}
+		i = builtin_opt.optind;
+	}
+	if ((lflag && t) || (!wp[i] && !lflag)) {
+#ifndef MKSH_SMALL
+		shf_puts("usage:\tkill [-s signame | -signum | -signame]"
+		    " { job | pid | pgrp } ...\n"
+		    "\tkill -l [exit_status ...]\n", shl_out);
+#endif
+		bi_errorfz();
+		return (1);
+	}
+
+	if (lflag) {
+		if (wp[i]) {
+			for (; wp[i]; i++) {
+				if (!bi_getn(wp[i], &n))
+					return (1);
+				if (n > 128 && n < 128 + NSIG)
+					n -= 128;
+				if (n > 0 && n < NSIG)
+					shprintf("%s\n", sigtraps[n].name);
+				else
+					shprintf("%d\n", n);
+			}
+		} else {
+			int w, j, mess_cols, mess_octs;
+			struct kill_info ki;
+
+			for (j = NSIG, ki.num_width = 1; j >= 10; j /= 10)
+				ki.num_width++;
+			ki.name_width = mess_cols = mess_octs = 0;
+			for (j = 0; j < NSIG; j++) {
+				w = strlen(sigtraps[j].name);
+				if (w > ki.name_width)
+					ki.name_width = w;
+				w = strlen(sigtraps[j].mess);
+				if (w > mess_octs)
+					mess_octs = w;
+				w = utf_mbswidth(sigtraps[j].mess);
+				if (w > mess_cols)
+					mess_cols = w;
+			}
+
+			print_columns(shl_stdout, NSIG - 1,
+			    kill_fmt_entry, (void *)&ki,
+			    ki.num_width + 1 + ki.name_width + 1 + mess_octs,
+			    ki.num_width + 1 + ki.name_width + 1 + mess_cols,
+			    true);
+		}
+		return (0);
+	}
+	rv = 0;
+	sig = t ? t->signal : SIGTERM;
+	for (; (p = wp[i]); i++) {
+		if (*p == '%') {
+			if (j_kill(p, sig))
+				rv = 1;
+		} else if (!getn(p, &n)) {
+			bi_errorf("%s: arguments must be jobs or process IDs",
+			    p);
+			rv = 1;
+		} else {
+			if (mksh_kill(n, sig) < 0) {
+				bi_errorf("%s: %s", p, strerror(errno));
+				rv = 1;
+			}
+		}
+	}
+	return (rv);
+}
+
+void
+getopts_reset(int val)
+{
+	if (val >= 1) {
+		ksh_getopt_reset(&user_opt, GF_NONAME | GF_PLUSOPT);
+		user_opt.optind = user_opt.uoptind = val;
+	}
+}
+
+int
+c_getopts(const char **wp)
+{
+	int argc, optc, rv;
+	const char *opts, *var;
+	char buf[3];
+	struct tbl *vq, *voptarg;
+
+	if (ksh_getopt(wp, &builtin_opt, null) == '?')
+		return (1);
+	wp += builtin_opt.optind;
+
+	opts = *wp++;
+	if (!opts) {
+		bi_errorf("missing options argument");
+		return (1);
+	}
+
+	var = *wp++;
+	if (!var) {
+		bi_errorf("missing name argument");
+		return (1);
+	}
+	if (!*var || *skip_varname(var, true)) {
+		bi_errorf("%s: is not an identifier", var);
+		return (1);
+	}
+
+	if (e->loc->next == NULL) {
+		internal_warningf("c_getopts: no argv");
+		return (1);
+	}
+	/* Which arguments are we parsing... */
+	if (*wp == NULL)
+		wp = e->loc->next->argv;
+	else
+		*--wp = e->loc->next->argv[0];
+
+	/* Check that our saved state won't cause a core dump... */
+	for (argc = 0; wp[argc]; argc++)
+		;
+	if (user_opt.optind > argc ||
+	    (user_opt.p != 0 &&
+	    user_opt.p > strlen(wp[user_opt.optind - 1]))) {
+		bi_errorf("arguments changed since last call");
+		return (1);
+	}
+
+	user_opt.optarg = NULL;
+	optc = ksh_getopt(wp, &user_opt, opts);
+
+	if (optc >= 0 && optc != '?' && (user_opt.info & GI_PLUS)) {
+		buf[0] = '+';
+		buf[1] = optc;
+		buf[2] = '\0';
+	} else {
+		/* POSIX says var is set to ? at end-of-options, AT&T ksh
+		 * sets it to null - we go with POSIX...
+		 */
+		buf[0] = optc < 0 ? '?' : optc;
+		buf[1] = '\0';
+	}
+
+	/* AT&T ksh93 in fact does change OPTIND for unknown options too */
+	user_opt.uoptind = user_opt.optind;
+
+	voptarg = global("OPTARG");
+	voptarg->flag &= ~RDONLY;	/* AT&T ksh clears ro and int */
+	/* Paranoia: ensure no bizarre results. */
+	if (voptarg->flag & INTEGER)
+	    typeset("OPTARG", 0, INTEGER, 0, 0);
+	if (user_opt.optarg == NULL)
+		unset(voptarg, 1);
+	else
+		/* This can't fail (have cleared readonly/integer) */
+		setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR);
+
+	rv = 0;
+
+	vq = global(var);
+	/* Error message already printed (integer, readonly) */
+	if (!setstr(vq, buf, KSH_RETURN_ERROR))
+		rv = 1;
+	if (Flag(FEXPORT))
+		typeset(var, EXPORT, 0, 0, 0);
+
+	return (optc < 0 ? 1 : rv);
+}
+
+int
+c_bind(const char **wp)
+{
+	int optc, rv = 0;
+#ifndef MKSH_SMALL
+	bool macro = false;
+#endif
+	bool list = false;
+	const char *cp;
+	char *up;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt,
+#ifndef MKSH_SMALL
+	    "lm"
+#else
+	    "l"
+#endif
+	    )) != -1)
+		switch (optc) {
+		case 'l':
+			list = true;
+			break;
+#ifndef MKSH_SMALL
+		case 'm':
+			macro = true;
+			break;
+#endif
+		case '?':
+			return (1);
+		}
+	wp += builtin_opt.optind;
+
+	if (*wp == NULL)	/* list all */
+		rv = x_bind(NULL, NULL,
+#ifndef MKSH_SMALL
+		    false,
+#endif
+		    list);
+
+	for (; *wp != NULL; wp++) {
+		if ((cp = cstrchr(*wp, '=')) == NULL)
+			up = NULL;
+		else {
+			strdupx(up, *wp, ATEMP);
+			up[cp++ - *wp] = '\0';
+		}
+		if (x_bind(up ? up : *wp, cp,
+#ifndef MKSH_SMALL
+		    macro,
+#endif
+		    false))
+			rv = 1;
+		afree(up, ATEMP);
+	}
+
+	return (rv);
+}
+
+/* :, false and true (and ulimit if MKSH_NO_LIMITS) */
+int
+c_label(const char **wp)
+{
+	return (wp[0][0] == 'f' ? 1 : 0);
+}
+
+int
+c_shift(const char **wp)
+{
+	struct block *l = e->loc;
+	int n;
+	mksh_ari_t val;
+	const char *arg;
+
+	if (ksh_getopt(wp, &builtin_opt, null) == '?')
+		return (1);
+	arg = wp[builtin_opt.optind];
+
+	if (arg) {
+		evaluate(arg, &val, KSH_UNWIND_ERROR, false);
+		n = val;
+	} else
+		n = 1;
+	if (n < 0) {
+		bi_errorf("%s: bad number", arg);
+		return (1);
+	}
+	if (l->argc < n) {
+		bi_errorf("nothing to shift");
+		return (1);
+	}
+	l->argv[n] = l->argv[0];
+	l->argv += n;
+	l->argc -= n;
+	return (0);
+}
+
+int
+c_umask(const char **wp)
+{
+	int i, optc;
+	const char *cp;
+	bool symbolic = false;
+	mode_t old_umask;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "S")) != -1)
+		switch (optc) {
+		case 'S':
+			symbolic = true;
+			break;
+		case '?':
+			return (1);
+		}
+	cp = wp[builtin_opt.optind];
+	if (cp == NULL) {
+		old_umask = umask((mode_t)0);
+		umask(old_umask);
+		if (symbolic) {
+			char buf[18], *p;
+			int j;
+
+			old_umask = ~old_umask;
+			p = buf;
+			for (i = 0; i < 3; i++) {
+				*p++ = "ugo"[i];
+				*p++ = '=';
+				for (j = 0; j < 3; j++)
+					if (old_umask & (1 << (8 - (3*i + j))))
+						*p++ = "rwx"[j];
+				*p++ = ',';
+			}
+			p[-1] = '\0';
+			shprintf("%s\n", buf);
+		} else
+			shprintf("%#3.3o\n", (unsigned int)old_umask);
+	} else {
+		mode_t new_umask;
+
+		if (ksh_isdigit(*cp)) {
+			for (new_umask = 0; *cp >= '0' && *cp <= '7'; cp++)
+				new_umask = new_umask * 8 + (*cp - '0');
+			if (*cp) {
+				bi_errorf("bad number");
+				return (1);
+			}
+		} else {
+			/* symbolic format */
+			int positions, new_val;
+			char op;
+
+			old_umask = umask((mode_t)0);
+			umask(old_umask); /* in case of error */
+			old_umask = ~old_umask;
+			new_umask = old_umask;
+			positions = 0;
+			while (*cp) {
+				while (*cp && vstrchr("augo", *cp))
+					switch (*cp++) {
+					case 'a':
+						positions |= 0111;
+						break;
+					case 'u':
+						positions |= 0100;
+						break;
+					case 'g':
+						positions |= 0010;
+						break;
+					case 'o':
+						positions |= 0001;
+						break;
+					}
+				if (!positions)
+					positions = 0111; /* default is a */
+				if (!vstrchr("=+-", op = *cp))
+					break;
+				cp++;
+				new_val = 0;
+				while (*cp && vstrchr("rwxugoXs", *cp))
+					switch (*cp++) {
+					case 'r': new_val |= 04; break;
+					case 'w': new_val |= 02; break;
+					case 'x': new_val |= 01; break;
+					case 'u':
+						new_val |= old_umask >> 6;
+						break;
+					case 'g':
+						new_val |= old_umask >> 3;
+						break;
+					case 'o':
+						new_val |= old_umask >> 0;
+						break;
+					case 'X':
+						if (old_umask & 0111)
+							new_val |= 01;
+						break;
+					case 's':
+						/* ignored */
+						break;
+					}
+				new_val = (new_val & 07) * positions;
+				switch (op) {
+				case '-':
+					new_umask &= ~new_val;
+					break;
+				case '=':
+					new_umask = new_val |
+					    (new_umask & ~(positions * 07));
+					break;
+				case '+':
+					new_umask |= new_val;
+				}
+				if (*cp == ',') {
+					positions = 0;
+					cp++;
+				} else if (!vstrchr("=+-", *cp))
+					break;
+			}
+			if (*cp) {
+				bi_errorf("bad mask");
+				return (1);
+			}
+			new_umask = ~new_umask;
+		}
+		umask(new_umask);
+	}
+	return (0);
+}
+
+int
+c_dot(const char **wp)
+{
+	const char *file, *cp, **argv;
+	int argc, i, errcode;
+
+	if (ksh_getopt(wp, &builtin_opt, null) == '?')
+		return (1);
+
+	if ((cp = wp[builtin_opt.optind]) == NULL) {
+		bi_errorf("missing argument");
+		return (1);
+	}
+	if ((file = search(cp, path, R_OK, &errcode)) == NULL) {
+		bi_errorf("%s: %s", cp,
+		    errcode ? strerror(errcode) : "not found");
+		return (1);
+	}
+
+	/* Set positional parameters? */
+	if (wp[builtin_opt.optind + 1]) {
+		argv = wp + builtin_opt.optind;
+		argv[0] = e->loc->argv[0]; /* preserve $0 */
+		for (argc = 0; argv[argc + 1]; argc++)
+			;
+	} else {
+		argc = 0;
+		argv = NULL;
+	}
+	if ((i = include(file, argc, argv, 0)) < 0) {
+		/* should not happen */
+		bi_errorf("%s: %s", cp, strerror(errno));
+		return (1);
+	}
+	return (i);
+}
+
+int
+c_wait(const char **wp)
+{
+	int rv = 0, sig;
+
+	if (ksh_getopt(wp, &builtin_opt, null) == '?')
+		return (1);
+	wp += builtin_opt.optind;
+	if (*wp == NULL) {
+		while (waitfor(NULL, &sig) >= 0)
+			;
+		rv = sig;
+	} else {
+		for (; *wp; wp++)
+			rv = waitfor(*wp, &sig);
+		if (rv < 0)
+			rv = sig ? sig : 127; /* magic exit code: bad job-id */
+	}
+	return (rv);
+}
+
+int
+c_read(const char **wp)
+{
+	int c = 0, ecode = 0, fd = 0, optc;
+	bool expande = true, historyr = false, expanding;
+	const char *cp, *emsg;
+	struct shf *shf;
+	XString cs, xs = { NULL, NULL, 0, NULL};
+	struct tbl *vp;
+	char *ccp, *xp = NULL, *wpalloc = NULL;
+	static char REPLY[] = "REPLY";
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != -1)
+		switch (optc) {
+		case 'p':
+			if ((fd = coproc_getfd(R_OK, &emsg)) < 0) {
+				bi_errorf("-p: %s", emsg);
+				return (1);
+			}
+			break;
+		case 'r':
+			expande = false;
+			break;
+		case 's':
+			historyr = true;
+			break;
+		case 'u':
+			if (!*(cp = builtin_opt.optarg))
+				fd = 0;
+			else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) {
+				bi_errorf("-u: %s: %s", cp, emsg);
+				return (1);
+			}
+			break;
+		case '?':
+			return (1);
+		}
+	wp += builtin_opt.optind;
+
+	if (*wp == NULL)
+		*--wp = REPLY;
+
+	/* Since we can't necessarily seek backwards on non-regular files,
+	 * don't buffer them so we can't read too much.
+	 */
+	shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare);
+
+	if ((cp = cstrchr(*wp, '?')) != NULL) {
+		strdupx(wpalloc, *wp, ATEMP);
+		wpalloc[cp - *wp] = '\0';
+		*wp = wpalloc;
+		if (isatty(fd)) {
+			/* AT&T ksh says it prints prompt on fd if it's open
+			 * for writing and is a tty, but it doesn't do it
+			 * (it also doesn't check the interactive flag,
+			 * as is indicated in the Kornshell book).
+			 */
+			shellf("%s", cp+1);
+		}
+	}
+
+	/* If we are reading from the co-process for the first time,
+	 * make sure the other side of the pipe is closed first. This allows
+	 * the detection of eof.
+	 *
+	 * This is not compatible with AT&T ksh... the fd is kept so another
+	 * coproc can be started with same output, however, this means eof
+	 * can't be detected... This is why it is closed here.
+	 * If this call is removed, remove the eof check below, too.
+	 * coproc_readw_close(fd);
+	 */
+
+	if (historyr)
+		Xinit(xs, xp, 128, ATEMP);
+	expanding = false;
+	Xinit(cs, ccp, 128, ATEMP);
+	for (; *wp != NULL; wp++) {
+		for (ccp = Xstring(cs, ccp); ; ) {
+			if (c == '\n' || c == EOF)
+				break;
+			while (1) {
+				c = shf_getc(shf);
+				if (c == '\0')
+					continue;
+				if (c == EOF && shf_error(shf) &&
+				    shf_errno(shf) == EINTR) {
+					/* Was the offending signal one that
+					 * would normally kill a process?
+					 * If so, pretend the read was killed.
+					 */
+					ecode = fatal_trap_check();
+
+					/* non fatal (eg, CHLD), carry on */
+					if (!ecode) {
+						shf_clearerr(shf);
+						continue;
+					}
+				}
+				break;
+			}
+			if (historyr) {
+				Xcheck(xs, xp);
+				Xput(xs, xp, c);
+			}
+			Xcheck(cs, ccp);
+			if (expanding) {
+				expanding = false;
+				if (c == '\n') {
+					c = 0;
+					if (Flag(FTALKING_I) && isatty(fd)) {
+						/* set prompt in case this is
+						 * called from .profile or $ENV
+						 */
+						set_prompt(PS2, NULL);
+						pprompt(prompt, 0);
+					}
+				} else if (c != EOF)
+					Xput(cs, ccp, c);
+				continue;
+			}
+			if (expande && c == '\\') {
+				expanding = true;
+				continue;
+			}
+			if (c == '\n' || c == EOF)
+				break;
+			if (ctype(c, C_IFS)) {
+				if (Xlength(cs, ccp) == 0 && ctype(c, C_IFSWS))
+					continue;
+				if (wp[1])
+					break;
+			}
+			Xput(cs, ccp, c);
+		}
+		/* strip trailing IFS white space from last variable */
+		if (!wp[1])
+			while (Xlength(cs, ccp) && ctype(ccp[-1], C_IFS) &&
+			    ctype(ccp[-1], C_IFSWS))
+				ccp--;
+		Xput(cs, ccp, '\0');
+		vp = global(*wp);
+		/* Must be done before setting export. */
+		if (vp->flag & RDONLY) {
+			shf_flush(shf);
+			bi_errorf("%s is read only", *wp);
+			afree(wpalloc, ATEMP);
+			return (1);
+		}
+		if (Flag(FEXPORT))
+			typeset(*wp, EXPORT, 0, 0, 0);
+		if (!setstr(vp, Xstring(cs, ccp), KSH_RETURN_ERROR)) {
+			shf_flush(shf);
+			afree(wpalloc, ATEMP);
+			return (1);
+		}
+	}
+
+	shf_flush(shf);
+	if (historyr) {
+		Xput(xs, xp, '\0');
+		histsave(&source->line, Xstring(xs, xp), true, false);
+		Xfree(xs, xp);
+	}
+	/* if this is the co-process fd, close the file descriptor
+	 * (can get eof if and only if all processes are have died, ie,
+	 * coproc.njobs is 0 and the pipe is closed).
+	 */
+	if (c == EOF && !ecode)
+		coproc_read_close(fd);
+
+	afree(wpalloc, ATEMP);
+	return (ecode ? ecode : c == EOF);
+}
+
+int
+c_eval(const char **wp)
+{
+	struct source *s, *saves = source;
+	unsigned char savef;
+	int rv;
+
+	if (ksh_getopt(wp, &builtin_opt, null) == '?')
+		return (1);
+	s = pushs(SWORDS, ATEMP);
+	s->u.strv = wp + builtin_opt.optind;
+
+	/*-
+	 * The following code handles the case where the command is
+	 * empty due to failed command substitution, for example by
+	 *	eval "$(false)"
+	 * This has historically returned 1 by AT&T ksh88. In this
+	 * case, shell() will not set or change exstat because the
+	 * compiled tree is empty, so it will use the value we pass
+	 * from subst_exstat, which is cleared in execute(), so it
+	 * should have been 0 if there were no substitutions.
+	 *
+	 * POSIX however says we don't do this, even though it is
+	 * traditionally done. AT&T ksh93 agrees with POSIX, so we
+	 * do. The following is an excerpt from SUSv4 [1003.2-2008]:
+	 *
+	 * 2.9.1: Simple Commands
+	 *	... If there is a command name, execution shall
+	 *	continue as described in 2.9.1.1 [Command Search
+	 *	and Execution]. If there is no command name, but
+	 *	the command contained a command substitution, the
+	 *	command shall complete with the exit status of the
+	 *	last command substitution performed.
+	 * 2.9.1.1: Command Search and Execution
+	 *	(1) a. If the command name matches the name of a
+	 *	special built-in utility, that special built-in
+	 *	utility shall be invoked.
+	 * 2.14.5: eval
+	 *	If there are no arguments, or only null arguments,
+	 *	eval shall return a zero exit status; ...
+	 */
+	/* exstat = subst_exstat; */	/* AT&T ksh88 */
+	exstat = 0;			/* SUSv4 */
+
+	savef = Flag(FERREXIT);
+	Flag(FERREXIT) = 0;
+	rv = shell(s, false);
+	Flag(FERREXIT) = savef;
+	source = saves;
+	afree(s, ATEMP);
+	return (rv);
+}
+
+int
+c_trap(const char **wp)
+{
+	int i;
+	const char *s;
+	Trap *p;
+
+	if (ksh_getopt(wp, &builtin_opt, null) == '?')
+		return (1);
+	wp += builtin_opt.optind;
+
+	if (*wp == NULL) {
+		for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
+			if (p->trap != NULL) {
+				shf_puts("trap -- ", shl_stdout);
+				print_value_quoted(p->trap);
+				shprintf(" %s\n", p->name);
+			}
+		return (0);
+	}
+
+	/*
+	 * Use case sensitive lookup for first arg so the
+	 * command 'exit' isn't confused with the pseudo-signal
+	 * 'EXIT'.
+	 */
+	s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; /* get command */
+	if (s != NULL && s[0] == '-' && s[1] == '\0')
+		s = NULL;
+
+	/* set/clear traps */
+	while (*wp != NULL) {
+		p = gettrap(*wp++, true);
+		if (p == NULL) {
+			bi_errorf("bad signal %s", wp[-1]);
+			return (1);
+		}
+		settrap(p, s);
+	}
+	return (0);
+}
+
+int
+c_exitreturn(const char **wp)
+{
+	int n, how = LEXIT;
+	const char *arg;
+
+	if (ksh_getopt(wp, &builtin_opt, null) == '?')
+		return (1);
+	arg = wp[builtin_opt.optind];
+
+	if (arg) {
+		if (!getn(arg, &n)) {
+			exstat = 1;
+			warningf(true, "%s: bad number", arg);
+		} else
+			exstat = n;
+	}
+	if (wp[0][0] == 'r') { /* return */
+		struct env *ep;
+
+		/* need to tell if this is exit or return so trap exit will
+		 * work right (POSIX)
+		 */
+		for (ep = e; ep; ep = ep->oenv)
+			if (STOP_RETURN(ep->type)) {
+				how = LRETURN;
+				break;
+			}
+	}
+
+	if (how == LEXIT && !really_exit && j_stopped_running()) {
+		really_exit = 1;
+		how = LSHELL;
+	}
+
+	quitenv(NULL);	/* get rid of any i/o redirections */
+	unwind(how);
+	/* NOTREACHED */
+}
+
+int
+c_brkcont(const char **wp)
+{
+	int n, quit;
+	struct env *ep, *last_ep = NULL;
+	const char *arg;
+
+	if (ksh_getopt(wp, &builtin_opt, null) == '?')
+		return (1);
+	arg = wp[builtin_opt.optind];
+
+	if (!arg)
+		n = 1;
+	else if (!bi_getn(arg, &n))
+		return (1);
+	quit = n;
+	if (quit <= 0) {
+		/* AT&T ksh does this for non-interactive shells only - weird */
+		bi_errorf("%s: bad value", arg);
+		return (1);
+	}
+
+	/* Stop at E_NONE, E_PARSE, E_FUNC, or E_INCL */
+	for (ep = e; ep && !STOP_BRKCONT(ep->type); ep = ep->oenv)
+		if (ep->type == E_LOOP) {
+			if (--quit == 0)
+				break;
+			ep->flags |= EF_BRKCONT_PASS;
+			last_ep = ep;
+		}
+
+	if (quit) {
+		/* AT&T ksh doesn't print a message - just does what it
+		 * can. We print a message 'cause it helps in debugging
+		 * scripts, but don't generate an error (ie, keep going).
+		 */
+		if (n == quit) {
+			warningf(true, "%s: cannot %s", wp[0], wp[0]);
+			return (0);
+		}
+		/* POSIX says if n is too big, the last enclosing loop
+		 * shall be used. Doesn't say to print an error but we
+		 * do anyway 'cause the user messed up.
+		 */
+		if (last_ep)
+			last_ep->flags &= ~EF_BRKCONT_PASS;
+		warningf(true, "%s: can only %s %d level(s)",
+		    wp[0], wp[0], n - quit);
+	}
+
+	unwind(*wp[0] == 'b' ? LBREAK : LCONTIN);
+	/* NOTREACHED */
+}
+
+int
+c_set(const char **wp)
+{
+	int argi;
+	bool setargs;
+	struct block *l = e->loc;
+	const char **owp;
+
+	if (wp[1] == NULL) {
+		static const char *args[] = { "set", "-", NULL };
+		return (c_typeset(args));
+	}
+
+	argi = parse_args(wp, OF_SET, &setargs);
+	if (argi < 0)
+		return (1);
+	/* set $# and $* */
+	if (setargs) {
+		wp += argi - 1;
+		owp = wp;
+		wp[0] = l->argv[0]; /* save $0 */
+		while (*++wp != NULL)
+			strdupx(*wp, *wp, &l->area);
+		l->argc = wp - owp - 1;
+		l->argv = alloc((l->argc + 2) * sizeof(char *), &l->area);
+		for (wp = l->argv; (*wp++ = *owp++) != NULL; )
+			;
+	}
+	/*-
+	 * POSIX says set exit status is 0, but old scripts that use
+	 * getopt(1) use the construct
+	 *	set -- $(getopt ab:c "$@")
+	 * which assumes the exit value set will be that of the $()
+	 * (subst_exstat is cleared in execute() so that it will be 0
+	 * if there are no command substitutions).
+	 * Switched ksh (!posix !sh) to POSIX in mksh R39b.
+	 */
+	return (Flag(FSH) ? subst_exstat : 0);
+}
+
+int
+c_unset(const char **wp)
+{
+	const char *id;
+	int optc;
+	bool unset_var = true;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != -1)
+		switch (optc) {
+		case 'f':
+			unset_var = false;
+			break;
+		case 'v':
+			unset_var = true;
+			break;
+		case '?':
+			return (1);
+		}
+	wp += builtin_opt.optind;
+	for (; (id = *wp) != NULL; wp++)
+		if (unset_var) {	/* unset variable */
+			struct tbl *vp;
+			char *cp = NULL;
+			size_t n;
+
+			n = strlen(id);
+			if (n > 3 && id[n-3] == '[' && id[n-2] == '*' &&
+			    id[n-1] == ']') {
+				strndupx(cp, id, n - 3, ATEMP);
+				id = cp;
+				optc = 3;
+			} else
+				optc = vstrchr(id, '[') ? 0 : 1;
+
+			vp = global(id);
+			afree(cp, ATEMP);
+
+			if ((vp->flag&RDONLY)) {
+				bi_errorf("%s is read only", vp->name);
+				return (1);
+			}
+			unset(vp, optc);
+		} else			/* unset function */
+			define(id, NULL);
+	return (0);
+}
+
+static void
+p_time(struct shf *shf, bool posix, long tv_sec, int tv_usec, int width,
+    const char *prefix, const char *suffix)
+{
+	tv_usec /= 10000;
+	if (posix)
+		shf_fprintf(shf, "%s%*ld.%02d%s", prefix, width,
+		    tv_sec, tv_usec, suffix);
+	else
+		shf_fprintf(shf, "%s%*ldm%d.%02ds%s", prefix, width,
+		    tv_sec / 60, (int)(tv_sec % 60), tv_usec, suffix);
+}
+
+int
+c_times(const char **wp MKSH_A_UNUSED)
+{
+	struct rusage usage;
+
+	getrusage(RUSAGE_SELF, &usage);
+	p_time(shl_stdout, false, usage.ru_utime.tv_sec,
+	    usage.ru_utime.tv_usec, 0, null, " ");
+	p_time(shl_stdout, false, usage.ru_stime.tv_sec,
+	    usage.ru_stime.tv_usec, 0, null, "\n");
+
+	getrusage(RUSAGE_CHILDREN, &usage);
+	p_time(shl_stdout, false, usage.ru_utime.tv_sec,
+	    usage.ru_utime.tv_usec, 0, null, " ");
+	p_time(shl_stdout, false, usage.ru_stime.tv_sec,
+	    usage.ru_stime.tv_usec, 0, null, "\n");
+
+	return (0);
+}
+
+/*
+ * time pipeline (really a statement, not a built-in command)
+ */
+int
+timex(struct op *t, int f, volatile int *xerrok)
+{
+#define TF_NOARGS	BIT(0)
+#define TF_NOREAL	BIT(1)		/* don't report real time */
+#define TF_POSIX	BIT(2)		/* report in POSIX format */
+	int rv = 0, tf = 0;
+	struct rusage ru0, ru1, cru0, cru1;
+	struct timeval usrtime, systime, tv0, tv1;
+
+	gettimeofday(&tv0, NULL);
+	getrusage(RUSAGE_SELF, &ru0);
+	getrusage(RUSAGE_CHILDREN, &cru0);
+	if (t->left) {
+		/*
+		 * Two ways of getting cpu usage of a command: just use t0
+		 * and t1 (which will get cpu usage from other jobs that
+		 * finish while we are executing t->left), or get the
+		 * cpu usage of t->left. AT&T ksh does the former, while
+		 * pdksh tries to do the later (the j_usrtime hack doesn't
+		 * really work as it only counts the last job).
+		 */
+		timerclear(&j_usrtime);
+		timerclear(&j_systime);
+		rv = execute(t->left, f | XTIME, xerrok);
+		if (t->left->type == TCOM)
+			tf |= t->left->str[0];
+		gettimeofday(&tv1, NULL);
+		getrusage(RUSAGE_SELF, &ru1);
+		getrusage(RUSAGE_CHILDREN, &cru1);
+	} else
+		tf = TF_NOARGS;
+
+	if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */
+		tf |= TF_NOREAL;
+		timeradd(&ru0.ru_utime, &cru0.ru_utime, &usrtime);
+		timeradd(&ru0.ru_stime, &cru0.ru_stime, &systime);
+	} else {
+		timersub(&ru1.ru_utime, &ru0.ru_utime, &usrtime);
+		timeradd(&usrtime, &j_usrtime, &usrtime);
+		timersub(&ru1.ru_stime, &ru0.ru_stime, &systime);
+		timeradd(&systime, &j_systime, &systime);
+	}
+
+	if (!(tf & TF_NOREAL)) {
+		timersub(&tv1, &tv0, &tv1);
+		if (tf & TF_POSIX)
+			p_time(shl_out, true, tv1.tv_sec, tv1.tv_usec,
+			    5, "real ", "\n");
+		else
+			p_time(shl_out, false, tv1.tv_sec, tv1.tv_usec,
+			    5, null, " real ");
+	}
+	if (tf & TF_POSIX)
+		p_time(shl_out, true, usrtime.tv_sec, usrtime.tv_usec,
+		    5, "user ", "\n");
+	else
+		p_time(shl_out, false, usrtime.tv_sec, usrtime.tv_usec,
+		    5, null, " user ");
+	if (tf & TF_POSIX)
+		p_time(shl_out, true, systime.tv_sec, systime.tv_usec,
+		    5, "sys  ", "\n");
+	else
+		p_time(shl_out, false, systime.tv_sec, systime.tv_usec,
+		    5, null, " system\n");
+	shf_flush(shl_out);
+
+	return (rv);
+}
+
+void
+timex_hook(struct op *t, char **volatile *app)
+{
+	char **wp = *app;
+	int optc, i, j;
+	Getopt opt;
+
+	ksh_getopt_reset(&opt, 0);
+	opt.optind = 0;	/* start at the start */
+	while ((optc = ksh_getopt((const char **)wp, &opt, ":p")) != -1)
+		switch (optc) {
+		case 'p':
+			t->str[0] |= TF_POSIX;
+			break;
+		case '?':
+			errorf("time: -%s unknown option", opt.optarg);
+		case ':':
+			errorf("time: -%s requires an argument",
+			    opt.optarg);
+		}
+	/* Copy command words down over options. */
+	if (opt.optind != 0) {
+		for (i = 0; i < opt.optind; i++)
+			afree(wp[i], ATEMP);
+		for (i = 0, j = opt.optind; (wp[i] = wp[j]); i++, j++)
+			;
+	}
+	if (!wp[0])
+		t->str[0] |= TF_NOARGS;
+	*app = wp;
+}
+
+/* exec with no args - args case is taken care of in comexec() */
+int
+c_exec(const char **wp MKSH_A_UNUSED)
+{
+	int i;
+
+	/* make sure redirects stay in place */
+	if (e->savefd != NULL) {
+		for (i = 0; i < NUFILE; i++) {
+			if (e->savefd[i] > 0)
+				close(e->savefd[i]);
+			/*
+			 * keep all file descriptors > 2 private for ksh,
+			 * but not for POSIX or legacy/kludge sh
+			 */
+			if (!Flag(FPOSIX) && !Flag(FSH) && i > 2 &&
+			    e->savefd[i])
+				fcntl(i, F_SETFD, FD_CLOEXEC);
+		}
+		e->savefd = NULL;
+	}
+	return (0);
+}
+
+#if HAVE_MKNOD
+int
+c_mknod(const char **wp)
+{
+	int argc, optc, rv = 0;
+	bool ismkfifo = false;
+	const char **argv;
+	void *set = NULL;
+	mode_t mode = 0, oldmode = 0;
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, "m:")) != -1) {
+		switch (optc) {
+		case 'm':
+			set = setmode(builtin_opt.optarg);
+			if (set == NULL) {
+				bi_errorf("invalid file mode");
+				return (1);
+			}
+			mode = getmode(set, (mode_t)(DEFFILEMODE));
+			free(set);
+			break;
+		default:
+			goto c_mknod_usage;
+		}
+	}
+	argv = &wp[builtin_opt.optind];
+	if (argv[0] == NULL)
+		goto c_mknod_usage;
+	for (argc = 0; argv[argc]; argc++)
+		;
+	if (argc == 2 && argv[1][0] == 'p')
+		ismkfifo = true;
+	else if (argc != 4 || (argv[1][0] != 'b' && argv[1][0] != 'c'))
+		goto c_mknod_usage;
+
+	if (set != NULL)
+		oldmode = umask((mode_t)0);
+	else
+		mode = DEFFILEMODE;
+
+	mode |= (argv[1][0] == 'b') ? S_IFBLK :
+	    (argv[1][0] == 'c') ? S_IFCHR : 0;
+
+	if (!ismkfifo) {
+		unsigned long majnum, minnum;
+		dev_t dv;
+		char *c;
+
+		majnum = strtoul(argv[2], &c, 0);
+		if ((c == argv[2]) || (*c != '\0')) {
+			bi_errorf("non-numeric device major '%s'", argv[2]);
+			goto c_mknod_err;
+		}
+		minnum = strtoul(argv[3], &c, 0);
+		if ((c == argv[3]) || (*c != '\0')) {
+			bi_errorf("non-numeric device minor '%s'", argv[3]);
+			goto c_mknod_err;
+		}
+		dv = makedev(majnum, minnum);
+		if ((unsigned long)(major(dv)) != majnum) {
+			bi_errorf("device major too large: %lu", majnum);
+			goto c_mknod_err;
+		}
+		if ((unsigned long)(minor(dv)) != minnum) {
+			bi_errorf("device minor too large: %lu", minnum);
+			goto c_mknod_err;
+		}
+		if (mknod(argv[0], mode, dv))
+			goto c_mknod_failed;
+	} else if (mkfifo(argv[0], mode)) {
+ c_mknod_failed:
+		bi_errorf("%s: %s", *wp, strerror(errno));
+ c_mknod_err:
+		rv = 1;
+	}
+
+	if (set)
+		umask(oldmode);
+	return (rv);
+ c_mknod_usage:
+	bi_errorf("usage: mknod [-m mode] name b|c major minor");
+	bi_errorf("usage: mknod [-m mode] name p");
+	return (1);
+}
+#endif
+
+/* dummy function, special case in comexec() */
+int
+c_builtin(const char **wp MKSH_A_UNUSED)
+{
+	return (0);
+}
+
+/* test(1) accepts the following grammar:
+	oexpr	::= aexpr | aexpr "-o" oexpr ;
+	aexpr	::= nexpr | nexpr "-a" aexpr ;
+	nexpr	::= primary | "!" nexpr ;
+	primary	::= unary-operator operand
+		| operand binary-operator operand
+		| operand
+		| "(" oexpr ")"
+		;
+
+	unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
+			   "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
+			   "-L"|"-h"|"-S"|"-H";
+
+	binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+			    "-nt"|"-ot"|"-ef"|
+			    "<"|">"	# rules used for [[ .. ]] expressions
+			    ;
+	operand ::= <any thing>
+*/
+
+#define T_ERR_EXIT	2	/* POSIX says > 1 for errors */
+
+int
+c_test(const char **wp)
+{
+	int argc, res;
+	Test_env te;
+
+	te.flags = 0;
+	te.isa = ptest_isa;
+	te.getopnd = ptest_getopnd;
+	te.eval = test_eval;
+	te.error = ptest_error;
+
+	for (argc = 0; wp[argc]; argc++)
+		;
+
+	if (strcmp(wp[0], "[") == 0) {
+		if (strcmp(wp[--argc], "]") != 0) {
+			bi_errorf("missing ]");
+			return (T_ERR_EXIT);
+		}
+	}
+
+	te.pos.wp = wp + 1;
+	te.wp_end = wp + argc;
+
+	/*
+	 * Handle the special cases from POSIX.2, section 4.62.4.
+	 * Implementation of all the rules isn't necessary since
+	 * our parser does the right thing for the omitted steps.
+	 */
+	if (argc <= 5) {
+		const char **owp = wp;
+		int invert = 0;
+		Test_op op;
+		const char *opnd1, *opnd2;
+
+		while (--argc >= 0) {
+			if ((*te.isa)(&te, TM_END))
+				return (!0);
+			if (argc == 3) {
+				opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
+				if ((op = (*te.isa)(&te, TM_BINOP))) {
+					opnd2 = (*te.getopnd)(&te, op, 1);
+					res = (*te.eval)(&te, op, opnd1,
+					    opnd2, 1);
+					if (te.flags & TEF_ERROR)
+						return (T_ERR_EXIT);
+					if (invert & 1)
+						res = !res;
+					return (!res);
+				}
+				/* back up to opnd1 */
+				te.pos.wp--;
+			}
+			if (argc == 1) {
+				opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
+				if (strcmp(opnd1, "-t") == 0)
+				    break;
+				res = (*te.eval)(&te, TO_STNZE, opnd1,
+				    NULL, 1);
+				if (invert & 1)
+					res = !res;
+				return (!res);
+			}
+			if ((*te.isa)(&te, TM_NOT)) {
+				invert++;
+			} else
+				break;
+		}
+		te.pos.wp = owp + 1;
+	}
+
+	return (test_parse(&te));
+}
+
+/*
+ * Generic test routines.
+ */
+
+Test_op
+test_isop(Test_meta meta, const char *s)
+{
+	char sc1;
+	const struct t_op *tbl;
+
+	tbl = meta == TM_UNOP ? u_ops : b_ops;
+	if (*s) {
+		sc1 = s[1];
+		for (; tbl->op_text[0]; tbl++)
+			if (sc1 == tbl->op_text[1] && !strcmp(s, tbl->op_text))
+				return (tbl->op_num);
+	}
+	return (TO_NONOP);
+}
+
+int
+test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
+    bool do_eval)
+{
+	int i, s;
+	size_t k;
+	struct stat b1, b2;
+	mksh_ari_t v1, v2;
+
+	if (!do_eval)
+		return (0);
+
+	switch ((int)op) {
+	/*
+	 * Unary Operators
+	 */
+	case TO_STNZE: /* -n */
+		return (*opnd1 != '\0');
+	case TO_STZER: /* -z */
+		return (*opnd1 == '\0');
+	case TO_OPTION: /* -o */
+		if ((i = *opnd1) == '!' || i == '?')
+			opnd1++;
+		if ((k = option(opnd1)) == (size_t)-1)
+			return (0);
+		return (i == '?' ? 1 : i == '!' ? !Flag(k) : Flag(k));
+	case TO_FILRD: /* -r */
+		return (test_eaccess(opnd1, R_OK) == 0);
+	case TO_FILWR: /* -w */
+		return (test_eaccess(opnd1, W_OK) == 0);
+	case TO_FILEX: /* -x */
+		return (test_eaccess(opnd1, X_OK) == 0);
+	case TO_FILAXST: /* -a */
+	case TO_FILEXST: /* -e */
+		return (stat(opnd1, &b1) == 0);
+	case TO_FILREG: /* -r */
+		return (stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode));
+	case TO_FILID: /* -d */
+		return (stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode));
+	case TO_FILCDEV: /* -c */
+		return (stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode));
+	case TO_FILBDEV: /* -b */
+		return (stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode));
+	case TO_FILFIFO: /* -p */
+		return (stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode));
+	case TO_FILSYM: /* -h -L */
+		return (lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode));
+	case TO_FILSOCK: /* -S */
+		return (stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode));
+	case TO_FILCDF:/* -H HP context dependent files (directories) */
+		return (0);
+	case TO_FILSETU: /* -u */
+		return (stat(opnd1, &b1) == 0 &&
+		    (b1.st_mode & S_ISUID) == S_ISUID);
+	case TO_FILSETG: /* -g */
+		return (stat(opnd1, &b1) == 0 &&
+		    (b1.st_mode & S_ISGID) == S_ISGID);
+	case TO_FILSTCK: /* -k */
+#ifdef S_ISVTX
+		return (stat(opnd1, &b1) == 0 &&
+		    (b1.st_mode & S_ISVTX) == S_ISVTX);
+#else
+		return (0);
+#endif
+	case TO_FILGZ: /* -s */
+		return (stat(opnd1, &b1) == 0 && b1.st_size > 0L);
+	case TO_FILTT: /* -t */
+		if (opnd1 && !bi_getn(opnd1, &i)) {
+			te->flags |= TEF_ERROR;
+			i = 0;
+		} else
+			i = isatty(opnd1 ? i : 0);
+		return (i);
+	case TO_FILUID: /* -O */
+		return (stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid);
+	case TO_FILGID: /* -G */
+		return (stat(opnd1, &b1) == 0 && b1.st_gid == getegid());
+	/*
+	 * Binary Operators
+	 */
+	case TO_STEQL: /* = */
+		if (te->flags & TEF_DBRACKET)
+			return (gmatchx(opnd1, opnd2, false));
+		return (strcmp(opnd1, opnd2) == 0);
+	case TO_STNEQ: /* != */
+		if (te->flags & TEF_DBRACKET)
+			return (!gmatchx(opnd1, opnd2, false));
+		return (strcmp(opnd1, opnd2) != 0);
+	case TO_STLT: /* < */
+		return (strcmp(opnd1, opnd2) < 0);
+	case TO_STGT: /* > */
+		return (strcmp(opnd1, opnd2) > 0);
+	case TO_INTEQ: /* -eq */
+	case TO_INTNE: /* -ne */
+	case TO_INTGE: /* -ge */
+	case TO_INTGT: /* -gt */
+	case TO_INTLE: /* -le */
+	case TO_INTLT: /* -lt */
+		if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR, false) ||
+		    !evaluate(opnd2, &v2, KSH_RETURN_ERROR, false)) {
+			/* error already printed.. */
+			te->flags |= TEF_ERROR;
+			return (1);
+		}
+		switch ((int)op) {
+		case TO_INTEQ:
+			return (v1 == v2);
+		case TO_INTNE:
+			return (v1 != v2);
+		case TO_INTGE:
+			return (v1 >= v2);
+		case TO_INTGT:
+			return (v1 > v2);
+		case TO_INTLE:
+			return (v1 <= v2);
+		case TO_INTLT:
+			return (v1 < v2);
+		}
+	case TO_FILNT: /* -nt */
+		/* ksh88/ksh93 succeed if file2 can't be stated
+		 * (subtly different from 'does not exist').
+		 */
+		return (stat(opnd1, &b1) == 0 &&
+		    (((s = stat(opnd2, &b2)) == 0 &&
+		    b1.st_mtime > b2.st_mtime) || s < 0));
+	case TO_FILOT: /* -ot */
+		/* ksh88/ksh93 succeed if file1 can't be stated
+		 * (subtly different from 'does not exist').
+		 */
+		return (stat(opnd2, &b2) == 0 &&
+		    (((s = stat(opnd1, &b1)) == 0 &&
+		    b1.st_mtime < b2.st_mtime) || s < 0));
+	case TO_FILEQ: /* -ef */
+		return (stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 &&
+		    b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
+	}
+	(*te->error)(te, 0, "internal error: unknown op");
+	return (1);
+}
+
+/* On most/all unixen, access() says everything is executable for root... */
+static int
+test_eaccess(const char *pathl, int mode)
+{
+	int rv;
+
+	if ((rv = access(pathl, mode)) == 0 && ksheuid == 0 && (mode & X_OK)) {
+		struct stat statb;
+
+		if (stat(pathl, &statb) < 0)
+			rv = -1;
+		else if (S_ISDIR(statb.st_mode))
+			rv = 0;
+		else
+			rv = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ?
+			    0 : -1;
+	}
+	return (rv);
+}
+
+int
+test_parse(Test_env *te)
+{
+	int rv;
+
+	rv = test_oexpr(te, 1);
+
+	if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END))
+		(*te->error)(te, 0, "unexpected operator/operand");
+
+	return ((te->flags & TEF_ERROR) ? T_ERR_EXIT : !rv);
+}
+
+static int
+test_oexpr(Test_env *te, bool do_eval)
+{
+	int rv;
+
+	if ((rv = test_aexpr(te, do_eval)))
+		do_eval = false;
+	if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR))
+		return (test_oexpr(te, do_eval) || rv);
+	return (rv);
+}
+
+static int
+test_aexpr(Test_env *te, bool do_eval)
+{
+	int rv;
+
+	if (!(rv = test_nexpr(te, do_eval)))
+		do_eval = false;
+	if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND))
+		return (test_aexpr(te, do_eval) && rv);
+	return (rv);
+}
+
+static int
+test_nexpr(Test_env *te, bool do_eval)
+{
+	if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT))
+		return (!test_nexpr(te, do_eval));
+	return (test_primary(te, do_eval));
+}
+
+static int
+test_primary(Test_env *te, bool do_eval)
+{
+	const char *opnd1, *opnd2;
+	int rv;
+	Test_op op;
+
+	if (te->flags & TEF_ERROR)
+		return (0);
+	if ((*te->isa)(te, TM_OPAREN)) {
+		rv = test_oexpr(te, do_eval);
+		if (te->flags & TEF_ERROR)
+			return (0);
+		if (!(*te->isa)(te, TM_CPAREN)) {
+			(*te->error)(te, 0, "missing closing paren");
+			return (0);
+		}
+		return (rv);
+	}
+	/*
+	 * Binary should have precedence over unary in this case
+	 * so that something like test \( -f = -f \) is accepted
+	 */
+	if ((te->flags & TEF_DBRACKET) || (&te->pos.wp[1] < te->wp_end &&
+	    !test_isop(TM_BINOP, te->pos.wp[1]))) {
+		if ((op = (*te->isa)(te, TM_UNOP))) {
+			/* unary expression */
+			opnd1 = (*te->getopnd)(te, op, do_eval);
+			if (!opnd1) {
+				(*te->error)(te, -1, "missing argument");
+				return (0);
+			}
+
+			return ((*te->eval)(te, op, opnd1, NULL, do_eval));
+		}
+	}
+	opnd1 = (*te->getopnd)(te, TO_NONOP, do_eval);
+	if (!opnd1) {
+		(*te->error)(te, 0, "expression expected");
+		return (0);
+	}
+	if ((op = (*te->isa)(te, TM_BINOP))) {
+		/* binary expression */
+		opnd2 = (*te->getopnd)(te, op, do_eval);
+		if (!opnd2) {
+			(*te->error)(te, -1, "missing second argument");
+			return (0);
+		}
+
+		return ((*te->eval)(te, op, opnd1, opnd2, do_eval));
+	}
+	return ((*te->eval)(te, TO_STNZE, opnd1, NULL, do_eval));
+}
+
+/*
+ * Plain test (test and [ .. ]) specific routines.
+ */
+
+/*
+ * Test if the current token is a whatever. Accepts the current token if
+ * it is. Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static Test_op
+ptest_isa(Test_env *te, Test_meta meta)
+{
+	/* Order important - indexed by Test_meta values */
+	static const char *const tokens[] = {
+		"-o", "-a", "!", "(", ")"
+	};
+	Test_op rv;
+
+	if (te->pos.wp >= te->wp_end)
+		return (meta == TM_END ? TO_NONNULL : TO_NONOP);
+
+	if (meta == TM_UNOP || meta == TM_BINOP)
+		rv = test_isop(meta, *te->pos.wp);
+	else if (meta == TM_END)
+		rv = TO_NONOP;
+	else
+		rv = !strcmp(*te->pos.wp, tokens[(int)meta]) ?
+		    TO_NONNULL : TO_NONOP;
+
+	/* Accept the token? */
+	if (rv != TO_NONOP)
+		te->pos.wp++;
+
+	return (rv);
+}
+
+static const char *
+ptest_getopnd(Test_env *te, Test_op op, bool do_eval MKSH_A_UNUSED)
+{
+	if (te->pos.wp >= te->wp_end)
+		return (op == TO_FILTT ? "1" : NULL);
+	return (*te->pos.wp++);
+}
+
+static void
+ptest_error(Test_env *te, int ofs, const char *msg)
+{
+	const char *op;
+
+	te->flags |= TEF_ERROR;
+	if ((op = te->pos.wp + ofs >= te->wp_end ? NULL : te->pos.wp[ofs]))
+		bi_errorf("%s: %s", op, msg);
+	else
+		bi_errorf("%s", msg);
+}
+
+#ifndef MKSH_NO_LIMITS
+#define SOFT	0x1
+#define HARD	0x2
+
+struct limits {
+	const char *name;
+	int resource;		/* resource to get/set */
+	int factor;		/* multiply by to get rlim_{cur,max} values */
+	char option;
+};
+
+static void print_ulimit(const struct limits *, int);
+static int set_ulimit(const struct limits *, const char *, int);
+
+/* Magic to divine the 'm' and 'v' limits */
+
+#ifdef RLIMIT_AS
+#if !defined(RLIMIT_VMEM) || (RLIMIT_VMEM == RLIMIT_AS) || \
+    !defined(RLIMIT_RSS) || (RLIMIT_VMEM == RLIMIT_RSS)
+#define ULIMIT_V_IS_AS
+#elif defined(RLIMIT_VMEM)
+#if !defined(RLIMIT_RSS) || (RLIMIT_RSS == RLIMIT_AS)
+#define ULIMIT_V_IS_AS
+#else
+#define ULIMIT_V_IS_VMEM
+#endif
+#endif
+#endif
+
+#ifdef RLIMIT_RSS
+#ifdef ULIMIT_V_IS_VMEM
+#define ULIMIT_M_IS_RSS
+#elif defined(RLIMIT_VMEM) && (RLIMIT_VMEM == RLIMIT_RSS)
+#define ULIMIT_M_IS_VMEM
+#else
+#define ULIMIT_M_IS_RSS
+#endif
+#if defined(ULIMIT_M_IS_RSS) && defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS)
+#undef ULIMIT_M_IS_RSS
+#endif
+#endif
+
+#if !defined(RLIMIT_AS) && !defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_VMEM)
+#define ULIMIT_V_IS_VMEM
+#endif
+
+#if !defined(ULIMIT_V_IS_VMEM) && defined(RLIMIT_VMEM) && \
+    (!defined(RLIMIT_RSS) || (defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS)))
+#define ULIMIT_M_IS_VMEM
+#endif
+
+#if defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_AS) && \
+    (RLIMIT_VMEM == RLIMIT_AS)
+#undef ULIMIT_M_IS_VMEM
+#endif
+
+
+int
+c_ulimit(const char **wp)
+{
+	static const struct limits limits[] = {
+		/* do not use options -H, -S or -a or change the order */
+#ifdef RLIMIT_CPU
+		{ "time(cpu-seconds)", RLIMIT_CPU, 1, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+		{ "file(blocks)", RLIMIT_FSIZE, 512, 'f' },
+#endif
+#ifdef RLIMIT_CORE
+		{ "coredump(blocks)", RLIMIT_CORE, 512, 'c' },
+#endif
+#ifdef RLIMIT_DATA
+		{ "data(KiB)", RLIMIT_DATA, 1024, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+		{ "stack(KiB)", RLIMIT_STACK, 1024, 's' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+		{ "lockedmem(KiB)", RLIMIT_MEMLOCK, 1024, 'l' },
+#endif
+#ifdef RLIMIT_NOFILE
+		{ "nofiles(descriptors)", RLIMIT_NOFILE, 1, 'n' },
+#endif
+#ifdef RLIMIT_NPROC
+		{ "processes", RLIMIT_NPROC, 1, 'p' },
+#endif
+#ifdef RLIMIT_SWAP
+		{ "swap(KiB)", RLIMIT_SWAP, 1024, 'w' },
+#endif
+#ifdef RLIMIT_LOCKS
+		{ "flocks", RLIMIT_LOCKS, -1, 'L' },
+#endif
+#ifdef RLIMIT_TIME
+		{ "humantime(seconds)", RLIMIT_TIME, 1, 'T' },
+#endif
+#ifdef RLIMIT_NOVMON
+		{ "vnodemonitors", RLIMIT_NOVMON, 1, 'V' },
+#endif
+#ifdef RLIMIT_SIGPENDING
+		{ "sigpending", RLIMIT_SIGPENDING, 1, 'i' },
+#endif
+#ifdef RLIMIT_MSGQUEUE
+		{ "msgqueue(bytes)", RLIMIT_MSGQUEUE, 1, 'q' },
+#endif
+#ifdef RLIMIT_AIO_MEM
+		{ "AIOlockedmem(KiB)", RLIMIT_AIO_MEM, 1024, 'M' },
+#endif
+#ifdef RLIMIT_AIO_OPS
+		{ "AIOoperations", RLIMIT_AIO_OPS, 1, 'O' },
+#endif
+#ifdef RLIMIT_TCACHE
+		{ "cachedthreads", RLIMIT_TCACHE, 1, 'C' },
+#endif
+#ifdef RLIMIT_SBSIZE
+		{ "sockbufsiz(KiB)", RLIMIT_SBSIZE, 1024, 'B' },
+#endif
+#ifdef RLIMIT_PTHREAD
+		{ "threadsperprocess", RLIMIT_PTHREAD, 1, 'P' },
+#endif
+#ifdef RLIMIT_NICE
+		{ "maxnice", RLIMIT_NICE, 1, 'e' },
+#endif
+#ifdef RLIMIT_RTPRIO
+		{ "maxrtprio", RLIMIT_RTPRIO, 1, 'r' },
+#endif
+#if defined(ULIMIT_M_IS_RSS)
+		{ "resident-set(KiB)", RLIMIT_RSS, 1024, 'm' },
+#elif defined(ULIMIT_M_IS_VMEM)
+		{ "memory(KiB)", RLIMIT_VMEM, 1024, 'm' },
+#endif
+#if defined(ULIMIT_V_IS_VMEM)
+		{ "virtual-memory(KiB)", RLIMIT_VMEM, 1024, 'v' },
+#elif defined(ULIMIT_V_IS_AS)
+		{ "address-space(KiB)", RLIMIT_AS, 1024, 'v' },
+#endif
+		{ NULL, 0, 0, 0 }
+	};
+	static char opts[3 + NELEM(limits)];
+	int how = SOFT | HARD, optc, what = 'f';
+	bool all = false;
+	const struct limits *l;
+
+	if (!opts[0]) {
+		/* build options string on first call - yuck */
+		char *p = opts;
+
+		*p++ = 'H'; *p++ = 'S'; *p++ = 'a';
+		for (l = limits; l->name; l++)
+			*p++ = l->option;
+		*p = '\0';
+	}
+
+	while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1)
+		switch (optc) {
+		case 'H':
+			how = HARD;
+			break;
+		case 'S':
+			how = SOFT;
+			break;
+		case 'a':
+			all = true;
+			break;
+		case '?':
+			bi_errorf("usage: ulimit [-acdfHLlmnpSsTtvw] [value]");
+			return (1);
+		default:
+			what = optc;
+		}
+
+	for (l = limits; l->name && l->option != what; l++)
+		;
+	if (!l->name) {
+		internal_warningf("ulimit: %c", what);
+		return (1);
+	}
+
+	if (wp[builtin_opt.optind]) {
+		if (all || wp[builtin_opt.optind + 1]) {
+			bi_errorf("too many arguments");
+			return (1);
+		}
+		return (set_ulimit(l, wp[builtin_opt.optind], how));
+	}
+	if (!all)
+		print_ulimit(l, how);
+	else for (l = limits; l->name; l++) {
+		shprintf("%-20s ", l->name);
+		print_ulimit(l, how);
+	}
+	return (0);
+}
+
+static int
+set_ulimit(const struct limits *l, const char *v, int how)
+{
+	rlim_t val = (rlim_t)0;
+	struct rlimit limit;
+
+	if (strcmp(v, "unlimited") == 0)
+		val = (rlim_t)RLIM_INFINITY;
+	else {
+		mksh_ari_t rval;
+
+		if (!evaluate(v, &rval, KSH_RETURN_ERROR, false))
+			return (1);
+		/*
+		 * Avoid problems caused by typos that evaluate misses due
+		 * to evaluating unset parameters to 0...
+		 * If this causes problems, will have to add parameter to
+		 * evaluate() to control if unset params are 0 or an error.
+		 */
+		if (!rval && !ksh_isdigit(v[0])) {
+			bi_errorf("invalid %s limit: %s", l->name, v);
+			return (1);
+		}
+		val = (rlim_t)((rlim_t)rval * l->factor);
+	}
+
+	if (getrlimit(l->resource, &limit) < 0) {
+		/* some cannot be read, e.g. Linux RLIMIT_LOCKS */
+		limit.rlim_cur = RLIM_INFINITY;
+		limit.rlim_max = RLIM_INFINITY;
+	}
+	if (how & SOFT)
+		limit.rlim_cur = val;
+	if (how & HARD)
+		limit.rlim_max = val;
+	if (!setrlimit(l->resource, &limit))
+		return (0);
+	if (errno == EPERM)
+		bi_errorf("%s exceeds allowable %s limit", v, l->name);
+	else
+		bi_errorf("bad %s limit: %s", l->name, strerror(errno));
+	return (1);
+}
+
+static void
+print_ulimit(const struct limits *l, int how)
+{
+	rlim_t val = (rlim_t)0;
+	struct rlimit limit;
+
+	if (getrlimit(l->resource, &limit)) {
+		shf_puts("unknown\n", shl_stdout);
+		return;
+	}
+	if (how & SOFT)
+		val = limit.rlim_cur;
+	else if (how & HARD)
+		val = limit.rlim_max;
+	if (val == (rlim_t)RLIM_INFINITY)
+		shf_puts("unlimited\n", shl_stdout);
+	else
+		shprintf("%ld\n", (long)(val / l->factor));
+}
+#endif
+
+int
+c_rename(const char **wp)
+{
+	int rv = 1;
+
+	if (wp == NULL		/* argv */ ||
+	    wp[0] == NULL	/* name of builtin */ ||
+	    wp[1] == NULL	/* first argument */ ||
+	    wp[2] == NULL	/* second argument */ ||
+	    wp[3] != NULL	/* no further args please */)
+		bi_errorf(T_synerr);
+	else if ((rv = rename(wp[1], wp[2])) != 0) {
+		rv = errno;
+		bi_errorf("failed: %s", strerror(rv));
+	}
+
+	return (rv);
+}
+
+int
+c_realpath(const char **wp)
+{
+	int rv = 1;
+	char *buf;
+
+	if (wp != NULL && wp[0] != NULL && wp[1] != NULL) {
+		if (strcmp(wp[1], "--")) {
+			if (wp[2] == NULL) {
+				wp += 1;
+				rv = 0;
+			}
+		} else {
+			if (wp[2] != NULL && wp[3] == NULL) {
+				wp += 2;
+				rv = 0;
+			}
+		}
+	}
+
+	if (rv)
+		bi_errorf(T_synerr);
+	else if ((buf = do_realpath(*wp)) == NULL) {
+		rv = errno;
+		bi_errorf("%s: %s", *wp, strerror(rv));
+		if ((unsigned int)rv > 255)
+			rv = 255;
+	} else {
+		shprintf("%s\n", buf);
+		afree(buf, ATEMP);
+	}
+
+	return (rv);
+}
diff --git a/mksh/src/histrap.c b/mksh/src/histrap.c
new file mode 100644
index 0000000..2ac4c38
--- /dev/null
+++ b/mksh/src/histrap.c
@@ -0,0 +1,1483 @@
+/*	$OpenBSD: history.c,v 1.39 2010/05/19 17:36:08 jasper Exp $	*/
+/*	$OpenBSD: trap.c,v 1.23 2010/05/19 17:36:08 jasper Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+#if HAVE_PERSISTENT_HISTORY
+#include <sys/file.h>
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.98 2010/07/24 17:08:29 tg Exp $");
+
+/*-
+ * MirOS: This is the default mapping type, and need not be specified.
+ * IRIX doesn't have this constant.
+ */
+#ifndef MAP_FILE
+#define MAP_FILE	0
+#endif
+
+Trap sigtraps[NSIG + 1];
+static struct sigaction Sigact_ign;
+
+#if HAVE_PERSISTENT_HISTORY
+static int hist_count_lines(unsigned char *, int);
+static int hist_shrink(unsigned char *, int);
+static unsigned char *hist_skip_back(unsigned char *,int *,int);
+static void histload(Source *, unsigned char *, int);
+static void histinsert(Source *, int, const char *);
+static void writehistfile(int, char *);
+static int sprinkle(int);
+#endif
+
+static int hist_execute(char *);
+static int hist_replace(char **, const char *, const char *, bool);
+static char **hist_get(const char *, bool, bool);
+static char **hist_get_oldest(void);
+static void histbackup(void);
+
+static char **current;		/* current position in history[] */
+static int hstarted;		/* set after hist_init() called */
+static Source *hist_source;
+
+#if HAVE_PERSISTENT_HISTORY
+static char *hname;		/* current name of history file */
+static int histfd;
+static int hsize;
+#endif
+
+int
+c_fc(const char **wp)
+{
+	struct shf *shf;
+	struct temp *tf;
+	const char *p;
+	char *editor = NULL;
+	bool gflag = false, lflag = false, nflag = false, rflag = false,
+	    sflag = false;
+	int optc;
+	const char *first = NULL, *last = NULL;
+	char **hfirst, **hlast, **hp;
+
+	if (!Flag(FTALKING_I)) {
+		bi_errorf("history functions not available");
+		return (1);
+	}
+
+	while ((optc = ksh_getopt(wp, &builtin_opt,
+	    "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
+		switch (optc) {
+		case 'e':
+			p = builtin_opt.optarg;
+			if (ksh_isdash(p))
+				sflag = true;
+			else {
+				size_t len = strlen(p);
+				editor = alloc(len + 4, ATEMP);
+				memcpy(editor, p, len);
+				memcpy(editor + len, " $_", 4);
+			}
+			break;
+		case 'g': /* non-AT&T ksh */
+			gflag = true;
+			break;
+		case 'l':
+			lflag = true;
+			break;
+		case 'n':
+			nflag = true;
+			break;
+		case 'r':
+			rflag = true;
+			break;
+		case 's':	/* POSIX version of -e - */
+			sflag = true;
+			break;
+		/* kludge city - accept -num as -- -num (kind of) */
+		case '0': case '1': case '2': case '3': case '4':
+		case '5': case '6': case '7': case '8': case '9':
+			p = shf_smprintf("-%c%s",
+					optc, builtin_opt.optarg);
+			if (!first)
+				first = p;
+			else if (!last)
+				last = p;
+			else {
+				bi_errorf("too many arguments");
+				return (1);
+			}
+			break;
+		case '?':
+			return (1);
+		}
+	wp += builtin_opt.optind;
+
+	/* Substitute and execute command */
+	if (sflag) {
+		char *pat = NULL, *rep = NULL;
+
+		if (editor || lflag || nflag || rflag) {
+			bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
+			return (1);
+		}
+
+		/* Check for pattern replacement argument */
+		if (*wp && **wp && (p = cstrchr(*wp + 1, '='))) {
+			strdupx(pat, *wp, ATEMP);
+			rep = pat + (p - *wp);
+			*rep++ = '\0';
+			wp++;
+		}
+		/* Check for search prefix */
+		if (!first && (first = *wp))
+			wp++;
+		if (last || *wp) {
+			bi_errorf("too many arguments");
+			return (1);
+		}
+
+		hp = first ? hist_get(first, false, false) :
+		    hist_get_newest(false);
+		if (!hp)
+			return (1);
+		return (hist_replace(hp, pat, rep, gflag));
+	}
+
+	if (editor && (lflag || nflag)) {
+		bi_errorf("can't use -l, -n with -e");
+		return (1);
+	}
+
+	if (!first && (first = *wp))
+		wp++;
+	if (!last && (last = *wp))
+		wp++;
+	if (*wp) {
+		bi_errorf("too many arguments");
+		return (1);
+	}
+	if (!first) {
+		hfirst = lflag ? hist_get("-16", true, true) :
+		    hist_get_newest(false);
+		if (!hfirst)
+			return (1);
+		/* can't fail if hfirst didn't fail */
+		hlast = hist_get_newest(false);
+	} else {
+		/* POSIX says not an error if first/last out of bounds
+		 * when range is specified; AT&T ksh and pdksh allow out of
+		 * bounds for -l as well.
+		 */
+		hfirst = hist_get(first, (lflag || last) ? true : false, lflag);
+		if (!hfirst)
+			return (1);
+		hlast = last ? hist_get(last, true, lflag) :
+		    (lflag ? hist_get_newest(false) : hfirst);
+		if (!hlast)
+			return (1);
+	}
+	if (hfirst > hlast) {
+		char **temp;
+
+		temp = hfirst; hfirst = hlast; hlast = temp;
+		rflag = !rflag; /* POSIX */
+	}
+
+	/* List history */
+	if (lflag) {
+		char *s, *t;
+
+		for (hp = rflag ? hlast : hfirst;
+		    hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) {
+			if (!nflag)
+				shf_fprintf(shl_stdout, "%d",
+				    hist_source->line - (int)(histptr - hp));
+			shf_putc('\t', shl_stdout);
+			/* print multi-line commands correctly */
+			s = *hp;
+			while ((t = strchr(s, '\n'))) {
+				*t = '\0';
+				shf_fprintf(shl_stdout, "%s\n\t", s);
+				*t++ = '\n';
+				s = t;
+			}
+			shf_fprintf(shl_stdout, "%s\n", s);
+		}
+		shf_flush(shl_stdout);
+		return (0);
+	}
+
+	/* Run editor on selected lines, then run resulting commands */
+
+	tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
+	if (!(shf = tf->shf)) {
+		bi_errorf("cannot create temp file %s - %s",
+		    tf->name, strerror(errno));
+		return (1);
+	}
+	for (hp = rflag ? hlast : hfirst;
+	    hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
+		shf_fprintf(shf, "%s\n", *hp);
+	if (shf_close(shf) == EOF) {
+		bi_errorf("error writing temporary file - %s", strerror(errno));
+		return (1);
+	}
+
+	/* Ignore setstr errors here (arbitrary) */
+	setstr(local("_", false), tf->name, KSH_RETURN_ERROR);
+
+	/* XXX: source should not get trashed by this.. */
+	{
+		Source *sold = source;
+		int ret;
+
+		ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0);
+		source = sold;
+		if (ret)
+			return (ret);
+	}
+
+	{
+		struct stat statb;
+		XString xs;
+		char *xp;
+		int n;
+
+		if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
+			bi_errorf("cannot open temp file %s", tf->name);
+			return (1);
+		}
+
+		n = stat(tf->name, &statb) < 0 ? 128 : statb.st_size + 1;
+		Xinit(xs, xp, n, hist_source->areap);
+		while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
+			xp += n;
+			if (Xnleft(xs, xp) <= 0)
+				XcheckN(xs, xp, Xlength(xs, xp));
+		}
+		if (n < 0) {
+			bi_errorf("error reading temp file %s - %s",
+			    tf->name, strerror(shf_errno(shf)));
+			shf_close(shf);
+			return (1);
+		}
+		shf_close(shf);
+		*xp = '\0';
+		strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
+		return (hist_execute(Xstring(xs, xp)));
+	}
+}
+
+/* Save cmd in history, execute cmd (cmd gets trashed) */
+static int
+hist_execute(char *cmd)
+{
+	Source *sold;
+	int ret;
+	char *p, *q;
+
+	histbackup();
+
+	for (p = cmd; p; p = q) {
+		if ((q = strchr(p, '\n'))) {
+			*q++ = '\0'; /* kill the newline */
+			if (!*q) /* ignore trailing newline */
+				q = NULL;
+		}
+		histsave(&hist_source->line, p, true, true);
+
+		shellf("%s\n", p); /* POSIX doesn't say this is done... */
+		if (q)		/* restore \n (trailing \n not restored) */
+			q[-1] = '\n';
+	}
+
+	/*
+	 * Commands are executed here instead of pushing them onto the
+	 * input 'cause POSIX says the redirection and variable assignments
+	 * in
+	 *	X=y fc -e - 42 2> /dev/null
+	 * are to effect the repeated commands environment.
+	 */
+	/* XXX: source should not get trashed by this.. */
+	sold = source;
+	ret = command(cmd, 0);
+	source = sold;
+	return (ret);
+}
+
+static int
+hist_replace(char **hp, const char *pat, const char *rep, bool globr)
+{
+	char *line;
+
+	if (!pat)
+		strdupx(line, *hp, ATEMP);
+	else {
+		char *s, *s1;
+		int pat_len = strlen(pat);
+		int rep_len = strlen(rep);
+		int len;
+		XString xs;
+		char *xp;
+		bool any_subst = false;
+
+		Xinit(xs, xp, 128, ATEMP);
+		for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || globr);
+		    s = s1 + pat_len) {
+			any_subst = true;
+			len = s1 - s;
+			XcheckN(xs, xp, len + rep_len);
+			memcpy(xp, s, len);		/* first part */
+			xp += len;
+			memcpy(xp, rep, rep_len);	/* replacement */
+			xp += rep_len;
+		}
+		if (!any_subst) {
+			bi_errorf("substitution failed");
+			return (1);
+		}
+		len = strlen(s) + 1;
+		XcheckN(xs, xp, len);
+		memcpy(xp, s, len);
+		xp += len;
+		line = Xclose(xs, xp);
+	}
+	return (hist_execute(line));
+}
+
+/*
+ * get pointer to history given pattern
+ * pattern is a number or string
+ */
+static char **
+hist_get(const char *str, bool approx, bool allow_cur)
+{
+	char **hp = NULL;
+	int n;
+
+	if (getn(str, &n)) {
+		hp = histptr + (n < 0 ? n : (n - hist_source->line));
+		if ((ptrdiff_t)hp < (ptrdiff_t)history) {
+			if (approx)
+				hp = hist_get_oldest();
+			else {
+				bi_errorf("%s: not in history", str);
+				hp = NULL;
+			}
+		} else if ((ptrdiff_t)hp > (ptrdiff_t)histptr) {
+			if (approx)
+				hp = hist_get_newest(allow_cur);
+			else {
+				bi_errorf("%s: not in history", str);
+				hp = NULL;
+			}
+		} else if (!allow_cur && hp == histptr) {
+			bi_errorf("%s: invalid range", str);
+			hp = NULL;
+		}
+	} else {
+		int anchored = *str == '?' ? (++str, 0) : 1;
+
+		/* the -1 is to avoid the current fc command */
+		if ((n = findhist(histptr - history - 1, 0, str, anchored)) < 0)
+			bi_errorf("%s: not in history", str);
+		else
+			hp = &history[n];
+	}
+	return (hp);
+}
+
+/* Return a pointer to the newest command in the history */
+char **
+hist_get_newest(bool allow_cur)
+{
+	if (histptr < history || (!allow_cur && histptr == history)) {
+		bi_errorf("no history (yet)");
+		return (NULL);
+	}
+	return (allow_cur ? histptr : histptr - 1);
+}
+
+/* Return a pointer to the oldest command in the history */
+static char **
+hist_get_oldest(void)
+{
+	if (histptr <= history) {
+		bi_errorf("no history (yet)");
+		return (NULL);
+	}
+	return (history);
+}
+
+/******************************/
+/* Back up over last histsave */
+/******************************/
+static void
+histbackup(void)
+{
+	static int last_line = -1;
+
+	if (histptr >= history && last_line != hist_source->line) {
+		hist_source->line--;
+		afree(*histptr, APERM);
+		histptr--;
+		last_line = hist_source->line;
+	}
+}
+
+/*
+ * Return the current position.
+ */
+char **
+histpos(void)
+{
+	return (current);
+}
+
+int
+histnum(int n)
+{
+	int last = histptr - history;
+
+	if (n < 0 || n >= last) {
+		current = histptr;
+		return (last);
+	} else {
+		current = &history[n];
+		return (n);
+	}
+}
+
+/*
+ * This will become unnecessary if hist_get is modified to allow
+ * searching from positions other than the end, and in either
+ * direction.
+ */
+int
+findhist(int start, int fwd, const char *str, int anchored)
+{
+	char	**hp;
+	int	maxhist = histptr - history;
+	int	incr = fwd ? 1 : -1;
+	int	len = strlen(str);
+
+	if (start < 0 || start >= maxhist)
+		start = maxhist;
+
+	hp = &history[start];
+	for (; hp >= history && hp <= histptr; hp += incr)
+		if ((anchored && strncmp(*hp, str, len) == 0) ||
+		    (!anchored && strstr(*hp, str)))
+			return (hp - history);
+
+	return (-1);
+}
+
+int
+findhistrel(const char *str)
+{
+	int	maxhist = histptr - history;
+	int	start = maxhist - 1;
+	int	rec;
+
+	getn(str, &rec);
+	if (rec == 0)
+		return (-1);
+	if (rec > 0) {
+		if (rec > maxhist)
+			return (-1);
+		return (rec - 1);
+	}
+	if (rec > maxhist)
+		return (-1);
+	return (start + rec + 1);
+}
+
+/*
+ *	set history
+ *	this means reallocating the dataspace
+ */
+void
+sethistsize(int n)
+{
+	if (n > 0 && n != histsize) {
+		int cursize = histptr - history;
+
+		/* save most recent history */
+		if (n < cursize) {
+			memmove(history, histptr - n, n * sizeof(char *));
+			cursize = n;
+		}
+
+		history = aresize(history, n * sizeof(char *), APERM);
+
+		histsize = n;
+		histptr = history + cursize;
+	}
+}
+
+#if HAVE_PERSISTENT_HISTORY
+/*
+ *	set history file
+ *	This can mean reloading/resetting/starting history file
+ *	maintenance
+ */
+void
+sethistfile(const char *name)
+{
+	/* if not started then nothing to do */
+	if (hstarted == 0)
+		return;
+
+	/* if the name is the same as the name we have */
+	if (hname && strcmp(hname, name) == 0)
+		return;
+
+	/*
+	 * its a new name - possibly
+	 */
+	if (histfd) {
+		/* yes the file is open */
+		(void)close(histfd);
+		histfd = 0;
+		hsize = 0;
+		afree(hname, APERM);
+		hname = NULL;
+		/* let's reset the history */
+		histptr = history - 1;
+		hist_source->line = 0;
+	}
+
+	hist_init(hist_source);
+}
+#endif
+
+/*
+ *	initialise the history vector
+ */
+void
+init_histvec(void)
+{
+	if (history == (char **)NULL) {
+		histsize = HISTORYSIZE;
+		history = alloc(histsize * sizeof(char *), APERM);
+		histptr = history - 1;
+	}
+}
+
+
+/*
+ *	Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
+ *	a) permit HISTSIZE to control number of lines of history stored
+ *	b) maintain a physical history file
+ *
+ *	It turns out that there is a lot of ghastly hackery here
+ */
+
+#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
+/* do not save command in history but possibly sync */
+bool
+histsync(void)
+{
+	bool changed = false;
+
+	if (histfd) {
+		int lno = hist_source->line;
+
+		hist_source->line++;
+		writehistfile(0, NULL);
+		hist_source->line--;
+
+		if (lno != hist_source->line)
+			changed = true;
+	}
+
+	return (changed);
+}
+#endif
+
+/*
+ * save command in history
+ */
+void
+histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups)
+{
+	char **hp;
+	char *c, *cp;
+
+	strdupx(c, cmd, APERM);
+	if ((cp = strchr(c, '\n')) != NULL)
+		*cp = '\0';
+
+	if (ignoredups && !strcmp(c, *histptr)
+#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
+	    && !histsync()
+#endif
+	    ) {
+		afree(c, APERM);
+		return;
+	}
+	++*lnp;
+
+#if HAVE_PERSISTENT_HISTORY
+	if (histfd && dowrite)
+		writehistfile(*lnp, c);
+#endif
+
+	hp = histptr;
+
+	if (++hp >= history + histsize) { /* remove oldest command */
+		afree(*history, APERM);
+		for (hp = history; hp < history + histsize - 1; hp++)
+			hp[0] = hp[1];
+	}
+	*hp = c;
+	histptr = hp;
+}
+
+/*
+ *	Write history data to a file nominated by HISTFILE
+ *	if HISTFILE is unset then history still happens, but
+ *	the data is not written to a file
+ *	All copies of ksh looking at the file will maintain the
+ *	same history. This is ksh behaviour.
+ *
+ *	This stuff uses mmap()
+ *	if your system ain't got it - then you'll have to undef HISTORYFILE
+ */
+
+/*
+ *	Open a history file
+ *	Format is:
+ *	Bytes 1, 2:
+ *		HMAGIC - just to check that we are dealing with
+ *		the correct object
+ *	Then follows a number of stored commands
+ *	Each command is
+ *	<command byte><command number(4 bytes)><bytes><null>
+ */
+#define HMAGIC1		0xab
+#define HMAGIC2		0xcd
+#define COMMAND		0xff
+
+void
+hist_init(Source *s)
+{
+#if HAVE_PERSISTENT_HISTORY
+	unsigned char *base;
+	int lines, fd, rv = 0;
+#endif
+
+	if (Flag(FTALKING) == 0)
+		return;
+
+	hstarted = 1;
+
+	hist_source = s;
+
+#if HAVE_PERSISTENT_HISTORY
+	if ((hname = str_val(global("HISTFILE"))) == NULL)
+		return;
+	strdupx(hname, hname, APERM);
+
+ retry:
+	/* we have a file and are interactive */
+	if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0)
+		return;
+
+	histfd = savefd(fd);
+	if (histfd != fd)
+		close(fd);
+
+	(void)flock(histfd, LOCK_EX);
+
+	hsize = lseek(histfd, (off_t)0, SEEK_END);
+
+	if (hsize == 0) {
+		/* add magic */
+		if (sprinkle(histfd)) {
+			hist_finish();
+			return;
+		}
+	} else if (hsize > 0) {
+		/*
+		 * we have some data
+		 */
+		base = (void *)mmap(NULL, hsize, PROT_READ,
+		    MAP_FILE | MAP_PRIVATE, histfd, (off_t)0);
+		/*
+		 * check on its validity
+		 */
+		if (base == (unsigned char *)MAP_FAILED ||
+		    *base != HMAGIC1 || base[1] != HMAGIC2) {
+			if (base != (unsigned char *)MAP_FAILED)
+				munmap((caddr_t)base, hsize);
+			hist_finish();
+			if (unlink(hname) /* fails */)
+				goto hiniterr;
+			goto retry;
+		}
+		if (hsize > 2) {
+			lines = hist_count_lines(base+2, hsize-2);
+			if (lines > histsize) {
+				/* we need to make the file smaller */
+				if (hist_shrink(base, hsize))
+					rv = unlink(hname);
+				munmap((caddr_t)base, hsize);
+				hist_finish();
+				if (rv) {
+ hiniterr:
+					bi_errorf("cannot unlink HISTFILE %s"
+					    " - %s", hname, strerror(errno));
+					hsize = 0;
+					return;
+				}
+				goto retry;
+			}
+		}
+		histload(hist_source, base+2, hsize-2);
+		munmap((caddr_t)base, hsize);
+	}
+	(void)flock(histfd, LOCK_UN);
+	hsize = lseek(histfd, (off_t)0, SEEK_END);
+#endif
+}
+
+#if HAVE_PERSISTENT_HISTORY
+typedef enum state {
+	shdr,		/* expecting a header */
+	sline,		/* looking for a null byte to end the line */
+	sn1,		/* bytes 1 to 4 of a line no */
+	sn2, sn3, sn4
+} State;
+
+static int
+hist_count_lines(unsigned char *base, int bytes)
+{
+	State state = shdr;
+	int lines = 0;
+
+	while (bytes--) {
+		switch (state) {
+		case shdr:
+			if (*base == COMMAND)
+				state = sn1;
+			break;
+		case sn1:
+			state = sn2; break;
+		case sn2:
+			state = sn3; break;
+		case sn3:
+			state = sn4; break;
+		case sn4:
+			state = sline; break;
+		case sline:
+			if (*base == '\0') {
+				lines++;
+				state = shdr;
+			}
+		}
+		base++;
+	}
+	return (lines);
+}
+
+/*
+ *	Shrink the history file to histsize lines
+ */
+static int
+hist_shrink(unsigned char *oldbase, int oldbytes)
+{
+	int fd, rv = 0;
+	char *nfile = NULL;
+	struct	stat statb;
+	unsigned char *nbase = oldbase;
+	int nbytes = oldbytes;
+
+	nbase = hist_skip_back(nbase, &nbytes, histsize);
+	if (nbase == NULL)
+		return (1);
+	if (nbase == oldbase)
+		return (0);
+
+	/*
+	 *	create temp file
+	 */
+	nfile = shf_smprintf("%s.%d", hname, (int)procpid);
+	if ((fd = open(nfile, O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0)
+		goto errout;
+	if (fstat(histfd, &statb) >= 0 &&
+	    chown(nfile, statb.st_uid, statb.st_gid))
+		goto errout;
+
+	if (sprinkle(fd) || write(fd, nbase, nbytes) != nbytes)
+		goto errout;
+	close(fd);
+	fd = -1;
+
+	/*
+	 *	rename
+	 */
+	if (rename(nfile, hname) < 0) {
+ errout:
+		if (fd >= 0) {
+			close(fd);
+			if (nfile)
+				unlink(nfile);
+		}
+		rv = 1;
+	}
+	afree(nfile, ATEMP);
+	return (rv);
+}
+
+/*
+ *	find a pointer to the data 'no' back from the end of the file
+ *	return the pointer and the number of bytes left
+ */
+static unsigned char *
+hist_skip_back(unsigned char *base, int *bytes, int no)
+{
+	int lines = 0;
+	unsigned char *ep;
+
+	for (ep = base + *bytes; --ep > base; ) {
+		/*
+		 * this doesn't really work: the 4 byte line number that
+		 * is encoded after the COMMAND byte can itself contain
+		 * the COMMAND byte....
+		 */
+		for (; ep > base && *ep != COMMAND; ep--)
+			;
+		if (ep == base)
+			break;
+		if (++lines == no) {
+			*bytes = *bytes - ((char *)ep - (char *)base);
+			return (ep);
+		}
+	}
+	return (NULL);
+}
+
+/*
+ *	load the history structure from the stored data
+ */
+static void
+histload(Source *s, unsigned char *base, int bytes)
+{
+	State state;
+	int lno = 0;
+	unsigned char *line = NULL;
+
+	for (state = shdr; bytes-- > 0; base++) {
+		switch (state) {
+		case shdr:
+			if (*base == COMMAND)
+				state = sn1;
+			break;
+		case sn1:
+			lno = (((*base)&0xff)<<24);
+			state = sn2;
+			break;
+		case sn2:
+			lno |= (((*base)&0xff)<<16);
+			state = sn3;
+			break;
+		case sn3:
+			lno |= (((*base)&0xff)<<8);
+			state = sn4;
+			break;
+		case sn4:
+			lno |= (*base)&0xff;
+			line = base+1;
+			state = sline;
+			break;
+		case sline:
+			if (*base == '\0') {
+				/* worry about line numbers */
+				if (histptr >= history && lno-1 != s->line) {
+					/* a replacement ? */
+					histinsert(s, lno, (char *)line);
+				} else {
+					s->line = lno--;
+					histsave(&lno, (char *)line, false,
+					    false);
+				}
+				state = shdr;
+			}
+		}
+	}
+}
+
+/*
+ *	Insert a line into the history at a specified number
+ */
+static void
+histinsert(Source *s, int lno, const char *line)
+{
+	char **hp;
+
+	if (lno >= s->line - (histptr - history) && lno <= s->line) {
+		hp = &histptr[lno - s->line];
+		if (*hp)
+			afree(*hp, APERM);
+		strdupx(*hp, line, APERM);
+	}
+}
+
+/*
+ *	write a command to the end of the history file
+ *	This *MAY* seem easy but it's also necessary to check
+ *	that the history file has not changed in size.
+ *	If it has - then some other shell has written to it
+ *	and we should read those commands to update our history
+ */
+static void
+writehistfile(int lno, char *cmd)
+{
+	int	sizenow;
+	unsigned char	*base;
+	unsigned char	*news;
+	int	bytes;
+	unsigned char	hdr[5];
+
+	(void)flock(histfd, LOCK_EX);
+	sizenow = lseek(histfd, (off_t)0, SEEK_END);
+	if (sizenow != hsize) {
+		/*
+		 *	Things have changed
+		 */
+		if (sizenow > hsize) {
+			/* someone has added some lines */
+			bytes = sizenow - hsize;
+			base = (void *)mmap(NULL, sizenow, PROT_READ,
+			    MAP_FILE | MAP_PRIVATE, histfd, (off_t)0);
+			if (base == (unsigned char *)MAP_FAILED)
+				goto bad;
+			news = base + hsize;
+			if (*news != COMMAND) {
+				munmap((caddr_t)base, sizenow);
+				goto bad;
+			}
+			hist_source->line--;
+			histload(hist_source, news, bytes);
+			hist_source->line++;
+			lno = hist_source->line;
+			munmap((caddr_t)base, sizenow);
+			hsize = sizenow;
+		} else {
+			/* it has shrunk */
+			/* but to what? */
+			/* we'll give up for now */
+			goto bad;
+		}
+	}
+	if (cmd) {
+		/*
+		 *	we can write our bit now
+		 */
+		hdr[0] = COMMAND;
+		hdr[1] = (lno>>24)&0xff;
+		hdr[2] = (lno>>16)&0xff;
+		hdr[3] = (lno>>8)&0xff;
+		hdr[4] = lno&0xff;
+		bytes = strlen(cmd) + 1;
+		if ((write(histfd, hdr, 5) != 5) ||
+		    (write(histfd, cmd, bytes) != bytes))
+			goto bad;
+		hsize = lseek(histfd, (off_t)0, SEEK_END);
+	}
+	(void)flock(histfd, LOCK_UN);
+	return;
+ bad:
+	hist_finish();
+}
+
+void
+hist_finish(void)
+{
+	(void)flock(histfd, LOCK_UN);
+	(void)close(histfd);
+	histfd = 0;
+}
+
+/*
+ *	add magic to the history file
+ */
+static int
+sprinkle(int fd)
+{
+	static const unsigned char mag[] = { HMAGIC1, HMAGIC2 };
+
+	return (write(fd, mag, 2) != 2);
+}
+#endif
+
+#if !HAVE_SYS_SIGNAME
+static const struct mksh_sigpair {
+	const char *const name;
+	int nr;
+} mksh_sigpairs[] = {
+#include "signames.inc"
+	{ NULL, 0 }
+};
+#endif
+
+void
+inittraps(void)
+{
+	int i;
+	const char *cs;
+
+	/* Populate sigtraps based on sys_signame and sys_siglist. */
+	for (i = 0; i <= NSIG; i++) {
+		sigtraps[i].signal = i;
+		if (i == SIGERR_) {
+			sigtraps[i].name = "ERR";
+			sigtraps[i].mess = "Error handler";
+		} else {
+#if HAVE_SYS_SIGNAME
+			cs = sys_signame[i];
+#else
+			const struct mksh_sigpair *pair = mksh_sigpairs;
+			while ((pair->nr != i) && (pair->name != NULL))
+				++pair;
+			cs = pair->name;
+#endif
+			if ((cs == NULL) ||
+			    (cs[0] == '\0'))
+				sigtraps[i].name = shf_smprintf("%d", i);
+			else {
+				char *s;
+
+				if (!strncasecmp(cs, "SIG", 3))
+					cs += 3;
+				strdupx(s, cs, APERM);
+				sigtraps[i].name = s;
+				while ((*s = ksh_toupper(*s)))
+					++s;
+			}
+#if HAVE_SYS_SIGLIST
+			sigtraps[i].mess = sys_siglist[i];
+#elif HAVE_STRSIGNAL
+			sigtraps[i].mess = strsignal(i);
+#else
+			sigtraps[i].mess = NULL;
+#endif
+			if ((sigtraps[i].mess == NULL) ||
+			    (sigtraps[i].mess[0] == '\0'))
+				sigtraps[i].mess = shf_smprintf("Signal %d", i);
+		}
+	}
+	sigtraps[SIGEXIT_].name = "EXIT";	/* our name for signal 0 */
+
+	(void)sigemptyset(&Sigact_ign.sa_mask);
+	Sigact_ign.sa_flags = 0; /* interruptible */
+	Sigact_ign.sa_handler = SIG_IGN;
+
+	sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
+	sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
+	sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */
+	sigtraps[SIGHUP].flags |= TF_FATAL;
+	sigtraps[SIGCHLD].flags |= TF_SHELL_USES;
+
+	/* these are always caught so we can clean up any temporary files. */
+	setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG);
+	setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG);
+	setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG);
+	setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG);
+}
+
+static void alarm_catcher(int sig);
+
+void
+alarm_init(void)
+{
+	sigtraps[SIGALRM].flags |= TF_SHELL_USES;
+	setsig(&sigtraps[SIGALRM], alarm_catcher,
+		SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+}
+
+/* ARGSUSED */
+static void
+alarm_catcher(int sig MKSH_A_UNUSED)
+{
+	/* this runs inside interrupt context, with errno saved */
+
+	if (ksh_tmout_state == TMOUT_READING) {
+		int left = alarm(0);
+
+		if (left == 0) {
+			ksh_tmout_state = TMOUT_LEAVING;
+			intrsig = 1;
+		} else
+			alarm(left);
+	}
+}
+
+Trap *
+gettrap(const char *name, int igncase)
+{
+	int n = NSIG + 1;
+	Trap *p;
+	const char *n2;
+	int (*cmpfunc)(const char *, const char *) = strcmp;
+
+	if (ksh_isdigit(*name)) {
+		if (getn(name, &n) && 0 <= n && n < NSIG)
+			return (&sigtraps[n]);
+		else
+			return (NULL);
+	}
+
+	n2 = strncasecmp(name, "SIG", 3) ? NULL : name + 3;
+	if (igncase)
+		cmpfunc = strcasecmp;
+	for (p = sigtraps; --n >= 0; p++)
+		if (!cmpfunc(p->name, name) || (n2 && !cmpfunc(p->name, n2)))
+			return (p);
+	return (NULL);
+}
+
+/*
+ * trap signal handler
+ */
+void
+trapsig(int i)
+{
+	Trap *p = &sigtraps[i];
+	int errno_ = errno;
+
+	trap = p->set = 1;
+	if (p->flags & TF_DFL_INTR)
+		intrsig = 1;
+	if ((p->flags & TF_FATAL) && !p->trap) {
+		fatal_trap = 1;
+		intrsig = 1;
+	}
+	if (p->shtrap)
+		(*p->shtrap)(i);
+	errno = errno_;
+}
+
+/*
+ * called when we want to allow the user to ^C out of something - won't
+ * work if user has trapped SIGINT.
+ */
+void
+intrcheck(void)
+{
+	if (intrsig)
+		runtraps(TF_DFL_INTR|TF_FATAL);
+}
+
+/*
+ * called after EINTR to check if a signal with normally causes process
+ * termination has been received.
+ */
+int
+fatal_trap_check(void)
+{
+	int i;
+	Trap *p;
+
+	/* todo: should check if signal is fatal, not the TF_DFL_INTR flag */
+	for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
+		if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL)))
+			/* return value is used as an exit code */
+			return (128 + p->signal);
+	return (0);
+}
+
+/*
+ * Returns the signal number of any pending traps: ie, a signal which has
+ * occurred for which a trap has been set or for which the TF_DFL_INTR flag
+ * is set.
+ */
+int
+trap_pending(void)
+{
+	int i;
+	Trap *p;
+
+	for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
+		if (p->set && ((p->trap && p->trap[0]) ||
+		    ((p->flags & (TF_DFL_INTR|TF_FATAL)) && !p->trap)))
+			return (p->signal);
+	return (0);
+}
+
+/*
+ * run any pending traps. If intr is set, only run traps that
+ * can interrupt commands.
+ */
+void
+runtraps(int flag)
+{
+	int i;
+	Trap *p;
+
+	if (ksh_tmout_state == TMOUT_LEAVING) {
+		ksh_tmout_state = TMOUT_EXECUTING;
+		warningf(false, "timed out waiting for input");
+		unwind(LEXIT);
+	} else
+		/*
+		 * XXX: this means the alarm will have no effect if a trap
+		 * is caught after the alarm() was started...not good.
+		 */
+		ksh_tmout_state = TMOUT_EXECUTING;
+	if (!flag)
+		trap = 0;
+	if (flag & TF_DFL_INTR)
+		intrsig = 0;
+	if (flag & TF_FATAL)
+		fatal_trap = 0;
+	for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
+		if (p->set && (!flag ||
+		    ((p->flags & flag) && p->trap == NULL)))
+			runtrap(p);
+}
+
+void
+runtrap(Trap *p)
+{
+	int	i = p->signal;
+	char	*trapstr = p->trap;
+	int	oexstat;
+	int	old_changed = 0;
+
+	p->set = 0;
+	if (trapstr == NULL) { /* SIG_DFL */
+		if (p->flags & TF_FATAL) {
+			/* eg, SIGHUP */
+			exstat = 128 + i;
+			unwind(LLEAVE);
+		}
+		if (p->flags & TF_DFL_INTR) {
+			/* eg, SIGINT, SIGQUIT, SIGTERM, etc. */
+			exstat = 128 + i;
+			unwind(LINTR);
+		}
+		return;
+	}
+	if (trapstr[0] == '\0') /* SIG_IGN */
+		return;
+	if (i == SIGEXIT_ || i == SIGERR_) {	/* avoid recursion on these */
+		old_changed = p->flags & TF_CHANGED;
+		p->flags &= ~TF_CHANGED;
+		p->trap = NULL;
+	}
+	oexstat = exstat;
+	/*
+	 * Note: trapstr is fully parsed before anything is executed, thus
+	 * no problem with afree(p->trap) in settrap() while still in use.
+	 */
+	command(trapstr, current_lineno);
+	exstat = oexstat;
+	if (i == SIGEXIT_ || i == SIGERR_) {
+		if (p->flags & TF_CHANGED)
+			/* don't clear TF_CHANGED */
+			afree(trapstr, APERM);
+		else
+			p->trap = trapstr;
+		p->flags |= old_changed;
+	}
+}
+
+/* clear pending traps and reset user's trap handlers; used after fork(2) */
+void
+cleartraps(void)
+{
+	int i;
+	Trap *p;
+
+	trap = 0;
+	intrsig = 0;
+	fatal_trap = 0;
+	for (i = NSIG+1, p = sigtraps; --i >= 0; p++) {
+		p->set = 0;
+		if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0]))
+			settrap(p, NULL);
+	}
+}
+
+/* restore signals just before an exec(2) */
+void
+restoresigs(void)
+{
+	int i;
+	Trap *p;
+
+	for (i = NSIG+1, p = sigtraps; --i >= 0; p++)
+		if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL))
+			setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL,
+			    SS_RESTORE_CURR|SS_FORCE);
+}
+
+void
+settrap(Trap *p, const char *s)
+{
+	sig_t f;
+
+	if (p->trap)
+		afree(p->trap, APERM);
+	strdupx(p->trap, s, APERM); /* handles s == 0 */
+	p->flags |= TF_CHANGED;
+	f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN;
+
+	p->flags |= TF_USER_SET;
+	if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL)
+		f = trapsig;
+	else if (p->flags & TF_SHELL_USES) {
+		if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) {
+			/* do what user wants at exec time */
+			p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
+			if (f == SIG_IGN)
+				p->flags |= TF_EXEC_IGN;
+			else
+				p->flags |= TF_EXEC_DFL;
+		}
+
+		/*
+		 * assumes handler already set to what shell wants it
+		 * (normally trapsig, but could be j_sigchld() or SIG_IGN)
+		 */
+		return;
+	}
+
+	/* todo: should we let user know signal is ignored? how? */
+	setsig(p, f, SS_RESTORE_CURR|SS_USER);
+}
+
+/*
+ * Called by c_print() when writing to a co-process to ensure SIGPIPE won't
+ * kill shell (unless user catches it and exits)
+ */
+int
+block_pipe(void)
+{
+	int restore_dfl = 0;
+	Trap *p = &sigtraps[SIGPIPE];
+
+	if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
+		setsig(p, SIG_IGN, SS_RESTORE_CURR);
+		if (p->flags & TF_ORIG_DFL)
+			restore_dfl = 1;
+	} else if (p->cursig == SIG_DFL) {
+		setsig(p, SIG_IGN, SS_RESTORE_CURR);
+		restore_dfl = 1; /* restore to SIG_DFL */
+	}
+	return (restore_dfl);
+}
+
+/* Called by c_print() to undo whatever block_pipe() did */
+void
+restore_pipe(int restore_dfl)
+{
+	if (restore_dfl)
+		setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
+}
+
+/*
+ * Set action for a signal. Action may not be set if original
+ * action was SIG_IGN, depending on the value of flags and FTALKING.
+ */
+int
+setsig(Trap *p, sig_t f, int flags)
+{
+	struct sigaction sigact;
+
+	if (p->signal == SIGEXIT_ || p->signal == SIGERR_)
+		return (1);
+
+	/*
+	 * First time setting this signal? If so, get and note the current
+	 * setting.
+	 */
+	if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
+		sigaction(p->signal, &Sigact_ign, &sigact);
+		p->flags |= sigact.sa_handler == SIG_IGN ?
+		    TF_ORIG_IGN : TF_ORIG_DFL;
+		p->cursig = SIG_IGN;
+	}
+
+	/*-
+	 * Generally, an ignored signal stays ignored, except if
+	 *	- the user of an interactive shell wants to change it
+	 *	- the shell wants for force a change
+	 */
+	if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE) &&
+	    (!(flags & SS_USER) || !Flag(FTALKING)))
+		return (0);
+
+	setexecsig(p, flags & SS_RESTORE_MASK);
+
+	/*
+	 * This is here 'cause there should be a way of clearing
+	 * shtraps, but don't know if this is a sane way of doing
+	 * it. At the moment, all users of shtrap are lifetime
+	 * users (SIGALRM, SIGCHLD, SIGWINCH).
+	 */
+	if (!(flags & SS_USER))
+		p->shtrap = (sig_t)NULL;
+	if (flags & SS_SHTRAP) {
+		p->shtrap = f;
+		f = trapsig;
+	}
+
+	if (p->cursig != f) {
+		p->cursig = f;
+		(void)sigemptyset(&sigact.sa_mask);
+		sigact.sa_flags = 0 /* interruptible */;
+		sigact.sa_handler = f;
+		sigaction(p->signal, &sigact, NULL);
+	}
+
+	return (1);
+}
+
+/* control what signal is set to before an exec() */
+void
+setexecsig(Trap *p, int restore)
+{
+	/* XXX debugging */
+	if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL)))
+		internal_errorf("setexecsig: unset signal %d(%s)",
+		    p->signal, p->name);
+
+	/* restore original value for exec'd kids */
+	p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
+	switch (restore & SS_RESTORE_MASK) {
+	case SS_RESTORE_CURR: /* leave things as they currently are */
+		break;
+	case SS_RESTORE_ORIG:
+		p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
+		break;
+	case SS_RESTORE_DFL:
+		p->flags |= TF_EXEC_DFL;
+		break;
+	case SS_RESTORE_IGN:
+		p->flags |= TF_EXEC_IGN;
+		break;
+	}
+}
diff --git a/mksh/src/jobs.c b/mksh/src/jobs.c
new file mode 100644
index 0000000..47326a1
--- /dev/null
+++ b/mksh/src/jobs.c
@@ -0,0 +1,1648 @@
+/*	$OpenBSD: jobs.c,v 1.38 2009/12/12 04:28:44 deraadt Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.69 2010/07/04 17:33:54 tg Exp $");
+
+#if HAVE_KILLPG
+#define mksh_killpg		killpg
+#else
+/* cross fingers and hope kill is killpg-endowed */
+#define mksh_killpg(p,s)	kill(-(p), (s))
+#endif
+
+/* Order important! */
+#define PRUNNING	0
+#define PEXITED		1
+#define PSIGNALLED	2
+#define PSTOPPED	3
+
+typedef struct proc	Proc;
+struct proc {
+	Proc *next;		/* next process in pipeline (if any) */
+	pid_t pid;		/* process id */
+	int state;
+	int status;		/* wait status */
+	char command[48];	/* process command string */
+};
+
+/* Notify/print flag - j_print() argument */
+#define JP_NONE		0	/* don't print anything */
+#define JP_SHORT	1	/* print signals processes were killed by */
+#define JP_MEDIUM	2	/* print [job-num] -/+ command */
+#define JP_LONG		3	/* print [job-num] -/+ pid command */
+#define JP_PGRP		4	/* print pgrp */
+
+/* put_job() flags */
+#define PJ_ON_FRONT	0	/* at very front */
+#define PJ_PAST_STOPPED	1	/* just past any stopped jobs */
+
+/* Job.flags values */
+#define JF_STARTED	0x001	/* set when all processes in job are started */
+#define JF_WAITING	0x002	/* set if j_waitj() is waiting on job */
+#define JF_W_ASYNCNOTIFY 0x004	/* set if waiting and async notification ok */
+#define JF_XXCOM	0x008	/* set for $(command) jobs */
+#define JF_FG		0x010	/* running in foreground (also has tty pgrp) */
+#define JF_SAVEDTTY	0x020	/* j->ttystate is valid */
+#define JF_CHANGED	0x040	/* process has changed state */
+#define JF_KNOWN	0x080	/* $! referenced */
+#define JF_ZOMBIE	0x100	/* known, unwaited process */
+#define JF_REMOVE	0x200	/* flagged for removal (j_jobs()/j_noityf()) */
+#define JF_USETTYMODE	0x400	/* tty mode saved if process exits normally */
+#define JF_SAVEDTTYPGRP	0x800	/* j->saved_ttypgrp is valid */
+
+typedef struct job Job;
+struct job {
+	Job *next;		/* next job in list */
+	Proc *proc_list;	/* process list */
+	Proc *last_proc;	/* last process in list */
+	struct timeval systime;	/* system time used by job */
+	struct timeval usrtime;	/* user time used by job */
+	pid_t pgrp;		/* process group of job */
+	pid_t ppid;		/* pid of process that forked job */
+	int job;		/* job number: %n */
+	int flags;		/* see JF_* */
+	volatile int state;	/* job state */
+	int status;		/* exit status of last process */
+	int32_t	age;		/* number of jobs started */
+	Coproc_id coproc_id;	/* 0 or id of coprocess output pipe */
+#ifndef MKSH_UNEMPLOYED
+	struct termios ttystate;/* saved tty state for stopped jobs */
+	pid_t saved_ttypgrp;	/* saved tty process group for stopped jobs */
+#endif
+};
+
+/* Flags for j_waitj() */
+#define JW_NONE		0x00
+#define JW_INTERRUPT	0x01	/* ^C will stop the wait */
+#define JW_ASYNCNOTIFY	0x02	/* asynchronous notification during wait ok */
+#define JW_STOPPEDWAIT	0x04	/* wait even if job stopped */
+
+/* Error codes for j_lookup() */
+#define JL_OK		0
+#define JL_NOSUCH	1	/* no such job */
+#define JL_AMBIG	2	/* %foo or %?foo is ambiguous */
+#define JL_INVALID	3	/* non-pid, non-% job id */
+
+static const char *const lookup_msgs[] = {
+	null,
+	"no such job",
+	"ambiguous",
+	"argument must be %job or process id",
+	NULL
+};
+
+static Job *job_list;		/* job list */
+static Job *last_job;
+static Job *async_job;
+static pid_t async_pid;
+
+static int nzombie;		/* # of zombies owned by this process */
+static int32_t njobs;		/* # of jobs started */
+
+#ifndef CHILD_MAX
+#define CHILD_MAX	25
+#endif
+
+/* held_sigchld is set if sigchld occurs before a job is completely started */
+static volatile sig_atomic_t held_sigchld;
+
+#ifndef MKSH_UNEMPLOYED
+static struct shf	*shl_j;
+static bool		ttypgrp_ok;	/* set if can use tty pgrps */
+static pid_t		restore_ttypgrp = -1;
+static int const	tt_sigs[] = { SIGTSTP, SIGTTIN, SIGTTOU };
+#endif
+
+static void		j_set_async(Job *);
+static void		j_startjob(Job *);
+static int		j_waitj(Job *, int, const char *);
+static void		j_sigchld(int);
+static void		j_print(Job *, int, struct shf *);
+static Job		*j_lookup(const char *, int *);
+static Job		*new_job(void);
+static Proc		*new_proc(void);
+static void		check_job(Job *);
+static void		put_job(Job *, int);
+static void		remove_job(Job *, const char *);
+static int		kill_job(Job *, int);
+
+/* initialise job control */
+void
+j_init(void)
+{
+#ifndef MKSH_UNEMPLOYED
+	bool mflagset = Flag(FMONITOR) != 127;
+
+	Flag(FMONITOR) = 0;
+#endif
+
+	(void)sigemptyset(&sm_default);
+	sigprocmask(SIG_SETMASK, &sm_default, NULL);
+
+	(void)sigemptyset(&sm_sigchld);
+	(void)sigaddset(&sm_sigchld, SIGCHLD);
+
+	setsig(&sigtraps[SIGCHLD], j_sigchld,
+	    SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+
+#ifndef MKSH_UNEMPLOYED
+	if (!mflagset && Flag(FTALKING))
+		Flag(FMONITOR) = 1;
+
+	/*
+	 * shl_j is used to do asynchronous notification (used in
+	 * an interrupt handler, so need a distinct shf)
+	 */
+	shl_j = shf_fdopen(2, SHF_WR, NULL);
+
+	if (Flag(FMONITOR) || Flag(FTALKING)) {
+		int i;
+
+		/*
+		 * the TF_SHELL_USES test is a kludge that lets us know if
+		 * if the signals have been changed by the shell.
+		 */
+		for (i = NELEM(tt_sigs); --i >= 0; ) {
+			sigtraps[tt_sigs[i]].flags |= TF_SHELL_USES;
+			/* j_change() sets this to SS_RESTORE_DFL if FMONITOR */
+			setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+			    SS_RESTORE_IGN|SS_FORCE);
+		}
+	}
+
+	/* j_change() calls tty_init() */
+	if (Flag(FMONITOR))
+		j_change();
+	else
+#endif
+	  if (Flag(FTALKING))
+		tty_init(true, true);
+}
+
+/* job cleanup before shell exit */
+void
+j_exit(void)
+{
+	/* kill stopped, and possibly running, jobs */
+	Job	*j;
+	int	killed = 0;
+
+	for (j = job_list; j != NULL; j = j->next) {
+		if (j->ppid == procpid &&
+		    (j->state == PSTOPPED ||
+		    (j->state == PRUNNING &&
+		    ((j->flags & JF_FG) ||
+		    (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid))))) {
+			killed = 1;
+			if (j->pgrp == 0)
+				kill_job(j, SIGHUP);
+			else
+				mksh_killpg(j->pgrp, SIGHUP);
+#ifndef MKSH_UNEMPLOYED
+			if (j->state == PSTOPPED) {
+				if (j->pgrp == 0)
+					kill_job(j, SIGCONT);
+				else
+					mksh_killpg(j->pgrp, SIGCONT);
+			}
+#endif
+		}
+	}
+	if (killed)
+		sleep(1);
+	j_notify();
+
+#ifndef MKSH_UNEMPLOYED
+	if (kshpid == procpid && restore_ttypgrp >= 0) {
+		/*
+		 * Need to restore the tty pgrp to what it was when the
+		 * shell started up, so that the process that started us
+		 * will be able to access the tty when we are done.
+		 * Also need to restore our process group in case we are
+		 * about to do an exec so that both our parent and the
+		 * process we are to become will be able to access the tty.
+		 */
+		tcsetpgrp(tty_fd, restore_ttypgrp);
+		setpgid(0, restore_ttypgrp);
+	}
+	if (Flag(FMONITOR)) {
+		Flag(FMONITOR) = 0;
+		j_change();
+	}
+#endif
+}
+
+#ifndef MKSH_UNEMPLOYED
+/* turn job control on or off according to Flag(FMONITOR) */
+void
+j_change(void)
+{
+	int i;
+
+	if (Flag(FMONITOR)) {
+		bool use_tty = Flag(FTALKING);
+
+		/* Don't call tcgetattr() 'til we own the tty process group */
+		if (use_tty)
+			tty_init(false, true);
+
+		/* no controlling tty, no SIGT* */
+		if ((ttypgrp_ok = use_tty && tty_fd >= 0 && tty_devtty)) {
+			setsig(&sigtraps[SIGTTIN], SIG_DFL,
+			    SS_RESTORE_ORIG|SS_FORCE);
+			/* wait to be given tty (POSIX.1, B.2, job control) */
+			while (1) {
+				pid_t ttypgrp;
+
+				if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) {
+					warningf(false,
+					    "j_init: tcgetpgrp() failed: %s",
+					    strerror(errno));
+					ttypgrp_ok = false;
+					break;
+				}
+				if (ttypgrp == kshpgrp)
+					break;
+				kill(0, SIGTTIN);
+			}
+		}
+		for (i = NELEM(tt_sigs); --i >= 0; )
+			setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+			    SS_RESTORE_DFL|SS_FORCE);
+		if (ttypgrp_ok && kshpgrp != kshpid) {
+			if (setpgid(0, kshpid) < 0) {
+				warningf(false,
+				    "j_init: setpgid() failed: %s",
+				    strerror(errno));
+				ttypgrp_ok = false;
+			} else {
+				if (tcsetpgrp(tty_fd, kshpid) < 0) {
+					warningf(false,
+					    "j_init: tcsetpgrp() failed: %s",
+					    strerror(errno));
+					ttypgrp_ok = false;
+				} else
+					restore_ttypgrp = kshpgrp;
+				kshpgrp = kshpid;
+			}
+		}
+		if (use_tty && !ttypgrp_ok)
+			warningf(false, "warning: won't have full job control");
+		if (tty_fd >= 0)
+			tcgetattr(tty_fd, &tty_state);
+	} else {
+		ttypgrp_ok = false;
+		if (Flag(FTALKING))
+			for (i = NELEM(tt_sigs); --i >= 0; )
+				setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+				    SS_RESTORE_IGN|SS_FORCE);
+		else
+			for (i = NELEM(tt_sigs); --i >= 0; ) {
+				if (sigtraps[tt_sigs[i]].flags &
+				    (TF_ORIG_IGN | TF_ORIG_DFL))
+					setsig(&sigtraps[tt_sigs[i]],
+					    (sigtraps[tt_sigs[i]].flags & TF_ORIG_IGN) ?
+					    SIG_IGN : SIG_DFL,
+					    SS_RESTORE_ORIG|SS_FORCE);
+			}
+		if (!Flag(FTALKING))
+			tty_close();
+	}
+}
+#endif
+
+/* execute tree in child subprocess */
+int
+exchild(struct op *t, int flags,
+    volatile int *xerrok,
+    /* used if XPCLOSE or XCCLOSE */ int close_fd)
+{
+	static Proc *last_proc;		/* for pipelines */
+
+	int rv = 0, forksleep;
+	sigset_t omask;
+	struct {
+		Proc *p;
+		Job *j;
+		pid_t cldpid;
+	} pi;
+
+	if (flags & XEXEC)
+		/*
+		 * Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND
+		 * (also done in another execute() below)
+		 */
+		return (execute(t, flags & (XEXEC | XERROK), xerrok));
+
+	/* no SIGCHLDs while messing with job and process lists */
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+	pi.p = new_proc();
+	pi.p->next = NULL;
+	pi.p->state = PRUNNING;
+	pi.p->status = 0;
+	pi.p->pid = 0;
+
+	/* link process into jobs list */
+	if (flags & XPIPEI) {
+		/* continuing with a pipe */
+		if (!last_job)
+			internal_errorf(
+			    "exchild: XPIPEI and no last_job - pid %d",
+			    (int)procpid);
+		pi.j = last_job;
+		if (last_proc)
+			last_proc->next = pi.p;
+		last_proc = pi.p;
+	} else {
+		pi.j = new_job(); /* fills in pi.j->job */
+		/*
+		 * we don't consider XXCOMs foreground since they don't get
+		 * tty process group and we don't save or restore tty modes.
+		 */
+		pi.j->flags = (flags & XXCOM) ? JF_XXCOM :
+		    ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE));
+		timerclear(&pi.j->usrtime);
+		timerclear(&pi.j->systime);
+		pi.j->state = PRUNNING;
+		pi.j->pgrp = 0;
+		pi.j->ppid = procpid;
+		pi.j->age = ++njobs;
+		pi.j->proc_list = pi.p;
+		pi.j->coproc_id = 0;
+		last_job = pi.j;
+		last_proc = pi.p;
+		put_job(pi.j, PJ_PAST_STOPPED);
+	}
+
+	snptreef(pi.p->command, sizeof(pi.p->command), "%T", t);
+
+	/* create child process */
+	forksleep = 1;
+	while ((pi.cldpid = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
+		if (intrsig)	 /* allow user to ^C out... */
+			break;
+		sleep(forksleep);
+		forksleep <<= 1;
+	}
+	if (pi.cldpid < 0) {
+		kill_job(pi.j, SIGKILL);
+		remove_job(pi.j, "fork failed");
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		errorf("cannot fork - try again");
+	}
+	pi.p->pid = pi.cldpid ? pi.cldpid : (procpid = getpid());
+
+	/*
+	 * ensure next child gets a (slightly) different $RANDOM sequence
+	 * from its parent process and other child processes
+	 */
+	change_random(&pi, sizeof(pi));
+
+#ifndef MKSH_UNEMPLOYED
+	/* job control set up */
+	if (Flag(FMONITOR) && !(flags&XXCOM)) {
+		int	dotty = 0;
+		if (pi.j->pgrp == 0) {	/* First process */
+			pi.j->pgrp = pi.p->pid;
+			dotty = 1;
+		}
+
+		/* set pgrp in both parent and child to deal with race
+		 * condition
+		 */
+		setpgid(pi.p->pid, pi.j->pgrp);
+		if (ttypgrp_ok && dotty && !(flags & XBGND))
+			tcsetpgrp(tty_fd, pi.j->pgrp);
+	}
+#endif
+
+	/* used to close pipe input fd */
+	if (close_fd >= 0 && (((flags & XPCLOSE) && pi.cldpid) ||
+	    ((flags & XCCLOSE) && !pi.cldpid)))
+		close(close_fd);
+	if (!pi.cldpid) {
+		/* child */
+
+		/* Do this before restoring signal */
+		if (flags & XCOPROC)
+			coproc_cleanup(false);
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		cleanup_parents_env();
+#ifndef MKSH_UNEMPLOYED
+		/* If FMONITOR or FTALKING is set, these signals are ignored,
+		 * if neither FMONITOR nor FTALKING are set, the signals have
+		 * their inherited values.
+		 */
+		if (Flag(FMONITOR) && !(flags & XXCOM)) {
+			for (forksleep = NELEM(tt_sigs); --forksleep >= 0; )
+				setsig(&sigtraps[tt_sigs[forksleep]], SIG_DFL,
+				    SS_RESTORE_DFL|SS_FORCE);
+		}
+#endif
+#if HAVE_NICE
+		if (Flag(FBGNICE) && (flags & XBGND))
+			(void)nice(4);
+#endif
+		if ((flags & XBGND)
+#ifndef MKSH_UNEMPLOYED
+		    && !Flag(FMONITOR)
+#endif
+		    ) {
+			setsig(&sigtraps[SIGINT], SIG_IGN,
+			    SS_RESTORE_IGN|SS_FORCE);
+			setsig(&sigtraps[SIGQUIT], SIG_IGN,
+			    SS_RESTORE_IGN|SS_FORCE);
+			if ((!(flags & (XPIPEI | XCOPROC))) &&
+			    ((forksleep = open("/dev/null", 0)) > 0)) {
+				(void)ksh_dup2(forksleep, 0, true);
+				close(forksleep);
+			}
+		}
+		remove_job(pi.j, "child");	/* in case of $(jobs) command */
+		nzombie = 0;
+#ifndef MKSH_UNEMPLOYED
+		ttypgrp_ok = false;
+		Flag(FMONITOR) = 0;
+#endif
+		Flag(FTALKING) = 0;
+		tty_close();
+		cleartraps();
+		/* no return */
+		execute(t, (flags & XERROK) | XEXEC, NULL);
+#ifndef MKSH_SMALL
+		if (t->type == TPIPE)
+			unwind(LLEAVE);
+		internal_warningf("exchild: execute() returned");
+		fptreef(shl_out, 2, "exchild: tried to execute {\n%T\n}\n", t);
+		shf_flush(shl_out);
+#endif
+		unwind(LLEAVE);
+		/* NOTREACHED */
+	}
+
+	/* shell (parent) stuff */
+	if (!(flags & XPIPEO)) {	/* last process in a job */
+		j_startjob(pi.j);
+		if (flags & XCOPROC) {
+			pi.j->coproc_id = coproc.id;
+			/* n jobs using co-process output */
+			coproc.njobs++;
+			/* j using co-process input */
+			coproc.job = (void *)pi.j;
+		}
+		if (flags & XBGND) {
+			j_set_async(pi.j);
+			if (Flag(FTALKING)) {
+				shf_fprintf(shl_out, "[%d]", pi.j->job);
+				for (pi.p = pi.j->proc_list; pi.p;
+				    pi.p = pi.p->next)
+					shf_fprintf(shl_out, " %d",
+					    (int)pi.p->pid);
+				shf_putchar('\n', shl_out);
+				shf_flush(shl_out);
+			}
+		} else
+			rv = j_waitj(pi.j, JW_NONE, "jw:last proc");
+	}
+
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+
+	return (rv);
+}
+
+/* start the last job: only used for $(command) jobs */
+void
+startlast(void)
+{
+	sigset_t omask;
+
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+	if (last_job) { /* no need to report error - waitlast() will do it */
+		/* ensure it isn't removed by check_job() */
+		last_job->flags |= JF_WAITING;
+		j_startjob(last_job);
+	}
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+}
+
+/* wait for last job: only used for $(command) jobs */
+int
+waitlast(void)
+{
+	int	rv;
+	Job	*j;
+	sigset_t omask;
+
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+	j = last_job;
+	if (!j || !(j->flags & JF_STARTED)) {
+		if (!j)
+			warningf(true, "waitlast: no last job");
+		else
+			internal_warningf("waitlast: not started");
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		return (125); /* not so arbitrary, non-zero value */
+	}
+
+	rv = j_waitj(j, JW_NONE, "jw:waitlast");
+
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+
+	return (rv);
+}
+
+/* wait for child, interruptable. */
+int
+waitfor(const char *cp, int *sigp)
+{
+	int	rv;
+	Job	*j;
+	int	ecode;
+	int	flags = JW_INTERRUPT|JW_ASYNCNOTIFY;
+	sigset_t omask;
+
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+	*sigp = 0;
+
+	if (cp == NULL) {
+		/*
+		 * wait for an unspecified job - always returns 0, so
+		 * don't have to worry about exited/signaled jobs
+		 */
+		for (j = job_list; j; j = j->next)
+			/* AT&T ksh will wait for stopped jobs - we don't */
+			if (j->ppid == procpid && j->state == PRUNNING)
+				break;
+		if (!j) {
+			sigprocmask(SIG_SETMASK, &omask, NULL);
+			return (-1);
+		}
+	} else if ((j = j_lookup(cp, &ecode))) {
+		/* don't report normal job completion */
+		flags &= ~JW_ASYNCNOTIFY;
+		if (j->ppid != procpid) {
+			sigprocmask(SIG_SETMASK, &omask, NULL);
+			return (-1);
+		}
+	} else {
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		if (ecode != JL_NOSUCH)
+			bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+		return (-1);
+	}
+
+	/* AT&T ksh will wait for stopped jobs - we don't */
+	rv = j_waitj(j, flags, "jw:waitfor");
+
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+
+	if (rv < 0) /* we were interrupted */
+		*sigp = 128 + -rv;
+
+	return (rv);
+}
+
+/* kill (built-in) a job */
+int
+j_kill(const char *cp, int sig)
+{
+	Job	*j;
+	int	rv = 0;
+	int	ecode;
+	sigset_t omask;
+
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+	if ((j = j_lookup(cp, &ecode)) == NULL) {
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+		return (1);
+	}
+
+	if (j->pgrp == 0) {	/* started when !Flag(FMONITOR) */
+		if (kill_job(j, sig) < 0) {
+			bi_errorf("%s: %s", cp, strerror(errno));
+			rv = 1;
+		}
+	} else {
+#ifndef MKSH_UNEMPLOYED
+		if (j->state == PSTOPPED && (sig == SIGTERM || sig == SIGHUP))
+			mksh_killpg(j->pgrp, SIGCONT);
+#endif
+		if (mksh_killpg(j->pgrp, sig) < 0) {
+			bi_errorf("%s: %s", cp, strerror(errno));
+			rv = 1;
+		}
+	}
+
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+
+	return (rv);
+}
+
+#ifndef MKSH_UNEMPLOYED
+/* fg and bg built-ins: called only if Flag(FMONITOR) set */
+int
+j_resume(const char *cp, int bg)
+{
+	Job	*j;
+	Proc	*p;
+	int	ecode;
+	int	running;
+	int	rv = 0;
+	sigset_t omask;
+
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+	if ((j = j_lookup(cp, &ecode)) == NULL) {
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+		return (1);
+	}
+
+	if (j->pgrp == 0) {
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		bi_errorf("job not job-controlled");
+		return (1);
+	}
+
+	if (bg)
+		shprintf("[%d] ", j->job);
+
+	running = 0;
+	for (p = j->proc_list; p != NULL; p = p->next) {
+		if (p->state == PSTOPPED) {
+			p->state = PRUNNING;
+			p->status = 0;
+			running = 1;
+		}
+		shf_puts(p->command, shl_stdout);
+		if (p->next)
+			shf_puts("| ", shl_stdout);
+	}
+	shf_putc('\n', shl_stdout);
+	shf_flush(shl_stdout);
+	if (running)
+		j->state = PRUNNING;
+
+	put_job(j, PJ_PAST_STOPPED);
+	if (bg)
+		j_set_async(j);
+	else {
+		/* attach tty to job */
+		if (j->state == PRUNNING) {
+			if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
+				tcsetattr(tty_fd, TCSADRAIN, &j->ttystate);
+			/* See comment in j_waitj regarding saved_ttypgrp. */
+			if (ttypgrp_ok &&
+			    tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ?
+			    j->saved_ttypgrp : j->pgrp) < 0) {
+				rv = errno;
+				if (j->flags & JF_SAVEDTTY)
+					tcsetattr(tty_fd, TCSADRAIN, &tty_state);
+				sigprocmask(SIG_SETMASK, &omask,
+				    NULL);
+				bi_errorf("1st tcsetpgrp(%d, %d) failed: %s",
+				    tty_fd,
+				    (int)((j->flags & JF_SAVEDTTYPGRP) ?
+				    j->saved_ttypgrp : j->pgrp),
+				    strerror(rv));
+				return (1);
+			}
+		}
+		j->flags |= JF_FG;
+		j->flags &= ~JF_KNOWN;
+		if (j == async_job)
+			async_job = NULL;
+	}
+
+	if (j->state == PRUNNING && mksh_killpg(j->pgrp, SIGCONT) < 0) {
+		int err = errno;
+
+		if (!bg) {
+			j->flags &= ~JF_FG;
+			if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
+				tcsetattr(tty_fd, TCSADRAIN, &tty_state);
+			if (ttypgrp_ok && tcsetpgrp(tty_fd, kshpgrp) < 0)
+				warningf(true,
+				    "fg: 2nd tcsetpgrp(%d, %ld) failed: %s",
+				    tty_fd, (long)kshpgrp, strerror(errno));
+		}
+		sigprocmask(SIG_SETMASK, &omask, NULL);
+		bi_errorf("cannot continue job %s: %s",
+		    cp, strerror(err));
+		return (1);
+	}
+	if (!bg) {
+		if (ttypgrp_ok) {
+			j->flags &= ~(JF_SAVEDTTY | JF_SAVEDTTYPGRP);
+		}
+		rv = j_waitj(j, JW_NONE, "jw:resume");
+	}
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+	return (rv);
+}
+#endif
+
+/* are there any running or stopped jobs ? */
+int
+j_stopped_running(void)
+{
+	Job	*j;
+	int	which = 0;
+
+	for (j = job_list; j != NULL; j = j->next) {
+#ifndef MKSH_UNEMPLOYED
+		if (j->ppid == procpid && j->state == PSTOPPED)
+			which |= 1;
+#endif
+		if (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid &&
+		    j->ppid == procpid && j->state == PRUNNING)
+			which |= 2;
+	}
+	if (which) {
+		shellf("You have %s%s%s jobs\n",
+		    which & 1 ? "stopped" : "",
+		    which == 3 ? " and " : "",
+		    which & 2 ? "running" : "");
+		return (1);
+	}
+
+	return (0);
+}
+
+int
+j_njobs(void)
+{
+	Job *j;
+	int nj = 0;
+	sigset_t omask;
+
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+	for (j = job_list; j; j = j->next)
+		nj++;
+
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+	return (nj);
+}
+
+
+/* list jobs for jobs built-in */
+int
+j_jobs(const char *cp, int slp,
+    int nflag)		/* 0: short, 1: long, 2: pgrp */
+{
+	Job	*j, *tmp;
+	int	how;
+	int	zflag = 0;
+	sigset_t omask;
+
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+	if (nflag < 0) { /* kludge: print zombies */
+		nflag = 0;
+		zflag = 1;
+	}
+	if (cp) {
+		int	ecode;
+
+		if ((j = j_lookup(cp, &ecode)) == NULL) {
+			sigprocmask(SIG_SETMASK, &omask, NULL);
+			bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+			return (1);
+		}
+	} else
+		j = job_list;
+	how = slp == 0 ? JP_MEDIUM : (slp == 1 ? JP_LONG : JP_PGRP);
+	for (; j; j = j->next) {
+		if ((!(j->flags & JF_ZOMBIE) || zflag) &&
+		    (!nflag || (j->flags & JF_CHANGED))) {
+			j_print(j, how, shl_stdout);
+			if (j->state == PEXITED || j->state == PSIGNALLED)
+				j->flags |= JF_REMOVE;
+		}
+		if (cp)
+			break;
+	}
+	/* Remove jobs after printing so there won't be multiple + or - jobs */
+	for (j = job_list; j; j = tmp) {
+		tmp = j->next;
+		if (j->flags & JF_REMOVE)
+			remove_job(j, "jobs");
+	}
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+	return (0);
+}
+
+/* list jobs for top-level notification */
+void
+j_notify(void)
+{
+	Job	*j, *tmp;
+	sigset_t omask;
+
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+	for (j = job_list; j; j = j->next) {
+#ifndef MKSH_UNEMPLOYED
+		if (Flag(FMONITOR) && (j->flags & JF_CHANGED))
+			j_print(j, JP_MEDIUM, shl_out);
+#endif
+		/* Remove job after doing reports so there aren't
+		 * multiple +/- jobs.
+		 */
+		if (j->state == PEXITED || j->state == PSIGNALLED)
+			j->flags |= JF_REMOVE;
+	}
+	for (j = job_list; j; j = tmp) {
+		tmp = j->next;
+		if (j->flags & JF_REMOVE)
+			remove_job(j, "notify");
+	}
+	shf_flush(shl_out);
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+}
+
+/* Return pid of last process in last asynchronous job */
+pid_t
+j_async(void)
+{
+	sigset_t omask;
+
+	sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+	if (async_job)
+		async_job->flags |= JF_KNOWN;
+
+	sigprocmask(SIG_SETMASK, &omask, NULL);
+
+	return (async_pid);
+}
+
+/*
+ * Make j the last async process
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_set_async(Job *j)
+{
+	Job	*jl, *oldest;
+
+	if (async_job && (async_job->flags & (JF_KNOWN|JF_ZOMBIE)) == JF_ZOMBIE)
+		remove_job(async_job, "async");
+	if (!(j->flags & JF_STARTED)) {
+		internal_warningf("j_async: job not started");
+		return;
+	}
+	async_job = j;
+	async_pid = j->last_proc->pid;
+	while (nzombie > CHILD_MAX) {
+		oldest = NULL;
+		for (jl = job_list; jl; jl = jl->next)
+			if (jl != async_job && (jl->flags & JF_ZOMBIE) &&
+			    (!oldest || jl->age < oldest->age))
+				oldest = jl;
+		if (!oldest) {
+			/* XXX debugging */
+			if (!(async_job->flags & JF_ZOMBIE) || nzombie != 1) {
+				internal_warningf("j_async: bad nzombie (%d)",
+				    nzombie);
+				nzombie = 0;
+			}
+			break;
+		}
+		remove_job(oldest, "zombie");
+	}
+}
+
+/*
+ * Start a job: set STARTED, check for held signals and set j->last_proc
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_startjob(Job *j)
+{
+	Proc	*p;
+
+	j->flags |= JF_STARTED;
+	for (p = j->proc_list; p->next; p = p->next)
+		;
+	j->last_proc = p;
+
+	if (held_sigchld) {
+		held_sigchld = 0;
+		/* Don't call j_sigchld() as it may remove job... */
+		kill(procpid, SIGCHLD);
+	}
+}
+
+/*
+ * wait for job to complete or change state
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static int
+j_waitj(Job *j,
+    int flags,			/* see JW_* */
+    const char *where)
+{
+	int	rv;
+
+	/*
+	 * No auto-notify on the job we are waiting on.
+	 */
+	j->flags |= JF_WAITING;
+	if (flags & JW_ASYNCNOTIFY)
+		j->flags |= JF_W_ASYNCNOTIFY;
+
+#ifndef MKSH_UNEMPLOYED
+	if (!Flag(FMONITOR))
+#endif
+		flags |= JW_STOPPEDWAIT;
+
+	while (j->state == PRUNNING ||
+	    ((flags & JW_STOPPEDWAIT) && j->state == PSTOPPED)) {
+		sigsuspend(&sm_default);
+		if (fatal_trap) {
+			int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY);
+			j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+			runtraps(TF_FATAL);
+			j->flags |= oldf; /* not reached... */
+		}
+		if ((flags & JW_INTERRUPT) && (rv = trap_pending())) {
+			j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+			return (-rv);
+		}
+	}
+	j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+
+	if (j->flags & JF_FG) {
+		j->flags &= ~JF_FG;
+#ifndef MKSH_UNEMPLOYED
+		if (Flag(FMONITOR) && ttypgrp_ok && j->pgrp) {
+			/*
+			 * Save the tty's current pgrp so it can be restored
+			 * when the job is foregrounded. This is to
+			 * deal with things like the GNU su which does
+			 * a fork/exec instead of an exec (the fork means
+			 * the execed shell gets a different pid from its
+			 * pgrp, so naturally it sets its pgrp and gets hosed
+			 * when it gets foregrounded by the parent shell which
+			 * has restored the tty's pgrp to that of the su
+			 * process).
+			 */
+			if (j->state == PSTOPPED &&
+			    (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0)
+				j->flags |= JF_SAVEDTTYPGRP;
+			if (tcsetpgrp(tty_fd, kshpgrp) < 0)
+				warningf(true,
+				    "j_waitj: tcsetpgrp(%d, %ld) failed: %s",
+				    tty_fd, (long)kshpgrp, strerror(errno));
+			if (j->state == PSTOPPED) {
+				j->flags |= JF_SAVEDTTY;
+				tcgetattr(tty_fd, &j->ttystate);
+			}
+		}
+#endif
+		if (tty_fd >= 0) {
+			/*
+			 * Only restore tty settings if job was originally
+			 * started in the foreground. Problems can be
+			 * caused by things like 'more foobar &' which will
+			 * typically get and save the shell's vi/emacs tty
+			 * settings before setting up the tty for itself;
+			 * when more exits, it restores the 'original'
+			 * settings, and things go down hill from there...
+			 */
+			if (j->state == PEXITED && j->status == 0 &&
+			    (j->flags & JF_USETTYMODE)) {
+				tcgetattr(tty_fd, &tty_state);
+			} else {
+				tcsetattr(tty_fd, TCSADRAIN, &tty_state);
+				/*-
+				 * Don't use tty mode if job is stopped and
+				 * later restarted and exits. Consider
+				 * the sequence:
+				 *	vi foo (stopped)
+				 *	...
+				 *	stty something
+				 *	...
+				 *	fg (vi; ZZ)
+				 * mode should be that of the stty, not what
+				 * was before the vi started.
+				 */
+				if (j->state == PSTOPPED)
+					j->flags &= ~JF_USETTYMODE;
+			}
+		}
+#ifndef MKSH_UNEMPLOYED
+		/*
+		 * If it looks like user hit ^C to kill a job, pretend we got
+		 * one too to break out of for loops, etc. (AT&T ksh does this
+		 * even when not monitoring, but this doesn't make sense since
+		 * a tty generated ^C goes to the whole process group)
+		 */
+		{
+			int status;
+
+			status = j->last_proc->status;
+			if (Flag(FMONITOR) && j->state == PSIGNALLED &&
+			    WIFSIGNALED(status) &&
+			    (sigtraps[WTERMSIG(status)].flags & TF_TTY_INTR))
+				trapsig(WTERMSIG(status));
+		}
+#endif
+	}
+
+	j_usrtime = j->usrtime;
+	j_systime = j->systime;
+	rv = j->status;
+
+	if (!(flags & JW_ASYNCNOTIFY)
+#ifndef MKSH_UNEMPLOYED
+	    && (!Flag(FMONITOR) || j->state != PSTOPPED)
+#endif
+	    ) {
+		j_print(j, JP_SHORT, shl_out);
+		shf_flush(shl_out);
+	}
+	if (j->state != PSTOPPED
+#ifndef MKSH_UNEMPLOYED
+	    && (!Flag(FMONITOR) || !(flags & JW_ASYNCNOTIFY))
+#endif
+	    )
+		remove_job(j, where);
+
+	return (rv);
+}
+
+/*
+ * SIGCHLD handler to reap children and update job states
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+/* ARGSUSED */
+static void
+j_sigchld(int sig MKSH_A_UNUSED)
+{
+	/* this runs inside interrupt context, with errno saved */
+
+	Job *j;
+	Proc *p = NULL;
+	pid_t pid;
+	int status;
+	struct rusage ru0, ru1;
+
+	/*
+	 * Don't wait for any processes if a job is partially started.
+	 * This is so we don't do away with the process group leader
+	 * before all the processes in a pipe line are started (so the
+	 * setpgid() won't fail)
+	 */
+	for (j = job_list; j; j = j->next)
+		if (j->ppid == procpid && !(j->flags & JF_STARTED)) {
+			held_sigchld = 1;
+			return;
+		}
+
+	getrusage(RUSAGE_CHILDREN, &ru0);
+	do {
+		pid = waitpid(-1, &status, (WNOHANG|WUNTRACED));
+
+		/*
+		 * return if this would block (0) or no children
+		 * or interrupted (-1)
+		 */
+		if (pid <= 0)
+			return;
+
+		getrusage(RUSAGE_CHILDREN, &ru1);
+
+		/* find job and process structures for this pid */
+		for (j = job_list; j != NULL; j = j->next)
+			for (p = j->proc_list; p != NULL; p = p->next)
+				if (p->pid == pid)
+					goto found;
+ found:
+		if (j == NULL) {
+			/* Can occur if process has kids, then execs shell
+			warningf(true, "bad process waited for (pid = %d)",
+				pid);
+			 */
+			ru0 = ru1;
+			continue;
+		}
+
+		timeradd(&j->usrtime, &ru1.ru_utime, &j->usrtime);
+		timersub(&j->usrtime, &ru0.ru_utime, &j->usrtime);
+		timeradd(&j->systime, &ru1.ru_stime, &j->systime);
+		timersub(&j->systime, &ru0.ru_stime, &j->systime);
+		ru0 = ru1;
+		p->status = status;
+#ifndef MKSH_UNEMPLOYED
+		if (WIFSTOPPED(status))
+			p->state = PSTOPPED;
+		else
+#endif
+		  if (WIFSIGNALED(status))
+			p->state = PSIGNALLED;
+		else
+			p->state = PEXITED;
+
+		check_job(j);	/* check to see if entire job is done */
+	} while (1);
+}
+
+/*
+ * Called only when a process in j has exited/stopped (ie, called only
+ * from j_sigchld()). If no processes are running, the job status
+ * and state are updated, asynchronous job notification is done and,
+ * if unneeded, the job is removed.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+check_job(Job *j)
+{
+	int	jstate;
+	Proc	*p;
+
+	/* XXX debugging (nasty - interrupt routine using shl_out) */
+	if (!(j->flags & JF_STARTED)) {
+		internal_warningf("check_job: job started (flags 0x%x)",
+		    j->flags);
+		return;
+	}
+
+	jstate = PRUNNING;
+	for (p=j->proc_list; p != NULL; p = p->next) {
+		if (p->state == PRUNNING)
+			return;	/* some processes still running */
+		if (p->state > jstate)
+			jstate = p->state;
+	}
+	j->state = jstate;
+
+	switch (j->last_proc->state) {
+	case PEXITED:
+		j->status = WEXITSTATUS(j->last_proc->status);
+		break;
+	case PSIGNALLED:
+		j->status = 128 + WTERMSIG(j->last_proc->status);
+		break;
+	default:
+		j->status = 0;
+		break;
+	}
+
+	/*
+	 * Note when co-process dies: can't be done in j_wait() nor
+	 * remove_job() since neither may be called for non-interactive
+	 * shells.
+	 */
+	if (j->state == PEXITED || j->state == PSIGNALLED) {
+		/*
+		 * No need to keep co-process input any more
+		 * (at least, this is what ksh93d thinks)
+		 */
+		if (coproc.job == j) {
+			coproc.job = NULL;
+			/*
+			 * XXX would be nice to get the closes out of here
+			 * so they aren't done in the signal handler.
+			 * Would mean a check in coproc_getfd() to
+			 * do "if job == 0 && write >= 0, close write".
+			 */
+			coproc_write_close(coproc.write);
+		}
+		/* Do we need to keep the output? */
+		if (j->coproc_id && j->coproc_id == coproc.id &&
+		    --coproc.njobs == 0)
+			coproc_readw_close(coproc.read);
+	}
+
+	j->flags |= JF_CHANGED;
+#ifndef MKSH_UNEMPLOYED
+	if (Flag(FMONITOR) && !(j->flags & JF_XXCOM)) {
+		/*
+		 * Only put stopped jobs at the front to avoid confusing
+		 * the user (don't want finished jobs effecting %+ or %-)
+		 */
+		if (j->state == PSTOPPED)
+			put_job(j, PJ_ON_FRONT);
+		if (Flag(FNOTIFY) &&
+		    (j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY)) != JF_WAITING) {
+			/* Look for the real file descriptor 2 */
+			{
+				struct env *ep;
+				int fd = 2;
+
+				for (ep = e; ep; ep = ep->oenv)
+					if (ep->savefd && ep->savefd[2])
+						fd = ep->savefd[2];
+				shf_reopen(fd, SHF_WR, shl_j);
+			}
+			/*
+			 * Can't call j_notify() as it removes jobs. The job
+			 * must stay in the job list as j_waitj() may be
+			 * running with this job.
+			 */
+			j_print(j, JP_MEDIUM, shl_j);
+			shf_flush(shl_j);
+			if (!(j->flags & JF_WAITING) && j->state != PSTOPPED)
+				remove_job(j, "notify");
+		}
+	}
+#endif
+	if (
+#ifndef MKSH_UNEMPLOYED
+	    !Flag(FMONITOR) &&
+#endif
+	    !(j->flags & (JF_WAITING|JF_FG)) &&
+	    j->state != PSTOPPED) {
+		if (j == async_job || (j->flags & JF_KNOWN)) {
+			j->flags |= JF_ZOMBIE;
+			j->job = -1;
+			nzombie++;
+		} else
+			remove_job(j, "checkjob");
+	}
+}
+
+/*
+ * Print job status in either short, medium or long format.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_print(Job *j, int how, struct shf *shf)
+{
+	Proc	*p;
+	int	state;
+	int	status;
+	int	coredumped;
+	char	jobchar = ' ';
+	char	buf[64];
+	const char *filler;
+	int	output = 0;
+
+	if (how == JP_PGRP) {
+		/*
+		 * POSIX doesn't say what to do it there is no process
+		 * group leader (ie, !FMONITOR). We arbitrarily return
+		 * last pid (which is what $! returns).
+		 */
+		shf_fprintf(shf, "%d\n", (int)(j->pgrp ? j->pgrp :
+		    (j->last_proc ? j->last_proc->pid : 0)));
+		return;
+	}
+	j->flags &= ~JF_CHANGED;
+	filler = j->job > 10 ? "\n       " : "\n      ";
+	if (j == job_list)
+		jobchar = '+';
+	else if (j == job_list->next)
+		jobchar = '-';
+
+	for (p = j->proc_list; p != NULL;) {
+		coredumped = 0;
+		switch (p->state) {
+		case PRUNNING:
+			memcpy(buf, "Running", 8);
+			break;
+		case PSTOPPED:
+			strlcpy(buf, sigtraps[WSTOPSIG(p->status)].mess,
+			    sizeof(buf));
+			break;
+		case PEXITED:
+			if (how == JP_SHORT)
+				buf[0] = '\0';
+			else if (WEXITSTATUS(p->status) == 0)
+				memcpy(buf, "Done", 5);
+			else
+				shf_snprintf(buf, sizeof(buf), "Done (%d)",
+				    WEXITSTATUS(p->status));
+			break;
+		case PSIGNALLED:
+#ifdef WCOREDUMP
+			if (WCOREDUMP(p->status))
+				coredumped = 1;
+#endif
+			/*
+			 * kludge for not reporting 'normal termination
+			 * signals' (i.e. SIGINT, SIGPIPE)
+			 */
+			if (how == JP_SHORT && !coredumped &&
+			    (WTERMSIG(p->status) == SIGINT ||
+			    WTERMSIG(p->status) == SIGPIPE)) {
+				buf[0] = '\0';
+			} else
+				strlcpy(buf, sigtraps[WTERMSIG(p->status)].mess,
+				    sizeof(buf));
+			break;
+		}
+
+		if (how != JP_SHORT) {
+			if (p == j->proc_list)
+				shf_fprintf(shf, "[%d] %c ", j->job, jobchar);
+			else
+				shf_fprintf(shf, "%s", filler);
+		}
+
+		if (how == JP_LONG)
+			shf_fprintf(shf, "%5d ", (int)p->pid);
+
+		if (how == JP_SHORT) {
+			if (buf[0]) {
+				output = 1;
+				shf_fprintf(shf, "%s%s ",
+				    buf, coredumped ? " (core dumped)" : null);
+			}
+		} else {
+			output = 1;
+			shf_fprintf(shf, "%-20s %s%s%s", buf, p->command,
+			    p->next ? "|" : null,
+			    coredumped ? " (core dumped)" : null);
+		}
+
+		state = p->state;
+		status = p->status;
+		p = p->next;
+		while (p && p->state == state && p->status == status) {
+			if (how == JP_LONG)
+				shf_fprintf(shf, "%s%5d %-20s %s%s", filler,
+				    (int)p->pid, " ", p->command,
+				    p->next ? "|" : null);
+			else if (how == JP_MEDIUM)
+				shf_fprintf(shf, " %s%s", p->command,
+				    p->next ? "|" : null);
+			p = p->next;
+		}
+	}
+	if (output)
+		shf_putc('\n', shf);
+}
+
+/*
+ * Convert % sequence to job
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Job *
+j_lookup(const char *cp, int *ecodep)
+{
+	Job		*j, *last_match;
+	Proc		*p;
+	int		len, job = 0;
+
+	if (ksh_isdigit(*cp)) {
+		getn(cp, &job);
+		/* Look for last_proc->pid (what $! returns) first... */
+		for (j = job_list; j != NULL; j = j->next)
+			if (j->last_proc && j->last_proc->pid == job)
+				return (j);
+		/*
+		 * ...then look for process group (this is non-POSIX,
+		 * but should not break anything
+		 */
+		for (j = job_list; j != NULL; j = j->next)
+			if (j->pgrp && j->pgrp == job)
+				return (j);
+		if (ecodep)
+			*ecodep = JL_NOSUCH;
+		return (NULL);
+	}
+	if (*cp != '%') {
+		if (ecodep)
+			*ecodep = JL_INVALID;
+		return (NULL);
+	}
+	switch (*++cp) {
+	case '\0': /* non-standard */
+	case '+':
+	case '%':
+		if (job_list != NULL)
+			return (job_list);
+		break;
+
+	case '-':
+		if (job_list != NULL && job_list->next)
+			return (job_list->next);
+		break;
+
+	case '0': case '1': case '2': case '3': case '4':
+	case '5': case '6': case '7': case '8': case '9':
+		getn(cp, &job);
+		for (j = job_list; j != NULL; j = j->next)
+			if (j->job == job)
+				return (j);
+		break;
+
+	case '?':		/* %?string */
+		last_match = NULL;
+		for (j = job_list; j != NULL; j = j->next)
+			for (p = j->proc_list; p != NULL; p = p->next)
+				if (strstr(p->command, cp+1) != NULL) {
+					if (last_match) {
+						if (ecodep)
+							*ecodep = JL_AMBIG;
+						return (NULL);
+					}
+					last_match = j;
+				}
+		if (last_match)
+			return (last_match);
+		break;
+
+	default:		/* %string */
+		len = strlen(cp);
+		last_match = NULL;
+		for (j = job_list; j != NULL; j = j->next)
+			if (strncmp(cp, j->proc_list->command, len) == 0) {
+				if (last_match) {
+					if (ecodep)
+						*ecodep = JL_AMBIG;
+					return (NULL);
+				}
+				last_match = j;
+			}
+		if (last_match)
+			return (last_match);
+		break;
+	}
+	if (ecodep)
+		*ecodep = JL_NOSUCH;
+	return (NULL);
+}
+
+static Job	*free_jobs;
+static Proc	*free_procs;
+
+/*
+ * allocate a new job and fill in the job number.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Job *
+new_job(void)
+{
+	int	i;
+	Job	*newj, *j;
+
+	if (free_jobs != NULL) {
+		newj = free_jobs;
+		free_jobs = free_jobs->next;
+	} else
+		newj = alloc(sizeof(Job), APERM);
+
+	/* brute force method */
+	for (i = 1; ; i++) {
+		for (j = job_list; j && j->job != i; j = j->next)
+			;
+		if (j == NULL)
+			break;
+	}
+	newj->job = i;
+
+	return (newj);
+}
+
+/*
+ * Allocate new process struct
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Proc *
+new_proc(void)
+{
+	Proc	*p;
+
+	if (free_procs != NULL) {
+		p = free_procs;
+		free_procs = free_procs->next;
+	} else
+		p = alloc(sizeof(Proc), APERM);
+
+	return (p);
+}
+
+/*
+ * Take job out of job_list and put old structures into free list.
+ * Keeps nzombies, last_job and async_job up to date.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+remove_job(Job *j, const char *where)
+{
+	Proc	*p, *tmp;
+	Job	**prev, *curr;
+
+	prev = &job_list;
+	curr = *prev;
+	for (; curr != NULL && curr != j; prev = &curr->next, curr = *prev)
+		;
+	if (curr != j) {
+		internal_warningf("remove_job: job not found (%s)", where);
+		return;
+	}
+	*prev = curr->next;
+
+	/* free up proc structures */
+	for (p = j->proc_list; p != NULL; ) {
+		tmp = p;
+		p = p->next;
+		tmp->next = free_procs;
+		free_procs = tmp;
+	}
+
+	if ((j->flags & JF_ZOMBIE) && j->ppid == procpid)
+		--nzombie;
+	j->next = free_jobs;
+	free_jobs = j;
+
+	if (j == last_job)
+		last_job = NULL;
+	if (j == async_job)
+		async_job = NULL;
+}
+
+/*
+ * put j in a particular location (taking it out job_list if it is there
+ * already)
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+put_job(Job *j, int where)
+{
+	Job	**prev, *curr;
+
+	/* Remove job from list (if there) */
+	prev = &job_list;
+	curr = job_list;
+	for (; curr && curr != j; prev = &curr->next, curr = *prev)
+		;
+	if (curr == j)
+		*prev = curr->next;
+
+	switch (where) {
+	case PJ_ON_FRONT:
+		j->next = job_list;
+		job_list = j;
+		break;
+
+	case PJ_PAST_STOPPED:
+		prev = &job_list;
+		curr = job_list;
+		for (; curr && curr->state == PSTOPPED; prev = &curr->next,
+		    curr = *prev)
+			;
+		j->next = curr;
+		*prev = j;
+		break;
+	}
+}
+
+/*
+ * nuke a job (called when unable to start full job).
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static int
+kill_job(Job *j, int sig)
+{
+	Proc	*p;
+	int	rval = 0;
+
+	for (p = j->proc_list; p != NULL; p = p->next)
+		if (p->pid != 0)
+			if (kill(p->pid, sig) < 0)
+				rval = -1;
+	return (rval);
+}
diff --git a/mksh/src/lalloc.c b/mksh/src/lalloc.c
new file mode 100644
index 0000000..79627d1
--- /dev/null
+++ b/mksh/src/lalloc.c
@@ -0,0 +1,123 @@
+/*-
+ * Copyright © 2009
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un‐
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person’s immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/lalloc.c,v 1.11 2009/08/08 13:08:51 tg Exp $");
+
+/* build with CPPFLAGS+= -DUSE_REALLOC_MALLOC=0 on ancient systems */
+#if defined(USE_REALLOC_MALLOC) && (USE_REALLOC_MALLOC == 0)
+#define remalloc(p,n)	((p) == NULL ? malloc(n) : realloc((p), (n)))
+#else
+#define remalloc(p,n)	realloc((p), (n))
+#endif
+
+#define ALLOC_ISUNALIGNED(p) (((ptrdiff_t)(p)) % ALLOC_SIZE)
+
+static ALLOC_ITEM *findptr(ALLOC_ITEM **, char *, Area *);
+
+void
+ainit(Area *ap)
+{
+	/* area pointer is an ALLOC_ITEM, just the head of the list */
+	ap->next = NULL;
+}
+
+static ALLOC_ITEM *
+findptr(ALLOC_ITEM **lpp, char *ptr, Area *ap)
+{
+	void *lp;
+
+#ifndef MKSH_SMALL
+	if (ALLOC_ISUNALIGNED(ptr))
+		goto fail;
+#endif
+	/* get address of ALLOC_ITEM from user item */
+	/*
+	 * note: the alignment of "ptr" to ALLOC_SIZE is checked
+	 * above; the "void *" gets us rid of a gcc 2.95 warning
+	 */
+	*lpp = (lp = ptr - ALLOC_SIZE);
+	/* search for allocation item in group list */
+	while (ap->next != lp)
+		if ((ap = ap->next) == NULL) {
+#ifndef MKSH_SMALL
+ fail:
+#endif
+			internal_errorf("rogue pointer %p", ptr);
+		}
+	return (ap);
+}
+
+void *
+aresize(void *ptr, size_t numb, Area *ap)
+{
+	ALLOC_ITEM *lp = NULL;
+
+	/* resizing (true) or newly allocating? */
+	if (ptr != NULL) {
+		ALLOC_ITEM *pp;
+
+		pp = findptr(&lp, ptr, ap);
+		pp->next = lp->next;
+	}
+
+	if ((numb >= SIZE_MAX - ALLOC_SIZE) ||
+	    (lp = remalloc(lp, numb + ALLOC_SIZE)) == NULL
+#ifndef MKSH_SMALL
+	    || ALLOC_ISUNALIGNED(lp)
+#endif
+	    )
+		internal_errorf("cannot allocate %lu data bytes",
+		    (unsigned long)numb);
+	/* this only works because Area is an ALLOC_ITEM */
+	lp->next = ap->next;
+	ap->next = lp;
+	/* return user item address */
+	return ((char *)lp + ALLOC_SIZE);
+}
+
+void
+afree(void *ptr, Area *ap)
+{
+	if (ptr != NULL) {
+		ALLOC_ITEM *lp, *pp;
+
+		pp = findptr(&lp, ptr, ap);
+		/* unhook */
+		pp->next = lp->next;
+		/* now free ALLOC_ITEM */
+		free(lp);
+	}
+}
+
+void
+afreeall(Area *ap)
+{
+	ALLOC_ITEM *lp;
+
+	/* traverse group (linked list) */
+	while ((lp = ap->next) != NULL) {
+		/* make next ALLOC_ITEM head of list */
+		ap->next = lp->next;
+		/* free old head */
+		free(lp);
+	}
+}
diff --git a/mksh/src/lex.c b/mksh/src/lex.c
new file mode 100644
index 0000000..d0219e7
--- /dev/null
+++ b/mksh/src/lex.c
@@ -0,0 +1,1782 @@
+/*	$OpenBSD: lex.c,v 1.44 2008/07/03 17:52:08 otto Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.118 2010/07/25 11:35:41 tg Exp $");
+
+/*
+ * states while lexing word
+ */
+#define SBASE		0	/* outside any lexical constructs */
+#define SWORD		1	/* implicit quoting for substitute() */
+#define SLETPAREN	2	/* inside (( )), implicit quoting */
+#define SSQUOTE		3	/* inside '' */
+#define SDQUOTE		4	/* inside "" */
+#define SEQUOTE		5	/* inside $'' */
+#define SBRACE		6	/* inside ${} */
+#define SQBRACE		7	/* inside "${}" */
+#define SCSPAREN	8	/* inside $() */
+#define SBQUOTE		9	/* inside `` */
+#define SASPAREN	10	/* inside $(( )) */
+#define SHEREDELIM	11	/* parsing <<,<<- delimiter */
+#define SHEREDQUOTE	12	/* parsing " in <<,<<- delimiter */
+#define SPATTERN	13	/* parsing *(...|...) pattern (*+?@!) */
+#define STBRACE		14	/* parsing ${...[#%]...} */
+#define SLETARRAY	15	/* inside =( ), just copy */
+#define SADELIM		16	/* like SBASE, looking for delimiter */
+#define SHERESTRING	17	/* parsing <<< string */
+
+/* Structure to keep track of the lexing state and the various pieces of info
+ * needed for each particular state. */
+typedef struct lex_state Lex_state;
+struct lex_state {
+	int ls_state;
+	union {
+		/* $(...) */
+		struct scsparen_info {
+			int nparen;	/* count open parenthesis */
+			int csstate;	/* XXX remove */
+#define ls_scsparen ls_info.u_scsparen
+		} u_scsparen;
+
+		/* $((...)) */
+		struct sasparen_info {
+			int nparen;	/* count open parenthesis */
+			int start;	/* marks start of $(( in output str */
+#define ls_sasparen ls_info.u_sasparen
+		} u_sasparen;
+
+		/* ((...)) */
+		struct sletparen_info {
+			int nparen;	/* count open parenthesis */
+#define ls_sletparen ls_info.u_sletparen
+		} u_sletparen;
+
+		/* `...` */
+		struct sbquote_info {
+			int indquotes;	/* true if in double quotes: "`...`" */
+#define ls_sbquote ls_info.u_sbquote
+		} u_sbquote;
+
+#ifndef MKSH_SMALL
+		/* =(...) */
+		struct sletarray_info {
+			int nparen;	/* count open parentheses */
+#define ls_sletarray ls_info.u_sletarray
+		} u_sletarray;
+#endif
+
+		/* ADELIM */
+		struct sadelim_info {
+			unsigned char nparen;	/* count open parentheses */
+#define SADELIM_BASH	0
+#define SADELIM_MAKE	1
+			unsigned char style;
+			unsigned char delimiter;
+			unsigned char num;
+			unsigned char flags;	/* ofs. into sadelim_flags[] */
+#define ls_sadelim ls_info.u_sadelim
+		} u_sadelim;
+
+		/* $'...' */
+		struct sequote_info {
+			bool got_NUL;	/* ignore rest of string */
+#define ls_sequote ls_info.u_sequote
+		} u_sequote;
+
+		Lex_state *base;	/* used to point to next state block */
+	} ls_info;
+};
+
+typedef struct {
+	Lex_state *base;
+	Lex_state *end;
+} State_info;
+
+static void readhere(struct ioword *);
+static int getsc__(void);
+static void getsc_line(Source *);
+static int getsc_bn(void);
+static int s_get(void);
+static void s_put(int);
+static char *get_brace_var(XString *, char *);
+static int arraysub(char **);
+static const char *ungetsc(int);
+static void gethere(bool);
+static Lex_state *push_state_(State_info *, Lex_state *);
+static Lex_state *pop_state_(State_info *, Lex_state *);
+
+static int dopprompt(const char *, int, bool);
+
+static int backslash_skip;
+static int ignore_backslash_newline;
+
+/* optimised getsc_bn() */
+#define _getsc()	(*source->str != '\0' && *source->str != '\\' \
+			 && !backslash_skip && !(source->flags & SF_FIRST) \
+			 ? *source->str++ : getsc_bn())
+/* optimised getsc__() */
+#define	_getsc_()	((*source->str != '\0') && !(source->flags & SF_FIRST) \
+			 ? *source->str++ : getsc__())
+
+#ifdef MKSH_SMALL
+static int getsc(void);
+static int getsc_(void);
+
+static int
+getsc(void)
+{
+	return (_getsc());
+}
+
+static int
+getsc_(void)
+{
+	return (_getsc_());
+}
+#else
+/* !MKSH_SMALL: use them inline */
+#define getsc()		_getsc()
+#define getsc_()	_getsc_()
+#endif
+
+#define STATE_BSIZE	32
+
+#define PUSH_STATE(s)	do {					\
+	if (++statep == state_info.end)				\
+		statep = push_state_(&state_info, statep);	\
+	state = statep->ls_state = (s);				\
+} while (0)
+
+#define POP_STATE()	do {					\
+	if (--statep == state_info.base)			\
+		statep = pop_state_(&state_info, statep);	\
+	state = statep->ls_state;				\
+} while (0)
+
+/**
+ * Lexical analyser
+ *
+ * tokens are not regular expressions, they are LL(1).
+ * for example, "${var:-${PWD}}", and "$(size $(whence ksh))".
+ * hence the state stack.
+ */
+
+int
+yylex(int cf)
+{
+	Lex_state states[STATE_BSIZE], *statep, *s2, *base;
+	State_info state_info;
+	int c, c2, state;
+	XString ws;		/* expandable output word */
+	char *wp;		/* output word pointer */
+	char *sp, *dp;
+
+ Again:
+	states[0].ls_state = -1;
+	states[0].ls_info.base = NULL;
+	statep = &states[1];
+	state_info.base = states;
+	state_info.end = &state_info.base[STATE_BSIZE];
+
+	Xinit(ws, wp, 64, ATEMP);
+
+	backslash_skip = 0;
+	ignore_backslash_newline = 0;
+
+	if (cf&ONEWORD)
+		state = SWORD;
+	else if (cf&LETEXPR) {
+		/* enclose arguments in (double) quotes */
+		*wp++ = OQUOTE;
+		state = SLETPAREN;
+		statep->ls_sletparen.nparen = 0;
+#ifndef MKSH_SMALL
+	} else if (cf&LETARRAY) {
+		state = SLETARRAY;
+		statep->ls_sletarray.nparen = 0;
+#endif
+	} else {		/* normal lexing */
+		state = (cf & HEREDELIM) ? SHEREDELIM : SBASE;
+		while ((c = getsc()) == ' ' || c == '\t')
+			;
+		if (c == '#') {
+			ignore_backslash_newline++;
+			while ((c = getsc()) != '\0' && c != '\n')
+				;
+			ignore_backslash_newline--;
+		}
+		ungetsc(c);
+	}
+	if (source->flags & SF_ALIAS) {	/* trailing ' ' in alias definition */
+		source->flags &= ~SF_ALIAS;
+		cf |= ALIAS;
+	}
+
+	/* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */
+	statep->ls_state = state;
+
+	/* check for here string */
+	if (state == SHEREDELIM) {
+		c = getsc();
+		if (c == '<') {
+			state = SHERESTRING;
+			while ((c = getsc()) == ' ' || c == '\t')
+				;
+			ungetsc(c);
+			c = '<';
+			goto accept_nonword;
+		}
+		ungetsc(c);
+	}
+
+	/* collect non-special or quoted characters to form word */
+	while (!((c = getsc()) == 0 ||
+	    ((state == SBASE || state == SHEREDELIM || state == SHERESTRING) &&
+	    ctype(c, C_LEX1)))) {
+ accept_nonword:
+		Xcheck(ws, wp);
+		switch (state) {
+		case SADELIM:
+			if (c == '(')
+				statep->ls_sadelim.nparen++;
+			else if (c == ')')
+				statep->ls_sadelim.nparen--;
+			else if (statep->ls_sadelim.nparen == 0 &&
+			    (c == /*{*/ '}' || c == statep->ls_sadelim.delimiter)) {
+				*wp++ = ADELIM;
+				*wp++ = c;
+				if (c == /*{*/ '}' || --statep->ls_sadelim.num == 0)
+					POP_STATE();
+				if (c == /*{*/ '}')
+					POP_STATE();
+				break;
+			}
+			/* FALLTHROUGH */
+		case SBASE:
+			if (c == '[' && (cf & (VARASN|ARRAYVAR))) {
+				*wp = EOS;	/* temporary */
+				if (is_wdvarname(Xstring(ws, wp), false)) {
+					char *p, *tmp;
+
+					if (arraysub(&tmp)) {
+						*wp++ = CHAR;
+						*wp++ = c;
+						for (p = tmp; *p; ) {
+							Xcheck(ws, wp);
+							*wp++ = CHAR;
+							*wp++ = *p++;
+						}
+						afree(tmp, ATEMP);
+						break;
+					} else {
+						Source *s;
+
+						s = pushs(SREREAD,
+						    source->areap);
+						s->start = s->str =
+						    s->u.freeme = tmp;
+						s->next = source;
+						source = s;
+					}
+				}
+				*wp++ = CHAR;
+				*wp++ = c;
+				break;
+			}
+			/* FALLTHROUGH */
+ Sbase1:		/* includes *(...|...) pattern (*+?@!) */
+			if (c == '*' || c == '@' || c == '+' || c == '?' ||
+			    c == '!') {
+				c2 = getsc();
+				if (c2 == '(' /*)*/ ) {
+					*wp++ = OPAT;
+					*wp++ = c;
+					PUSH_STATE(SPATTERN);
+					break;
+				}
+				ungetsc(c2);
+			}
+			/* FALLTHROUGH */
+ Sbase2:		/* doesn't include *(...|...) pattern (*+?@!) */
+			switch (c) {
+			case '\\':
+ getsc_qchar:
+				if ((c = getsc())) {
+					/* trailing \ is lost */
+					*wp++ = QCHAR;
+					*wp++ = c;
+				}
+				break;
+			case '\'':
+ open_ssquote:
+				*wp++ = OQUOTE;
+				ignore_backslash_newline++;
+				PUSH_STATE(SSQUOTE);
+				break;
+			case '"':
+ open_sdquote:
+				*wp++ = OQUOTE;
+				PUSH_STATE(SDQUOTE);
+				break;
+			default:
+				goto Subst;
+			}
+			break;
+
+ Subst:
+			switch (c) {
+			case '\\':
+				c = getsc();
+				switch (c) {
+				case '"':
+					if ((cf & HEREDOC))
+						goto heredocquote;
+					/* FALLTHROUGH */
+				case '\\':
+				case '$': case '`':
+ store_qchar:
+					*wp++ = QCHAR;
+					*wp++ = c;
+					break;
+				default:
+ heredocquote:
+					Xcheck(ws, wp);
+					if (c) {
+						/* trailing \ is lost */
+						*wp++ = CHAR;
+						*wp++ = '\\';
+						*wp++ = CHAR;
+						*wp++ = c;
+					}
+					break;
+				}
+				break;
+			case '$':
+ subst_dollar:
+				c = getsc();
+				if (c == '(') /*)*/ {
+					c = getsc();
+					if (c == '(') /*)*/ {
+						PUSH_STATE(SASPAREN);
+						statep->ls_sasparen.nparen = 2;
+						statep->ls_sasparen.start =
+						    Xsavepos(ws, wp);
+						*wp++ = EXPRSUB;
+					} else {
+						ungetsc(c);
+						PUSH_STATE(SCSPAREN);
+						statep->ls_scsparen.nparen = 1;
+						statep->ls_scsparen.csstate = 0;
+						*wp++ = COMSUB;
+					}
+				} else if (c == '{') /*}*/ {
+					*wp++ = OSUBST;
+					*wp++ = '{'; /*}*/
+					wp = get_brace_var(&ws, wp);
+					c = getsc();
+					/* allow :# and :% (ksh88 compat) */
+					if (c == ':') {
+						*wp++ = CHAR;
+						*wp++ = c;
+						c = getsc();
+						if (c == ':') {
+							*wp++ = CHAR;
+							*wp++ = '0';
+							*wp++ = ADELIM;
+							*wp++ = ':';
+							PUSH_STATE(SBRACE);
+							PUSH_STATE(SADELIM);
+							statep->ls_sadelim.style = SADELIM_BASH;
+							statep->ls_sadelim.delimiter = ':';
+							statep->ls_sadelim.num = 1;
+							statep->ls_sadelim.nparen = 0;
+							break;
+						} else if (ksh_isdigit(c) ||
+						    c == '('/*)*/ || c == ' ' ||
+						    c == '$' /* XXX what else? */) {
+							/* substring subst. */
+							if (c != ' ') {
+								*wp++ = CHAR;
+								*wp++ = ' ';
+							}
+							ungetsc(c);
+							PUSH_STATE(SBRACE);
+							PUSH_STATE(SADELIM);
+							statep->ls_sadelim.style = SADELIM_BASH;
+							statep->ls_sadelim.delimiter = ':';
+							statep->ls_sadelim.num = 2;
+							statep->ls_sadelim.nparen = 0;
+							break;
+						}
+					} else if (c == '/') {
+						*wp++ = CHAR;
+						*wp++ = c;
+						if ((c = getsc()) == '/') {
+							*wp++ = ADELIM;
+							*wp++ = c;
+						} else
+							ungetsc(c);
+						PUSH_STATE(SBRACE);
+						PUSH_STATE(SADELIM);
+						statep->ls_sadelim.style = SADELIM_BASH;
+						statep->ls_sadelim.delimiter = '/';
+						statep->ls_sadelim.num = 1;
+						statep->ls_sadelim.nparen = 0;
+						break;
+					}
+					/* If this is a trim operation,
+					 * treat (,|,) specially in STBRACE.
+					 */
+					if (ctype(c, C_SUBOP2)) {
+						ungetsc(c);
+						PUSH_STATE(STBRACE);
+					} else {
+						ungetsc(c);
+						if (state == SDQUOTE)
+							PUSH_STATE(SQBRACE);
+						else
+							PUSH_STATE(SBRACE);
+					}
+				} else if (ksh_isalphx(c)) {
+					*wp++ = OSUBST;
+					*wp++ = 'X';
+					do {
+						Xcheck(ws, wp);
+						*wp++ = c;
+						c = getsc();
+					} while (ksh_isalnux(c));
+					*wp++ = '\0';
+					*wp++ = CSUBST;
+					*wp++ = 'X';
+					ungetsc(c);
+				} else if (ctype(c, C_VAR1 | C_DIGIT)) {
+					Xcheck(ws, wp);
+					*wp++ = OSUBST;
+					*wp++ = 'X';
+					*wp++ = c;
+					*wp++ = '\0';
+					*wp++ = CSUBST;
+					*wp++ = 'X';
+				} else if (c == '\'' && (state == SBASE)) {
+					/* XXX which other states are valid? */
+					*wp++ = OQUOTE;
+					ignore_backslash_newline++;
+					PUSH_STATE(SEQUOTE);
+					statep->ls_sequote.got_NUL = false;
+					break;
+				} else {
+					*wp++ = CHAR;
+					*wp++ = '$';
+					ungetsc(c);
+				}
+				break;
+			case '`':
+ subst_gravis:
+				PUSH_STATE(SBQUOTE);
+				*wp++ = COMSUB;
+				/* Need to know if we are inside double quotes
+				 * since sh/AT&T-ksh translate the \" to " in
+				 * "`...\"...`".
+				 * This is not done in POSIX mode (section
+				 * 3.2.3, Double Quotes: "The backquote shall
+				 * retain its special meaning introducing the
+				 * other form of command substitution (see
+				 * 3.6.3). The portion of the quoted string
+				 * from the initial backquote and the
+				 * characters up to the next backquote that
+				 * is not preceded by a backslash (having
+				 * escape characters removed) defines that
+				 * command whose output replaces `...` when
+				 * the word is expanded."
+				 * Section 3.6.3, Command Substitution:
+				 * "Within the backquoted style of command
+				 * substitution, backslash shall retain its
+				 * literal meaning, except when followed by
+				 * $ ` \.").
+				 */
+				statep->ls_sbquote.indquotes = 0;
+				s2 = statep;
+				base = state_info.base;
+				while (1) {
+					for (; s2 != base; s2--) {
+						if (s2->ls_state == SDQUOTE) {
+							statep->ls_sbquote.indquotes = 1;
+							break;
+						}
+					}
+					if (s2 != base)
+						break;
+					if (!(s2 = s2->ls_info.base))
+						break;
+					base = s2-- - STATE_BSIZE;
+				}
+				break;
+			case QCHAR:
+				if (cf & LQCHAR) {
+					*wp++ = QCHAR;
+					*wp++ = getsc();
+					break;
+				}
+				/* FALLTHROUGH */
+			default:
+ store_char:
+				*wp++ = CHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SEQUOTE:
+			if (c == '\'') {
+				POP_STATE();
+				*wp++ = CQUOTE;
+				ignore_backslash_newline--;
+			} else if (c == '\\') {
+				if ((c2 = unbksl(true, s_get, s_put)) == -1)
+					c2 = s_get();
+				if (c2 == 0)
+					statep->ls_sequote.got_NUL = true;
+				if (!statep->ls_sequote.got_NUL) {
+					char ts[4];
+
+					if ((unsigned int)c2 < 0x100) {
+						*wp++ = QCHAR;
+						*wp++ = c2;
+					} else {
+						c = utf_wctomb(ts, c2 - 0x100);
+						ts[c] = 0;
+						for (c = 0; ts[c]; ++c) {
+							*wp++ = QCHAR;
+							*wp++ = ts[c];
+						}
+					}
+				}
+			} else if (!statep->ls_sequote.got_NUL) {
+				*wp++ = QCHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SSQUOTE:
+			if (c == '\'') {
+				POP_STATE();
+				*wp++ = CQUOTE;
+				ignore_backslash_newline--;
+			} else {
+				*wp++ = QCHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SDQUOTE:
+			if (c == '"') {
+				POP_STATE();
+				*wp++ = CQUOTE;
+			} else
+				goto Subst;
+			break;
+
+		case SCSPAREN:	/* $( ... ) */
+			/* todo: deal with $(...) quoting properly
+			 * kludge to partly fake quoting inside $(...): doesn't
+			 * really work because nested $(...) or ${...} inside
+			 * double quotes aren't dealt with.
+			 */
+			switch (statep->ls_scsparen.csstate) {
+			case 0:	/* normal */
+				switch (c) {
+				case '(':
+					statep->ls_scsparen.nparen++;
+					break;
+				case ')':
+					statep->ls_scsparen.nparen--;
+					break;
+				case '\\':
+					statep->ls_scsparen.csstate = 1;
+					break;
+				case '"':
+					statep->ls_scsparen.csstate = 2;
+					break;
+				case '\'':
+					statep->ls_scsparen.csstate = 4;
+					ignore_backslash_newline++;
+					break;
+				}
+				break;
+
+			case 1:	/* backslash in normal mode */
+			case 3:	/* backslash in double quotes */
+				--statep->ls_scsparen.csstate;
+				break;
+
+			case 2:	/* double quotes */
+				if (c == '"')
+					statep->ls_scsparen.csstate = 0;
+				else if (c == '\\')
+					statep->ls_scsparen.csstate = 3;
+				break;
+
+			case 4:	/* single quotes */
+				if (c == '\'') {
+					statep->ls_scsparen.csstate = 0;
+					ignore_backslash_newline--;
+				}
+				break;
+			}
+			if (statep->ls_scsparen.nparen == 0) {
+				POP_STATE();
+				*wp++ = 0;	/* end of COMSUB */
+			} else
+				*wp++ = c;
+			break;
+
+		case SASPAREN:	/* $(( ... )) */
+			/* XXX should nest using existing state machine
+			 * (embed "...", $(...), etc.) */
+			if (c == '(')
+				statep->ls_sasparen.nparen++;
+			else if (c == ')') {
+				statep->ls_sasparen.nparen--;
+				if (statep->ls_sasparen.nparen == 1) {
+					/*(*/
+					if ((c2 = getsc()) == ')') {
+						POP_STATE();
+						/* end of EXPRSUB */
+						*wp++ = 0;
+						break;
+					} else {
+						char *s;
+
+						ungetsc(c2);
+						/* mismatched parenthesis -
+						 * assume we were really
+						 * parsing a $(...) expression
+						 */
+						s = Xrestpos(ws, wp,
+						    statep->ls_sasparen.start);
+						memmove(s + 1, s, wp - s);
+						*s++ = COMSUB;
+						*s = '('; /*)*/
+						wp++;
+						statep->ls_scsparen.nparen = 1;
+						statep->ls_scsparen.csstate = 0;
+						state = statep->ls_state =
+						    SCSPAREN;
+					}
+				}
+			}
+			*wp++ = c;
+			break;
+
+		case SQBRACE:
+			if (c == '\\') {
+				/*
+				 * perform POSIX "quote removal" if the back-
+				 * slash is "special", i.e. same cases as the
+				 * {case '\\':} in Subst: plus closing brace;
+				 * in mksh code "quote removal" on '\c' means
+				 * write QCHAR+c, otherwise CHAR+\+CHAR+c are
+				 * emitted (in heredocquote:)
+				 */
+				if ((c = getsc()) == '"' || c == '\\' ||
+				    c == '$' || c == '`' || c == /*{*/'}')
+					goto store_qchar;
+				goto heredocquote;
+			}
+			goto common_SQBRACE;
+
+		case SBRACE:
+			if (c == '\'')
+				goto open_ssquote;
+			else if (c == '\\')
+				goto getsc_qchar;
+ common_SQBRACE:
+			if (c == '"')
+				goto open_sdquote;
+			else if (c == '$')
+				goto subst_dollar;
+			else if (c == '`')
+				goto subst_gravis;
+			else if (c != /*{*/ '}')
+				goto store_char;
+			POP_STATE();
+			*wp++ = CSUBST;
+			*wp++ = /*{*/ '}';
+			break;
+
+		case STBRACE:
+			/* Same as SBASE, except (,|,) treated specially */
+			if (c == /*{*/ '}') {
+				POP_STATE();
+				*wp++ = CSUBST;
+				*wp++ = /*{*/ '}';
+			} else if (c == '|') {
+				*wp++ = SPAT;
+			} else if (c == '(') {
+				*wp++ = OPAT;
+				*wp++ = ' ';	/* simile for @ */
+				PUSH_STATE(SPATTERN);
+			} else
+				goto Sbase1;
+			break;
+
+		case SBQUOTE:
+			if (c == '`') {
+				*wp++ = 0;
+				POP_STATE();
+			} else if (c == '\\') {
+				switch (c = getsc()) {
+				case '\\':
+				case '$': case '`':
+					*wp++ = c;
+					break;
+				case '"':
+					if (statep->ls_sbquote.indquotes) {
+						*wp++ = c;
+						break;
+					}
+					/* FALLTHROUGH */
+				default:
+					if (c) {
+						/* trailing \ is lost */
+						*wp++ = '\\';
+						*wp++ = c;
+					}
+					break;
+				}
+			} else
+				*wp++ = c;
+			break;
+
+		case SWORD:	/* ONEWORD */
+			goto Subst;
+
+		case SLETPAREN:	/* LETEXPR: (( ... )) */
+			/*(*/
+			if (c == ')') {
+				if (statep->ls_sletparen.nparen > 0)
+					--statep->ls_sletparen.nparen;
+				else if ((c2 = getsc()) == /*(*/ ')') {
+					c = 0;
+					*wp++ = CQUOTE;
+					goto Done;
+				} else {
+					Source *s;
+
+					ungetsc(c2);
+					/* mismatched parenthesis -
+					 * assume we were really
+					 * parsing a $(...) expression
+					 */
+					*wp = EOS;
+					sp = Xstring(ws, wp);
+					dp = wdstrip(sp, true, false);
+					s = pushs(SREREAD, source->areap);
+					s->start = s->str = s->u.freeme = dp;
+					s->next = source;
+					source = s;
+					return ('('/*)*/);
+				}
+			} else if (c == '(')
+				/* parenthesis inside quotes and backslashes
+				 * are lost, but AT&T ksh doesn't count them
+				 * either
+				 */
+				++statep->ls_sletparen.nparen;
+			goto Sbase2;
+
+#ifndef MKSH_SMALL
+		case SLETARRAY:	/* LETARRAY: =( ... ) */
+			if (c == '('/*)*/)
+				++statep->ls_sletarray.nparen;
+			else if (c == /*(*/')')
+				if (statep->ls_sletarray.nparen-- == 0) {
+					c = 0;
+					goto Done;
+				}
+			*wp++ = CHAR;
+			*wp++ = c;
+			break;
+#endif
+
+		case SHERESTRING:	/* <<< delimiter */
+			if (c == '\\') {
+				c = getsc();
+				if (c) {
+					/* trailing \ is lost */
+					*wp++ = QCHAR;
+					*wp++ = c;
+				}
+				/* invoke quoting mode */
+				Xstring(ws, wp)[0] = QCHAR;
+			} else if (c == '$') {
+				if ((c2 = getsc()) == '\'') {
+					PUSH_STATE(SEQUOTE);
+					statep->ls_sequote.got_NUL = false;
+					goto sherestring_quoted;
+				}
+				ungetsc(c2);
+				goto sherestring_regular;
+			} else if (c == '\'') {
+				PUSH_STATE(SSQUOTE);
+ sherestring_quoted:
+				*wp++ = OQUOTE;
+				ignore_backslash_newline++;
+				/* invoke quoting mode */
+				Xstring(ws, wp)[0] = QCHAR;
+			} else if (c == '"') {
+				state = statep->ls_state = SHEREDQUOTE;
+				*wp++ = OQUOTE;
+				/* just don't IFS split; no quoting mode */
+			} else {
+ sherestring_regular:
+				*wp++ = CHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SHEREDELIM:	/* <<,<<- delimiter */
+			/* XXX chuck this state (and the next) - use
+			 * the existing states ($ and \`...` should be
+			 * stripped of their specialness after the
+			 * fact).
+			 */
+			/* here delimiters need a special case since
+			 * $ and `...` are not to be treated specially
+			 */
+			if (c == '\\') {
+				c = getsc();
+				if (c) {
+					/* trailing \ is lost */
+					*wp++ = QCHAR;
+					*wp++ = c;
+				}
+			} else if (c == '$') {
+				if ((c2 = getsc()) == '\'') {
+					PUSH_STATE(SEQUOTE);
+					statep->ls_sequote.got_NUL = false;
+					goto sheredelim_quoted;
+				}
+				ungetsc(c2);
+				goto sheredelim_regular;
+			} else if (c == '\'') {
+				PUSH_STATE(SSQUOTE);
+ sheredelim_quoted:
+				*wp++ = OQUOTE;
+				ignore_backslash_newline++;
+			} else if (c == '"') {
+				state = statep->ls_state = SHEREDQUOTE;
+				*wp++ = OQUOTE;
+			} else {
+ sheredelim_regular:
+				*wp++ = CHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SHEREDQUOTE:	/* " in <<,<<- delimiter */
+			if (c == '"') {
+				*wp++ = CQUOTE;
+				state = statep->ls_state =
+				    /* dp[1] == '<' means here string */
+				    Xstring(ws, wp)[1] == '<' ?
+				    SHERESTRING : SHEREDELIM;
+			} else {
+				if (c == '\\') {
+					switch (c = getsc()) {
+					case '\\': case '"':
+					case '$': case '`':
+						break;
+					default:
+						if (c) {
+							/* trailing \ lost */
+							*wp++ = CHAR;
+							*wp++ = '\\';
+						}
+						break;
+					}
+				}
+				*wp++ = CHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SPATTERN:	/* in *(...|...) pattern (*+?@!) */
+			if ( /*(*/ c == ')') {
+				*wp++ = CPAT;
+				POP_STATE();
+			} else if (c == '|') {
+				*wp++ = SPAT;
+			} else if (c == '(') {
+				*wp++ = OPAT;
+				*wp++ = ' ';	/* simile for @ */
+				PUSH_STATE(SPATTERN);
+			} else
+				goto Sbase1;
+			break;
+		}
+	}
+ Done:
+	Xcheck(ws, wp);
+	if (statep != &states[1])
+		/* XXX figure out what is missing */
+		yyerror("no closing quote\n");
+
+#ifndef MKSH_SMALL
+	if (state == SLETARRAY && statep->ls_sletarray.nparen != -1)
+		yyerror("%s: ')' missing\n", T_synerr);
+#endif
+
+	/* This done to avoid tests for SHEREDELIM wherever SBASE tested */
+	if (state == SHEREDELIM || state == SHERESTRING)
+		state = SBASE;
+
+	dp = Xstring(ws, wp);
+	if ((c == '<' || c == '>' || c == '&') && state == SBASE) {
+		struct ioword *iop = alloc(sizeof(struct ioword), ATEMP);
+
+		if (Xlength(ws, wp) == 0)
+			iop->unit = c == '<' ? 0 : 1;
+		else for (iop->unit = 0, c2 = 0; c2 < Xlength(ws, wp); c2 += 2) {
+			if (dp[c2] != CHAR)
+				goto no_iop;
+			if (!ksh_isdigit(dp[c2 + 1]))
+				goto no_iop;
+			iop->unit = (iop->unit * 10) + dp[c2 + 1] - '0';
+		}
+
+		if (iop->unit >= FDBASE)
+			goto no_iop;
+
+		if (c == '&') {
+			if ((c2 = getsc()) != '>') {
+				ungetsc(c2);
+				goto no_iop;
+			}
+			c = c2;
+			iop->flag = IOBASH;
+		} else
+			iop->flag = 0;
+
+		c2 = getsc();
+		/* <<, >>, <> are ok, >< is not */
+		if (c == c2 || (c == '<' && c2 == '>')) {
+			iop->flag |= c == c2 ?
+			    (c == '>' ? IOCAT : IOHERE) : IORDWR;
+			if (iop->flag == IOHERE) {
+				if ((c2 = getsc()) == '-')
+					iop->flag |= IOSKIP;
+				else
+					ungetsc(c2);
+			}
+		} else if (c2 == '&')
+			iop->flag |= IODUP | (c == '<' ? IORDUP : 0);
+		else {
+			iop->flag |= c == '>' ? IOWRITE : IOREAD;
+			if (c == '>' && c2 == '|')
+				iop->flag |= IOCLOB;
+			else
+				ungetsc(c2);
+		}
+
+		iop->name = NULL;
+		iop->delim = NULL;
+		iop->heredoc = NULL;
+		Xfree(ws, wp);	/* free word */
+		yylval.iop = iop;
+		return (REDIR);
+ no_iop:
+		;
+	}
+
+	if (wp == dp && state == SBASE) {
+		Xfree(ws, wp);	/* free word */
+		/* no word, process LEX1 character */
+		if ((c == '|') || (c == '&') || (c == ';') || (c == '('/*)*/)) {
+			if ((c2 = getsc()) == c)
+				c = (c == ';') ? BREAK :
+				    (c == '|') ? LOGOR :
+				    (c == '&') ? LOGAND :
+				    /* c == '(' ) */ MDPAREN;
+			else if (c == '|' && c2 == '&')
+				c = COPROC;
+			else
+				ungetsc(c2);
+		} else if (c == '\n') {
+			gethere(false);
+			if (cf & CONTIN)
+				goto Again;
+		} else if (c == '\0')
+			/* need here strings at EOF */
+			gethere(true);
+		return (c);
+	}
+
+	*wp++ = EOS;		/* terminate word */
+	yylval.cp = Xclose(ws, wp);
+	if (state == SWORD || state == SLETPAREN
+	    /* XXX ONEWORD? */
+#ifndef MKSH_SMALL
+	    || state == SLETARRAY
+#endif
+	    )
+		return (LWORD);
+
+	/* unget terminator */
+	ungetsc(c);
+
+	/*
+	 * note: the alias-vs-function code below depends on several
+	 * interna: starting from here, source->str is not modified;
+	 * the way getsc() and ungetsc() operate; etc.
+	 */
+
+	/* copy word to unprefixed string ident */
+	sp = yylval.cp;
+	dp = ident;
+	if ((cf & HEREDELIM) && (sp[1] == '<'))
+		while (dp < ident+IDENT) {
+			if ((c = *sp++) == CHAR)
+				*dp++ = *sp++;
+			else if ((c != OQUOTE) && (c != CQUOTE))
+				break;
+		}
+	else
+		while (dp < ident+IDENT && (c = *sp++) == CHAR)
+			*dp++ = *sp++;
+	/* Make sure the ident array stays '\0' padded */
+	memset(dp, 0, (ident+IDENT) - dp + 1);
+	if (c != EOS)
+		*ident = '\0';	/* word is not unquoted */
+
+	if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) {
+		struct tbl *p;
+		uint32_t h = hash(ident);
+
+		/* { */
+		if ((cf & KEYWORD) && (p = ktsearch(&keywords, ident, h)) &&
+		    (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) {
+			afree(yylval.cp, ATEMP);
+			return (p->val.i);
+		}
+		if ((cf & ALIAS) && (p = ktsearch(&aliases, ident, h)) &&
+		    (p->flag & ISSET)) {
+			/*
+			 * this still points to the same character as the
+			 * ungetsc'd terminator from above
+			 */
+			const char *cp = source->str;
+
+			/* prefer POSIX but not Korn functions over aliases */
+			while (*cp == ' ' || *cp == '\t')
+				/*
+				 * this is like getsc() without skipping
+				 * over Source boundaries (including not
+				 * parsing ungetsc'd characters that got
+				 * pushed into an SREREAD) which is what
+				 * we want here anyway: find out whether
+				 * the alias name is followed by a POSIX
+				 * function definition (only the opening
+				 * parenthesis is checked though)
+				 */
+				++cp;
+			/* prefer functions over aliases */
+			if (*cp == '(' /*)*/)
+				/*
+				 * delete alias upon encountering function
+				 * definition
+				 */
+				ktdelete(p);
+			else {
+				Source *s = source;
+
+				while (s && (s->flags & SF_HASALIAS))
+					if (s->u.tblp == p)
+						return (LWORD);
+					else
+						s = s->next;
+				/* push alias expansion */
+				s = pushs(SALIAS, source->areap);
+				s->start = s->str = p->val.s;
+				s->u.tblp = p;
+				s->flags |= SF_HASALIAS;
+				s->next = source;
+				if (source->type == SEOF) {
+					/* prevent infinite recursion at EOS */
+					source->u.tblp = p;
+					source->flags |= SF_HASALIAS;
+				}
+				source = s;
+				afree(yylval.cp, ATEMP);
+				goto Again;
+			}
+		}
+	}
+
+	return (LWORD);
+}
+
+static void
+gethere(bool iseof)
+{
+	struct ioword **p;
+
+	for (p = heres; p < herep; p++)
+		if (iseof && (*p)->delim[1] != '<')
+			/* only here strings at EOF */
+			return;
+		else
+			readhere(*p);
+	herep = heres;
+}
+
+/*
+ * read "<<word" text into temp file
+ */
+
+static void
+readhere(struct ioword *iop)
+{
+	int c;
+	char *volatile eof;
+	char *eofp;
+	int skiptabs;
+	XString xs;
+	char *xp;
+	int xpos;
+
+	if (iop->delim[1] == '<') {
+		/* process the here string */
+		xp = iop->heredoc = evalstr(iop->delim, DOBLANK);
+		c = strlen(xp) - 1;
+		memmove(xp, xp + 1, c);
+		xp[c] = '\n';
+		return;
+	}
+
+	eof = evalstr(iop->delim, 0);
+
+	if (!(iop->flag & IOEVAL))
+		ignore_backslash_newline++;
+
+	Xinit(xs, xp, 256, ATEMP);
+
+	for (;;) {
+		eofp = eof;
+		skiptabs = iop->flag & IOSKIP;
+		xpos = Xsavepos(xs, xp);
+		while ((c = getsc()) != 0) {
+			if (skiptabs) {
+				if (c == '\t')
+					continue;
+				skiptabs = 0;
+			}
+			if (c != *eofp)
+				break;
+			Xcheck(xs, xp);
+			Xput(xs, xp, c);
+			eofp++;
+		}
+		/* Allow EOF here so commands with out trailing newlines
+		 * will work (eg, ksh -c '...', $(...), etc).
+		 */
+		if (*eofp == '\0' && (c == 0 || c == '\n')) {
+			xp = Xrestpos(xs, xp, xpos);
+			break;
+		}
+		ungetsc(c);
+		while ((c = getsc()) != '\n') {
+			if (c == 0)
+				yyerror("here document '%s' unclosed\n", eof);
+			Xcheck(xs, xp);
+			Xput(xs, xp, c);
+		}
+		Xcheck(xs, xp);
+		Xput(xs, xp, c);
+	}
+	Xput(xs, xp, '\0');
+	iop->heredoc = Xclose(xs, xp);
+
+	if (!(iop->flag & IOEVAL))
+		ignore_backslash_newline--;
+}
+
+void
+yyerror(const char *fmt, ...)
+{
+	va_list va;
+
+	/* pop aliases and re-reads */
+	while (source->type == SALIAS || source->type == SREREAD)
+		source = source->next;
+	source->str = null;	/* zap pending input */
+
+	error_prefix(true);
+	va_start(va, fmt);
+	shf_vfprintf(shl_out, fmt, va);
+	va_end(va);
+	errorfz();
+}
+
+/*
+ * input for yylex with alias expansion
+ */
+
+Source *
+pushs(int type, Area *areap)
+{
+	Source *s;
+
+	s = alloc(sizeof(Source), areap);
+	memset(s, 0, sizeof(Source));
+	s->type = type;
+	s->str = null;
+	s->areap = areap;
+	if (type == SFILE || type == SSTDIN)
+		XinitN(s->xs, 256, s->areap);
+	return (s);
+}
+
+static int
+getsc__(void)
+{
+	Source *s = source;
+	int c;
+
+ getsc_again:
+	while ((c = *s->str++) == 0) {
+		s->str = NULL;		/* return 0 for EOF by default */
+		switch (s->type) {
+		case SEOF:
+			s->str = null;
+			return (0);
+
+		case SSTDIN:
+		case SFILE:
+			getsc_line(s);
+			break;
+
+		case SWSTR:
+			break;
+
+		case SSTRING:
+			break;
+
+		case SWORDS:
+			s->start = s->str = *s->u.strv++;
+			s->type = SWORDSEP;
+			break;
+
+		case SWORDSEP:
+			if (*s->u.strv == NULL) {
+				s->start = s->str = "\n";
+				s->type = SEOF;
+			} else {
+				s->start = s->str = " ";
+				s->type = SWORDS;
+			}
+			break;
+
+		case SALIAS:
+			if (s->flags & SF_ALIASEND) {
+				/* pass on an unused SF_ALIAS flag */
+				source = s->next;
+				source->flags |= s->flags & SF_ALIAS;
+				s = source;
+			} else if (*s->u.tblp->val.s &&
+			    (c = strnul(s->u.tblp->val.s)[-1], ksh_isspace(c))) {
+				source = s = s->next;	/* pop source stack */
+				/* Note that this alias ended with a space,
+				 * enabling alias expansion on the following
+				 * word.
+				 */
+				s->flags |= SF_ALIAS;
+			} else {
+				/* At this point, we need to keep the current
+				 * alias in the source list so recursive
+				 * aliases can be detected and we also need
+				 * to return the next character. Do this
+				 * by temporarily popping the alias to get
+				 * the next character and then put it back
+				 * in the source list with the SF_ALIASEND
+				 * flag set.
+				 */
+				source = s->next;	/* pop source stack */
+				source->flags |= s->flags & SF_ALIAS;
+				c = getsc__();
+				if (c) {
+					s->flags |= SF_ALIASEND;
+					s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+					s->start = s->str = s->ugbuf;
+					s->next = source;
+					source = s;
+				} else {
+					s = source;
+					/* avoid reading eof twice */
+					s->str = NULL;
+					break;
+				}
+			}
+			continue;
+
+		case SREREAD:
+			if (s->start != s->ugbuf)	/* yuck */
+				afree(s->u.freeme, ATEMP);
+			source = s = s->next;
+			continue;
+		}
+		if (s->str == NULL) {
+			s->type = SEOF;
+			s->start = s->str = null;
+			return ('\0');
+		}
+		if (s->flags & SF_ECHO) {
+			shf_puts(s->str, shl_out);
+			shf_flush(shl_out);
+		}
+	}
+	/* check for UTF-8 byte order mark */
+	if (s->flags & SF_FIRST) {
+		s->flags &= ~SF_FIRST;
+		if (((unsigned char)c == 0xEF) &&
+		    (((const unsigned char *)(s->str))[0] == 0xBB) &&
+		    (((const unsigned char *)(s->str))[1] == 0xBF)) {
+			s->str += 2;
+			UTFMODE = 1;
+			goto getsc_again;
+		}
+	}
+	return (c);
+}
+
+static void
+getsc_line(Source *s)
+{
+	char *xp = Xstring(s->xs, xp), *cp;
+	bool interactive = Flag(FTALKING) && s->type == SSTDIN;
+	int have_tty = interactive && (s->flags & SF_TTY);
+
+	/* Done here to ensure nothing odd happens when a timeout occurs */
+	XcheckN(s->xs, xp, LINE);
+	*xp = '\0';
+	s->start = s->str = xp;
+
+	if (have_tty && ksh_tmout) {
+		ksh_tmout_state = TMOUT_READING;
+		alarm(ksh_tmout);
+	}
+	if (interactive)
+		change_winsz();
+	if (have_tty && (
+#if !MKSH_S_NOVI
+	    Flag(FVI) ||
+#endif
+	    Flag(FEMACS) || Flag(FGMACS))) {
+		int nread;
+
+		nread = x_read(xp, LINE);
+		if (nread < 0)	/* read error */
+			nread = 0;
+		xp[nread] = '\0';
+		xp += nread;
+	} else {
+		if (interactive)
+			pprompt(prompt, 0);
+		else
+			s->line++;
+
+		while (1) {
+			char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf);
+
+			if (!p && shf_error(s->u.shf) &&
+			    shf_errno(s->u.shf) == EINTR) {
+				shf_clearerr(s->u.shf);
+				if (trap)
+					runtraps(0);
+				continue;
+			}
+			if (!p || (xp = p, xp[-1] == '\n'))
+				break;
+			/* double buffer size */
+			xp++;	/* move past NUL so doubling works... */
+			XcheckN(s->xs, xp, Xlength(s->xs, xp));
+			xp--;	/* ...and move back again */
+		}
+		/* flush any unwanted input so other programs/builtins
+		 * can read it. Not very optimal, but less error prone
+		 * than flushing else where, dealing with redirections,
+		 * etc.
+		 * todo: reduce size of shf buffer (~128?) if SSTDIN
+		 */
+		if (s->type == SSTDIN)
+			shf_flush(s->u.shf);
+	}
+	/* XXX: temporary kludge to restore source after a
+	 * trap may have been executed.
+	 */
+	source = s;
+	if (have_tty && ksh_tmout) {
+		ksh_tmout_state = TMOUT_EXECUTING;
+		alarm(0);
+	}
+	cp = Xstring(s->xs, xp);
+#ifndef MKSH_SMALL
+	if (interactive && *cp == '!' && cur_prompt == PS1) {
+		int linelen;
+
+		linelen = Xlength(s->xs, xp);
+		XcheckN(s->xs, xp, fc_e_n + /* NUL */ 1);
+		/* reload after potential realloc */
+		cp = Xstring(s->xs, xp);
+		/* change initial '!' into space */
+		*cp = ' ';
+		/* NUL terminate the current string */
+		*xp = '\0';
+		/* move the actual string forward */
+		memmove(cp + fc_e_n, cp, linelen + /* NUL */ 1);
+		xp += fc_e_n;
+		/* prepend it with "fc -e -" */
+		memcpy(cp, fc_e_, fc_e_n);
+	}
+#endif
+	s->start = s->str = cp;
+	strip_nuls(Xstring(s->xs, xp), Xlength(s->xs, xp));
+	/* Note: if input is all nulls, this is not eof */
+	if (Xlength(s->xs, xp) == 0) {
+		/* EOF */
+		if (s->type == SFILE)
+			shf_fdclose(s->u.shf);
+		s->str = NULL;
+	} else if (interactive && *s->str &&
+	    (cur_prompt != PS1 || !ctype(*s->str, C_IFS | C_IFSWS))) {
+		histsave(&s->line, s->str, true, true);
+#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
+	} else if (interactive && cur_prompt == PS1) {
+		cp = Xstring(s->xs, xp);
+		while (*cp && ctype(*cp, C_IFSWS))
+			++cp;
+		if (!*cp)
+			histsync();
+#endif
+	}
+	if (interactive)
+		set_prompt(PS2, NULL);
+}
+
+void
+set_prompt(int to, Source *s)
+{
+	cur_prompt = to;
+
+	switch (to) {
+	case PS1:	/* command */
+		/* Substitute ! and !! here, before substitutions are done
+		 * so ! in expanded variables are not expanded.
+		 * NOTE: this is not what AT&T ksh does (it does it after
+		 * substitutions, POSIX doesn't say which is to be done.
+		 */
+		{
+			struct shf *shf;
+			char * volatile ps1;
+			Area *saved_atemp;
+
+			ps1 = str_val(global("PS1"));
+			shf = shf_sopen(NULL, strlen(ps1) * 2,
+			    SHF_WR | SHF_DYNAMIC, NULL);
+			while (*ps1)
+				if (*ps1 != '!' || *++ps1 == '!')
+					shf_putchar(*ps1++, shf);
+				else
+					shf_fprintf(shf, "%d",
+						s ? s->line + 1 : 0);
+			ps1 = shf_sclose(shf);
+			saved_atemp = ATEMP;
+			newenv(E_ERRH);
+			if (sigsetjmp(e->jbuf, 0)) {
+				prompt = safe_prompt;
+				/* Don't print an error - assume it has already
+				 * been printed. Reason is we may have forked
+				 * to run a command and the child may be
+				 * unwinding its stack through this code as it
+				 * exits.
+				 */
+			} else {
+				char *cp = substitute(ps1, 0);
+				strdupx(prompt, cp, saved_atemp);
+			}
+			quitenv(NULL);
+		}
+		break;
+	case PS2:	/* command continuation */
+		prompt = str_val(global("PS2"));
+		break;
+	}
+}
+
+static int
+dopprompt(const char *cp, int ntruncate, bool doprint)
+{
+	int columns = 0, lines = 0, indelimit = 0;
+	char delimiter = 0;
+
+	/* Undocumented AT&T ksh feature:
+	 * If the second char in the prompt string is \r then the first char
+	 * is taken to be a non-printing delimiter and any chars between two
+	 * instances of the delimiter are not considered to be part of the
+	 * prompt length
+	 */
+	if (*cp && cp[1] == '\r') {
+		delimiter = *cp;
+		cp += 2;
+	}
+	for (; *cp; cp++) {
+		if (indelimit && *cp != delimiter)
+			;
+		else if (*cp == '\n' || *cp == '\r') {
+			lines += columns / x_cols + ((*cp == '\n') ? 1 : 0);
+			columns = 0;
+		} else if (*cp == '\t') {
+			columns = (columns | 7) + 1;
+		} else if (*cp == '\b') {
+			if (columns > 0)
+				columns--;
+		} else if (*cp == delimiter)
+			indelimit = !indelimit;
+		else if (UTFMODE && ((unsigned char)*cp > 0x7F)) {
+			const char *cp2;
+			columns += utf_widthadj(cp, &cp2);
+			if (doprint && (indelimit ||
+			    (ntruncate < (x_cols * lines + columns))))
+				shf_write(cp, cp2 - cp, shl_out);
+			cp = cp2 - /* loop increment */ 1;
+			continue;
+		} else
+			columns++;
+		if (doprint && (*cp != delimiter) &&
+		    (indelimit || (ntruncate < (x_cols * lines + columns))))
+			shf_putc(*cp, shl_out);
+	}
+	if (doprint)
+		shf_flush(shl_out);
+	return (x_cols * lines + columns);
+}
+
+
+void
+pprompt(const char *cp, int ntruncate)
+{
+	dopprompt(cp, ntruncate, true);
+}
+
+int
+promptlen(const char *cp)
+{
+	return (dopprompt(cp, 0, false));
+}
+
+/* Read the variable part of a ${...} expression (ie, up to but not including
+ * the :[-+?=#%] or close-brace.
+ */
+static char *
+get_brace_var(XString *wsp, char *wp)
+{
+	enum parse_state {
+		PS_INITIAL, PS_SAW_HASH, PS_IDENT,
+		PS_NUMBER, PS_VAR1
+	} state;
+	char c;
+
+	state = PS_INITIAL;
+	while (1) {
+		c = getsc();
+		/* State machine to figure out where the variable part ends. */
+		switch (state) {
+		case PS_INITIAL:
+			if (c == '#' || c == '!' || c == '%') {
+				state = PS_SAW_HASH;
+				break;
+			}
+			/* FALLTHROUGH */
+		case PS_SAW_HASH:
+			if (ksh_isalphx(c))
+				state = PS_IDENT;
+			else if (ksh_isdigit(c))
+				state = PS_NUMBER;
+			else if (ctype(c, C_VAR1))
+				state = PS_VAR1;
+			else
+				goto out;
+			break;
+		case PS_IDENT:
+			if (!ksh_isalnux(c)) {
+				if (c == '[') {
+					char *tmp, *p;
+
+					if (!arraysub(&tmp))
+						yyerror("missing ]\n");
+					*wp++ = c;
+					for (p = tmp; *p; ) {
+						Xcheck(*wsp, wp);
+						*wp++ = *p++;
+					}
+					afree(tmp, ATEMP);
+					c = getsc();	/* the ] */
+				}
+				goto out;
+			}
+			break;
+		case PS_NUMBER:
+			if (!ksh_isdigit(c))
+				goto out;
+			break;
+		case PS_VAR1:
+			goto out;
+		}
+		Xcheck(*wsp, wp);
+		*wp++ = c;
+	}
+ out:
+	*wp++ = '\0';	/* end of variable part */
+	ungetsc(c);
+	return (wp);
+}
+
+/*
+ * Save an array subscript - returns true if matching bracket found, false
+ * if eof or newline was found.
+ * (Returned string double null terminated)
+ */
+static int
+arraysub(char **strp)
+{
+	XString ws;
+	char	*wp;
+	char	c;
+	int	depth = 1;	/* we are just past the initial [ */
+
+	Xinit(ws, wp, 32, ATEMP);
+
+	do {
+		c = getsc();
+		Xcheck(ws, wp);
+		*wp++ = c;
+		if (c == '[')
+			depth++;
+		else if (c == ']')
+			depth--;
+	} while (depth > 0 && c && c != '\n');
+
+	*wp++ = '\0';
+	*strp = Xclose(ws, wp);
+
+	return (depth == 0 ? 1 : 0);
+}
+
+/* Unget a char: handles case when we are already at the start of the buffer */
+static const char *
+ungetsc(int c)
+{
+	if (backslash_skip)
+		backslash_skip--;
+	/* Don't unget eof... */
+	if (source->str == null && c == '\0')
+		return (source->str);
+	if (source->str > source->start)
+		source->str--;
+	else {
+		Source *s;
+
+		s = pushs(SREREAD, source->areap);
+		s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+		s->start = s->str = s->ugbuf;
+		s->next = source;
+		source = s;
+	}
+	return (source->str);
+}
+
+
+/* Called to get a char that isn't a \newline sequence. */
+static int
+getsc_bn(void)
+{
+	int c, c2;
+
+	if (ignore_backslash_newline)
+		return (getsc_());
+
+	if (backslash_skip == 1) {
+		backslash_skip = 2;
+		return (getsc_());
+	}
+
+	backslash_skip = 0;
+
+	while (1) {
+		c = getsc_();
+		if (c == '\\') {
+			if ((c2 = getsc_()) == '\n')
+				/* ignore the \newline; get the next char... */
+				continue;
+			ungetsc(c2);
+			backslash_skip = 1;
+		}
+		return (c);
+	}
+}
+
+static Lex_state *
+push_state_(State_info *si, Lex_state *old_end)
+{
+	Lex_state *news = alloc(STATE_BSIZE * sizeof(Lex_state), ATEMP);
+
+	news[0].ls_info.base = old_end;
+	si->base = &news[0];
+	si->end = &news[STATE_BSIZE];
+	return (&news[1]);
+}
+
+static Lex_state *
+pop_state_(State_info *si, Lex_state *old_end)
+{
+	Lex_state *old_base = si->base;
+
+	si->base = old_end->ls_info.base - STATE_BSIZE;
+	si->end = old_end->ls_info.base;
+
+	afree(old_base, ATEMP);
+
+	return (si->base + STATE_BSIZE - 1);
+}
+
+static int
+s_get(void)
+{
+	return (getsc());
+}
+
+static void
+s_put(int c)
+{
+	ungetsc(c);
+}
diff --git a/mksh/src/main.c b/mksh/src/main.c
new file mode 100644
index 0000000..f962dd4
--- /dev/null
+++ b/mksh/src/main.c
@@ -0,0 +1,1479 @@
+/*	$OpenBSD: main.c,v 1.46 2010/05/19 17:36:08 jasper Exp $	*/
+/*	$OpenBSD: tty.c,v 1.9 2006/03/14 22:08:01 deraadt Exp $	*/
+/*	$OpenBSD: io.c,v 1.22 2006/03/17 16:30:13 millert Exp $	*/
+/*	$OpenBSD: table.c,v 1.13 2009/01/17 22:06:44 millert Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#define	EXTERN
+#include "sh.h"
+
+#if HAVE_LANGINFO_CODESET
+#include <langinfo.h>
+#endif
+#if HAVE_SETLOCALE_CTYPE
+#include <locale.h>
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/main.c,v 1.167 2010/07/04 17:45:15 tg Exp $");
+
+extern char **environ;
+
+#if !HAVE_SETRESUGID
+extern uid_t kshuid;
+extern gid_t kshgid, kshegid;
+#endif
+
+#ifndef MKSHRC_PATH
+#define MKSHRC_PATH	"~/.mkshrc"
+#endif
+
+#ifndef MKSH_DEFAULT_TMPDIR
+#define MKSH_DEFAULT_TMPDIR	"/tmp"
+#endif
+
+static void reclaim(void);
+static void remove_temps(struct temp *);
+void chvt_reinit(void);
+Source *mksh_init(int, const char *[]);
+#ifdef SIGWINCH
+static void x_sigwinch(int);
+#endif
+
+static const char initifs[] = "IFS= \t\n";
+
+static const char initsubs[] =
+    "${PS2=> } ${PS3=#? } ${PS4=+ } ${SECONDS=0} ${TMOUT=0}";
+
+static const char *initcoms[] = {
+	T_typeset, "-r", initvsn, NULL,
+	T_typeset, "-x", "HOME", "PATH", "RANDOM", "SHELL", NULL,
+	T_typeset, "-i10", "COLUMNS", "LINES", "OPTIND", "PGRP", "PPID",
+	    "RANDOM", "SECONDS", "TMOUT", "USER_ID", NULL,
+	"alias",
+	"integer=typeset -i",
+	T_local_typeset,
+	"hash=alias -t",	/* not "alias -t --": hash -r needs to work */
+	"type=whence -v",
+#ifndef MKSH_UNEMPLOYED
+	"suspend=kill -STOP $$",
+#endif
+	"autoload=typeset -fu",
+	"functions=typeset -f",
+	"history=fc -l",
+	"nameref=typeset -n",
+	"nohup=nohup ",
+	r_fc_e_,
+	"source=PATH=$PATH:. command .",
+	"login=exec login",
+	NULL,
+	 /* this is what AT&T ksh seems to track, with the addition of emacs */
+	"alias", "-tU",
+	"cat", "cc", "chmod", "cp", "date", "ed", "emacs", "grep", "ls",
+	"make", "mv", "pr", "rm", "sed", "sh", "vi", "who", NULL,
+	NULL
+};
+
+static int initio_done;
+
+struct env *e = &kshstate_v.env_;
+
+void
+chvt_reinit(void)
+{
+	kshpid = procpid = getpid();
+	ksheuid = geteuid();
+	kshpgrp = getpgrp();
+	kshppid = getppid();
+}
+
+Source *
+mksh_init(int argc, const char *argv[])
+{
+	int argi, i;
+	Source *s;
+	struct block *l;
+	unsigned char restricted, errexit, utf_flag;
+	const char **wp;
+	struct tbl *vp;
+	struct stat s_stdin;
+#if !defined(_PATH_DEFPATH) && defined(_CS_PATH)
+	size_t k;
+	char *cp;
+#endif
+
+	/* do things like getpgrp() et al. */
+	chvt_reinit();
+
+	/* make sure argv[] is sane */
+	if (!*argv) {
+		static const char *empty_argv[] = {
+			"mksh", NULL
+		};
+
+		argv = empty_argv;
+		argc = 1;
+	}
+	kshname = *argv;
+
+	ainit(&aperm);		/* initialise permanent Area */
+
+	/* set up base environment */
+	kshstate_v.env_.type = E_NONE;
+	ainit(&kshstate_v.env_.area);
+	newblock();		/* set up global l->vars and l->funs */
+
+	/* Do this first so output routines (eg, errorf, shellf) can work */
+	initio();
+
+	argi = parse_args(argv, OF_FIRSTTIME, NULL);
+	if (argi < 0)
+		return (NULL);
+
+	initvar();
+
+	initctypes();
+
+	inittraps();
+
+	coproc_init();
+
+	/* set up variable and command dictionaries */
+	ktinit(&taliases, APERM, 0);
+	ktinit(&aliases, APERM, 0);
+#ifndef MKSH_NOPWNAM
+	ktinit(&homedirs, APERM, 0);
+#endif
+
+	/* define shell keywords */
+	initkeywords();
+
+	/* define built-in commands */
+	ktinit(&builtins, APERM,
+	    /* must be 80% of 2^n (currently 44 builtins) */ 64);
+	for (i = 0; mkshbuiltins[i].name != NULL; i++)
+		builtin(mkshbuiltins[i].name, mkshbuiltins[i].func);
+
+	init_histvec();
+
+#ifdef _PATH_DEFPATH
+	def_path = _PATH_DEFPATH;
+#else
+#ifdef _CS_PATH
+	if ((k = confstr(_CS_PATH, NULL, 0)) != (size_t)-1 && k > 0 &&
+	    confstr(_CS_PATH, cp = alloc(k + 1, APERM), k + 1) == k + 1)
+		def_path = cp;
+	else
+#endif
+		/*
+		 * this is uniform across all OSes unless it
+		 * breaks somewhere; don't try to optimise,
+		 * e.g. add stuff for Interix or remove /usr
+		 * for HURD, because e.g. Debian GNU/HURD is
+		 * "keeping a regular /usr"; this is supposed
+		 * to be a sane 'basic' default PATH
+		 */
+		def_path = "/bin:/usr/bin:/sbin:/usr/sbin";
+#endif
+
+	/* Set PATH to def_path (will set the path global variable).
+	 * (import of environment below will probably change this setting).
+	 */
+	vp = global("PATH");
+	/* setstr can't fail here */
+	setstr(vp, def_path, KSH_RETURN_ERROR);
+
+	/* Turn on nohup by default for now - will change to off
+	 * by default once people are aware of its existence
+	 * (AT&T ksh does not have a nohup option - it always sends
+	 * the hup).
+	 */
+	Flag(FNOHUP) = 1;
+
+	/* Turn on brace expansion by default. AT&T kshs that have
+	 * alternation always have it on.
+	 */
+	Flag(FBRACEEXPAND) = 1;
+
+	/* Set edit mode to emacs by default, may be overridden
+	 * by the environment or the user. Also, we want tab completion
+	 * on in vi by default. */
+	change_flag(FEMACS, OF_SPECIAL, 1);
+#if !MKSH_S_NOVI
+	Flag(FVITABCOMPLETE) = 1;
+#endif
+
+#ifdef MKSH_BINSHREDUCED
+	/* set FSH if we're called as -sh or /bin/sh or so */
+	{
+		const char *cc;
+
+		cc = kshname;
+		i = 0; argi = 0;
+		while (cc[i] != '\0')
+			/* the following line matches '-' and '/' ;-) */
+			if ((cc[i++] | 2) == '/')
+				argi = i;
+		if (((cc[argi] | 0x20) == 's') && ((cc[argi + 1] | 0x20) == 'h'))
+			change_flag(FSH, OF_FIRSTTIME, 1);
+	}
+#endif
+
+	/* import environment */
+	if (environ != NULL)
+		for (wp = (const char **)environ; *wp != NULL; wp++)
+			typeset(*wp, IMPORT | EXPORT, 0, 0, 0);
+
+	typeset(initifs, 0, 0, 0, 0);	/* for security */
+
+	/* assign default shell variable values */
+	substitute(initsubs, 0);
+
+	/* Figure out the current working directory and set $PWD */
+	{
+		struct stat s_pwd, s_dot;
+		struct tbl *pwd_v = global("PWD");
+		char *pwd = str_val(pwd_v);
+		char *pwdx = pwd;
+
+		/* Try to use existing $PWD if it is valid */
+		if (pwd[0] != '/' ||
+		    stat(pwd, &s_pwd) < 0 || stat(".", &s_dot) < 0 ||
+		    s_pwd.st_dev != s_dot.st_dev ||
+		    s_pwd.st_ino != s_dot.st_ino)
+			pwdx = NULL;
+		set_current_wd(pwdx);
+		if (current_wd[0])
+			simplify_path(current_wd);
+		/* Only set pwd if we know where we are or if it had a
+		 * bogus value
+		 */
+		if (current_wd[0] || pwd != null)
+			/* setstr can't fail here */
+			setstr(pwd_v, current_wd, KSH_RETURN_ERROR);
+	}
+
+	for (wp = initcoms; *wp != NULL; wp++) {
+		shcomexec(wp);
+		while (*wp != NULL)
+			wp++;
+	}
+	setint(global("COLUMNS"), 0);
+	setint(global("LINES"), 0);
+	setint(global("OPTIND"), 1);
+
+	safe_prompt = ksheuid ? "$ " : "# ";
+	vp = global("PS1");
+	/* Set PS1 if unset or we are root and prompt doesn't contain a # */
+	if (!(vp->flag & ISSET) ||
+	    (!ksheuid && !strchr(str_val(vp), '#')))
+		/* setstr can't fail here */
+		setstr(vp, safe_prompt, KSH_RETURN_ERROR);
+	setint((vp = global("PGRP")), (mksh_uari_t)kshpgrp);
+	vp->flag |= INT_U;
+	setint((vp = global("PPID")), (mksh_uari_t)kshppid);
+	vp->flag |= INT_U;
+	setint((vp = global("RANDOM")), (mksh_uari_t)evilhash(kshname));
+	vp->flag |= INT_U;
+	setint((vp = global("USER_ID")), (mksh_uari_t)ksheuid);
+	vp->flag |= INT_U;
+
+	/* Set this before parsing arguments */
+#if HAVE_SETRESUGID
+	Flag(FPRIVILEGED) = getuid() != ksheuid || getgid() != getegid();
+#else
+	Flag(FPRIVILEGED) = (kshuid = getuid()) != ksheuid ||
+	    (kshgid = getgid()) != (kshegid = getegid());
+#endif
+
+	/* this to note if monitor is set on command line (see below) */
+#ifndef MKSH_UNEMPLOYED
+	Flag(FMONITOR) = 127;
+#endif
+	/* this to note if utf-8 mode is set on command line (see below) */
+	UTFMODE = 2;
+
+	argi = parse_args(argv, OF_CMDLINE, NULL);
+	if (argi < 0)
+		return (NULL);
+
+	/* process this later only, default to off (hysterical raisins) */
+	utf_flag = UTFMODE;
+	UTFMODE = 0;
+
+	if (Flag(FCOMMAND)) {
+		s = pushs(SSTRING, ATEMP);
+		if (!(s->start = s->str = argv[argi++]))
+			errorf("-c requires an argument");
+#ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT
+		/* compatibility to MidnightBSD 0.1 /bin/sh (kludge) */
+		if (Flag(FSH) && argv[argi] && !strcmp(argv[argi], "--"))
+			++argi;
+#endif
+		if (argv[argi])
+			kshname = argv[argi++];
+	} else if (argi < argc && !Flag(FSTDIN)) {
+		s = pushs(SFILE, ATEMP);
+		s->file = argv[argi++];
+		s->u.shf = shf_open(s->file, O_RDONLY, 0,
+		    SHF_MAPHI | SHF_CLEXEC);
+		if (s->u.shf == NULL) {
+			shl_stdout_ok = 0;
+			warningf(true, "%s: %s", s->file, strerror(errno));
+			/* mandated by SUSv4 */
+			exstat = 127;
+			unwind(LERROR);
+		}
+		kshname = s->file;
+	} else {
+		Flag(FSTDIN) = 1;
+		s = pushs(SSTDIN, ATEMP);
+		s->file = "<stdin>";
+		s->u.shf = shf_fdopen(0, SHF_RD | can_seek(0),
+		    NULL);
+		if (isatty(0) && isatty(2)) {
+			Flag(FTALKING) = Flag(FTALKING_I) = 1;
+			/* The following only if isatty(0) */
+			s->flags |= SF_TTY;
+			s->u.shf->flags |= SHF_INTERRUPT;
+			s->file = NULL;
+		}
+	}
+
+	/* this bizarreness is mandated by POSIX */
+	if (fstat(0, &s_stdin) >= 0 && S_ISCHR(s_stdin.st_mode) &&
+	    Flag(FTALKING))
+		reset_nonblock(0);
+
+	/* initialise job control */
+	j_init();
+	/* set: 0/1; unset: 2->0 */
+	UTFMODE = utf_flag & 1;
+	/* Do this after j_init(), as tty_fd is not initialised until then */
+	if (Flag(FTALKING)) {
+		if (utf_flag == 2) {
+#ifndef MKSH_ASSUME_UTF8
+#define isuc(x)	(((x) != NULL) && \
+		    (stristr((x), "UTF-8") || stristr((x), "utf8")))
+		/* Check if we're in a UTF-8 locale */
+			const char *ccp;
+
+#if HAVE_SETLOCALE_CTYPE
+			ccp = setlocale(LC_CTYPE, "");
+#if HAVE_LANGINFO_CODESET
+			if (!isuc(ccp))
+				ccp = nl_langinfo(CODESET);
+#endif
+#else
+			/* these were imported from environ earlier */
+			ccp = str_val(global("LC_ALL"));
+			if (ccp == null)
+				ccp = str_val(global("LC_CTYPE"));
+			if (ccp == null)
+				ccp = str_val(global("LANG"));
+#endif
+			UTFMODE = isuc(ccp);
+#undef isuc
+#elif MKSH_ASSUME_UTF8
+			UTFMODE = 1;
+#else
+			UTFMODE = 0;
+#endif
+		}
+		x_init();
+	}
+
+#ifdef SIGWINCH
+	sigtraps[SIGWINCH].flags |= TF_SHELL_USES;
+	setsig(&sigtraps[SIGWINCH], x_sigwinch,
+	    SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+#endif
+
+	l = e->loc;
+	l->argv = &argv[argi - 1];
+	l->argc = argc - argi;
+	l->argv[0] = kshname;
+	getopts_reset(1);
+
+	/* Disable during .profile/ENV reading */
+	restricted = Flag(FRESTRICTED);
+	Flag(FRESTRICTED) = 0;
+	errexit = Flag(FERREXIT);
+	Flag(FERREXIT) = 0;
+
+	/* Do this before profile/$ENV so that if it causes problems in them,
+	 * user will know why things broke.
+	 */
+	if (!current_wd[0] && Flag(FTALKING))
+		warningf(false, "Cannot determine current working directory");
+
+	if (Flag(FLOGIN)) {
+		include(KSH_SYSTEM_PROFILE, 0, NULL, 1);
+		if (!Flag(FPRIVILEGED))
+			include(substitute("$HOME/.profile", 0), 0,
+			    NULL, 1);
+	}
+	if (Flag(FPRIVILEGED))
+		include("/etc/suid_profile", 0, NULL, 1);
+	else if (Flag(FTALKING)) {
+		char *env_file;
+
+		/* include $ENV */
+		env_file = substitute(substitute("${ENV:-" MKSHRC_PATH "}", 0),
+		    DOTILDE);
+		if (*env_file != '\0')
+			include(env_file, 0, NULL, 1);
+	}
+
+	if (restricted) {
+		static const char *restr_com[] = {
+			T_typeset, "-r", "PATH",
+			"ENV", "SHELL",
+			NULL
+		};
+		shcomexec(restr_com);
+		/* After typeset command... */
+		Flag(FRESTRICTED) = 1;
+	}
+	Flag(FERREXIT) = errexit;
+
+	if (Flag(FTALKING)) {
+		hist_init(s);
+		alarm_init();
+	} else
+		Flag(FTRACKALL) = 1;	/* set after ENV */
+
+	return (s);
+}
+
+int
+main(int argc, const char *argv[])
+{
+	Source *s;
+
+	kshstate_v.lcg_state_ = 5381;
+
+	if ((s = mksh_init(argc, argv))) {
+		/* put more entropy into the LCG */
+		change_random(s, sizeof(*s));
+		/* doesn’t return */
+		shell(s, true);
+	}
+	return (1);
+}
+
+int
+include(const char *name, int argc, const char **argv, int intr_ok)
+{
+	Source *volatile s = NULL;
+	struct shf *shf;
+	const char **volatile old_argv;
+	volatile int old_argc;
+	int i;
+
+	shf = shf_open(name, O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC);
+	if (shf == NULL)
+		return (-1);
+
+	if (argv) {
+		old_argv = e->loc->argv;
+		old_argc = e->loc->argc;
+	} else {
+		old_argv = NULL;
+		old_argc = 0;
+	}
+	newenv(E_INCL);
+	i = sigsetjmp(e->jbuf, 0);
+	if (i) {
+		quitenv(s ? s->u.shf : NULL);
+		if (old_argv) {
+			e->loc->argv = old_argv;
+			e->loc->argc = old_argc;
+		}
+		switch (i) {
+		case LRETURN:
+		case LERROR:
+			return (exstat & 0xff); /* see below */
+		case LINTR:
+			/* intr_ok is set if we are including .profile or $ENV.
+			 * If user ^Cs out, we don't want to kill the shell...
+			 */
+			if (intr_ok && (exstat - 128) != SIGTERM)
+				return (1);
+			/* FALLTHROUGH */
+		case LEXIT:
+		case LLEAVE:
+		case LSHELL:
+			unwind(i);
+			/* NOTREACHED */
+		default:
+			internal_errorf("include: %d", i);
+			/* NOTREACHED */
+		}
+	}
+	if (argv) {
+		e->loc->argv = argv;
+		e->loc->argc = argc;
+	}
+	s = pushs(SFILE, ATEMP);
+	s->u.shf = shf;
+	strdupx(s->file, name, ATEMP);
+	i = shell(s, false);
+	quitenv(s->u.shf);
+	if (old_argv) {
+		e->loc->argv = old_argv;
+		e->loc->argc = old_argc;
+	}
+	return (i & 0xff);	/* & 0xff to ensure value not -1 */
+}
+
+/* spawn a command into a shell optionally keeping track of the line number */
+int
+command(const char *comm, int line)
+{
+	Source *s;
+
+	s = pushs(SSTRING, ATEMP);
+	s->start = s->str = comm;
+	s->line = line;
+	return (shell(s, false));
+}
+
+/*
+ * run the commands from the input source, returning status.
+ */
+int
+shell(Source * volatile s, volatile int toplevel)
+{
+	struct op *t;
+	volatile int wastty = s->flags & SF_TTY;
+	volatile int attempts = 13;
+	volatile int interactive = Flag(FTALKING) && toplevel;
+	Source *volatile old_source = source;
+	int i;
+
+	s->flags |= SF_FIRST;	/* enable UTF-8 BOM check */
+
+	newenv(E_PARSE);
+	if (interactive)
+		really_exit = 0;
+	i = sigsetjmp(e->jbuf, 0);
+	if (i) {
+		switch (i) {
+		case LINTR: /* we get here if SIGINT not caught or ignored */
+		case LERROR:
+		case LSHELL:
+			if (interactive) {
+				if (i == LINTR)
+					shellf("\n");
+				/* Reset any eof that was read as part of a
+				 * multiline command.
+				 */
+				if (Flag(FIGNOREEOF) && s->type == SEOF &&
+				    wastty)
+					s->type = SSTDIN;
+				/* Used by exit command to get back to
+				 * top level shell. Kind of strange since
+				 * interactive is set if we are reading from
+				 * a tty, but to have stopped jobs, one only
+				 * needs FMONITOR set (not FTALKING/SF_TTY)...
+				 */
+				/* toss any input we have so far */
+				s->start = s->str = null;
+				break;
+			}
+			/* FALLTHROUGH */
+		case LEXIT:
+		case LLEAVE:
+		case LRETURN:
+			source = old_source;
+			quitenv(NULL);
+			unwind(i);	/* keep on going */
+			/* NOTREACHED */
+		default:
+			source = old_source;
+			quitenv(NULL);
+			internal_errorf("shell: %d", i);
+			/* NOTREACHED */
+		}
+	}
+	while (1) {
+		if (trap)
+			runtraps(0);
+
+		if (s->next == NULL) {
+			if (Flag(FVERBOSE))
+				s->flags |= SF_ECHO;
+			else
+				s->flags &= ~SF_ECHO;
+		}
+		if (interactive) {
+			j_notify();
+			set_prompt(PS1, s);
+		}
+		t = compile(s);
+		if (t != NULL && t->type == TEOF) {
+			if (wastty && Flag(FIGNOREEOF) && --attempts > 0) {
+				shellf("Use 'exit' to leave ksh\n");
+				s->type = SSTDIN;
+			} else if (wastty && !really_exit &&
+			    j_stopped_running()) {
+				really_exit = 1;
+				s->type = SSTDIN;
+			} else {
+				/* this for POSIX which says EXIT traps
+				 * shall be taken in the environment
+				 * immediately after the last command
+				 * executed.
+				 */
+				if (toplevel)
+					unwind(LEXIT);
+				break;
+			}
+		}
+		if (t && (!Flag(FNOEXEC) || (s->flags & SF_TTY)))
+			exstat = execute(t, 0, NULL);
+
+		if (t != NULL && t->type != TEOF && interactive && really_exit)
+			really_exit = 0;
+
+		reclaim();
+	}
+	quitenv(NULL);
+	source = old_source;
+	return (exstat);
+}
+
+/* return to closest error handler or shell(), exit if none found */
+void
+unwind(int i)
+{
+	/* ordering for EXIT vs ERR is a bit odd (this is what AT&T ksh does) */
+	if (i == LEXIT || (Flag(FERREXIT) && (i == LERROR || i == LINTR) &&
+	    sigtraps[SIGEXIT_].trap)) {
+		runtrap(&sigtraps[SIGEXIT_]);
+		i = LLEAVE;
+	} else if (Flag(FERREXIT) && (i == LERROR || i == LINTR)) {
+		runtrap(&sigtraps[SIGERR_]);
+		i = LLEAVE;
+	}
+	while (1) {
+		switch (e->type) {
+		case E_PARSE:
+		case E_FUNC:
+		case E_INCL:
+		case E_LOOP:
+		case E_ERRH:
+			siglongjmp(e->jbuf, i);
+			/* NOTREACHED */
+		case E_NONE:
+			if (i == LINTR)
+				e->flags |= EF_FAKE_SIGDIE;
+			/* FALLTHROUGH */
+		default:
+			quitenv(NULL);
+		}
+	}
+}
+
+void
+newenv(int type)
+{
+	struct env *ep;
+	char *cp;
+
+	/*
+	 * struct env includes ALLOC_ITEM for alignment constraints
+	 * so first get the actually used memory, then assign it
+	 */
+	cp = alloc(sizeof(struct env) - ALLOC_SIZE, ATEMP);
+	ep = (void *)(cp - ALLOC_SIZE);	/* undo what alloc() did */
+	/* initialise public members of struct env (not the ALLOC_ITEM) */
+	ainit(&ep->area);
+	ep->oenv = e;
+	ep->loc = e->loc;
+	ep->savefd = NULL;
+	ep->temps = NULL;
+	ep->type = type;
+	ep->flags = 0;
+	/* jump buffer is invalid because flags == 0 */
+	e = ep;
+}
+
+void
+quitenv(struct shf *shf)
+{
+	struct env *ep = e;
+	char *cp;
+	int fd;
+
+	if (ep->oenv && ep->oenv->loc != ep->loc)
+		popblock();
+	if (ep->savefd != NULL) {
+		for (fd = 0; fd < NUFILE; fd++)
+			/* if ep->savefd[fd] < 0, means fd was closed */
+			if (ep->savefd[fd])
+				restfd(fd, ep->savefd[fd]);
+		if (ep->savefd[2])	/* Clear any write errors */
+			shf_reopen(2, SHF_WR, shl_out);
+	}
+	/* Bottom of the stack.
+	 * Either main shell is exiting or cleanup_parents_env() was called.
+	 */
+	if (ep->oenv == NULL) {
+		if (ep->type == E_NONE) {	/* Main shell exiting? */
+#if HAVE_PERSISTENT_HISTORY
+			if (Flag(FTALKING))
+				hist_finish();
+#endif
+			j_exit();
+			if (ep->flags & EF_FAKE_SIGDIE) {
+				int sig = exstat - 128;
+
+				/* ham up our death a bit (AT&T ksh
+				 * only seems to do this for SIGTERM)
+				 * Don't do it for SIGQUIT, since we'd
+				 * dump a core..
+				 */
+				if ((sig == SIGINT || sig == SIGTERM) &&
+				    (kshpgrp == kshpid)) {
+					setsig(&sigtraps[sig], SIG_DFL,
+					    SS_RESTORE_CURR | SS_FORCE);
+					kill(0, sig);
+				}
+			}
+		}
+		if (shf)
+			shf_close(shf);
+		reclaim();
+		exit(exstat);
+	}
+	if (shf)
+		shf_close(shf);
+	reclaim();
+
+	e = e->oenv;
+
+	/* free the struct env - tricky due to the ALLOC_ITEM inside */
+	cp = (void *)ep;
+	afree(cp + ALLOC_SIZE, ATEMP);
+}
+
+/* Called after a fork to cleanup stuff left over from parents environment */
+void
+cleanup_parents_env(void)
+{
+	struct env *ep;
+	int fd;
+
+	mkssert(e != NULL);
+
+	/*
+	 * Don't clean up temporary files - parent will probably need them.
+	 * Also, can't easily reclaim memory since variables, etc. could be
+	 * anywhere.
+	 */
+
+	/* close all file descriptors hiding in savefd */
+	for (ep = e; ep; ep = ep->oenv) {
+		if (ep->savefd) {
+			for (fd = 0; fd < NUFILE; fd++)
+				if (ep->savefd[fd] > 0)
+					close(ep->savefd[fd]);
+			afree(ep->savefd, &ep->area);
+			ep->savefd = NULL;
+		}
+	}
+	e->oenv = NULL;
+}
+
+/* Called just before an execve cleanup stuff temporary files */
+void
+cleanup_proc_env(void)
+{
+	struct env *ep;
+
+	for (ep = e; ep; ep = ep->oenv)
+		remove_temps(ep->temps);
+}
+
+/* remove temp files and free ATEMP Area */
+static void
+reclaim(void)
+{
+	remove_temps(e->temps);
+	e->temps = NULL;
+	afreeall(&e->area);
+}
+
+static void
+remove_temps(struct temp *tp)
+{
+	for (; tp != NULL; tp = tp->next)
+		if (tp->pid == procpid)
+			unlink(tp->name);
+}
+
+/* Initialise tty_fd. Used for saving/reseting tty modes upon
+ * foreground job completion and for setting up tty process group.
+ */
+void
+tty_init(bool init_ttystate, bool need_tty)
+{
+	bool do_close = true;
+	int tfd;
+
+	if (tty_fd >= 0) {
+		close(tty_fd);
+		tty_fd = -1;
+	}
+	tty_devtty = 1;
+
+#ifdef _UWIN
+	/* XXX imake style */
+	if (isatty(3))
+		tfd = 3;
+	else
+#endif
+	if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) {
+		tty_devtty = 0;
+		if (need_tty)
+			warningf(false,
+			    "No controlling tty (open /dev/tty: %s)",
+			    strerror(errno));
+	}
+	if (tfd < 0) {
+		do_close = false;
+		if (isatty(0))
+			tfd = 0;
+		else if (isatty(2))
+			tfd = 2;
+		else {
+			if (need_tty)
+				warningf(false,
+				    "Can't find tty file descriptor");
+			return;
+		}
+	}
+	if ((tty_fd = fcntl(tfd, F_DUPFD, FDBASE)) < 0) {
+		if (need_tty)
+			warningf(false, "j_ttyinit: dup of tty fd failed: %s",
+			    strerror(errno));
+	} else if (fcntl(tty_fd, F_SETFD, FD_CLOEXEC) < 0) {
+		if (need_tty)
+			warningf(false,
+			    "j_ttyinit: can't set close-on-exec flag: %s",
+			    strerror(errno));
+		close(tty_fd);
+		tty_fd = -1;
+	} else if (init_ttystate)
+		tcgetattr(tty_fd, &tty_state);
+	if (do_close)
+		close(tfd);
+}
+
+void
+tty_close(void)
+{
+	if (tty_fd >= 0) {
+		close(tty_fd);
+		tty_fd = -1;
+	}
+}
+
+/* A shell error occurred (eg, syntax error, etc.) */
+void
+errorf(const char *fmt, ...)
+{
+	va_list va;
+
+	shl_stdout_ok = 0;	/* debugging: note that stdout not valid */
+	exstat = 1;
+	if (*fmt != 1) {
+		error_prefix(true);
+		va_start(va, fmt);
+		shf_vfprintf(shl_out, fmt, va);
+		va_end(va);
+		shf_putchar('\n', shl_out);
+	}
+	shf_flush(shl_out);
+	unwind(LERROR);
+}
+
+/* like errorf(), but no unwind is done */
+void
+warningf(bool fileline, const char *fmt, ...)
+{
+	va_list va;
+
+	error_prefix(fileline);
+	va_start(va, fmt);
+	shf_vfprintf(shl_out, fmt, va);
+	va_end(va);
+	shf_putchar('\n', shl_out);
+	shf_flush(shl_out);
+}
+
+/* Used by built-in utilities to prefix shell and utility name to message
+ * (also unwinds environments for special builtins).
+ */
+void
+bi_errorf(const char *fmt, ...)
+{
+	va_list va;
+
+	shl_stdout_ok = 0;	/* debugging: note that stdout not valid */
+	exstat = 1;
+	if (*fmt != 1) {
+		error_prefix(true);
+		/* not set when main() calls parse_args() */
+		if (builtin_argv0)
+			shf_fprintf(shl_out, "%s: ", builtin_argv0);
+		va_start(va, fmt);
+		shf_vfprintf(shl_out, fmt, va);
+		va_end(va);
+		shf_putchar('\n', shl_out);
+	}
+	shf_flush(shl_out);
+	/* POSIX special builtins and ksh special builtins cause
+	 * non-interactive shells to exit.
+	 * XXX odd use of KEEPASN; also may not want LERROR here
+	 */
+	if (builtin_flag & SPEC_BI) {
+		builtin_argv0 = NULL;
+		unwind(LERROR);
+	}
+}
+
+/* Called when something that shouldn't happen does */
+void
+internal_verrorf(const char *fmt, va_list ap)
+{
+	shf_fprintf(shl_out, "internal error: ");
+	shf_vfprintf(shl_out, fmt, ap);
+	shf_putchar('\n', shl_out);
+	shf_flush(shl_out);
+}
+
+void
+internal_errorf(const char *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	internal_verrorf(fmt, va);
+	va_end(va);
+	unwind(LERROR);
+}
+
+void
+internal_warningf(const char *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	internal_verrorf(fmt, va);
+	va_end(va);
+}
+
+/* used by error reporting functions to print "ksh: .kshrc[25]: " */
+void
+error_prefix(bool fileline)
+{
+	/* Avoid foo: foo[2]: ... */
+	if (!fileline || !source || !source->file ||
+	    strcmp(source->file, kshname) != 0)
+		shf_fprintf(shl_out, "%s: ", kshname + (*kshname == '-'));
+	if (fileline && source && source->file != NULL) {
+		shf_fprintf(shl_out, "%s[%d]: ", source->file,
+		    source->errline > 0 ? source->errline : source->line);
+		source->errline = 0;
+	}
+}
+
+/* printf to shl_out (stderr) with flush */
+void
+shellf(const char *fmt, ...)
+{
+	va_list va;
+
+	if (!initio_done) /* shl_out may not be set up yet... */
+		return;
+	va_start(va, fmt);
+	shf_vfprintf(shl_out, fmt, va);
+	va_end(va);
+	shf_flush(shl_out);
+}
+
+/* printf to shl_stdout (stdout) */
+void
+shprintf(const char *fmt, ...)
+{
+	va_list va;
+
+	if (!shl_stdout_ok)
+		internal_errorf("shl_stdout not valid");
+	va_start(va, fmt);
+	shf_vfprintf(shl_stdout, fmt, va);
+	va_end(va);
+}
+
+/* test if we can seek backwards fd (returns 0 or SHF_UNBUF) */
+int
+can_seek(int fd)
+{
+	struct stat statb;
+
+	return (fstat(fd, &statb) == 0 && !S_ISREG(statb.st_mode) ?
+	    SHF_UNBUF : 0);
+}
+
+struct shf shf_iob[3];
+
+void
+initio(void)
+{
+	shf_fdopen(1, SHF_WR, shl_stdout);	/* force buffer allocation */
+	shf_fdopen(2, SHF_WR, shl_out);
+	shf_fdopen(2, SHF_WR, shl_spare);	/* force buffer allocation */
+	initio_done = 1;
+}
+
+/* A dup2() with error checking */
+int
+ksh_dup2(int ofd, int nfd, bool errok)
+{
+	int rv;
+
+	if (((rv = dup2(ofd, nfd)) < 0) && !errok && (errno != EBADF))
+		errorf("too many files open in shell");
+
+#ifdef __ultrix
+	/* XXX imake style */
+	if (rv >= 0)
+		fcntl(nfd, F_SETFD, 0);
+#endif
+
+	return (rv);
+}
+
+/*
+ * move fd from user space (0<=fd<10) to shell space (fd>=10),
+ * set close-on-exec flag.
+ */
+short
+savefd(int fd)
+{
+	int nfd = fd;
+
+	if (fd < FDBASE && (nfd = fcntl(fd, F_DUPFD, FDBASE)) < 0 &&
+	    errno == EBADF)
+		return (-1);
+	if (nfd < 0 || nfd > SHRT_MAX)
+		errorf("too many files open in shell");
+	fcntl(nfd, F_SETFD, FD_CLOEXEC);
+	return ((short)nfd);
+}
+
+void
+restfd(int fd, int ofd)
+{
+	if (fd == 2)
+		shf_flush(&shf_iob[fd]);
+	if (ofd < 0)		/* original fd closed */
+		close(fd);
+	else if (fd != ofd) {
+		ksh_dup2(ofd, fd, true); /* XXX: what to do if this fails? */
+		close(ofd);
+	}
+}
+
+void
+openpipe(int *pv)
+{
+	int lpv[2];
+
+	if (pipe(lpv) < 0)
+		errorf("can't create pipe - try again");
+	pv[0] = savefd(lpv[0]);
+	if (pv[0] != lpv[0])
+		close(lpv[0]);
+	pv[1] = savefd(lpv[1]);
+	if (pv[1] != lpv[1])
+		close(lpv[1]);
+}
+
+void
+closepipe(int *pv)
+{
+	close(pv[0]);
+	close(pv[1]);
+}
+
+/* Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn
+ * a string (the X in 2>&X, read -uX, print -uX) into a file descriptor.
+ */
+int
+check_fd(const char *name, int mode, const char **emsgp)
+{
+	int fd, fl;
+
+	if (name[0] == 'p' && !name[1])
+		return (coproc_getfd(mode, emsgp));
+	for (fd = 0; ksh_isdigit(*name); ++name)
+		fd = (fd * 10) + *name - '0';
+	if (*name || fd >= FDBASE) {
+		if (emsgp)
+			*emsgp = "illegal file descriptor name";
+		return (-1);
+	}
+	if ((fl = fcntl(fd, F_GETFL, 0)) < 0) {
+		if (emsgp)
+			*emsgp = "bad file descriptor";
+		return (-1);
+	}
+	fl &= O_ACCMODE;
+	/* X_OK is a kludge to disable this check for dups (x<&1):
+	 * historical shells never did this check (XXX don't know what
+	 * POSIX has to say).
+	 */
+	if (!(mode & X_OK) && fl != O_RDWR && (
+	    ((mode & R_OK) && fl != O_RDONLY) ||
+	    ((mode & W_OK) && fl != O_WRONLY))) {
+		if (emsgp)
+			*emsgp = (fl == O_WRONLY) ?
+			    "fd not open for reading" :
+			    "fd not open for writing";
+		return (-1);
+	}
+	return (fd);
+}
+
+/* Called once from main */
+void
+coproc_init(void)
+{
+	coproc.read = coproc.readw = coproc.write = -1;
+	coproc.njobs = 0;
+	coproc.id = 0;
+}
+
+/* Called by c_read() when eof is read - close fd if it is the co-process fd */
+void
+coproc_read_close(int fd)
+{
+	if (coproc.read >= 0 && fd == coproc.read) {
+		coproc_readw_close(fd);
+		close(coproc.read);
+		coproc.read = -1;
+	}
+}
+
+/* Called by c_read() and by iosetup() to close the other side of the
+ * read pipe, so reads will actually terminate.
+ */
+void
+coproc_readw_close(int fd)
+{
+	if (coproc.readw >= 0 && coproc.read >= 0 && fd == coproc.read) {
+		close(coproc.readw);
+		coproc.readw = -1;
+	}
+}
+
+/* Called by c_print when a write to a fd fails with EPIPE and by iosetup
+ * when co-process input is dup'd
+ */
+void
+coproc_write_close(int fd)
+{
+	if (coproc.write >= 0 && fd == coproc.write) {
+		close(coproc.write);
+		coproc.write = -1;
+	}
+}
+
+/* Called to check for existence of/value of the co-process file descriptor.
+ * (Used by check_fd() and by c_read/c_print to deal with -p option).
+ */
+int
+coproc_getfd(int mode, const char **emsgp)
+{
+	int fd = (mode & R_OK) ? coproc.read : coproc.write;
+
+	if (fd >= 0)
+		return (fd);
+	if (emsgp)
+		*emsgp = "no coprocess";
+	return (-1);
+}
+
+/* called to close file descriptors related to the coprocess (if any)
+ * Should be called with SIGCHLD blocked.
+ */
+void
+coproc_cleanup(int reuse)
+{
+	/* This to allow co-processes to share output pipe */
+	if (!reuse || coproc.readw < 0 || coproc.read < 0) {
+		if (coproc.read >= 0) {
+			close(coproc.read);
+			coproc.read = -1;
+		}
+		if (coproc.readw >= 0) {
+			close(coproc.readw);
+			coproc.readw = -1;
+		}
+	}
+	if (coproc.write >= 0) {
+		close(coproc.write);
+		coproc.write = -1;
+	}
+}
+
+struct temp *
+maketemp(Area *ap, Temp_type type, struct temp **tlist)
+{
+	struct temp *tp;
+	int len;
+	int fd;
+	char *pathname;
+	const char *dir;
+
+	dir = tmpdir ? tmpdir : MKSH_DEFAULT_TMPDIR;
+#if HAVE_MKSTEMP
+	len = strlen(dir) + 6 + 10 + 1;
+#else
+	pathname = tempnam(dir, "mksh.");
+	len = ((pathname == NULL) ? 0 : strlen(pathname)) + 1;
+#endif
+	tp = alloc(sizeof(struct temp) + len, ap);
+	tp->name = (char *)&tp[1];
+#if !HAVE_MKSTEMP
+	if (pathname == NULL)
+		tp->name[0] = '\0';
+	else {
+		memcpy(tp->name, pathname, len);
+		free(pathname);
+	}
+#endif
+	pathname = tp->name;
+	tp->shf = NULL;
+	tp->type = type;
+#if HAVE_MKSTEMP
+	shf_snprintf(pathname, len, "%s/mksh.XXXXXXXXXX", dir);
+	if ((fd = mkstemp(pathname)) >= 0)
+#else
+	if (tp->name[0] && (fd = open(tp->name, O_CREAT | O_RDWR, 0600)) >= 0)
+#endif
+		tp->shf = shf_fdopen(fd, SHF_WR, NULL);
+	tp->pid = procpid;
+
+	tp->next = *tlist;
+	*tlist = tp;
+	return (tp);
+}
+
+/*
+ * We use a similar collision resolution algorithm as Python 2.5.4
+ * but with a slightly tweaked implementation written from scratch.
+ */
+
+#define	INIT_TBLS	8	/* initial table size (power of 2) */
+#define PERTURB_SHIFT	5	/* see Python 2.5.4 Objects/dictobject.c */
+
+static void texpand(struct table *, size_t);
+static int tnamecmp(const void *, const void *);
+static struct tbl *ktscan(struct table *, const char *, uint32_t,
+    struct tbl ***);
+
+static void
+texpand(struct table *tp, size_t nsize)
+{
+	size_t i, j, osize = tp->size, perturb;
+	struct tbl *tblp, **pp;
+	struct tbl **ntblp, **otblp = tp->tbls;
+
+	ntblp = alloc(nsize * sizeof(struct tbl *), tp->areap);
+	for (i = 0; i < nsize; i++)
+		ntblp[i] = NULL;
+	tp->size = nsize;
+	tp->nfree = (nsize * 4) / 5;	/* table can get 80% full */
+	tp->tbls = ntblp;
+	if (otblp == NULL)
+		return;
+	nsize--;			/* from here on nsize := mask */
+	for (i = 0; i < osize; i++)
+		if ((tblp = otblp[i]) != NULL) {
+			if ((tblp->flag & DEFINED)) {
+				/* search for free hash table slot */
+				j = (perturb = tblp->ua.hval) & nsize;
+				goto find_first_empty_slot;
+ find_next_empty_slot:
+				j = (j << 2) + j + perturb + 1;
+				perturb >>= PERTURB_SHIFT;
+ find_first_empty_slot:
+				pp = &ntblp[j & nsize];
+				if (*pp != NULL)
+					goto find_next_empty_slot;
+				/* found an empty hash table slot */
+				*pp = tblp;
+				tp->nfree--;
+			} else if (!(tblp->flag & FINUSE)) {
+				afree(tblp, tp->areap);
+			}
+		}
+	afree(otblp, tp->areap);
+}
+
+void
+ktinit(struct table *tp, Area *ap, size_t tsize)
+{
+	tp->areap = ap;
+	tp->tbls = NULL;
+	tp->size = tp->nfree = 0;
+	if (tsize)
+		texpand(tp, tsize);
+}
+
+/* table, name (key) to search for, hash(name), rv pointer to tbl ptr */
+static struct tbl *
+ktscan(struct table *tp, const char *name, uint32_t h, struct tbl ***ppp)
+{
+	size_t j, perturb, mask;
+	struct tbl **pp, *p;
+
+	mask = tp->size - 1;
+	/* search for hash table slot matching name */
+	j = (perturb = h) & mask;
+	goto find_first_slot;
+ find_next_slot:
+	j = (j << 2) + j + perturb + 1;
+	perturb >>= PERTURB_SHIFT;
+ find_first_slot:
+	pp = &tp->tbls[j & mask];
+	if ((p = *pp) != NULL && (p->ua.hval != h || !(p->flag & DEFINED) ||
+	    strcmp(p->name, name)))
+		goto find_next_slot;
+	/* p == NULL if not found, correct found entry otherwise */
+	if (ppp)
+		*ppp = pp;
+	return (p);
+}
+
+/* table, name (key) to search for, hash(n) */
+struct tbl *
+ktsearch(struct table *tp, const char *n, uint32_t h)
+{
+	return (tp->size ? ktscan(tp, n, h, NULL) : NULL);
+}
+
+/* table, name (key) to enter, hash(n) */
+struct tbl *
+ktenter(struct table *tp, const char *n, uint32_t h)
+{
+	struct tbl **pp, *p;
+	int len;
+
+	if (tp->size == 0)
+		texpand(tp, INIT_TBLS);
+ Search:
+	if ((p = ktscan(tp, n, h, &pp)))
+		return (p);
+
+	if (tp->nfree <= 0) {
+		/* too full */
+		texpand(tp, 2 * tp->size);
+		goto Search;
+	}
+
+	/* create new tbl entry */
+	len = strlen(n) + 1;
+	p = alloc(offsetof(struct tbl, name[0]) + len, tp->areap);
+	p->flag = 0;
+	p->type = 0;
+	p->areap = tp->areap;
+	p->ua.hval = h;
+	p->u2.field = 0;
+	p->u.array = NULL;
+	memcpy(p->name, n, len);
+
+	/* enter in tp->tbls */
+	tp->nfree--;
+	*pp = p;
+	return (p);
+}
+
+void
+ktwalk(struct tstate *ts, struct table *tp)
+{
+	ts->left = tp->size;
+	ts->next = tp->tbls;
+}
+
+struct tbl *
+ktnext(struct tstate *ts)
+{
+	while (--ts->left >= 0) {
+		struct tbl *p = *ts->next++;
+		if (p != NULL && (p->flag & DEFINED))
+			return (p);
+	}
+	return (NULL);
+}
+
+static int
+tnamecmp(const void *p1, const void *p2)
+{
+	const struct tbl *a = *((const struct tbl * const *)p1);
+	const struct tbl *b = *((const struct tbl * const *)p2);
+
+	return (strcmp(a->name, b->name));
+}
+
+struct tbl **
+ktsort(struct table *tp)
+{
+	size_t i;
+	struct tbl **p, **sp, **dp;
+
+	p = alloc((tp->size + 1) * sizeof(struct tbl *), ATEMP);
+	sp = tp->tbls;		/* source */
+	dp = p;			/* dest */
+	i = (size_t)tp->size;
+	while (i--)
+		if ((*dp = *sp++) != NULL && (((*dp)->flag & DEFINED) ||
+		    ((*dp)->flag & ARRAY)))
+			dp++;
+	qsort(p, (i = dp - p), sizeof(void *), tnamecmp);
+	p[i] = NULL;
+	return (p);
+}
+
+#ifdef SIGWINCH
+static void
+x_sigwinch(int sig MKSH_A_UNUSED)
+{
+	/* this runs inside interrupt context, with errno saved */
+
+	got_winch = 1;
+}
+#endif
diff --git a/mksh/src/misc.c b/mksh/src/misc.c
new file mode 100644
index 0000000..75a4de1
--- /dev/null
+++ b/mksh/src/misc.c
@@ -0,0 +1,1579 @@
+/*	$OpenBSD: misc.c,v 1.37 2009/04/19 20:34:05 sthen Exp $	*/
+/*	$OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+#if !HAVE_GETRUSAGE
+#include <sys/times.h>
+#endif
+#if HAVE_GRP_H
+#include <grp.h>
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.141 2010/07/17 22:09:36 tg Exp $");
+
+unsigned char chtypes[UCHAR_MAX + 1];	/* type bits for unsigned char */
+
+#if !HAVE_SETRESUGID
+uid_t kshuid;
+gid_t kshgid, kshegid;
+#endif
+
+static int do_gmatch(const unsigned char *, const unsigned char *,
+    const unsigned char *, const unsigned char *);
+static const unsigned char *cclass(const unsigned char *, int);
+#ifdef TIOCSCTTY
+static void chvt(const char *);
+#endif
+
+/*
+ * Fast character classes
+ */
+void
+setctypes(const char *s, int t)
+{
+	unsigned int i;
+
+	if (t & C_IFS) {
+		for (i = 0; i < UCHAR_MAX + 1; i++)
+			chtypes[i] &= ~C_IFS;
+		chtypes[0] |= C_IFS; /* include \0 in C_IFS */
+	}
+	while (*s != 0)
+		chtypes[(unsigned char)*s++] |= t;
+}
+
+void
+initctypes(void)
+{
+	int c;
+
+	for (c = 'a'; c <= 'z'; c++)
+		chtypes[c] |= C_ALPHA;
+	for (c = 'A'; c <= 'Z'; c++)
+		chtypes[c] |= C_ALPHA;
+	chtypes['_'] |= C_ALPHA;
+	setctypes("0123456789", C_DIGIT);
+	setctypes(" \t\n|&;<>()", C_LEX1); /* \0 added automatically */
+	setctypes("*@#!$-?", C_VAR1);
+	setctypes(" \t\n", C_IFSWS);
+	setctypes("=-+?", C_SUBOP1);
+	setctypes("\t\n \"#$&'()*;<=>?[\\]`|", C_QUOTE);
+}
+
+/* called from XcheckN() to grow buffer */
+char *
+Xcheck_grow_(XString *xsp, const char *xp, unsigned int more)
+{
+	const char *old_beg = xsp->beg;
+
+	xsp->len += more > xsp->len ? more : xsp->len;
+	xsp->beg = aresize(xsp->beg, xsp->len + 8, xsp->areap);
+	xsp->end = xsp->beg + xsp->len;
+	return (xsp->beg + (xp - old_beg));
+}
+
+#define SHFLAGS_DEFNS
+#include "sh_flags.h"
+
+const struct shoption options[] = {
+#define SHFLAGS_ITEMS
+#include "sh_flags.h"
+};
+
+/*
+ * translate -o option into F* constant (also used for test -o option)
+ */
+size_t
+option(const char *n)
+{
+	size_t i;
+
+	if ((n[0] == '-' || n[0] == '+') && n[1] && !n[2]) {
+		for (i = 0; i < NELEM(options); i++)
+			if (options[i].c == n[1])
+				return (i);
+	} else for (i = 0; i < NELEM(options); i++)
+		if (options[i].name && strcmp(options[i].name, n) == 0)
+			return (i);
+
+	return ((size_t)-1);
+}
+
+struct options_info {
+	int opt_width;
+	int opts[NELEM(options)];
+};
+
+static char *options_fmt_entry(char *, int, int, const void *);
+static void printoptions(bool);
+
+/* format a single select menu item */
+static char *
+options_fmt_entry(char *buf, int buflen, int i, const void *arg)
+{
+	const struct options_info *oi = (const struct options_info *)arg;
+
+	shf_snprintf(buf, buflen, "%-*s %s",
+	    oi->opt_width, options[oi->opts[i]].name,
+	    Flag(oi->opts[i]) ? "on" : "off");
+	return (buf);
+}
+
+static void
+printoptions(bool verbose)
+{
+	int i = 0;
+
+	if (verbose) {
+		int n = 0, len, octs = 0;
+		struct options_info oi;
+
+		/* verbose version */
+		shf_puts("Current option settings\n", shl_stdout);
+
+		oi.opt_width = 0;
+		while (i < (int)NELEM(options)) {
+			if (options[i].name) {
+				oi.opts[n++] = i;
+				len = strlen(options[i].name);
+				if (len > octs)
+					octs = len;
+				len = utf_mbswidth(options[i].name);
+				if (len > oi.opt_width)
+					oi.opt_width = len;
+			}
+			++i;
+		}
+		print_columns(shl_stdout, n, options_fmt_entry, &oi,
+		    octs + 4, oi.opt_width + 4, true);
+	} else {
+		/* short version á la AT&T ksh93 */
+		shf_puts("set", shl_stdout);
+		while (i < (int)NELEM(options)) {
+			if (Flag(i) && options[i].name)
+				shprintf(" -o %s", options[i].name);
+			++i;
+		}
+		shf_putc('\n', shl_stdout);
+	}
+}
+
+char *
+getoptions(void)
+{
+	unsigned int i;
+	char m[(int) FNFLAGS + 1];
+	char *cp = m;
+
+	for (i = 0; i < NELEM(options); i++)
+		if (options[i].c && Flag(i))
+			*cp++ = options[i].c;
+	strndupx(cp, m, cp - m, ATEMP);
+	return (cp);
+}
+
+/* change a Flag(*) value; takes care of special actions */
+void
+change_flag(enum sh_flag f, int what, unsigned int newval)
+{
+	unsigned char oldval;
+
+	oldval = Flag(f);
+	Flag(f) = newval ? 1 : 0;	/* needed for tristates */
+#ifndef MKSH_UNEMPLOYED
+	if (f == FMONITOR) {
+		if (what != OF_CMDLINE && newval != oldval)
+			j_change();
+	} else
+#endif
+	  if ((
+#if !MKSH_S_NOVI
+	    f == FVI ||
+#endif
+	    f == FEMACS || f == FGMACS) && newval) {
+#if !MKSH_S_NOVI
+		Flag(FVI) =
+#endif
+		    Flag(FEMACS) = Flag(FGMACS) = 0;
+		Flag(f) = (unsigned char)newval;
+	} else if (f == FPRIVILEGED && oldval && !newval) {
+		/* Turning off -p? */
+#if HAVE_SETRESUGID
+		gid_t kshegid = getgid();
+
+		setresgid(kshegid, kshegid, kshegid);
+#if HAVE_SETGROUPS
+		setgroups(1, &kshegid);
+#endif
+		setresuid(ksheuid, ksheuid, ksheuid);
+#else
+		seteuid(ksheuid = kshuid = getuid());
+		setuid(ksheuid);
+		setegid(kshegid = kshgid = getgid());
+		setgid(kshegid);
+#endif
+	} else if ((f == FPOSIX || f == FSH) && newval) {
+		Flag(FPOSIX) = Flag(FSH) = Flag(FBRACEEXPAND) = 0;
+		Flag(f) = (unsigned char)newval;
+	}
+	/* Changing interactive flag? */
+	if (f == FTALKING) {
+		if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid)
+			Flag(FTALKING_I) = (unsigned char)newval;
+	}
+}
+
+/* Parse command line & set command arguments. Returns the index of
+ * non-option arguments, -1 if there is an error.
+ */
+int
+parse_args(const char **argv,
+    int what,			/* OF_CMDLINE or OF_SET */
+    bool *setargsp)
+{
+	static char cmd_opts[NELEM(options) + 5]; /* o:T:\0 */
+	static char set_opts[NELEM(options) + 6]; /* A:o;s\0 */
+	char set, *opts;
+	const char *array = NULL;
+	Getopt go;
+	size_t i;
+	int optc, sortargs = 0, arrayset = 0;
+
+	/* First call? Build option strings... */
+	if (cmd_opts[0] == '\0') {
+		char *p = cmd_opts, *q = set_opts;
+
+		/* see cmd_opts[] declaration */
+		*p++ = 'o';
+		*p++ = ':';
+#if !defined(MKSH_SMALL) || defined(TIOCSCTTY)
+		*p++ = 'T';
+		*p++ = ':';
+#endif
+		/* see set_opts[] declaration */
+		*q++ = 'A';
+		*q++ = ':';
+		*q++ = 'o';
+		*q++ = ';';
+		*q++ = 's';
+
+		for (i = 0; i < NELEM(options); i++) {
+			if (options[i].c) {
+				if (options[i].flags & OF_CMDLINE)
+					*p++ = options[i].c;
+				if (options[i].flags & OF_SET)
+					*q++ = options[i].c;
+			}
+		}
+		*p = '\0';
+		*q = '\0';
+	}
+
+	if (what == OF_CMDLINE) {
+		const char *p = argv[0], *q;
+		/* Set FLOGIN before parsing options so user can clear
+		 * flag using +l.
+		 */
+		if (*p != '-')
+			for (q = p; *q; )
+				if (*q++ == '/')
+					p = q;
+		Flag(FLOGIN) = (*p == '-');
+		opts = cmd_opts;
+	} else if (what == OF_FIRSTTIME) {
+		opts = cmd_opts;
+	} else
+		opts = set_opts;
+	ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT);
+	while ((optc = ksh_getopt(argv, &go, opts)) != -1) {
+		set = (go.info & GI_PLUS) ? 0 : 1;
+		switch (optc) {
+		case 'A':
+			if (what == OF_FIRSTTIME)
+				break;
+			arrayset = set ? 1 : -1;
+			array = go.optarg;
+			break;
+
+		case 'o':
+			if (what == OF_FIRSTTIME)
+				break;
+			if (go.optarg == NULL) {
+				/* lone -o: print options
+				 *
+				 * Note that on the command line, -o requires
+				 * an option (ie, can't get here if what is
+				 * OF_CMDLINE).
+				 */
+				printoptions(set);
+				break;
+			}
+			i = option(go.optarg);
+			if ((enum sh_flag)i == FARC4RANDOM) {
+				warningf(true, "Do not use set ±o arc4random,"
+				    " it will be removed in the next version"
+				    " of mksh!");
+				return (0);
+			}
+			if ((i != (size_t)-1) && set == Flag(i))
+				/* Don't check the context if the flag
+				 * isn't changing - makes "set -o interactive"
+				 * work if you're already interactive. Needed
+				 * if the output of "set +o" is to be used.
+				 */
+				;
+			else if ((i != (size_t)-1) && (options[i].flags & what))
+				change_flag((enum sh_flag)i, what, set);
+			else {
+				bi_errorf("%s: bad option", go.optarg);
+				return (-1);
+			}
+			break;
+
+#if !defined(MKSH_SMALL) || defined(TIOCSCTTY)
+		case 'T':
+			if (what != OF_FIRSTTIME)
+				break;
+#ifndef TIOCSCTTY
+			errorf("no TIOCSCTTY ioctl");
+#else
+			change_flag(FTALKING, OF_CMDLINE, 1);
+			chvt(go.optarg);
+			break;
+#endif
+#endif
+
+		case '?':
+			return (-1);
+
+		default:
+			if (what == OF_FIRSTTIME)
+				break;
+			/* -s: sort positional params (AT&T ksh stupidity) */
+			if (what == OF_SET && optc == 's') {
+				sortargs = 1;
+				break;
+			}
+			for (i = 0; i < NELEM(options); i++)
+				if (optc == options[i].c &&
+				    (what & options[i].flags)) {
+					change_flag((enum sh_flag)i, what, set);
+					break;
+				}
+			if (i == NELEM(options))
+				internal_errorf("parse_args: '%c'", optc);
+		}
+	}
+	if (!(go.info & GI_MINUSMINUS) && argv[go.optind] &&
+	    (argv[go.optind][0] == '-' || argv[go.optind][0] == '+') &&
+	    argv[go.optind][1] == '\0') {
+		/* lone - clears -v and -x flags */
+		if (argv[go.optind][0] == '-')
+			Flag(FVERBOSE) = Flag(FXTRACE) = 0;
+		/* set skips lone - or + option */
+		go.optind++;
+	}
+	if (setargsp)
+		/* -- means set $#/$* even if there are no arguments */
+		*setargsp = !arrayset && ((go.info & GI_MINUSMINUS) ||
+		    argv[go.optind]);
+
+	if (arrayset && (!*array || *skip_varname(array, false))) {
+		bi_errorf("%s: is not an identifier", array);
+		return (-1);
+	}
+	if (sortargs) {
+		for (i = go.optind; argv[i]; i++)
+			;
+		qsort(&argv[go.optind], i - go.optind, sizeof(void *),
+		    xstrcmp);
+	}
+	if (arrayset)
+		go.optind += set_array(array, arrayset > 0 ? true : false,
+		    argv + go.optind);
+
+	return (go.optind);
+}
+
+/* parse a decimal number: returns 0 if string isn't a number, 1 otherwise */
+int
+getn(const char *s, int *ai)
+{
+	int i, c, rv = 0;
+	bool neg = false;
+
+	do {
+		c = *s++;
+	} while (ksh_isspace(c));
+	if (c == '-') {
+		neg = true;
+		c = *s++;
+	} else if (c == '+')
+		c = *s++;
+	*ai = i = 0;
+	do {
+		if (!ksh_isdigit(c))
+			goto getn_out;
+		i *= 10;
+		if (i < *ai)
+			/* overflow */
+			goto getn_out;
+		i += c - '0';
+		*ai = i;
+	} while ((c = *s++));
+	rv = 1;
+
+ getn_out:
+	if (neg)
+		*ai = -*ai;
+	return (rv);
+}
+
+/* getn() that prints error */
+int
+bi_getn(const char *as, int *ai)
+{
+	int rv;
+
+	if (!(rv = getn(as, ai)))
+		bi_errorf("%s: bad number", as);
+	return (rv);
+}
+
+/* -------- gmatch.c -------- */
+
+/*
+ * int gmatch(string, pattern)
+ * char *string, *pattern;
+ *
+ * Match a pattern as in sh(1).
+ * pattern character are prefixed with MAGIC by expand.
+ */
+
+int
+gmatchx(const char *s, const char *p, bool isfile)
+{
+	const char *se, *pe;
+
+	if (s == NULL || p == NULL)
+		return (0);
+
+	se = s + strlen(s);
+	pe = p + strlen(p);
+	/* isfile is false iff no syntax check has been done on
+	 * the pattern. If check fails, just to a strcmp().
+	 */
+	if (!isfile && !has_globbing(p, pe)) {
+		size_t len = pe - p + 1;
+		char tbuf[64];
+		char *t = len <= sizeof(tbuf) ? tbuf : alloc(len, ATEMP);
+		debunk(t, p, len);
+		return (!strcmp(t, s));
+	}
+	return (do_gmatch((const unsigned char *) s, (const unsigned char *) se,
+	    (const unsigned char *) p, (const unsigned char *) pe));
+}
+
+/* Returns if p is a syntacticly correct globbing pattern, false
+ * if it contains no pattern characters or if there is a syntax error.
+ * Syntax errors are:
+ *	- [ with no closing ]
+ *	- imbalanced $(...) expression
+ *	- [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d))
+ */
+/*XXX
+- if no magic,
+	if dest given, copy to dst
+	return ?
+- if magic && (no globbing || syntax error)
+	debunk to dst
+	return ?
+- return ?
+*/
+int
+has_globbing(const char *xp, const char *xpe)
+{
+	const unsigned char *p = (const unsigned char *) xp;
+	const unsigned char *pe = (const unsigned char *) xpe;
+	int c;
+	int nest = 0, bnest = 0;
+	int saw_glob = 0;
+	int in_bracket = 0; /* inside [...] */
+
+	for (; p < pe; p++) {
+		if (!ISMAGIC(*p))
+			continue;
+		if ((c = *++p) == '*' || c == '?')
+			saw_glob = 1;
+		else if (c == '[') {
+			if (!in_bracket) {
+				saw_glob = 1;
+				in_bracket = 1;
+				if (ISMAGIC(p[1]) && p[2] == NOT)
+					p += 2;
+				if (ISMAGIC(p[1]) && p[2] == ']')
+					p += 2;
+			}
+			/* XXX Do we need to check ranges here? POSIX Q */
+		} else if (c == ']') {
+			if (in_bracket) {
+				if (bnest)		/* [a*(b]) */
+					return (0);
+				in_bracket = 0;
+			}
+		} else if ((c & 0x80) && vstrchr("*+?@! ", c & 0x7f)) {
+			saw_glob = 1;
+			if (in_bracket)
+				bnest++;
+			else
+				nest++;
+		} else if (c == '|') {
+			if (in_bracket && !bnest)	/* *(a[foo|bar]) */
+				return (0);
+		} else if (c == /*(*/ ')') {
+			if (in_bracket) {
+				if (!bnest--)		/* *(a[b)c] */
+					return (0);
+			} else if (nest)
+				nest--;
+		}
+		/*
+		 * else must be a MAGIC-MAGIC, or MAGIC-!,
+		 * MAGIC--, MAGIC-], MAGIC-{, MAGIC-, MAGIC-}
+		 */
+	}
+	return (saw_glob && !in_bracket && !nest);
+}
+
+/* Function must return either 0 or 1 (assumed by code for 0x80|'!') */
+static int
+do_gmatch(const unsigned char *s, const unsigned char *se,
+    const unsigned char *p, const unsigned char *pe)
+{
+	int sc, pc;
+	const unsigned char *prest, *psub, *pnext;
+	const unsigned char *srest;
+
+	if (s == NULL || p == NULL)
+		return (0);
+	while (p < pe) {
+		pc = *p++;
+		sc = s < se ? *s : '\0';
+		s++;
+		if (!ISMAGIC(pc)) {
+			if (sc != pc)
+				return (0);
+			continue;
+		}
+		switch (*p++) {
+		case '[':
+			if (sc == 0 || (p = cclass(p, sc)) == NULL)
+				return (0);
+			break;
+
+		case '?':
+			if (sc == 0)
+				return (0);
+			if (UTFMODE) {
+				--s;
+				s += utf_ptradj((const void *)s);
+			}
+			break;
+
+		case '*':
+			if (p == pe)
+				return (1);
+			s--;
+			do {
+				if (do_gmatch(s, se, p, pe))
+					return (1);
+			} while (s++ < se);
+			return (0);
+
+		/**
+		 * [*+?@!](pattern|pattern|..)
+		 * This is also needed for ${..%..}, etc.
+		 */
+		case 0x80|'+': /* matches one or more times */
+		case 0x80|'*': /* matches zero or more times */
+			if (!(prest = pat_scan(p, pe, 0)))
+				return (0);
+			s--;
+			/* take care of zero matches */
+			if (p[-1] == (0x80 | '*') &&
+			    do_gmatch(s, se, prest, pe))
+				return (1);
+			for (psub = p; ; psub = pnext) {
+				pnext = pat_scan(psub, pe, 1);
+				for (srest = s; srest <= se; srest++) {
+					if (do_gmatch(s, srest, psub, pnext - 2) &&
+					    (do_gmatch(srest, se, prest, pe) ||
+					    (s != srest && do_gmatch(srest,
+					    se, p - 2, pe))))
+						return (1);
+				}
+				if (pnext == prest)
+					break;
+			}
+			return (0);
+
+		case 0x80|'?': /* matches zero or once */
+		case 0x80|'@': /* matches one of the patterns */
+		case 0x80|' ': /* simile for @ */
+			if (!(prest = pat_scan(p, pe, 0)))
+				return (0);
+			s--;
+			/* Take care of zero matches */
+			if (p[-1] == (0x80 | '?') &&
+			    do_gmatch(s, se, prest, pe))
+				return (1);
+			for (psub = p; ; psub = pnext) {
+				pnext = pat_scan(psub, pe, 1);
+				srest = prest == pe ? se : s;
+				for (; srest <= se; srest++) {
+					if (do_gmatch(s, srest, psub, pnext - 2) &&
+					    do_gmatch(srest, se, prest, pe))
+						return (1);
+				}
+				if (pnext == prest)
+					break;
+			}
+			return (0);
+
+		case 0x80|'!': /* matches none of the patterns */
+			if (!(prest = pat_scan(p, pe, 0)))
+				return (0);
+			s--;
+			for (srest = s; srest <= se; srest++) {
+				int matched = 0;
+
+				for (psub = p; ; psub = pnext) {
+					pnext = pat_scan(psub, pe, 1);
+					if (do_gmatch(s, srest, psub,
+					    pnext - 2)) {
+						matched = 1;
+						break;
+					}
+					if (pnext == prest)
+						break;
+				}
+				if (!matched &&
+				    do_gmatch(srest, se, prest, pe))
+					return (1);
+			}
+			return (0);
+
+		default:
+			if (sc != p[-1])
+				return (0);
+			break;
+		}
+	}
+	return (s == se);
+}
+
+static const unsigned char *
+cclass(const unsigned char *p, int sub)
+{
+	int c, d, notp, found = 0;
+	const unsigned char *orig_p = p;
+
+	if ((notp = (ISMAGIC(*p) && *++p == NOT)))
+		p++;
+	do {
+		c = *p++;
+		if (ISMAGIC(c)) {
+			c = *p++;
+			if ((c & 0x80) && !ISMAGIC(c)) {
+				c &= 0x7f;/* extended pattern matching: *+?@! */
+				/* XXX the ( char isn't handled as part of [] */
+				if (c == ' ') /* simile for @: plain (..) */
+					c = '(' /*)*/;
+			}
+		}
+		if (c == '\0')
+			/* No closing ] - act as if the opening [ was quoted */
+			return (sub == '[' ? orig_p : NULL);
+		if (ISMAGIC(p[0]) && p[1] == '-' &&
+		    (!ISMAGIC(p[2]) || p[3] != ']')) {
+			p += 2; /* MAGIC- */
+			d = *p++;
+			if (ISMAGIC(d)) {
+				d = *p++;
+				if ((d & 0x80) && !ISMAGIC(d))
+					d &= 0x7f;
+			}
+			/* POSIX says this is an invalid expression */
+			if (c > d)
+				return (NULL);
+		} else
+			d = c;
+		if (c == sub || (c <= sub && sub <= d))
+			found = 1;
+	} while (!(ISMAGIC(p[0]) && p[1] == ']'));
+
+	return ((found != notp) ? p+2 : NULL);
+}
+
+/* Look for next ) or | (if match_sep) in *(foo|bar) pattern */
+const unsigned char *
+pat_scan(const unsigned char *p, const unsigned char *pe, int match_sep)
+{
+	int nest = 0;
+
+	for (; p < pe; p++) {
+		if (!ISMAGIC(*p))
+			continue;
+		if ((*++p == /*(*/ ')' && nest-- == 0) ||
+		    (*p == '|' && match_sep && nest == 0))
+			return (p + 1);
+		if ((*p & 0x80) && vstrchr("*+?@! ", *p & 0x7f))
+			nest++;
+	}
+	return (NULL);
+}
+
+int
+xstrcmp(const void *p1, const void *p2)
+{
+	return (strcmp(*(const char * const *)p1, *(const char * const *)p2));
+}
+
+/* Initialise a Getopt structure */
+void
+ksh_getopt_reset(Getopt *go, int flags)
+{
+	go->optind = 1;
+	go->optarg = NULL;
+	go->p = 0;
+	go->flags = flags;
+	go->info = 0;
+	go->buf[1] = '\0';
+}
+
+
+/* getopt() used for shell built-in commands, the getopts command, and
+ * command line options.
+ * A leading ':' in options means don't print errors, instead return '?'
+ * or ':' and set go->optarg to the offending option character.
+ * If GF_ERROR is set (and option doesn't start with :), errors result in
+ * a call to bi_errorf().
+ *
+ * Non-standard features:
+ *	- ';' is like ':' in options, except the argument is optional
+ *	  (if it isn't present, optarg is set to 0).
+ *	  Used for 'set -o'.
+ *	- ',' is like ':' in options, except the argument always immediately
+ *	  follows the option character (optarg is set to the null string if
+ *	  the option is missing).
+ *	  Used for 'read -u2', 'print -u2' and fc -40.
+ *	- '#' is like ':' in options, expect that the argument is optional
+ *	  and must start with a digit. If the argument doesn't start with a
+ *	  digit, it is assumed to be missing and normal option processing
+ *	  continues (optarg is set to 0 if the option is missing).
+ *	  Used for 'typeset -LZ4'.
+ *	- accepts +c as well as -c IF the GF_PLUSOPT flag is present. If an
+ *	  option starting with + is accepted, the GI_PLUS flag will be set
+ *	  in go->info.
+ */
+int
+ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
+{
+	char c;
+	const char *o;
+
+	if (go->p == 0 || (c = argv[go->optind - 1][go->p]) == '\0') {
+		const char *arg = argv[go->optind], flag = arg ? *arg : '\0';
+
+		go->p = 1;
+		if (flag == '-' && arg[1] == '-' && arg[2] == '\0') {
+			go->optind++;
+			go->p = 0;
+			go->info |= GI_MINUSMINUS;
+			return (-1);
+		}
+		if (arg == NULL ||
+		    ((flag != '-' ) && /* neither a - nor a + (if + allowed) */
+		    (!(go->flags & GF_PLUSOPT) || flag != '+')) ||
+		    (c = arg[1]) == '\0') {
+			go->p = 0;
+			return (-1);
+		}
+		go->optind++;
+		go->info &= ~(GI_MINUS|GI_PLUS);
+		go->info |= flag == '-' ? GI_MINUS : GI_PLUS;
+	}
+	go->p++;
+	if (c == '?' || c == ':' || c == ';' || c == ',' || c == '#' ||
+	    !(o = cstrchr(optionsp, c))) {
+		if (optionsp[0] == ':') {
+			go->buf[0] = c;
+			go->optarg = go->buf;
+		} else {
+			warningf(true, "%s%s-%c: unknown option",
+			    (go->flags & GF_NONAME) ? "" : argv[0],
+			    (go->flags & GF_NONAME) ? "" : ": ", c);
+			if (go->flags & GF_ERROR)
+				bi_errorfz();
+		}
+		return ('?');
+	}
+	/* : means argument must be present, may be part of option argument
+	 *   or the next argument
+	 * ; same as : but argument may be missing
+	 * , means argument is part of option argument, and may be null.
+	 */
+	if (*++o == ':' || *o == ';') {
+		if (argv[go->optind - 1][go->p])
+			go->optarg = argv[go->optind - 1] + go->p;
+		else if (argv[go->optind])
+			go->optarg = argv[go->optind++];
+		else if (*o == ';')
+			go->optarg = NULL;
+		else {
+			if (optionsp[0] == ':') {
+				go->buf[0] = c;
+				go->optarg = go->buf;
+				return (':');
+			}
+			warningf(true, "%s%s-'%c' requires argument",
+			    (go->flags & GF_NONAME) ? "" : argv[0],
+			    (go->flags & GF_NONAME) ? "" : ": ", c);
+			if (go->flags & GF_ERROR)
+				bi_errorfz();
+			return ('?');
+		}
+		go->p = 0;
+	} else if (*o == ',') {
+		/* argument is attached to option character, even if null */
+		go->optarg = argv[go->optind - 1] + go->p;
+		go->p = 0;
+	} else if (*o == '#') {
+		/* argument is optional and may be attached or unattached
+		 * but must start with a digit. optarg is set to 0 if the
+		 * argument is missing.
+		 */
+		if (argv[go->optind - 1][go->p]) {
+			if (ksh_isdigit(argv[go->optind - 1][go->p])) {
+				go->optarg = argv[go->optind - 1] + go->p;
+				go->p = 0;
+			} else
+				go->optarg = NULL;
+		} else {
+			if (argv[go->optind] && ksh_isdigit(argv[go->optind][0])) {
+				go->optarg = argv[go->optind++];
+				go->p = 0;
+			} else
+				go->optarg = NULL;
+		}
+	}
+	return (c);
+}
+
+/* print variable/alias value using necessary quotes
+ * (POSIX says they should be suitable for re-entry...)
+ * No trailing newline is printed.
+ */
+void
+print_value_quoted(const char *s)
+{
+	const char *p;
+	int inquote = 0;
+
+	/* Test if any quotes are needed */
+	for (p = s; *p; p++)
+		if (ctype(*p, C_QUOTE))
+			break;
+	if (!*p) {
+		shf_puts(s, shl_stdout);
+		return;
+	}
+	for (p = s; *p; p++) {
+		if (*p == '\'') {
+			if (inquote)
+				shf_putc('\'', shl_stdout);
+			shf_putc('\\', shl_stdout);
+			inquote = 0;
+		} else if (!inquote) {
+			shf_putc('\'', shl_stdout);
+			inquote = 1;
+		}
+		shf_putc(*p, shl_stdout);
+	}
+	if (inquote)
+		shf_putc('\'', shl_stdout);
+}
+
+/*
+ * Print things in columns and rows - func() is called to format
+ * the i-th element
+ */
+void
+print_columns(struct shf *shf, int n,
+    char *(*func)(char *, int, int, const void *),
+    const void *arg, int max_oct, int max_col, bool prefcol)
+{
+	int i, r, c, rows, cols, nspace;
+	char *str;
+
+	if (n <= 0) {
+#ifndef MKSH_SMALL
+		internal_warningf("print_columns called with n=%d <= 0", n);
+#endif
+		return;
+	}
+
+	++max_oct;
+	str = alloc(max_oct, ATEMP);
+
+	/* ensure x_cols is valid first */
+	if (x_cols < MIN_COLS)
+		change_winsz();
+
+	/*
+	 * We use (max_col + 1) to consider the space separator.
+	 * Note that no space is printed after the last column
+	 * to avoid problems with terminals that have auto-wrap.
+	 */
+	cols = x_cols / (max_col + 1);
+
+	/* if we can only print one column anyway, skip the goo */
+	if (cols < 2) {
+		for (i = 0; i < n; ++i)
+			shf_fprintf(shf, "%s \n",
+			    (*func)(str, max_oct, i, arg));
+		goto out;
+	}
+
+	rows = (n + cols - 1) / cols;
+	if (prefcol && cols > rows) {
+		i = rows;
+		rows = cols > n ? n : cols;
+		cols = i;
+	}
+
+	max_col = -max_col;
+	nspace = (x_cols + max_col * cols) / cols;
+	if (nspace <= 0)
+		nspace = 1;
+	for (r = 0; r < rows; r++) {
+		for (c = 0; c < cols; c++) {
+			i = c * rows + r;
+			if (i < n) {
+				shf_fprintf(shf, "%*s", max_col,
+				    (*func)(str, max_oct, i, arg));
+				if (c + 1 < cols)
+					shf_fprintf(shf, "%*s", nspace, null);
+			}
+		}
+		shf_putchar('\n', shf);
+	}
+ out:
+	afree(str, ATEMP);
+}
+
+/* Strip any nul bytes from buf - returns new length (nbytes - # of nuls) */
+void
+strip_nuls(char *buf, int nbytes)
+{
+	char *dst;
+
+	/* nbytes check because some systems (older FreeBSDs) have a buggy
+	 * memchr()
+	 */
+	if (nbytes && (dst = memchr(buf, '\0', nbytes))) {
+		char *end = buf + nbytes;
+		char *p, *q;
+
+		for (p = dst; p < end; p = q) {
+			/* skip a block of nulls */
+			while (++p < end && *p == '\0')
+				;
+			/* find end of non-null block */
+			if (!(q = memchr(p, '\0', end - p)))
+				q = end;
+			memmove(dst, p, q - p);
+			dst += q - p;
+		}
+		*dst = '\0';
+	}
+}
+
+/* Like read(2), but if read fails due to non-blocking flag, resets flag
+ * and restarts read.
+ */
+int
+blocking_read(int fd, char *buf, int nbytes)
+{
+	int ret;
+	int tried_reset = 0;
+
+	while ((ret = read(fd, buf, nbytes)) < 0) {
+		if (!tried_reset && errno == EAGAIN) {
+			if (reset_nonblock(fd) > 0) {
+				tried_reset = 1;
+				continue;
+			}
+			errno = EAGAIN;
+		}
+		break;
+	}
+	return (ret);
+}
+
+/* Reset the non-blocking flag on the specified file descriptor.
+ * Returns -1 if there was an error, 0 if non-blocking wasn't set,
+ * 1 if it was.
+ */
+int
+reset_nonblock(int fd)
+{
+	int flags;
+
+	if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+		return (-1);
+	if (!(flags & O_NONBLOCK))
+		return (0);
+	flags &= ~O_NONBLOCK;
+	if (fcntl(fd, F_SETFL, flags) < 0)
+		return (-1);
+	return (1);
+}
+
+
+/* Like getcwd(), except bsize is ignored if buf is 0 (PATH_MAX is used) */
+char *
+ksh_get_wd(size_t *dlen)
+{
+	char *ret, *b;
+	size_t len = 1;
+
+#ifdef NO_PATH_MAX
+	if ((b = get_current_dir_name())) {
+		len = strlen(b) + 1;
+		strndupx(ret, b, len - 1, ATEMP);
+		free(b);
+	} else
+		ret = NULL;
+#else
+	if ((ret = getcwd((b = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX)))
+		ret = aresize(b, len = (strlen(b) + 1), ATEMP);
+	else
+		afree(b, ATEMP);
+#endif
+
+	if (dlen)
+		*dlen = len;
+	return (ret);
+}
+
+/*
+ *	Makes a filename into result using the following algorithm.
+ *	- make result NULL
+ *	- if file starts with '/', append file to result & set cdpathp to NULL
+ *	- if file starts with ./ or ../ append cwd and file to result
+ *	  and set cdpathp to NULL
+ *	- if the first element of cdpathp doesnt start with a '/' xx or '.' xx
+ *	  then cwd is appended to result.
+ *	- the first element of cdpathp is appended to result
+ *	- file is appended to result
+ *	- cdpathp is set to the start of the next element in cdpathp (or NULL
+ *	  if there are no more elements.
+ *	The return value indicates whether a non-null element from cdpathp
+ *	was appended to result.
+ */
+int
+make_path(const char *cwd, const char *file,
+    char **cdpathp,		/* & of : separated list */
+    XString *xsp,
+    int *phys_pathp)
+{
+	int rval = 0;
+	bool use_cdpath = true;
+	char *plist;
+	int len, plen = 0;
+	char *xp = Xstring(*xsp, xp);
+
+	if (!file)
+		file = null;
+
+	if (file[0] == '/') {
+		*phys_pathp = 0;
+		use_cdpath = false;
+	} else {
+		if (file[0] == '.') {
+			char c = file[1];
+
+			if (c == '.')
+				c = file[2];
+			if (c == '/' || c == '\0')
+				use_cdpath = false;
+		}
+
+		plist = *cdpathp;
+		if (!plist)
+			use_cdpath = false;
+		else if (use_cdpath) {
+			char *pend;
+
+			for (pend = plist; *pend && *pend != ':'; pend++)
+				;
+			plen = pend - plist;
+			*cdpathp = *pend ? pend + 1 : NULL;
+		}
+
+		if ((!use_cdpath || !plen || plist[0] != '/') &&
+		    (cwd && *cwd)) {
+			len = strlen(cwd);
+			XcheckN(*xsp, xp, len);
+			memcpy(xp, cwd, len);
+			xp += len;
+			if (cwd[len - 1] != '/')
+				Xput(*xsp, xp, '/');
+		}
+		*phys_pathp = Xlength(*xsp, xp);
+		if (use_cdpath && plen) {
+			XcheckN(*xsp, xp, plen);
+			memcpy(xp, plist, plen);
+			xp += plen;
+			if (plist[plen - 1] != '/')
+				Xput(*xsp, xp, '/');
+			rval = 1;
+		}
+	}
+
+	len = strlen(file) + 1;
+	XcheckN(*xsp, xp, len);
+	memcpy(xp, file, len);
+
+	if (!use_cdpath)
+		*cdpathp = NULL;
+
+	return (rval);
+}
+
+/*
+ * Simplify pathnames containing "." and ".." entries.
+ * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b"
+ */
+void
+simplify_path(char *pathl)
+{
+	char *cur, *t;
+	bool isrooted;
+	char *very_start = pathl, *start;
+
+	if (!*pathl)
+		return;
+
+	if ((isrooted = pathl[0] == '/'))
+		very_start++;
+
+	/* Before			After
+	 * /foo/			/foo
+	 * /foo/../../bar		/bar
+	 * /foo/./blah/..		/foo
+	 * .				.
+	 * ..				..
+	 * ./foo			foo
+	 * foo/../../../bar		../../bar
+	 */
+
+	for (cur = t = start = very_start; ; ) {
+		/* treat multiple '/'s as one '/' */
+		while (*t == '/')
+			t++;
+
+		if (*t == '\0') {
+			if (cur == pathl)
+				/* convert empty path to dot */
+				*cur++ = '.';
+			*cur = '\0';
+			break;
+		}
+
+		if (t[0] == '.') {
+			if (!t[1] || t[1] == '/') {
+				t += 1;
+				continue;
+			} else if (t[1] == '.' && (!t[2] || t[2] == '/')) {
+				if (!isrooted && cur == start) {
+					if (cur != very_start)
+						*cur++ = '/';
+					*cur++ = '.';
+					*cur++ = '.';
+					start = cur;
+				} else if (cur != start)
+					while (--cur > start && *cur != '/')
+						;
+				t += 2;
+				continue;
+			}
+		}
+
+		if (cur != very_start)
+			*cur++ = '/';
+
+		/* find/copy next component of pathname */
+		while (*t && *t != '/')
+			*cur++ = *t++;
+	}
+}
+
+
+void
+set_current_wd(char *pathl)
+{
+	size_t len = 1;
+	char *p = pathl;
+
+	if (p == NULL) {
+		if ((p = ksh_get_wd(&len)) == NULL)
+			p = null;
+	} else
+		len = strlen(p) + 1;
+
+	if (len > current_wd_size) {
+		afree(current_wd, APERM);
+		current_wd = alloc(current_wd_size = len, APERM);
+	}
+	memcpy(current_wd, p, len);
+	if (p != pathl && p != null)
+		afree(p, ATEMP);
+}
+
+#ifdef TIOCSCTTY
+extern void chvt_reinit(void);
+
+static void
+chvt(const char *fn)
+{
+	char dv[20];
+	struct stat sb;
+	int fd;
+
+	/* for entropy */
+	kshstate_f.h = evilhash(fn);
+
+	if (*fn == '-') {
+		memcpy(dv, "-/dev/null", sizeof("-/dev/null"));
+		fn = dv + 1;
+	} else {
+		if (stat(fn, &sb)) {
+			memcpy(dv, "/dev/ttyC", 9);
+			strlcpy(dv + 9, fn, sizeof(dv) - 9);
+			if (stat(dv, &sb)) {
+				strlcpy(dv + 8, fn, sizeof(dv) - 8);
+				if (stat(dv, &sb))
+					errorf("chvt: can't find tty %s", fn);
+			}
+			fn = dv;
+		}
+		if (!(sb.st_mode & S_IFCHR))
+			errorf("chvt: not a char device: %s", fn);
+		if ((sb.st_uid != 0) && chown(fn, 0, 0))
+			warningf(false, "chvt: cannot chown root %s", fn);
+		if (((sb.st_mode & 07777) != 0600) && chmod(fn, (mode_t)0600))
+			warningf(false, "chvt: cannot chmod 0600 %s", fn);
+#if HAVE_REVOKE
+		if (revoke(fn))
+#endif
+			warningf(false, "chvt: cannot revoke %s, new shell is"
+			    " potentially insecure", fn);
+	}
+	if ((fd = open(fn, O_RDWR)) == -1) {
+		sleep(1);
+		if ((fd = open(fn, O_RDWR)) == -1)
+			errorf("chvt: cannot open %s", fn);
+	}
+	switch (fork()) {
+	case -1:
+		errorf("chvt: %s failed", "fork");
+	case 0:
+		break;
+	default:
+		exit(0);
+	}
+	if (setsid() == -1)
+		errorf("chvt: %s failed", "setsid");
+	if (fn != dv + 1) {
+		if (ioctl(fd, TIOCSCTTY, NULL) == -1)
+			errorf("chvt: %s failed", "TIOCSCTTY");
+		if (tcflush(fd, TCIOFLUSH))
+			errorf("chvt: %s failed", "TCIOFLUSH");
+	}
+	ksh_dup2(fd, 0, false);
+	ksh_dup2(fd, 1, false);
+	ksh_dup2(fd, 2, false);
+	if (fd > 2)
+		close(fd);
+	chvt_reinit();
+}
+#endif
+
+#ifdef DEBUG
+char longsizes_are_okay[sizeof(long) == sizeof(unsigned long) ? 1 : -1];
+char arisize_is_okay[sizeof(mksh_ari_t) == 4 ? 1 : -1];
+char uarisize_is_okay[sizeof(mksh_uari_t) == 4 ? 1 : -1];
+
+char *
+strchr(char *p, int ch)
+{
+	for (;; ++p) {
+		if (*p == ch)
+			return (p);
+		if (!*p)
+			return (NULL);
+	}
+	/* NOTREACHED */
+}
+
+char *
+strstr(char *b, const char *l)
+{
+	char first, c;
+	size_t n;
+
+	if ((first = *l++) == '\0')
+		return (b);
+	n = strlen(l);
+ strstr_look:
+	while ((c = *b++) != first)
+		if (c == '\0')
+			return (NULL);
+	if (strncmp(b, l, n))
+		goto strstr_look;
+	return (b - 1);
+}
+#endif
+
+#ifndef MKSH_ASSUME_UTF8
+#if !HAVE_STRCASESTR
+const char *
+stristr(const char *b, const char *l)
+{
+	char first, c;
+	size_t n;
+
+	if ((first = *l++), ((first = ksh_tolower(first)) == '\0'))
+		return (b);
+	n = strlen(l);
+ stristr_look:
+	while ((c = *b++), ((c = ksh_tolower(c)) != first))
+		if (c == '\0')
+			return (NULL);
+	if (strncasecmp(b, l, n))
+		goto stristr_look;
+	return (b - 1);
+}
+#endif
+#endif
+
+#ifdef MKSH_SMALL
+char *
+strndup_(const char *src, size_t len, Area *ap)
+{
+	char *dst = NULL;
+
+	if (src != NULL) {
+		dst = alloc(len + 1, ap);
+		memcpy(dst, src, len);
+		dst[len] = '\0';
+	}
+	return (dst);
+}
+
+char *
+strdup_(const char *src, Area *ap)
+{
+	return (src == NULL ? NULL : strndup_(src, strlen(src), ap));
+}
+#endif
+
+#if !HAVE_GETRUSAGE
+#define INVTCK(r,t)	do {						\
+	r.tv_usec = ((t) % (1000000 / CLK_TCK)) * (1000000 / CLK_TCK);	\
+	r.tv_sec = (t) / CLK_TCK;					\
+} while (/* CONSTCOND */ 0)
+
+int
+getrusage(int what, struct rusage *ru)
+{
+	struct tms tms;
+	clock_t u, s;
+
+	if (/* ru == NULL || */ times(&tms) == (clock_t)-1)
+		return (-1);
+
+	switch (what) {
+	case RUSAGE_SELF:
+		u = tms.tms_utime;
+		s = tms.tms_stime;
+		break;
+	case RUSAGE_CHILDREN:
+		u = tms.tms_cutime;
+		s = tms.tms_cstime;
+		break;
+	default:
+		errno = EINVAL;
+		return (-1);
+	}
+	INVTCK(ru->ru_utime, u);
+	INVTCK(ru->ru_stime, s);
+	return (0);
+}
+#endif
+
+/*
+ * process the string available via fg (get a char)
+ * and fp (put back a char) for backslash escapes,
+ * assuming the first call to *fg gets the char di-
+ * rectly after the backslash; return the character
+ * (0..0xFF), Unicode (wc + 0x100), or -1 if no known
+ * escape sequence was found
+ */
+int
+unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
+{
+	int wc, i, c, fc;
+
+	fc = (*fg)();
+	switch (fc) {
+	case 'a':
+		/*
+		 * according to the comments in pdksh, \007 seems
+		 * to be more portable than \a (due to HP-UX cc,
+		 * Ultrix cc, old pcc, etc.) so we avoid the escape
+		 * sequence altogether in mksh and assume ASCII
+		 */
+		wc = 7;
+		break;
+	case 'b':
+		wc = '\b';
+		break;
+	case 'c':
+		if (!cstyle)
+			goto unknown_escape;
+		c = (*fg)();
+		wc = CTRL(c);
+		break;
+	case 'E':
+	case 'e':
+		wc = 033;
+		break;
+	case 'f':
+		wc = '\f';
+		break;
+	case 'n':
+		wc = '\n';
+		break;
+	case 'r':
+		wc = '\r';
+		break;
+	case 't':
+		wc = '\t';
+		break;
+	case 'v':
+		/* assume ASCII here as well */
+		wc = 11;
+		break;
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+		if (!cstyle)
+			goto unknown_escape;
+		/* FALLTHROUGH */
+	case '0':
+		if (cstyle)
+			(*fp)(fc);
+		/*
+		 * look for an octal number with up to three
+		 * digits, not counting the leading zero;
+		 * convert it to a raw octet
+		 */
+		wc = 0;
+		i = 3;
+		while (i--)
+			if ((c = (*fg)()) >= '0' && c <= '7')
+				wc = (wc << 3) + (c - '0');
+			else {
+				(*fp)(c);
+				break;
+			}
+		break;
+	case 'U':
+		i = 8;
+		if (0)
+		/* FALLTHROUGH */
+	case 'u':
+		i = 4;
+		if (0)
+		/* FALLTHROUGH */
+	case 'x':
+		i = cstyle ? -1 : 2;
+		/*
+		 * x:	look for a hexadecimal number with up to
+		 *	two (C style: arbitrary) digits; convert
+		 *	to raw octet (C style: Unicode if >0xFF)
+		 * u/U:	look for a hexadecimal number with up to
+		 *	four (U: eight) digits; convert to Unicode
+		 */
+		wc = 0;
+		while (i--) {
+			wc <<= 4;
+			if ((c = (*fg)()) >= '0' && c <= '9')
+				wc += c - '0';
+			else if (c >= 'A' && c <= 'F')
+				wc += c - 'A' + 10;
+			else if (c >= 'a' && c <= 'f')
+				wc += c - 'a' + 10;
+			else {
+				wc >>= 4;
+				(*fp)(c);
+				break;
+			}
+		}
+		if ((cstyle && wc > 0xFF) || fc != 'x')
+			/* Unicode marker */
+			wc += 0x100;
+		break;
+	case '\'':
+		if (!cstyle)
+			goto unknown_escape;
+		wc = '\'';
+		break;
+	case '\\':
+		wc = '\\';
+		break;
+	default:
+ unknown_escape:
+		(*fp)(fc);
+		return (-1);
+	}
+
+	return (wc);
+}
diff --git a/mksh/src/sh.h b/mksh/src/sh.h
new file mode 100644
index 0000000..11588c9
--- /dev/null
+++ b/mksh/src/sh.h
@@ -0,0 +1,1752 @@
+/*	$OpenBSD: sh.h,v 1.30 2010/01/04 18:07:11 deraadt Exp $	*/
+/*	$OpenBSD: shf.h,v 1.6 2005/12/11 18:53:51 deraadt Exp $	*/
+/*	$OpenBSD: table.h,v 1.7 2005/12/11 20:31:21 otto Exp $	*/
+/*	$OpenBSD: tree.h,v 1.10 2005/03/28 21:28:22 deraadt Exp $	*/
+/*	$OpenBSD: expand.h,v 1.6 2005/03/30 17:16:37 deraadt Exp $	*/
+/*	$OpenBSD: lex.h,v 1.11 2006/05/29 18:22:24 otto Exp $	*/
+/*	$OpenBSD: proto.h,v 1.33 2010/05/19 17:36:08 jasper Exp $	*/
+/*	$OpenBSD: c_test.h,v 1.4 2004/12/20 11:34:26 otto Exp $	*/
+/*	$OpenBSD: tty.h,v 1.5 2004/12/20 11:34:26 otto Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#ifdef __dietlibc__
+/* XXX imake style */
+#define _BSD_SOURCE	/* live, BSD, live! */
+#endif
+
+#if HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#if HAVE_SYS_SYSMACROS_H
+#include <sys/sysmacros.h>
+#endif
+#if HAVE_SYS_MKDEV_H
+#include <sys/mkdev.h>
+#endif
+#if HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#if HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+#if HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#include <limits.h>
+#if HAVE_PATHS_H
+#include <paths.h>
+#endif
+#include <pwd.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdarg.h>
+#if HAVE_STDBOOL_H
+#include <stdbool.h>
+#endif
+#include <stddef.h>
+#if HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <termios.h>
+#include <time.h>
+#if HAVE_ULIMIT_H
+#include <ulimit.h>
+#endif
+#include <unistd.h>
+#if HAVE_VALUES_H
+#include <values.h>
+#endif
+
+#undef __attribute__
+#if HAVE_ATTRIBUTE_BOUNDED
+#define MKSH_A_BOUNDED(x,y,z)	__attribute__((bounded (x, y, z)))
+#else
+#define MKSH_A_BOUNDED(x,y,z)	/* nothing */
+#endif
+#if HAVE_ATTRIBUTE_FORMAT
+#define MKSH_A_FORMAT(x,y,z)	__attribute__((format (x, y, z)))
+#else
+#define MKSH_A_FORMAT(x,y,z)	/* nothing */
+#endif
+#if HAVE_ATTRIBUTE_NONNULL
+#define MKSH_A_NONNULL(a)	__attribute__(a)
+#else
+#define MKSH_A_NONNULL(a)	/* nothing */
+#endif
+#if HAVE_ATTRIBUTE_NORETURN
+#define MKSH_A_NORETURN		__attribute__((noreturn))
+#else
+#define MKSH_A_NORETURN		/* nothing */
+#endif
+#if HAVE_ATTRIBUTE_UNUSED
+#define MKSH_A_UNUSED		__attribute__((unused))
+#else
+#define MKSH_A_UNUSED		/* nothing */
+#endif
+#if HAVE_ATTRIBUTE_USED
+#define MKSH_A_USED		__attribute__((used))
+#else
+#define MKSH_A_USED		/* nothing */
+#endif
+
+#if defined(MirBSD) && (MirBSD >= 0x09A1) && \
+    defined(__ELF__) && defined(__GNUC__) && \
+    !defined(__llvm__) && !defined(__NWCC__)
+/*
+ * We got usable __IDSTRING __COPYRIGHT __RCSID __SCCSID macros
+ * which work for all cases; no need to redefine them using the
+ * "portable" macros from below when we might have the "better"
+ * gcc+ELF specific macros or other system dependent ones.
+ */
+#else
+#undef __IDSTRING
+#undef __IDSTRING_CONCAT
+#undef __IDSTRING_EXPAND
+#undef __COPYRIGHT
+#undef __RCSID
+#undef __SCCSID
+#define __IDSTRING_CONCAT(l,p)		__LINTED__ ## l ## _ ## p
+#define __IDSTRING_EXPAND(l,p)		__IDSTRING_CONCAT(l,p)
+#define __IDSTRING(prefix, string)				\
+	static const char __IDSTRING_EXPAND(__LINE__,prefix) []	\
+	    MKSH_A_USED = "@(""#)" #prefix ": " string
+#define __COPYRIGHT(x)		__IDSTRING(copyright,x)
+#define __RCSID(x)		__IDSTRING(rcsid,x)
+#define __SCCSID(x)		__IDSTRING(sccsid,x)
+#endif
+
+#ifdef EXTERN
+__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.405 2010/08/24 15:19:54 tg Exp $");
+#endif
+#define MKSH_VERSION "R39 2010/08/24"
+
+#ifndef MKSH_INCLUDES_ONLY
+
+/* extra types */
+
+#if !HAVE_GETRUSAGE
+#undef rusage
+#undef RUSAGE_SELF
+#undef RUSAGE_CHILDREN
+#define rusage mksh_rusage
+#define RUSAGE_SELF	0
+#define RUSAGE_CHILDREN	-1
+
+struct rusage {
+	struct timeval ru_utime;
+	struct timeval ru_stime;
+};
+#endif
+
+#if !HAVE_RLIM_T
+typedef long rlim_t;
+#endif
+
+#if !HAVE_SIG_T
+#undef sig_t
+typedef void (*sig_t)(int);
+#endif
+
+#if !HAVE_STDBOOL_H
+/* kludge, but enough for mksh */
+typedef int bool;
+#define false 0
+#define true 1
+#endif
+
+#if !HAVE_CAN_INTTYPES
+#if !HAVE_CAN_UCBINTS
+typedef signed int int32_t;
+typedef unsigned int uint32_t;
+#else
+typedef u_int32_t uint32_t;
+#endif
+#endif
+
+#if !HAVE_CAN_INT8TYPE
+#if !HAVE_CAN_UCBINT8
+typedef unsigned char uint8_t;
+#else
+typedef u_int8_t uint8_t;
+#endif
+#endif
+
+/* extra macros */
+
+#ifndef timerclear
+#define timerclear(tvp)							\
+	do {								\
+		(tvp)->tv_sec = (tvp)->tv_usec = 0;			\
+	} while (/* CONSTCOND */ 0)
+#endif
+#ifndef timeradd
+#define timeradd(tvp, uvp, vvp)						\
+	do {								\
+		(vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec;		\
+		(vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec;	\
+		if ((vvp)->tv_usec >= 1000000) {			\
+			(vvp)->tv_sec++;				\
+			(vvp)->tv_usec -= 1000000;			\
+		}							\
+	} while (/* CONSTCOND */ 0)
+#endif
+#ifndef timersub
+#define timersub(tvp, uvp, vvp)						\
+	do {								\
+		(vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec;		\
+		(vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec;	\
+		if ((vvp)->tv_usec < 0) {				\
+			(vvp)->tv_sec--;				\
+			(vvp)->tv_usec += 1000000;			\
+		}							\
+	} while (/* CONSTCOND */ 0)
+#endif
+
+#define ksh_isdigit(c)	(((c) >= '0') && ((c) <= '9'))
+#define ksh_islower(c)	(((c) >= 'a') && ((c) <= 'z'))
+#define ksh_isupper(c)	(((c) >= 'A') && ((c) <= 'Z'))
+#define ksh_tolower(c)	(((c) >= 'A') && ((c) <= 'Z') ? (c) - 'A' + 'a' : (c))
+#define ksh_toupper(c)	(((c) >= 'a') && ((c) <= 'z') ? (c) - 'a' + 'A' : (c))
+#define ksh_isdash(s)	(((s) != NULL) && ((s)[0] == '-') && ((s)[1] == '\0'))
+#define ksh_isspace(c)	((((c) >= 0x09) && ((c) <= 0x0D)) || ((c) == 0x20))
+
+#ifdef NO_PATH_MAX
+#undef PATH_MAX
+#else
+#ifndef PATH_MAX
+#define PATH_MAX	1024
+#endif
+#endif
+#ifndef SIZE_MAX
+#ifdef SIZE_T_MAX
+#define SIZE_MAX	SIZE_T_MAX
+#else
+#define SIZE_MAX	((size_t)-1)
+#endif
+#endif
+#ifndef S_ISLNK
+#define S_ISLNK(m)	((m & 0170000) == 0120000)
+#endif
+#ifndef S_ISSOCK
+#define S_ISSOCK(m)	((m & 0170000) == 0140000)
+#endif
+#ifndef DEFFILEMODE
+#define DEFFILEMODE	(S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
+#endif
+
+#if !defined(MAP_FAILED)
+/* XXX imake style */
+#  if defined(__linux)
+#define MAP_FAILED	((void *)-1)
+#  elif defined(__bsdi__) || defined(__osf__) || defined(__ultrix)
+#define MAP_FAILED	((caddr_t)-1)
+#  endif
+#endif
+
+#ifndef NSIG
+#if defined(_NSIG)
+#define NSIG		_NSIG
+#elif defined(SIGMAX)
+#define NSIG		(SIGMAX+1)
+#endif
+#endif
+
+#undef BAD		/* AIX defines that somewhere */
+
+/* OS-dependent additions (functions, variables, by OS) */
+
+#if !HAVE_FLOCK_DECL
+extern int flock(int, int);
+#endif
+
+#if !HAVE_GETRUSAGE
+extern int getrusage(int, struct rusage *);
+#endif
+
+#if !HAVE_REVOKE_DECL
+extern int revoke(const char *);
+#endif
+
+#if !HAVE_SETMODE
+mode_t getmode(const void *, mode_t);
+void *setmode(const char *);
+#endif
+
+#ifdef __ultrix
+/* XXX imake style */
+int strcasecmp(const char *, const char *);
+#endif
+
+#if !HAVE_STRCASESTR
+const char *stristr(const char *, const char *);
+#endif
+
+#if !HAVE_STRLCPY
+size_t strlcpy(char *, const char *, size_t);
+#endif
+
+#if !HAVE_SYS_SIGLIST_DECL
+extern const char *const sys_siglist[];
+#endif
+
+#ifdef __INTERIX
+/* XXX imake style */
+#define makedev mkdev
+extern int __cdecl seteuid(uid_t);
+extern int __cdecl setegid(gid_t);
+#endif
+
+/* remove redundances */
+
+#if defined(MirBSD) && (MirBSD >= 0x08A8)
+#define MKSH_mirbsd_wcwidth
+#define utf_wcwidth(i) wcwidth((__WCHAR_TYPE__)i)
+extern int wcwidth(__WCHAR_TYPE__);
+#endif
+
+
+/* some useful #defines */
+#ifdef EXTERN
+# define I__(i) = i
+#else
+# define I__(i)
+# define EXTERN extern
+# define EXTERN_DEFINED
+#endif
+
+#define NELEM(a)	(sizeof(a) / sizeof((a)[0]))
+#define BIT(i)		(1 << (i))	/* define bit in flag */
+
+/* Table flag type - needs > 16 and < 32 bits */
+typedef int32_t Tflag;
+
+/* arithmetics types */
+typedef int32_t mksh_ari_t;
+typedef uint32_t mksh_uari_t;
+
+/* these shall be smaller than 100 */
+#ifdef MKSH_CONSERVATIVE_FDS
+#define NUFILE		32	/* Number of user-accessible files */
+#define FDBASE		10	/* First file usable by Shell */
+#else
+#define NUFILE		56	/* Number of user-accessible files */
+#define FDBASE		24	/* First file usable by Shell */
+#endif
+
+/* Make MAGIC a char that might be printed to make bugs more obvious, but
+ * not a char that is used often. Also, can't use the high bit as it causes
+ * portability problems (calling strchr(x, 0x80|'x') is error prone).
+ */
+#define MAGIC		(7)	/* prefix for *?[!{,} during expand */
+#define ISMAGIC(c)	((unsigned char)(c) == MAGIC)
+#define NOT		'!'	/* might use ^ (ie, [!...] vs [^..]) */
+
+#define LINE		4096	/* input line size */
+
+EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */
+EXTERN const char initvsn[] I__("KSH_VERSION=@(#)MIRBSD KSH " MKSH_VERSION);
+#define KSH_VERSION	(initvsn + /* "KSH_VERSION=@(#)" */ 16)
+
+EXTERN const char digits_uc[] I__("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+EXTERN const char digits_lc[] I__("0123456789abcdefghijklmnopqrstuvwxyz");
+
+/*
+ * Evil hack for const correctness due to API brokenness
+ */
+union mksh_cchack {
+	char *rw;
+	const char *ro;
+};
+union mksh_ccphack {
+	char **rw;
+	const char **ro;
+};
+
+/* for const debugging */
+#if defined(DEBUG) && defined(__GNUC__) && !defined(__ICC) && \
+    !defined(__INTEL_COMPILER) && !defined(__SUNPRO_C)
+char *ucstrchr(char *, int);
+char *ucstrstr(char *, const char *);
+#undef strchr
+#define strchr ucstrchr
+#define strstr ucstrstr
+#define cstrchr(s,c) ({			\
+	union mksh_cchack in, out;	\
+					\
+	in.ro = (s);			\
+	out.rw = ucstrchr(in.rw, (c));	\
+	(out.ro);			\
+})
+#define cstrstr(b,l) ({			\
+	union mksh_cchack in, out;	\
+					\
+	in.ro = (b);			\
+	out.rw = ucstrstr(in.rw, (l));	\
+	(out.ro);			\
+})
+#define vstrchr(s,c)	(cstrchr((s), (c)) != NULL)
+#define vstrstr(b,l)	(cstrstr((b), (l)) != NULL)
+#define mkssert(e)	((e) ? (void)0 : exit(255))
+#else /* !DEBUG, !gcc */
+#define cstrchr(s,c)	((const char *)strchr((s), (c)))
+#define cstrstr(s,c)	((const char *)strstr((s), (c)))
+#define vstrchr(s,c)	(strchr((s), (c)) != NULL)
+#define vstrstr(b,l)	(strstr((b), (l)) != NULL)
+#define mkssert(e)	((void)0)
+#endif
+
+/* use this ipv strchr(s, 0) but no side effects in s! */
+#define strnul(s)	((s) + strlen(s))
+
+#define utf_ptradjx(src, dst) do {					\
+	(dst) = (src) + utf_ptradj(src);				\
+} while (/* CONSTCOND */ 0)
+
+#ifdef MKSH_SMALL
+#define strdupx(d, s, ap) do { \
+	(d) = strdup_((s), (ap)); \
+} while (/* CONSTCOND */ 0)
+#define strndupx(d, s, n, ap) do { \
+	(d) = strndup_((s), (n), (ap)); \
+} while (/* CONSTCOND */ 0)
+#else
+/* be careful to evaluate arguments only once! */
+#define strdupx(d, s, ap) do {						\
+	const char *strdup_src = (s);					\
+	char *strdup_dst = NULL;					\
+									\
+	if (strdup_src != NULL) {					\
+		size_t strdup_len = strlen(strdup_src) + 1;		\
+		strdup_dst = alloc(strdup_len, (ap));			\
+		memcpy(strdup_dst, strdup_src, strdup_len);		\
+	}								\
+	(d) = strdup_dst;						\
+} while (/* CONSTCOND */ 0)
+#define strndupx(d, s, n, ap) do {					\
+	const char *strdup_src = (s);					\
+	char *strdup_dst = NULL;					\
+									\
+	if (strdup_src != NULL) {					\
+		size_t strndup_len = (n);				\
+		strdup_dst = alloc(strndup_len + 1, (ap));		\
+		memcpy(strdup_dst, strdup_src, strndup_len);		\
+		strdup_dst[strndup_len] = '\0';				\
+	}								\
+	(d) = strdup_dst;						\
+} while (/* CONSTCOND */ 0)
+#endif
+
+#if HAVE_STRCASESTR
+#define stristr(b,l)	((const char *)strcasestr((b), (l)))
+#endif
+
+#ifdef MKSH_SMALL
+#ifndef MKSH_CONSERVATIVE_FDS
+#define MKSH_CONSERVATIVE_FDS	/* defined */
+#endif
+#ifndef MKSH_NOPWNAM
+#define MKSH_NOPWNAM		/* defined */
+#endif
+#ifndef MKSH_S_NOVI
+#define MKSH_S_NOVI		1
+#endif
+#endif
+
+#ifndef MKSH_S_NOVI
+#define MKSH_S_NOVI		0
+#endif
+
+/*
+ * simple grouping allocator
+ */
+
+/* 1. internal structure */
+struct lalloc {
+	struct lalloc *next;
+};
+
+/* 2. sizes */
+#define ALLOC_ITEM	struct lalloc
+#define ALLOC_SIZE	(sizeof(ALLOC_ITEM))
+
+/* 3. group structure (only the same for lalloc.c) */
+typedef struct lalloc Area;
+
+
+EXTERN Area aperm;		/* permanent object space */
+#define APERM	&aperm
+#define ATEMP	&e->area
+
+/*
+ * flags (the order of these enums MUST match the order in misc.c(options[]))
+ */
+enum sh_flag {
+#define SHFLAGS_ENUMS
+#include "sh_flags.h"
+	FNFLAGS		/* (place holder: how many flags are there) */
+};
+
+#define Flag(f)	(kshstate_v.shell_flags_[(int)(f)])
+#define UTFMODE	Flag(FUNICODE)
+
+/*
+ * parsing & execution environment
+ */
+extern struct env {
+	ALLOC_ITEM __alloc_i;	/* internal, do not touch */
+	Area area;		/* temporary allocation area */
+	struct env *oenv;	/* link to previous environment */
+	struct block *loc;	/* local variables and functions */
+	short *savefd;		/* original redirected fds */
+	struct temp *temps;	/* temp files */
+	sigjmp_buf jbuf;	/* long jump back to env creator */
+	short type;		/* environment type - see below */
+	short flags;		/* EF_* */
+} *e;
+
+/* struct env.type values */
+#define E_NONE	0	/* dummy environment */
+#define E_PARSE	1	/* parsing command # */
+#define E_FUNC	2	/* executing function # */
+#define E_INCL	3	/* including a file via . # */
+#define E_EXEC	4	/* executing command tree */
+#define E_LOOP	5	/* executing for/while # */
+#define E_ERRH	6	/* general error handler # */
+/* # indicates env has valid jbuf (see unwind()) */
+
+/* struct env.flag values */
+#define EF_FUNC_PARSE	BIT(0)	/* function being parsed */
+#define EF_BRKCONT_PASS	BIT(1)	/* set if E_LOOP must pass break/continue on */
+#define EF_FAKE_SIGDIE	BIT(2)	/* hack to get info from unwind to quitenv */
+
+/* Do breaks/continues stop at env type e? */
+#define STOP_BRKCONT(t)	((t) == E_NONE || (t) == E_PARSE \
+			 || (t) == E_FUNC || (t) == E_INCL)
+/* Do returns stop at env type e? */
+#define STOP_RETURN(t)	((t) == E_FUNC || (t) == E_INCL)
+
+/* values for siglongjmp(e->jbuf, 0) */
+#define LRETURN	1	/* return statement */
+#define LEXIT	2	/* exit statement */
+#define LERROR	3	/* errorf() called */
+#define LLEAVE	4	/* untrappable exit/error */
+#define LINTR	5	/* ^C noticed */
+#define LBREAK	6	/* break statement */
+#define LCONTIN	7	/* continue statement */
+#define LSHELL	8	/* return to interactive shell() */
+#define LAEXPR	9	/* error in arithmetic expression */
+
+/*
+ * some kind of global shell state, for change_random() mostly
+ */
+
+EXTERN struct mksh_kshstate_v {
+	/* for change_random */
+	struct timeval cr_tv;	/* timestamp */
+	const void *cr_dp;	/* argument address */
+	size_t cr_dsz;		/* argument length */
+	uint32_t lcg_state_;	/* previous LCG state */
+	/* global state */
+	pid_t procpid_;		/* PID of executing process */
+	int exstat_;		/* exit status */
+	int subst_exstat_;	/* exit status of last $(..)/`..` */
+	struct env env_;	/* top-level parsing & execution env. */
+	uint8_t shell_flags_[FNFLAGS];
+} kshstate_v;
+EXTERN struct mksh_kshstate_f {
+	const char *kshname_;	/* $0 */
+	pid_t kshpid_;		/* $$, shell PID */
+	pid_t kshpgrp_;		/* process group of shell */
+	uid_t ksheuid_;		/* effective UID of shell */
+	pid_t kshppid_;		/* PID of parent of shell */
+	uint32_t h;		/* some kind of hash */
+} kshstate_f;
+#define kshname		kshstate_f.kshname_
+#define kshpid		kshstate_f.kshpid_
+#define procpid		kshstate_v.procpid_
+#define kshpgrp		kshstate_f.kshpgrp_
+#define ksheuid		kshstate_f.ksheuid_
+#define kshppid		kshstate_f.kshppid_
+#define exstat		kshstate_v.exstat_
+#define subst_exstat	kshstate_v.subst_exstat_
+
+/* evil hack: return hash(kshstate_f concat (kshstate_f'.h:=hash(arg))) */
+uint32_t evilhash(const char *);
+
+
+/* option processing */
+#define OF_CMDLINE	0x01	/* command line */
+#define OF_SET		0x02	/* set builtin */
+#define OF_SPECIAL	0x04	/* a special variable changing */
+#define OF_INTERNAL	0x08	/* set internally by shell */
+#define OF_FIRSTTIME	0x10	/* as early as possible, once */
+#define OF_ANY		(OF_CMDLINE | OF_SET | OF_SPECIAL | OF_INTERNAL)
+
+struct shoption {
+	const char *name;	/* long name of option */
+	char c;			/* character flag (if any) */
+	unsigned char flags;	/* OF_* */
+};
+extern const struct shoption options[];
+
+/* null value for variable; comparision pointer for unset */
+EXTERN char null[] I__("");
+/* helpers for string pooling */
+#define T_synerr "syntax error"
+EXTERN const char r_fc_e_[] I__("r=fc -e -");
+#define fc_e_		(r_fc_e_ + 2)		/* "fc -e -" */
+#define fc_e_n		7			/* strlen(fc_e_) */
+EXTERN const char T_local_typeset[] I__("local=typeset");
+#define T__typeset	(T_local_typeset + 5)	/* "=typeset" */
+#define T_typeset	(T_local_typeset + 6)	/* "typeset" */
+
+enum temp_type {
+	TT_HEREDOC_EXP,	/* expanded heredoc */
+	TT_HIST_EDIT	/* temp file used for history editing (fc -e) */
+};
+typedef enum temp_type Temp_type;
+/* temp/heredoc files. The file is removed when the struct is freed. */
+struct temp {
+	struct temp *next;
+	struct shf *shf;
+	char *name;
+	int pid;	/* pid of process parsed here-doc */
+	Temp_type type;
+};
+
+/*
+ * stdio and our IO routines
+ */
+
+#define shl_spare	(&shf_iob[0])	/* for c_read()/c_print() */
+#define shl_stdout	(&shf_iob[1])
+#define shl_out		(&shf_iob[2])
+EXTERN int shl_stdout_ok;
+
+/*
+ * trap handlers
+ */
+typedef struct trap {
+	const char *name;	/* short name */
+	const char *mess;	/* descriptive name */
+	char *trap;		/* trap command */
+	sig_t cursig;		/* current handler (valid if TF_ORIG_* set) */
+	sig_t shtrap;		/* shell signal handler */
+	int signal;		/* signal number */
+	int flags;		/* TF_* */
+	volatile sig_atomic_t set; /* trap pending */
+} Trap;
+
+/* values for Trap.flags */
+#define TF_SHELL_USES	BIT(0)	/* shell uses signal, user can't change */
+#define TF_USER_SET	BIT(1)	/* user has (tried to) set trap */
+#define TF_ORIG_IGN	BIT(2)	/* original action was SIG_IGN */
+#define TF_ORIG_DFL	BIT(3)	/* original action was SIG_DFL */
+#define TF_EXEC_IGN	BIT(4)	/* restore SIG_IGN just before exec */
+#define TF_EXEC_DFL	BIT(5)	/* restore SIG_DFL just before exec */
+#define TF_DFL_INTR	BIT(6)	/* when received, default action is LINTR */
+#define TF_TTY_INTR	BIT(7)	/* tty generated signal (see j_waitj) */
+#define TF_CHANGED	BIT(8)	/* used by runtrap() to detect trap changes */
+#define TF_FATAL	BIT(9)	/* causes termination if not trapped */
+
+/* values for setsig()/setexecsig() flags argument */
+#define SS_RESTORE_MASK	0x3	/* how to restore a signal before an exec() */
+#define SS_RESTORE_CURR	0	/* leave current handler in place */
+#define SS_RESTORE_ORIG	1	/* restore original handler */
+#define SS_RESTORE_DFL	2	/* restore to SIG_DFL */
+#define SS_RESTORE_IGN	3	/* restore to SIG_IGN */
+#define SS_FORCE	BIT(3)	/* set signal even if original signal ignored */
+#define SS_USER		BIT(4)	/* user is doing the set (ie, trap command) */
+#define SS_SHTRAP	BIT(5)	/* trap for internal use (ALRM, CHLD, WINCH) */
+
+#define SIGEXIT_	0	/* for trap EXIT */
+#define SIGERR_		NSIG	/* for trap ERR */
+
+EXTERN volatile sig_atomic_t trap;	/* traps pending? */
+EXTERN volatile sig_atomic_t intrsig;	/* pending trap interrupts command */
+EXTERN volatile sig_atomic_t fatal_trap;/* received a fatal signal */
+extern	Trap	sigtraps[NSIG+1];
+
+/* got_winch = 1 when we need to re-adjust the window size */
+#ifdef SIGWINCH
+EXTERN volatile sig_atomic_t got_winch I__(1);
+#else
+#define got_winch	true
+#endif
+
+/*
+ * TMOUT support
+ */
+/* values for ksh_tmout_state */
+enum tmout_enum {
+	TMOUT_EXECUTING = 0,	/* executing commands */
+	TMOUT_READING,		/* waiting for input */
+	TMOUT_LEAVING		/* have timed out */
+};
+EXTERN unsigned int ksh_tmout;
+EXTERN enum tmout_enum ksh_tmout_state I__(TMOUT_EXECUTING);
+
+/* For "You have stopped jobs" message */
+EXTERN int really_exit;
+
+/*
+ * fast character classes
+ */
+#define C_ALPHA	 BIT(0)		/* a-z_A-Z */
+#define C_DIGIT	 BIT(1)		/* 0-9 */
+#define C_LEX1	 BIT(2)		/* \t \n\0|&;<>() */
+#define C_VAR1	 BIT(3)		/* *@#!$-? */
+#define C_IFSWS	 BIT(4)		/* \t \n (IFS white space) */
+#define C_SUBOP1 BIT(5)		/* "=-+?" */
+#define C_QUOTE	 BIT(6)		/* \t\n "#$&'()*;<=>?[\]`| (needing quoting) */
+#define C_IFS	 BIT(7)		/* $IFS */
+#define C_SUBOP2 BIT(8)		/* "#%" (magic, see below) */
+
+extern unsigned char chtypes[];
+
+#define ctype(c, t)	!!( ((t) == C_SUBOP2) ?				\
+			    (((c) == '#' || (c) == '%') ? 1 : 0) :	\
+			    (chtypes[(unsigned char)(c)]&(t)) )
+#define ksh_isalphx(c)	ctype((c), C_ALPHA)
+#define ksh_isalnux(c)	ctype((c), C_ALPHA | C_DIGIT)
+
+EXTERN int ifs0 I__(' ');	/* for "$*" */
+
+/* Argument parsing for built-in commands and getopts command */
+
+/* Values for Getopt.flags */
+#define GF_ERROR	BIT(0)	/* call errorf() if there is an error */
+#define GF_PLUSOPT	BIT(1)	/* allow +c as an option */
+#define GF_NONAME	BIT(2)	/* don't print argv[0] in errors */
+
+/* Values for Getopt.info */
+#define GI_MINUS	BIT(0)	/* an option started with -... */
+#define GI_PLUS		BIT(1)	/* an option started with +... */
+#define GI_MINUSMINUS	BIT(2)	/* arguments were ended with -- */
+
+typedef struct {
+	const char	*optarg;
+	int		optind;
+	int		uoptind;/* what user sees in $OPTIND */
+	int		flags;	/* see GF_* */
+	int		info;	/* see GI_* */
+	unsigned int	p;	/* 0 or index into argv[optind - 1] */
+	char		buf[2];	/* for bad option OPTARG value */
+} Getopt;
+
+EXTERN Getopt builtin_opt;	/* for shell builtin commands */
+EXTERN Getopt user_opt;		/* parsing state for getopts builtin command */
+
+/* This for co-processes */
+
+typedef int32_t Coproc_id; /* something that won't (realisticly) wrap */
+struct coproc {
+	void *job;	/* 0 or job of co-process using input pipe */
+	int read;	/* pipe from co-process's stdout */
+	int readw;	/* other side of read (saved temporarily) */
+	int write;	/* pipe to co-process's stdin */
+	int njobs;	/* number of live jobs using output pipe */
+	Coproc_id id;	/* id of current output pipe */
+};
+EXTERN struct coproc coproc;
+
+/* Used in jobs.c and by coprocess stuff in exec.c */
+EXTERN sigset_t		sm_default, sm_sigchld;
+
+/* name of called builtin function (used by error functions) */
+EXTERN const char *builtin_argv0;
+EXTERN Tflag builtin_flag;	/* flags of called builtin (SPEC_BI, etc.) */
+
+/* current working directory, and size of memory allocated for same */
+EXTERN char	*current_wd;
+EXTERN size_t	current_wd_size;
+
+/* Minimum required space to work with on a line - if the prompt leaves less
+ * space than this on a line, the prompt is truncated.
+ */
+#define MIN_EDIT_SPACE	7
+/* Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line
+ */
+#define MIN_COLS	(2 + MIN_EDIT_SPACE + 3)
+#define MIN_LINS	3
+EXTERN mksh_ari_t x_cols I__(80);	/* tty columns */
+EXTERN mksh_ari_t x_lins I__(-1);	/* tty lines */
+
+/* These to avoid bracket matching problems */
+#define OPAREN	'('
+#define CPAREN	')'
+#define OBRACK	'['
+#define CBRACK	']'
+#define OBRACE	'{'
+#define CBRACE	'}'
+
+/* Determine the location of the system (common) profile */
+#define KSH_SYSTEM_PROFILE "/etc/profile"
+
+/* Used by v_evaluate() and setstr() to control action when error occurs */
+#define KSH_UNWIND_ERROR	0	/* unwind the stack (longjmp) */
+#define KSH_RETURN_ERROR	1	/* return 1/0 for success/failure */
+
+/*
+ * Shell file I/O routines
+ */
+
+#define SHF_BSIZE	512
+
+#define shf_fileno(shf)	((shf)->fd)
+#define shf_setfileno(shf,nfd)	((shf)->fd = (nfd))
+#ifdef MKSH_SMALL
+int shf_getc(struct shf *);
+int shf_putc(int, struct shf *);
+#else
+#define shf_getc(shf)		((shf)->rnleft > 0 ? \
+				    (shf)->rnleft--, *(shf)->rp++ : \
+				    shf_getchar(shf))
+#define shf_putc(c, shf)	((shf)->wnleft == 0 ? \
+				    shf_putchar((c), (shf)) : \
+				    ((shf)->wnleft--, *(shf)->wp++ = (c)))
+#endif
+#define shf_eof(shf)		((shf)->flags & SHF_EOF)
+#define shf_error(shf)		((shf)->flags & SHF_ERROR)
+#define shf_errno(shf)		((shf)->errno_)
+#define shf_clearerr(shf)	((shf)->flags &= ~(SHF_EOF | SHF_ERROR))
+
+/* Flags passed to shf_*open() */
+#define SHF_RD		0x0001
+#define SHF_WR		0x0002
+#define SHF_RDWR	(SHF_RD|SHF_WR)
+#define SHF_ACCMODE	0x0003		/* mask */
+#define SHF_GETFL	0x0004		/* use fcntl() to figure RD/WR flags */
+#define SHF_UNBUF	0x0008		/* unbuffered I/O */
+#define SHF_CLEXEC	0x0010		/* set close on exec flag */
+#define SHF_MAPHI	0x0020		/* make fd > FDBASE (and close orig)
+					 * (shf_open() only) */
+#define SHF_DYNAMIC	0x0040		/* string: increase buffer as needed */
+#define SHF_INTERRUPT	0x0080		/* EINTR in read/write causes error */
+/* Flags used internally */
+#define SHF_STRING	0x0100		/* a string, not a file */
+#define SHF_ALLOCS	0x0200		/* shf and shf->buf were alloc()ed */
+#define SHF_ALLOCB	0x0400		/* shf->buf was alloc()ed */
+#define SHF_ERROR	0x0800		/* read()/write() error */
+#define SHF_EOF		0x1000		/* read eof (sticky) */
+#define SHF_READING	0x2000		/* currently reading: rnleft,rp valid */
+#define SHF_WRITING	0x4000		/* currently writing: wnleft,wp valid */
+
+
+struct shf {
+	Area *areap;		/* area shf/buf were allocated in */
+	unsigned char *rp;	/* read: current position in buffer */
+	unsigned char *wp;	/* write: current position in buffer */
+	unsigned char *buf;	/* buffer */
+	int flags;		/* see SHF_* */
+	int rbsize;		/* size of buffer (1 if SHF_UNBUF) */
+	int rnleft;		/* read: how much data left in buffer */
+	int wbsize;		/* size of buffer (0 if SHF_UNBUF) */
+	int wnleft;		/* write: how much space left in buffer */
+	int fd;			/* file descriptor */
+	int errno_;		/* saved value of errno after error */
+	int bsize;		/* actual size of buf */
+};
+
+extern struct shf shf_iob[];
+
+struct table {
+	Area *areap;		/* area to allocate entries */
+	struct tbl **tbls;	/* hashed table items */
+	short size, nfree;	/* hash size (always 2^^n), free entries */
+};
+
+struct tbl {			/* table item */
+	Area *areap;		/* area to allocate from */
+	union {
+		char *s;		/* string */
+		mksh_ari_t i;		/* integer */
+		mksh_uari_t u;		/* unsigned integer */
+		int (*f)(const char **);/* int function */
+		struct op *t;		/* "function" tree */
+	} val;			/* value */
+	union {
+		struct tbl *array;	/* array values */
+		const char *fpath;	/* temporary path to undef function */
+	} u;
+	union {
+		int field;	/* field with for -L/-R/-Z */
+		int errno_;	/* CEXEC/CTALIAS */
+	} u2;
+	int type;		/* command type (see below), base (if INTEGER),
+				 * or offset from val.s of value (if EXPORT) */
+	Tflag flag;		/* flags */
+	union {
+		uint32_t hval;		/* hash(name) */
+		uint32_t index;		/* index for an array */
+	} ua;
+	char name[4];		/* name -- variable length */
+};
+
+/* common flag bits */
+#define ALLOC		BIT(0)	/* val.s has been allocated */
+#define DEFINED		BIT(1)	/* is defined in block */
+#define ISSET		BIT(2)	/* has value, vp->val.[si] */
+#define EXPORT		BIT(3)	/* exported variable/function */
+#define TRACE		BIT(4)	/* var: user flagged, func: execution tracing */
+/* (start non-common flags at 8) */
+/* flag bits used for variables */
+#define SPECIAL		BIT(8)	/* PATH, IFS, SECONDS, etc */
+#define INTEGER		BIT(9)	/* val.i contains integer value */
+#define RDONLY		BIT(10)	/* read-only variable */
+#define LOCAL		BIT(11)	/* for local typeset() */
+#define ARRAY		BIT(13)	/* array */
+#define LJUST		BIT(14)	/* left justify */
+#define RJUST		BIT(15)	/* right justify */
+#define ZEROFIL		BIT(16)	/* 0 filled if RJUSTIFY, strip 0s if LJUSTIFY */
+#define LCASEV		BIT(17)	/* convert to lower case */
+#define UCASEV_AL	BIT(18) /* convert to upper case / autoload function */
+#define INT_U		BIT(19)	/* unsigned integer */
+#define INT_L		BIT(20)	/* long integer (no-op) */
+#define IMPORT		BIT(21)	/* flag to typeset(): no arrays, must have = */
+#define LOCAL_COPY	BIT(22)	/* with LOCAL - copy attrs from existing var */
+#define EXPRINEVAL	BIT(23)	/* contents currently being evaluated */
+#define EXPRLVALUE	BIT(24)	/* useable as lvalue (temp flag) */
+#define AINDEX		BIT(25) /* array index >0 = ua.index filled in */
+#define ASSOC		BIT(26) /* ARRAY ? associative : reference */
+/* flag bits used for taliases/builtins/aliases/keywords/functions */
+#define KEEPASN		BIT(8)	/* keep command assignments (eg, var=x cmd) */
+#define FINUSE		BIT(9)	/* function being executed */
+#define FDELETE		BIT(10)	/* function deleted while it was executing */
+#define FKSH		BIT(11)	/* function defined with function x (vs x()) */
+#define SPEC_BI		BIT(12)	/* a POSIX special builtin */
+#define REG_BI		BIT(13)	/* a POSIX regular builtin */
+/* Attributes that can be set by the user (used to decide if an unset param
+ * should be repoted by set/typeset). Does not include ARRAY or LOCAL.
+ */
+#define USERATTRIB	(EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL|\
+			    LCASEV|UCASEV_AL|INT_U|INT_L)
+
+#define arrayindex(vp)	((unsigned long)((vp)->flag & AINDEX ? \
+			    (vp)->ua.index : 0))
+
+/* command types */
+#define CNONE		0	/* undefined */
+#define CSHELL		1	/* built-in */
+#define CFUNC		2	/* function */
+#define CEXEC		4	/* executable command */
+#define CALIAS		5	/* alias */
+#define CKEYWD		6	/* keyword */
+#define CTALIAS		7	/* tracked alias */
+
+/* Flags for findcom()/comexec() */
+#define FC_SPECBI	BIT(0)	/* special builtin */
+#define FC_FUNC		BIT(1)	/* function builtin */
+#define FC_REGBI	BIT(2)	/* regular builtin */
+#define FC_UNREGBI	BIT(3)	/* un-regular builtin (!special,!regular) */
+#define FC_BI		(FC_SPECBI|FC_REGBI|FC_UNREGBI)
+#define FC_PATH		BIT(4)	/* do path search */
+#define FC_DEFPATH	BIT(5)	/* use default path in path search */
+
+
+#define AF_ARGV_ALLOC	0x1	/* argv[] array allocated */
+#define AF_ARGS_ALLOCED	0x2	/* argument strings allocated */
+#define AI_ARGV(a, i)	((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip])
+#define AI_ARGC(a)	((a).argc_ - (a).skip)
+
+/* Argument info. Used for $#, $* for shell, functions, includes, etc. */
+struct arg_info {
+	const char **argv;
+	int flags;	/* AF_* */
+	int argc_;
+	int skip;	/* first arg is argv[0], second is argv[1 + skip] */
+};
+
+/*
+ * activation record for function blocks
+ */
+struct block {
+	Area area;		/* area to allocate things */
+	const char **argv;
+	char *error;		/* error handler */
+	char *exit;		/* exit handler */
+	struct block *next;	/* enclosing block */
+	struct table vars;	/* local variables */
+	struct table funs;	/* local functions */
+	Getopt getopts_state;
+	int argc;
+	int flags;		/* see BF_* */
+};
+
+/* Values for struct block.flags */
+#define BF_DOGETOPTS	BIT(0)	/* save/restore getopts state */
+
+/*
+ * Used by ktwalk() and ktnext() routines.
+ */
+struct tstate {
+	struct tbl **next;
+	ssize_t left;
+};
+
+EXTERN struct table taliases;	/* tracked aliases */
+EXTERN struct table builtins;	/* built-in commands */
+EXTERN struct table aliases;	/* aliases */
+EXTERN struct table keywords;	/* keywords */
+#ifndef MKSH_NOPWNAM
+EXTERN struct table homedirs;	/* homedir() cache */
+#endif
+
+struct builtin {
+	const char *name;
+	int (*func)(const char **);
+};
+
+extern const struct builtin mkshbuiltins[];
+
+/* values for set_prompt() */
+#define PS1	0	/* command */
+#define PS2	1	/* command continuation */
+
+EXTERN char *path;		/* copy of either PATH or def_path */
+EXTERN const char *def_path;	/* path to use if PATH not set */
+EXTERN char *tmpdir;		/* TMPDIR value */
+EXTERN const char *prompt;
+EXTERN int cur_prompt;		/* PS1 or PS2 */
+EXTERN int current_lineno;	/* LINENO value */
+
+#define NOBLOCK	((struct op *)NULL)
+#define NOWORD	((char *)NULL)
+#define NOWORDS	((char **)NULL)
+
+/*
+ * Description of a command or an operation on commands.
+ */
+struct op {
+	const char **args;		/* arguments to a command */
+	char **vars;			/* variable assignments */
+	struct ioword **ioact;		/* IO actions (eg, < > >>) */
+	struct op *left, *right;	/* descendents */
+	char *str;			/* word for case; identifier for for,
+					 * select, and functions;
+					 * path to execute for TEXEC;
+					 * time hook for TCOM.
+					 */
+	int lineno;			/* TCOM/TFUNC: LINENO for this */
+	short type;			/* operation type, see below */
+	union { /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */
+		short evalflags;	/* TCOM: arg expansion eval() flags */
+		short ksh_func;		/* TFUNC: function x (vs x()) */
+	} u;
+};
+
+/* Tree.type values */
+#define TEOF		0
+#define TCOM		1	/* command */
+#define TPAREN		2	/* (c-list) */
+#define TPIPE		3	/* a | b */
+#define TLIST		4	/* a ; b */
+#define TOR		5	/* || */
+#define TAND		6	/* && */
+#define TBANG		7	/* ! */
+#define TDBRACKET	8	/* [[ .. ]] */
+#define TFOR		9
+#define TSELECT		10
+#define TCASE		11
+#define TIF		12
+#define TWHILE		13
+#define TUNTIL		14
+#define TELIF		15
+#define TPAT		16	/* pattern in case */
+#define TBRACE		17	/* {c-list} */
+#define TASYNC		18	/* c & */
+#define TFUNCT		19	/* function name { command; } */
+#define TTIME		20	/* time pipeline */
+#define TEXEC		21	/* fork/exec eval'd TCOM */
+#define TCOPROC		22	/* coprocess |& */
+
+/*
+ * prefix codes for words in command tree
+ */
+#define EOS	0	/* end of string */
+#define CHAR	1	/* unquoted character */
+#define QCHAR	2	/* quoted character */
+#define COMSUB	3	/* $() substitution (0 terminated) */
+#define EXPRSUB	4	/* $(()) substitution (0 terminated) */
+#define OQUOTE	5	/* opening " or ' */
+#define CQUOTE	6	/* closing " or ' */
+#define OSUBST	7	/* opening ${ subst (followed by { or X) */
+#define CSUBST	8	/* closing } of above (followed by } or X) */
+#define OPAT	9	/* open pattern: *(, @(, etc. */
+#define SPAT	10	/* separate pattern: | */
+#define CPAT	11	/* close pattern: ) */
+#define ADELIM	12	/* arbitrary delimiter: ${foo:2:3} ${foo/bar/baz} */
+
+/*
+ * IO redirection
+ */
+struct ioword {
+	int	unit;	/* unit affected */
+	int	flag;	/* action (below) */
+	char	*name;	/* file name (unused if heredoc) */
+	char	*delim;	/* delimiter for <<,<<- */
+	char	*heredoc;/* content of heredoc */
+};
+
+/* ioword.flag - type of redirection */
+#define IOTYPE	0xF	/* type: bits 0:3 */
+#define IOREAD	0x1	/* < */
+#define IOWRITE	0x2	/* > */
+#define IORDWR	0x3	/* <>: todo */
+#define IOHERE	0x4	/* << (here file) */
+#define IOCAT	0x5	/* >> */
+#define IODUP	0x6	/* <&/>& */
+#define IOEVAL	BIT(4)	/* expand in << */
+#define IOSKIP	BIT(5)	/* <<-, skip ^\t* */
+#define IOCLOB	BIT(6)	/* >|, override -o noclobber */
+#define IORDUP	BIT(7)	/* x<&y (as opposed to x>&y) */
+#define IONAMEXP BIT(8)	/* name has been expanded */
+#define IOBASH	BIT(9)	/* &> etc. */
+
+/* execute/exchild flags */
+#define XEXEC	BIT(0)		/* execute without forking */
+#define XFORK	BIT(1)		/* fork before executing */
+#define XBGND	BIT(2)		/* command & */
+#define XPIPEI	BIT(3)		/* input is pipe */
+#define XPIPEO	BIT(4)		/* output is pipe */
+#define XPIPE	(XPIPEI|XPIPEO)	/* member of pipe */
+#define XXCOM	BIT(5)		/* `...` command */
+#define XPCLOSE	BIT(6)		/* exchild: close close_fd in parent */
+#define XCCLOSE	BIT(7)		/* exchild: close close_fd in child */
+#define XERROK	BIT(8)		/* non-zero exit ok (for set -e) */
+#define XCOPROC BIT(9)		/* starting a co-process */
+#define XTIME	BIT(10)		/* timing TCOM command */
+
+/*
+ * flags to control expansion of words (assumed by t->evalflags to fit
+ * in a short)
+ */
+#define DOBLANK	BIT(0)		/* perform blank interpretation */
+#define DOGLOB	BIT(1)		/* expand [?* */
+#define DOPAT	BIT(2)		/* quote *?[ */
+#define DOTILDE	BIT(3)		/* normal ~ expansion (first char) */
+#define DONTRUNCOMMAND BIT(4)	/* do not run $(command) things */
+#define DOASNTILDE BIT(5)	/* assignment ~ expansion (after =, :) */
+#define DOBRACE_ BIT(6)		/* used by expand(): do brace expansion */
+#define DOMAGIC_ BIT(7)		/* used by expand(): string contains MAGIC */
+#define DOTEMP_	BIT(8)		/* ditto : in word part of ${..[%#=?]..} */
+#define DOVACHECK BIT(9)	/* var assign check (for typeset, set, etc) */
+#define DOMARKDIRS BIT(10)	/* force markdirs behaviour */
+
+/*
+ * The arguments of [[ .. ]] expressions are kept in t->args[] and flags
+ * indicating how the arguments have been munged are kept in t->vars[].
+ * The contents of t->vars[] are stuffed strings (so they can be treated
+ * like all other t->vars[]) in which the second character is the one that
+ * is examined. The DB_* defines are the values for these second characters.
+ */
+#define DB_NORM	1	/* normal argument */
+#define DB_OR	2	/* || -> -o conversion */
+#define DB_AND	3	/* && -> -a conversion */
+#define DB_BE	4	/* an inserted -BE */
+#define DB_PAT	5	/* a pattern argument */
+
+#define X_EXTRA	8	/* this many extra bytes in X string */
+
+typedef struct XString {
+	char *end, *beg;	/* end, begin of string */
+	size_t len;		/* length */
+	Area *areap;		/* area to allocate/free from */
+} XString;
+
+typedef char *XStringP;
+
+/* initialise expandable string */
+#define XinitN(xs, length, area) do {				\
+	(xs).len = (length);					\
+	(xs).areap = (area);					\
+	(xs).beg = alloc((xs).len + X_EXTRA, (xs).areap);	\
+	(xs).end = (xs).beg + (xs).len;				\
+} while (/* CONSTCOND */ 0)
+#define Xinit(xs, xp, length, area) do {			\
+	XinitN((xs), (length), (area));				\
+	(xp) = (xs).beg;					\
+} while (/* CONSTCOND */ 0)
+
+/* stuff char into string */
+#define Xput(xs, xp, c)	(*xp++ = (c))
+
+/* check if there are at least n bytes left */
+#define XcheckN(xs, xp, n) do {					\
+	int more = ((xp) + (n)) - (xs).end;			\
+	if (more > 0)						\
+		(xp) = Xcheck_grow_(&(xs), (xp), more);		\
+} while (/* CONSTCOND */ 0)
+
+/* check for overflow, expand string */
+#define Xcheck(xs, xp)	XcheckN((xs), (xp), 1)
+
+/* free string */
+#define Xfree(xs, xp)	afree((xs).beg, (xs).areap)
+
+/* close, return string */
+#define Xclose(xs, xp)	aresize((xs).beg, (xp) - (xs).beg, (xs).areap)
+
+/* begin of string */
+#define Xstring(xs, xp)	((xs).beg)
+
+#define Xnleft(xs, xp)	((xs).end - (xp))	/* may be less than 0 */
+#define Xlength(xs, xp)	((xp) - (xs).beg)
+#define Xsize(xs, xp)	((xs).end - (xs).beg)
+#define Xsavepos(xs, xp)	((xp) - (xs).beg)
+#define Xrestpos(xs, xp, n)	((xs).beg + (n))
+
+char *Xcheck_grow_(XString *, const char *, unsigned int);
+
+/*
+ * expandable vector of generic pointers
+ */
+
+typedef struct XPtrV {
+	void **cur;		/* next avail pointer */
+	void **beg, **end;	/* begin, end of vector */
+} XPtrV;
+
+#define XPinit(x, n) do {					\
+	void **vp__;						\
+	vp__ = alloc((n) * sizeof(void *), ATEMP);		\
+	(x).cur = (x).beg = vp__;				\
+	(x).end = vp__ + (n);					\
+} while (/* CONSTCOND */ 0)
+
+#define XPput(x, p) do {					\
+	if ((x).cur >= (x).end) {				\
+		size_t n = XPsize(x);				\
+		(x).beg = aresize((x).beg,			\
+		    n * 2 * sizeof(void *), ATEMP);		\
+		(x).cur = (x).beg + n;				\
+		(x).end = (x).cur + n;				\
+	}							\
+	*(x).cur++ = (p);					\
+} while (/* CONSTCOND */ 0)
+
+#define XPptrv(x)	((x).beg)
+#define XPsize(x)	((x).cur - (x).beg)
+#define XPclose(x)	aresize((x).beg, XPsize(x) * sizeof(void *), ATEMP)
+#define XPfree(x)	afree((x).beg, ATEMP)
+
+#define IDENT	64
+
+typedef struct source Source;
+struct source {
+	const char *str;	/* input pointer */
+	const char *start;	/* start of current buffer */
+	union {
+		const char **strv;	/* string [] */
+		struct shf *shf;	/* shell file */
+		struct tbl *tblp;	/* alias (SF_HASALIAS) */
+		char *freeme;		/* also for SREREAD */
+	} u;
+	const char *file;	/* input file name */
+	int	type;		/* input type */
+	int	line;		/* line number */
+	int	errline;	/* line the error occurred on (0 if not set) */
+	int	flags;		/* SF_* */
+	Area	*areap;
+	Source *next;		/* stacked source */
+	XString	xs;		/* input buffer */
+	char	ugbuf[2];	/* buffer for ungetsc() (SREREAD) and
+				 * alias (SALIAS) */
+};
+
+/* Source.type values */
+#define SEOF		0	/* input EOF */
+#define SFILE		1	/* file input */
+#define SSTDIN		2	/* read stdin */
+#define SSTRING		3	/* string */
+#define SWSTR		4	/* string without \n */
+#define SWORDS		5	/* string[] */
+#define SWORDSEP	6	/* string[] separator */
+#define SALIAS		7	/* alias expansion */
+#define SREREAD		8	/* read ahead to be re-scanned */
+
+/* Source.flags values */
+#define SF_ECHO		BIT(0)	/* echo input to shlout */
+#define SF_ALIAS	BIT(1)	/* faking space at end of alias */
+#define SF_ALIASEND	BIT(2)	/* faking space at end of alias */
+#define SF_TTY		BIT(3)	/* type == SSTDIN & it is a tty */
+#define SF_FIRST	BIT(4)	/* initial state (to ignore UTF-8 BOM) */
+#define SF_HASALIAS	BIT(5)	/* u.tblp valid (SALIAS, SEOF) */
+
+typedef union {
+	int i;
+	char *cp;
+	char **wp;
+	struct op *o;
+	struct ioword *iop;
+} YYSTYPE;
+
+/* If something is added here, add it to tokentab[] in syn.c as well */
+#define LWORD		256
+#define LOGAND		257	/* && */
+#define LOGOR		258	/* || */
+#define BREAK		259	/* ;; */
+#define IF		260
+#define THEN		261
+#define ELSE		262
+#define ELIF		263
+#define FI		264
+#define CASE		265
+#define ESAC		266
+#define FOR		267
+#define SELECT		268
+#define WHILE		269
+#define UNTIL		270
+#define DO		271
+#define DONE		272
+#define IN		273
+#define FUNCTION	274
+#define TIME		275
+#define REDIR		276
+#define MDPAREN		277	/* (( )) */
+#define BANG		278	/* ! */
+#define DBRACKET	279	/* [[ .. ]] */
+#define COPROC		280	/* |& */
+#define YYERRCODE	300
+
+/* flags to yylex */
+#define CONTIN		BIT(0)	/* skip new lines to complete command */
+#define ONEWORD		BIT(1)	/* single word for substitute() */
+#define ALIAS		BIT(2)	/* recognise alias */
+#define KEYWORD		BIT(3)	/* recognise keywords */
+#define LETEXPR		BIT(4)	/* get expression inside (( )) */
+#define VARASN		BIT(5)	/* check for var=word */
+#define ARRAYVAR	BIT(6)	/* parse x[1 & 2] as one word */
+#define ESACONLY	BIT(7)	/* only accept esac keyword */
+#define CMDWORD		BIT(8)	/* parsing simple command (alias related) */
+#define HEREDELIM	BIT(9)	/* parsing <<,<<- delimiter */
+#define LQCHAR		BIT(10)	/* source string contains QCHAR */
+#define HEREDOC		BIT(11)	/* parsing a here document */
+#define LETARRAY	BIT(12)	/* copy expression inside =( ) */
+
+#define HERES	10		/* max << in line */
+
+#undef CTRL
+#define	CTRL(x)		((x) == '?' ? 0x7F : (x) & 0x1F)	/* ASCII */
+#define	UNCTRL(x)	((x) ^ 0x40)				/* ASCII */
+
+EXTERN Source *source;		/* yyparse/yylex source */
+EXTERN YYSTYPE	yylval;		/* result from yylex */
+EXTERN struct ioword *heres [HERES], **herep;
+EXTERN char	ident [IDENT+1];
+
+#define HISTORYSIZE	500	/* size of saved history */
+
+EXTERN char **history;	/* saved commands */
+EXTERN char **histptr;	/* last history item */
+EXTERN int histsize;	/* history size */
+
+/* user and system time of last j_waitjed job */
+EXTERN struct timeval j_usrtime, j_systime;
+
+/* lalloc.c */
+void ainit(Area *);
+void afreeall(Area *);
+/* these cannot fail and can take NULL (not for ap) */
+#define alloc(n, ap)	aresize(NULL, (n), (ap))
+void *aresize(void *, size_t, Area *);
+void afree(void *, Area *);	/* can take NULL */
+/* edit.c */
+#ifndef MKSH_SMALL
+int x_bind(const char *, const char *, bool, bool);
+#else
+int x_bind(const char *, const char *, bool);
+#endif
+void x_init(void);
+int x_read(char *, size_t);
+/* eval.c */
+char *substitute(const char *, int);
+char **eval(const char **, int);
+char *evalstr(const char *cp, int);
+char *evalonestr(const char *cp, int);
+char *debunk(char *, const char *, size_t);
+void expand(const char *, XPtrV *, int);
+int glob_str(char *, XPtrV *, int);
+/* exec.c */
+int execute(struct op * volatile, volatile int, volatile int * volatile);
+int shcomexec(const char **);
+struct tbl *findfunc(const char *, uint32_t, bool);
+int define(const char *, struct op *);
+void builtin(const char *, int (*)(const char **));
+struct tbl *findcom(const char *, int);
+void flushcom(int);
+const char *search(const char *, const char *, int, int *);
+int search_access(const char *, int, int *);
+int pr_menu(const char * const *);
+int pr_list(char * const *);
+/* expr.c */
+int evaluate(const char *, mksh_ari_t *, int, bool);
+int v_evaluate(struct tbl *, const char *, volatile int, bool);
+/* UTF-8 stuff */
+size_t utf_mbtowc(unsigned int *, const char *);
+size_t utf_wctomb(char *, unsigned int);
+int utf_widthadj(const char *, const char **);
+int utf_mbswidth(const char *);
+const char *utf_skipcols(const char *, int);
+size_t utf_ptradj(const char *);
+#ifndef MKSH_mirbsd_wcwidth
+int utf_wcwidth(unsigned int);
+#endif
+/* funcs.c */
+int c_hash(const char **);
+int c_cd(const char **);
+int c_pwd(const char **);
+int c_print(const char **);
+#ifdef MKSH_PRINTF_BUILTIN
+int c_printf(const char **);
+#endif
+int c_whence(const char **);
+int c_command(const char **);
+int c_typeset(const char **);
+int c_alias(const char **);
+int c_unalias(const char **);
+int c_let(const char **);
+int c_jobs(const char **);
+#ifndef MKSH_UNEMPLOYED
+int c_fgbg(const char **);
+#endif
+int c_kill(const char **);
+void getopts_reset(int);
+int c_getopts(const char **);
+int c_bind(const char **);
+int c_label(const char **);
+int c_shift(const char **);
+int c_umask(const char **);
+int c_dot(const char **);
+int c_wait(const char **);
+int c_read(const char **);
+int c_eval(const char **);
+int c_trap(const char **);
+int c_brkcont(const char **);
+int c_exitreturn(const char **);
+int c_set(const char **);
+int c_unset(const char **);
+int c_ulimit(const char **);
+int c_times(const char **);
+int timex(struct op *, int, volatile int *);
+void timex_hook(struct op *, char ** volatile *);
+int c_exec(const char **);
+int c_builtin(const char **);
+int c_test(const char **);
+#if HAVE_MKNOD
+int c_mknod(const char **);
+#endif
+int c_realpath(const char **);
+int c_rename(const char **);
+/* histrap.c */
+void init_histvec(void);
+void hist_init(Source *);
+#if HAVE_PERSISTENT_HISTORY
+void hist_finish(void);
+#endif
+void histsave(int *, const char *, bool, bool);
+#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
+bool histsync(void);
+#endif
+int c_fc(const char **);
+void sethistsize(int);
+#if HAVE_PERSISTENT_HISTORY
+void sethistfile(const char *);
+#endif
+char **histpos(void);
+int histnum(int);
+int findhist(int, int, const char *, int);
+int findhistrel(const char *);
+char **hist_get_newest(bool);
+void inittraps(void);
+void alarm_init(void);
+Trap *gettrap(const char *, int);
+void trapsig(int);
+void intrcheck(void);
+int fatal_trap_check(void);
+int trap_pending(void);
+void runtraps(int intr);
+void runtrap(Trap *);
+void cleartraps(void);
+void restoresigs(void);
+void settrap(Trap *, const char *);
+int block_pipe(void);
+void restore_pipe(int);
+int setsig(Trap *, sig_t, int);
+void setexecsig(Trap *, int);
+/* jobs.c */
+void j_init(void);
+void j_exit(void);
+#ifndef MKSH_UNEMPLOYED
+void j_change(void);
+#endif
+int exchild(struct op *, int, volatile int *, int);
+void startlast(void);
+int waitlast(void);
+int waitfor(const char *, int *);
+int j_kill(const char *, int);
+#ifndef MKSH_UNEMPLOYED
+int j_resume(const char *, int);
+#endif
+int j_jobs(const char *, int, int);
+int j_njobs(void);
+void j_notify(void);
+pid_t j_async(void);
+int j_stopped_running(void);
+/* lex.c */
+int yylex(int);
+void yyerror(const char *, ...)
+    MKSH_A_NORETURN
+    MKSH_A_FORMAT(printf, 1, 2);
+Source *pushs(int, Area *);
+void set_prompt(int, Source *);
+void pprompt(const char *, int);
+int promptlen(const char *);
+/* main.c */
+int include(const char *, int, const char **, int);
+int command(const char *, int);
+int shell(Source *volatile, int volatile);
+void unwind(int) MKSH_A_NORETURN;
+void newenv(int);
+void quitenv(struct shf *);
+void cleanup_parents_env(void);
+void cleanup_proc_env(void);
+void errorf(const char *, ...)
+    MKSH_A_NORETURN
+    MKSH_A_FORMAT(printf, 1, 2);
+void warningf(bool, const char *, ...)
+    MKSH_A_FORMAT(printf, 2, 3);
+void bi_errorf(const char *, ...)
+    MKSH_A_FORMAT(printf, 1, 2);
+#define errorfz()	errorf("\1")
+#define bi_errorfz()	bi_errorf("\1")
+void internal_verrorf(const char *, va_list)
+    MKSH_A_FORMAT(printf, 1, 0);
+void internal_errorf(const char *, ...)
+    MKSH_A_NORETURN
+    MKSH_A_FORMAT(printf, 1, 2);
+void internal_warningf(const char *, ...)
+    MKSH_A_FORMAT(printf, 1, 2);
+void error_prefix(bool);
+void shellf(const char *, ...)
+    MKSH_A_FORMAT(printf, 1, 2);
+void shprintf(const char *, ...)
+    MKSH_A_FORMAT(printf, 1, 2);
+int can_seek(int);
+void initio(void);
+int ksh_dup2(int, int, bool);
+short savefd(int);
+void restfd(int, int);
+void openpipe(int *);
+void closepipe(int *);
+int check_fd(const char *, int, const char **);
+void coproc_init(void);
+void coproc_read_close(int);
+void coproc_readw_close(int);
+void coproc_write_close(int);
+int coproc_getfd(int, const char **);
+void coproc_cleanup(int);
+struct temp *maketemp(Area *, Temp_type, struct temp **);
+#define hash(s) oaathash_full((const uint8_t *)(s))
+uint32_t oaathash_full(register const uint8_t *);
+uint32_t hashmem(const void *, size_t);
+void ktinit(struct table *, Area *, size_t);
+struct tbl *ktsearch(struct table *, const char *, uint32_t);
+struct tbl *ktenter(struct table *, const char *, uint32_t);
+#define ktdelete(p)	do { p->flag = 0; } while (/* CONSTCOND */ 0)
+void ktwalk(struct tstate *, struct table *);
+struct tbl *ktnext(struct tstate *);
+struct tbl **ktsort(struct table *);
+/* misc.c */
+void setctypes(const char *, int);
+void initctypes(void);
+size_t option(const char *);
+char *getoptions(void);
+void change_flag(enum sh_flag, int, unsigned int);
+int parse_args(const char **, int, bool *);
+int getn(const char *, int *);
+int bi_getn(const char *, int *);
+int gmatchx(const char *, const char *, bool);
+int has_globbing(const char *, const char *);
+const unsigned char *pat_scan(const unsigned char *, const unsigned char *, int);
+int xstrcmp(const void *, const void *);
+void ksh_getopt_reset(Getopt *, int);
+int ksh_getopt(const char **, Getopt *, const char *);
+void print_value_quoted(const char *);
+void print_columns(struct shf *, int,
+    char *(*)(char *, int, int, const void *),
+    const void *, int, int, bool);
+void strip_nuls(char *, int);
+int blocking_read(int, char *, int)
+    MKSH_A_BOUNDED(buffer, 2, 3);
+int reset_nonblock(int);
+char *ksh_get_wd(size_t *);
+int make_path(const char *, const char *, char **, XString *, int *);
+void simplify_path(char *);
+void set_current_wd(char *);
+#ifdef MKSH_SMALL
+char *strdup_(const char *, Area *);
+char *strndup_(const char *, size_t, Area *);
+#endif
+int unbksl(bool, int (*)(void), void (*)(int));
+/* shf.c */
+struct shf *shf_open(const char *, int, int, int);
+struct shf *shf_fdopen(int, int, struct shf *);
+struct shf *shf_reopen(int, int, struct shf *);
+struct shf *shf_sopen(char *, int, int, struct shf *);
+int shf_close(struct shf *);
+int shf_fdclose(struct shf *);
+char *shf_sclose(struct shf *);
+int shf_flush(struct shf *);
+int shf_read(char *, int, struct shf *);
+char *shf_getse(char *, int, struct shf *);
+int shf_getchar(struct shf *s);
+int shf_ungetc(int, struct shf *);
+int shf_putchar(int, struct shf *);
+int shf_puts(const char *, struct shf *);
+int shf_write(const char *, int, struct shf *);
+int shf_fprintf(struct shf *, const char *, ...)
+    MKSH_A_FORMAT(printf, 2, 3);
+int shf_snprintf(char *, int, const char *, ...)
+    MKSH_A_FORMAT(printf, 3, 4)
+    MKSH_A_BOUNDED(string, 1, 2);
+char *shf_smprintf(const char *, ...)
+    MKSH_A_FORMAT(printf, 1, 2);
+int shf_vfprintf(struct shf *, const char *, va_list)
+    MKSH_A_FORMAT(printf, 2, 0);
+/* syn.c */
+void initkeywords(void);
+struct op *compile(Source *);
+/* tree.c */
+int fptreef(struct shf *, int, const char *, ...);
+char *snptreef(char *, int, const char *, ...);
+struct op *tcopy(struct op *, Area *);
+char *wdcopy(const char *, Area *);
+const char *wdscan(const char *, int);
+char *wdstrip(const char *, bool, bool);
+void tfree(struct op *, Area *);
+/* var.c */
+void newblock(void);
+void popblock(void);
+void initvar(void);
+struct tbl *global(const char *);
+struct tbl *local(const char *, bool);
+char *str_val(struct tbl *);
+int setstr(struct tbl *, const char *, int);
+struct tbl *setint_v(struct tbl *, struct tbl *, bool);
+void setint(struct tbl *, mksh_ari_t);
+struct tbl *typeset(const char *, Tflag, Tflag, int, int)
+    MKSH_A_NONNULL((nonnull (1)));
+void unset(struct tbl *, int);
+const char *skip_varname(const char *, int);
+const char *skip_wdvarname(const char *, int);
+int is_wdvarname(const char *, int);
+int is_wdvarassign(const char *);
+char **makenv(void);
+void change_random(const void *, size_t);
+void change_winsz(void);
+int array_ref_len(const char *);
+char *arrayname(const char *);
+mksh_uari_t set_array(const char *, bool, const char **);
+
+enum Test_op {
+	TO_NONOP = 0,	/* non-operator */
+	/* unary operators */
+	TO_STNZE, TO_STZER, TO_OPTION,
+	TO_FILAXST,
+	TO_FILEXST,
+	TO_FILREG, TO_FILBDEV, TO_FILCDEV, TO_FILSYM, TO_FILFIFO, TO_FILSOCK,
+	TO_FILCDF, TO_FILID, TO_FILGID, TO_FILSETG, TO_FILSTCK, TO_FILUID,
+	TO_FILRD, TO_FILGZ, TO_FILTT, TO_FILSETU, TO_FILWR, TO_FILEX,
+	/* binary operators */
+	TO_STEQL, TO_STNEQ, TO_STLT, TO_STGT, TO_INTEQ, TO_INTNE, TO_INTGT,
+	TO_INTGE, TO_INTLT, TO_INTLE, TO_FILEQ, TO_FILNT, TO_FILOT,
+	/* not an operator */
+	TO_NONNULL	/* !TO_NONOP */
+};
+typedef enum Test_op Test_op;
+
+/* Used by Test_env.isa() (order important - used to index *_tokens[] arrays) */
+enum Test_meta {
+	TM_OR,		/* -o or || */
+	TM_AND,		/* -a or && */
+	TM_NOT,		/* ! */
+	TM_OPAREN,	/* ( */
+	TM_CPAREN,	/* ) */
+	TM_UNOP,	/* unary operator */
+	TM_BINOP,	/* binary operator */
+	TM_END		/* end of input */
+};
+typedef enum Test_meta Test_meta;
+
+#define TEF_ERROR	BIT(0)		/* set if we've hit an error */
+#define TEF_DBRACKET	BIT(1)		/* set if [[ .. ]] test */
+
+typedef struct test_env {
+	union {
+		const char **wp;/* used by ptest_* */
+		XPtrV *av;	/* used by dbtestp_* */
+	} pos;
+	const char **wp_end;	/* used by ptest_* */
+	Test_op (*isa)(struct test_env *, Test_meta);
+	const char *(*getopnd) (struct test_env *, Test_op, bool);
+	int (*eval)(struct test_env *, Test_op, const char *, const char *, bool);
+	void (*error)(struct test_env *, int, const char *);
+	int flags;		/* TEF_* */
+} Test_env;
+
+extern const char *const dbtest_tokens[];
+
+Test_op	test_isop(Test_meta, const char *);
+int test_eval(Test_env *, Test_op, const char *, const char *, bool);
+int test_parse(Test_env *);
+
+EXTERN int tty_fd I__(-1);	/* dup'd tty file descriptor */
+EXTERN int tty_devtty;		/* true if tty_fd is from /dev/tty */
+EXTERN struct termios tty_state;	/* saved tty state */
+
+extern void tty_init(bool, bool);
+extern void tty_close(void);
+
+/* be sure not to interfere with anyone else's idea about EXTERN */
+#ifdef EXTERN_DEFINED
+# undef EXTERN_DEFINED
+# undef EXTERN
+#endif
+#undef I__
+
+#endif /* !MKSH_INCLUDES_ONLY */
diff --git a/mksh/src/sh_flags.h b/mksh/src/sh_flags.h
new file mode 100644
index 0000000..aa5481e
--- /dev/null
+++ b/mksh/src/sh_flags.h
@@ -0,0 +1,145 @@
+#if defined(SHFLAGS_DEFNS)
+__RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.7 2010/07/13 13:07:58 tg Exp $");
+#define FN(sname,cname,ochar,flags)	/* nothing */
+#elif defined(SHFLAGS_ENUMS)
+#define FN(sname,cname,ochar,flags)	cname,
+#define F0(sname,cname,ochar,flags)	cname = 0,
+#elif defined(SHFLAGS_ITEMS)
+#define FN(sname,cname,ochar,flags)	{ sname, ochar, flags },
+#endif
+
+#ifndef F0
+#define F0 FN
+#endif
+
+/*
+ * special cases (see parse_args()): -A, -o, -s
+ *
+ * options are sorted by their longnames
+ */
+
+/* -a	all new parameters are created with the export attribute */
+F0("allexport", FEXPORT, 'a', OF_ANY)
+
+/* ./.	backwards compat: dummy, emits a warning */
+FN("arc4random", FARC4RANDOM, 0, OF_ANY)
+
+#if HAVE_NICE
+/* ./.	bgnice */
+FN("bgnice", FBGNICE, 0, OF_ANY)
+#endif
+
+/* ./.	enable {} globbing (non-standard) */
+FN("braceexpand", FBRACEEXPAND, 0, OF_ANY)
+
+/* ./.	Emacs command line editing mode */
+FN("emacs", FEMACS, 0, OF_ANY)
+
+/* -e	quit on error */
+FN("errexit", FERREXIT, 'e', OF_ANY)
+
+/* ./.	Emacs command line editing mode, gmacs variant */
+FN("gmacs", FGMACS, 0, OF_ANY)
+
+/* ./.	reading EOF does not exit */
+FN("ignoreeof", FIGNOREEOF, 0, OF_ANY)
+
+/* -i	interactive shell */
+FN("interactive", FTALKING, 'i', OF_CMDLINE)
+
+/* -k	name=value are recognised anywhere */
+FN("keyword", FKEYWORD, 'k', OF_ANY)
+
+/* -l	login shell */
+FN("login", FLOGIN, 'l', OF_CMDLINE)
+
+/* -X	mark dirs with / in file name completion */
+FN("markdirs", FMARKDIRS, 'X', OF_ANY)
+
+#ifndef MKSH_UNEMPLOYED
+/* -m	job control monitoring */
+FN("monitor", FMONITOR, 'm', OF_ANY)
+#endif
+
+/* -C	don't overwrite existing files */
+FN("noclobber", FNOCLOBBER, 'C', OF_ANY)
+
+/* -n	don't execute any commands */
+FN("noexec", FNOEXEC, 'n', OF_ANY)
+
+/* -f	don't do file globbing */
+FN("noglob", FNOGLOB, 'f', OF_ANY)
+
+/* ./.	don't kill running jobs when login shell exits */
+FN("nohup", FNOHUP, 0, OF_ANY)
+
+/* ./.	don't save functions in history (no effect) */
+FN("nolog", FNOLOG, 0, OF_ANY)
+
+#ifndef MKSH_UNEMPLOYED
+/* -b	asynchronous job completion notification */
+FN("notify", FNOTIFY, 'b', OF_ANY)
+#endif
+
+/* -u	using an unset variable is an error */
+FN("nounset", FNOUNSET, 'u', OF_ANY)
+
+/* ./.	don't do logical cds/pwds (non-standard) */
+FN("physical", FPHYSICAL, 0, OF_ANY)
+
+/* ./.	pdksh compat: somewhat more POSIXish mode (non-standard) */
+FN("posix", FPOSIX, 0, OF_ANY)
+
+/* -p	use suid_profile; privileged shell */
+FN("privileged", FPRIVILEGED, 'p', OF_ANY)
+
+/* -r	restricted shell */
+FN("restricted", FRESTRICTED, 'r', OF_CMDLINE)
+
+/* ./.	pdksh compat: called as sh not mksh; kludge mode (non-standard) */
+FN("sh", FSH, 0, OF_ANY)
+
+/* -s	(invocation) parse stdin (pseudo non-standard) */
+FN("stdin", FSTDIN, 's', OF_CMDLINE)
+
+/* -h	create tracked aliases for all commands */
+FN("trackall", FTRACKALL, 'h', OF_ANY)
+
+/* -U	enable UTF-8 processing (non-standard) */
+FN("utf8-mode", FUNICODE, 'U', OF_ANY)
+
+/* -v	echo input */
+FN("verbose", FVERBOSE, 'v', OF_ANY)
+
+#if !MKSH_S_NOVI
+/* ./.	Vi command line editing mode */
+FN("vi", FVI, 0, OF_ANY)
+
+/* ./.	enable ESC as file name completion character (non-standard) */
+FN("vi-esccomplete", FVIESCCOMPLETE, 0, OF_ANY)
+
+/* ./.	enable Tab as file name completion character (non-standard) */
+FN("vi-tabcomplete", FVITABCOMPLETE, 0, OF_ANY)
+
+/* ./.	always read in raw mode (no effect) */
+FN("viraw", FVIRAW, 0, OF_ANY)
+#endif
+
+/* -x	execution trace (display commands as they are run) */
+FN("xtrace", FXTRACE, 'x', OF_ANY)
+
+/* -c	(invocation) execute specified command */
+FN(NULL, FCOMMAND, 'c', OF_CMDLINE)
+
+/*
+ * anonymous flags: used internally by shell only (not visible to user)
+ */
+
+/* ./.	(internal) initial shell was interactive */
+FN(NULL, FTALKING_I, 0, OF_INTERNAL)
+
+#undef FN
+#undef F0
+#undef SHFLAGS_DEFNS
+#undef SHFLAGS_ENUMS
+#undef SHFLAGS_ITEMS
diff --git a/mksh/src/shf.c b/mksh/src/shf.c
new file mode 100644
index 0000000..0962752
--- /dev/null
+++ b/mksh/src/shf.c
@@ -0,0 +1,1042 @@
+/*	$OpenBSD: shf.c,v 1.15 2006/04/02 00:48:33 deraadt Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.36 2010/07/19 22:41:04 tg Exp $");
+
+/* flags to shf_emptybuf() */
+#define EB_READSW	0x01	/* about to switch to reading */
+#define EB_GROW		0x02	/* grow buffer if necessary (STRING+DYNAMIC) */
+
+/*
+ * Replacement stdio routines. Stdio is too flakey on too many machines
+ * to be useful when you have multiple processes using the same underlying
+ * file descriptors.
+ */
+
+static int shf_fillbuf(struct shf *);
+static int shf_emptybuf(struct shf *, int);
+
+/* Open a file. First three args are for open(), last arg is flags for
+ * this package. Returns NULL if file could not be opened, or if a dup
+ * fails.
+ */
+struct shf *
+shf_open(const char *name, int oflags, int mode, int sflags)
+{
+	struct shf *shf;
+	int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+	int fd;
+
+	/* Done before open so if alloca fails, fd won't be lost. */
+	shf = alloc(sizeof(struct shf) + bsize, ATEMP);
+	shf->areap = ATEMP;
+	shf->buf = (unsigned char *)&shf[1];
+	shf->bsize = bsize;
+	shf->flags = SHF_ALLOCS;
+	/* Rest filled in by reopen. */
+
+	fd = open(name, oflags, mode);
+	if (fd < 0) {
+		afree(shf, shf->areap);
+		return (NULL);
+	}
+	if ((sflags & SHF_MAPHI) && fd < FDBASE) {
+		int nfd;
+
+		nfd = fcntl(fd, F_DUPFD, FDBASE);
+		close(fd);
+		if (nfd < 0) {
+			afree(shf, shf->areap);
+			return (NULL);
+		}
+		fd = nfd;
+	}
+	sflags &= ~SHF_ACCMODE;
+	sflags |= (oflags & O_ACCMODE) == O_RDONLY ? SHF_RD :
+	    ((oflags & O_ACCMODE) == O_WRONLY ? SHF_WR : SHF_RDWR);
+
+	return (shf_reopen(fd, sflags, shf));
+}
+
+/* Set up the shf structure for a file descriptor. Doesn't fail. */
+struct shf *
+shf_fdopen(int fd, int sflags, struct shf *shf)
+{
+	int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+
+	/* use fcntl() to figure out correct read/write flags */
+	if (sflags & SHF_GETFL) {
+		int flags = fcntl(fd, F_GETFL, 0);
+
+		if (flags < 0)
+			/* will get an error on first read/write */
+			sflags |= SHF_RDWR;
+		else {
+			switch (flags & O_ACCMODE) {
+			case O_RDONLY:
+				sflags |= SHF_RD;
+				break;
+			case O_WRONLY:
+				sflags |= SHF_WR;
+				break;
+			case O_RDWR:
+				sflags |= SHF_RDWR;
+				break;
+			}
+		}
+	}
+
+	if (!(sflags & (SHF_RD | SHF_WR)))
+		internal_errorf("shf_fdopen: missing read/write");
+
+	if (shf) {
+		if (bsize) {
+			shf->buf = alloc(bsize, ATEMP);
+			sflags |= SHF_ALLOCB;
+		} else
+			shf->buf = NULL;
+	} else {
+		shf = alloc(sizeof(struct shf) + bsize, ATEMP);
+		shf->buf = (unsigned char *)&shf[1];
+		sflags |= SHF_ALLOCS;
+	}
+	shf->areap = ATEMP;
+	shf->fd = fd;
+	shf->rp = shf->wp = shf->buf;
+	shf->rnleft = 0;
+	shf->rbsize = bsize;
+	shf->wnleft = 0; /* force call to shf_emptybuf() */
+	shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
+	shf->flags = sflags;
+	shf->errno_ = 0;
+	shf->bsize = bsize;
+	if (sflags & SHF_CLEXEC)
+		fcntl(fd, F_SETFD, FD_CLOEXEC);
+	return (shf);
+}
+
+/* Set up an existing shf (and buffer) to use the given fd */
+struct shf *
+shf_reopen(int fd, int sflags, struct shf *shf)
+{
+	int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+
+	/* use fcntl() to figure out correct read/write flags */
+	if (sflags & SHF_GETFL) {
+		int flags = fcntl(fd, F_GETFL, 0);
+
+		if (flags < 0)
+			/* will get an error on first read/write */
+			sflags |= SHF_RDWR;
+		else {
+			switch (flags & O_ACCMODE) {
+			case O_RDONLY:
+				sflags |= SHF_RD;
+				break;
+			case O_WRONLY:
+				sflags |= SHF_WR;
+				break;
+			case O_RDWR:
+				sflags |= SHF_RDWR;
+				break;
+			}
+		}
+	}
+
+	if (!(sflags & (SHF_RD | SHF_WR)))
+		internal_errorf("shf_reopen: missing read/write");
+	if (!shf || !shf->buf || shf->bsize < bsize)
+		internal_errorf("shf_reopen: bad shf/buf/bsize");
+
+	/* assumes shf->buf and shf->bsize already set up */
+	shf->fd = fd;
+	shf->rp = shf->wp = shf->buf;
+	shf->rnleft = 0;
+	shf->rbsize = bsize;
+	shf->wnleft = 0; /* force call to shf_emptybuf() */
+	shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
+	shf->flags = (shf->flags & (SHF_ALLOCS | SHF_ALLOCB)) | sflags;
+	shf->errno_ = 0;
+	if (sflags & SHF_CLEXEC)
+		fcntl(fd, F_SETFD, FD_CLOEXEC);
+	return (shf);
+}
+
+/* Open a string for reading or writing. If reading, bsize is the number
+ * of bytes that can be read. If writing, bsize is the maximum number of
+ * bytes that can be written. If shf is not null, it is filled in and
+ * returned, if it is null, shf is allocated. If writing and buf is null
+ * and SHF_DYNAMIC is set, the buffer is allocated (if bsize > 0, it is
+ * used for the initial size). Doesn't fail.
+ * When writing, a byte is reserved for a trailing null - see shf_sclose().
+ */
+struct shf *
+shf_sopen(char *buf, int bsize, int sflags, struct shf *shf)
+{
+	/* can't have a read+write string */
+	if (!(!(sflags & SHF_RD) ^ !(sflags & SHF_WR)))
+		internal_errorf("shf_sopen: flags 0x%x", sflags);
+
+	if (!shf) {
+		shf = alloc(sizeof(struct shf), ATEMP);
+		sflags |= SHF_ALLOCS;
+	}
+	shf->areap = ATEMP;
+	if (!buf && (sflags & SHF_WR) && (sflags & SHF_DYNAMIC)) {
+		if (bsize <= 0)
+			bsize = 64;
+		sflags |= SHF_ALLOCB;
+		buf = alloc(bsize, shf->areap);
+	}
+	shf->fd = -1;
+	shf->buf = shf->rp = shf->wp = (unsigned char *)buf;
+	shf->rnleft = bsize;
+	shf->rbsize = bsize;
+	shf->wnleft = bsize - 1;	/* space for a '\0' */
+	shf->wbsize = bsize;
+	shf->flags = sflags | SHF_STRING;
+	shf->errno_ = 0;
+	shf->bsize = bsize;
+
+	return (shf);
+}
+
+/* Flush and close file descriptor, free the shf structure */
+int
+shf_close(struct shf *shf)
+{
+	int ret = 0;
+
+	if (shf->fd >= 0) {
+		ret = shf_flush(shf);
+		if (close(shf->fd) < 0)
+			ret = EOF;
+	}
+	if (shf->flags & SHF_ALLOCS)
+		afree(shf, shf->areap);
+	else if (shf->flags & SHF_ALLOCB)
+		afree(shf->buf, shf->areap);
+
+	return (ret);
+}
+
+/* Flush and close file descriptor, don't free file structure */
+int
+shf_fdclose(struct shf *shf)
+{
+	int ret = 0;
+
+	if (shf->fd >= 0) {
+		ret = shf_flush(shf);
+		if (close(shf->fd) < 0)
+			ret = EOF;
+		shf->rnleft = 0;
+		shf->rp = shf->buf;
+		shf->wnleft = 0;
+		shf->fd = -1;
+	}
+
+	return (ret);
+}
+
+/* Close a string - if it was opened for writing, it is null terminated;
+ * returns a pointer to the string and frees shf if it was allocated
+ * (does not free string if it was allocated).
+ */
+char *
+shf_sclose(struct shf *shf)
+{
+	unsigned char *s = shf->buf;
+
+	/* null terminate */
+	if (shf->flags & SHF_WR) {
+		shf->wnleft++;
+		shf_putc('\0', shf);
+	}
+	if (shf->flags & SHF_ALLOCS)
+		afree(shf, shf->areap);
+	return ((char *)s);
+}
+
+/* Un-read what has been read but not examined, or write what has been
+ * buffered. Returns 0 for success, EOF for (write) error.
+ */
+int
+shf_flush(struct shf *shf)
+{
+	if (shf->flags & SHF_STRING)
+		return ((shf->flags & SHF_WR) ? EOF : 0);
+
+	if (shf->fd < 0)
+		internal_errorf("shf_flush: no fd");
+
+	if (shf->flags & SHF_ERROR) {
+		errno = shf->errno_;
+		return (EOF);
+	}
+
+	if (shf->flags & SHF_READING) {
+		shf->flags &= ~(SHF_EOF | SHF_READING);
+		if (shf->rnleft > 0) {
+			lseek(shf->fd, (off_t)-shf->rnleft, SEEK_CUR);
+			shf->rnleft = 0;
+			shf->rp = shf->buf;
+		}
+		return (0);
+	} else if (shf->flags & SHF_WRITING)
+		return (shf_emptybuf(shf, 0));
+
+	return (0);
+}
+
+/* Write out any buffered data. If currently reading, flushes the read
+ * buffer. Returns 0 for success, EOF for (write) error.
+ */
+static int
+shf_emptybuf(struct shf *shf, int flags)
+{
+	int ret = 0;
+
+	if (!(shf->flags & SHF_STRING) && shf->fd < 0)
+		internal_errorf("shf_emptybuf: no fd");
+
+	if (shf->flags & SHF_ERROR) {
+		errno = shf->errno_;
+		return (EOF);
+	}
+
+	if (shf->flags & SHF_READING) {
+		if (flags & EB_READSW) /* doesn't happen */
+			return (0);
+		ret = shf_flush(shf);
+		shf->flags &= ~SHF_READING;
+	}
+	if (shf->flags & SHF_STRING) {
+		unsigned char *nbuf;
+
+		/* Note that we assume SHF_ALLOCS is not set if SHF_ALLOCB
+		 * is set... (changing the shf pointer could cause problems)
+		 */
+		if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC) ||
+		    !(shf->flags & SHF_ALLOCB))
+			return (EOF);
+		/* allocate more space for buffer */
+		nbuf = aresize(shf->buf, 2 * shf->wbsize, shf->areap);
+		shf->rp = nbuf + (shf->rp - shf->buf);
+		shf->wp = nbuf + (shf->wp - shf->buf);
+		shf->rbsize += shf->wbsize;
+		shf->wnleft += shf->wbsize;
+		shf->wbsize *= 2;
+		shf->buf = nbuf;
+	} else {
+		if (shf->flags & SHF_WRITING) {
+			int ntowrite = shf->wp - shf->buf;
+			unsigned char *buf = shf->buf;
+			int n;
+
+			while (ntowrite > 0) {
+				n = write(shf->fd, buf, ntowrite);
+				if (n < 0) {
+					if (errno == EINTR &&
+					    !(shf->flags & SHF_INTERRUPT))
+						continue;
+					shf->flags |= SHF_ERROR;
+					shf->errno_ = errno;
+					shf->wnleft = 0;
+					if (buf != shf->buf) {
+						/* allow a second flush
+						 * to work */
+						memmove(shf->buf, buf,
+						    ntowrite);
+						shf->wp = shf->buf + ntowrite;
+					}
+					return (EOF);
+				}
+				buf += n;
+				ntowrite -= n;
+			}
+			if (flags & EB_READSW) {
+				shf->wp = shf->buf;
+				shf->wnleft = 0;
+				shf->flags &= ~SHF_WRITING;
+				return (0);
+			}
+		}
+		shf->wp = shf->buf;
+		shf->wnleft = shf->wbsize;
+	}
+	shf->flags |= SHF_WRITING;
+
+	return (ret);
+}
+
+/* Fill up a read buffer. Returns EOF for a read error, 0 otherwise. */
+static int
+shf_fillbuf(struct shf *shf)
+{
+	if (shf->flags & SHF_STRING)
+		return (0);
+
+	if (shf->fd < 0)
+		internal_errorf("shf_fillbuf: no fd");
+
+	if (shf->flags & (SHF_EOF | SHF_ERROR)) {
+		if (shf->flags & SHF_ERROR)
+			errno = shf->errno_;
+		return (EOF);
+	}
+
+	if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
+		return (EOF);
+
+	shf->flags |= SHF_READING;
+
+	shf->rp = shf->buf;
+	while (1) {
+		shf->rnleft = blocking_read(shf->fd, (char *) shf->buf,
+		    shf->rbsize);
+		if (shf->rnleft < 0 && errno == EINTR &&
+		    !(shf->flags & SHF_INTERRUPT))
+			continue;
+		break;
+	}
+	if (shf->rnleft <= 0) {
+		if (shf->rnleft < 0) {
+			shf->flags |= SHF_ERROR;
+			shf->errno_ = errno;
+			shf->rnleft = 0;
+			shf->rp = shf->buf;
+			return (EOF);
+		}
+		shf->flags |= SHF_EOF;
+	}
+	return (0);
+}
+
+/* Read a buffer from shf. Returns the number of bytes read into buf,
+ * if no bytes were read, returns 0 if end of file was seen, EOF if
+ * a read error occurred.
+ */
+int
+shf_read(char *buf, int bsize, struct shf *shf)
+{
+	int orig_bsize = bsize;
+	int ncopy;
+
+	if (!(shf->flags & SHF_RD))
+		internal_errorf("shf_read: flags %x", shf->flags);
+
+	if (bsize <= 0)
+		internal_errorf("shf_read: bsize %d", bsize);
+
+	while (bsize > 0) {
+		if (shf->rnleft == 0 &&
+		    (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
+			break;
+		ncopy = shf->rnleft;
+		if (ncopy > bsize)
+			ncopy = bsize;
+		memcpy(buf, shf->rp, ncopy);
+		buf += ncopy;
+		bsize -= ncopy;
+		shf->rp += ncopy;
+		shf->rnleft -= ncopy;
+	}
+	/* Note: fread(3S) returns 0 for errors - this doesn't */
+	return (orig_bsize == bsize ? (shf_error(shf) ? EOF : 0) :
+	    orig_bsize - bsize);
+}
+
+/* Read up to a newline or EOF. The newline is put in buf; buf is always
+ * null terminated. Returns NULL on read error or if nothing was read before
+ * end of file, returns a pointer to the null byte in buf otherwise.
+ */
+char *
+shf_getse(char *buf, int bsize, struct shf *shf)
+{
+	unsigned char *end;
+	int ncopy;
+	char *orig_buf = buf;
+
+	if (!(shf->flags & SHF_RD))
+		internal_errorf("shf_getse: flags %x", shf->flags);
+
+	if (bsize <= 0)
+		return (NULL);
+
+	--bsize;	/* save room for null */
+	do {
+		if (shf->rnleft == 0) {
+			if (shf_fillbuf(shf) == EOF)
+				return (NULL);
+			if (shf->rnleft == 0) {
+				*buf = '\0';
+				return (buf == orig_buf ? NULL : buf);
+			}
+		}
+		end = (unsigned char *)memchr((char *) shf->rp, '\n',
+		    shf->rnleft);
+		ncopy = end ? end - shf->rp + 1 : shf->rnleft;
+		if (ncopy > bsize)
+			ncopy = bsize;
+		memcpy(buf, (char *) shf->rp, ncopy);
+		shf->rp += ncopy;
+		shf->rnleft -= ncopy;
+		buf += ncopy;
+		bsize -= ncopy;
+	} while (!end && bsize);
+	*buf = '\0';
+	return (buf);
+}
+
+/* Returns the char read. Returns EOF for error and end of file. */
+int
+shf_getchar(struct shf *shf)
+{
+	if (!(shf->flags & SHF_RD))
+		internal_errorf("shf_getchar: flags %x", shf->flags);
+
+	if (shf->rnleft == 0 && (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
+		return (EOF);
+	--shf->rnleft;
+	return (*shf->rp++);
+}
+
+/* Put a character back in the input stream. Returns the character if
+ * successful, EOF if there is no room.
+ */
+int
+shf_ungetc(int c, struct shf *shf)
+{
+	if (!(shf->flags & SHF_RD))
+		internal_errorf("shf_ungetc: flags %x", shf->flags);
+
+	if ((shf->flags & SHF_ERROR) || c == EOF ||
+	    (shf->rp == shf->buf && shf->rnleft))
+		return (EOF);
+
+	if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
+		return (EOF);
+
+	if (shf->rp == shf->buf)
+		shf->rp = shf->buf + shf->rbsize;
+	if (shf->flags & SHF_STRING) {
+		/* Can unget what was read, but not something different - we
+		 * don't want to modify a string.
+		 */
+		if (shf->rp[-1] != c)
+			return (EOF);
+		shf->flags &= ~SHF_EOF;
+		shf->rp--;
+		shf->rnleft++;
+		return (c);
+	}
+	shf->flags &= ~SHF_EOF;
+	*--(shf->rp) = c;
+	shf->rnleft++;
+	return (c);
+}
+
+/* Write a character. Returns the character if successful, EOF if
+ * the char could not be written.
+ */
+int
+shf_putchar(int c, struct shf *shf)
+{
+	if (!(shf->flags & SHF_WR))
+		internal_errorf("shf_putchar: flags %x", shf->flags);
+
+	if (c == EOF)
+		return (EOF);
+
+	if (shf->flags & SHF_UNBUF) {
+		unsigned char cc = (unsigned char)c;
+		int n;
+
+		if (shf->fd < 0)
+			internal_errorf("shf_putchar: no fd");
+		if (shf->flags & SHF_ERROR) {
+			errno = shf->errno_;
+			return (EOF);
+		}
+		while ((n = write(shf->fd, &cc, 1)) != 1)
+			if (n < 0) {
+				if (errno == EINTR &&
+				    !(shf->flags & SHF_INTERRUPT))
+					continue;
+				shf->flags |= SHF_ERROR;
+				shf->errno_ = errno;
+				return (EOF);
+			}
+	} else {
+		/* Flush deals with strings and sticky errors */
+		if (shf->wnleft == 0 && shf_emptybuf(shf, EB_GROW) == EOF)
+			return (EOF);
+		shf->wnleft--;
+		*shf->wp++ = c;
+	}
+
+	return (c);
+}
+
+/* Write a string. Returns the length of the string if successful, EOF if
+ * the string could not be written.
+ */
+int
+shf_puts(const char *s, struct shf *shf)
+{
+	if (!s)
+		return (EOF);
+
+	return (shf_write(s, strlen(s), shf));
+}
+
+/* Write a buffer. Returns nbytes if successful, EOF if there is an error. */
+int
+shf_write(const char *buf, int nbytes, struct shf *shf)
+{
+	int n, ncopy, orig_nbytes = nbytes;
+
+	if (!(shf->flags & SHF_WR))
+		internal_errorf("shf_write: flags %x", shf->flags);
+
+	if (nbytes < 0)
+		internal_errorf("shf_write: nbytes %d", nbytes);
+
+	/* Don't buffer if buffer is empty and we're writting a large amount. */
+	if ((ncopy = shf->wnleft) &&
+	    (shf->wp != shf->buf || nbytes < shf->wnleft)) {
+		if (ncopy > nbytes)
+			ncopy = nbytes;
+		memcpy(shf->wp, buf, ncopy);
+		nbytes -= ncopy;
+		buf += ncopy;
+		shf->wp += ncopy;
+		shf->wnleft -= ncopy;
+	}
+	if (nbytes > 0) {
+		if (shf->flags & SHF_STRING) {
+			/* resize buffer until there's enough space left */
+			while (nbytes > shf->wnleft)
+				if (shf_emptybuf(shf, EB_GROW) == EOF)
+					return (EOF);
+			/* then write everything into the buffer */
+		} else {
+			/* flush deals with sticky errors */
+			if (shf_emptybuf(shf, EB_GROW) == EOF)
+				return (EOF);
+			/* write chunks larger than window size directly */
+			if (nbytes > shf->wbsize) {
+				ncopy = nbytes;
+				if (shf->wbsize)
+					ncopy -= nbytes % shf->wbsize;
+				nbytes -= ncopy;
+				while (ncopy > 0) {
+					n = write(shf->fd, buf, ncopy);
+					if (n < 0) {
+						if (errno == EINTR &&
+						    !(shf->flags & SHF_INTERRUPT))
+							continue;
+						shf->flags |= SHF_ERROR;
+						shf->errno_ = errno;
+						shf->wnleft = 0;
+						/*
+						 * Note: fwrite(3) returns 0
+						 * for errors - this doesn't
+						 */
+						return (EOF);
+					}
+					buf += n;
+					ncopy -= n;
+				}
+			}
+			/* ... and buffer the rest */
+		}
+		if (nbytes > 0) {
+			/* write remaining bytes to buffer */
+			memcpy(shf->wp, buf, nbytes);
+			shf->wp += nbytes;
+			shf->wnleft -= nbytes;
+		}
+	}
+
+	return (orig_nbytes);
+}
+
+int
+shf_fprintf(struct shf *shf, const char *fmt, ...)
+{
+	va_list args;
+	int n;
+
+	va_start(args, fmt);
+	n = shf_vfprintf(shf, fmt, args);
+	va_end(args);
+
+	return (n);
+}
+
+int
+shf_snprintf(char *buf, int bsize, const char *fmt, ...)
+{
+	struct shf shf;
+	va_list args;
+	int n;
+
+	if (!buf || bsize <= 0)
+		internal_errorf("shf_snprintf: buf %p, bsize %d", buf, bsize);
+
+	shf_sopen(buf, bsize, SHF_WR, &shf);
+	va_start(args, fmt);
+	n = shf_vfprintf(&shf, fmt, args);
+	va_end(args);
+	shf_sclose(&shf); /* null terminates */
+	return (n);
+}
+
+char *
+shf_smprintf(const char *fmt, ...)
+{
+	struct shf shf;
+	va_list args;
+
+	shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf);
+	va_start(args, fmt);
+	shf_vfprintf(&shf, fmt, args);
+	va_end(args);
+	return (shf_sclose(&shf)); /* null terminates */
+}
+
+#undef FP			/* if you want floating point stuff */
+
+#ifndef DMAXEXP
+# define DMAXEXP	128	/* should be big enough */
+#endif
+
+#define BUF_SIZE	128
+/* must be > MAX(DMAXEXP, log10(pow(2, DSIGNIF))) + ceil(log10(DMAXEXP)) + 8
+ * (I think); since it's hard to express as a constant, just use a large buffer
+ */
+#define FPBUF_SIZE	(DMAXEXP+16)
+
+#define	FL_HASH		0x001	/* '#' seen */
+#define FL_PLUS		0x002	/* '+' seen */
+#define FL_RIGHT	0x004	/* '-' seen */
+#define FL_BLANK	0x008	/* ' ' seen */
+#define FL_SHORT	0x010	/* 'h' seen */
+#define FL_LONG		0x020	/* 'l' seen */
+#define FL_ZERO		0x040	/* '0' seen */
+#define FL_DOT		0x080	/* '.' seen */
+#define FL_UPPER	0x100	/* format character was uppercase */
+#define FL_NUMBER	0x200	/* a number was formated %[douxefg] */
+
+
+int
+shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
+{
+	const char *s;
+	char c, *cp;
+	int tmp = 0, field, precision, len, flags;
+	unsigned long lnum;
+	/* %#o produces the longest output */
+	char numbuf[(8 * sizeof(long) + 2) / 3 + 1];
+	/* this stuff for dealing with the buffer */
+	int nwritten = 0;
+
+	if (!fmt)
+		return (0);
+
+	while ((c = *fmt++)) {
+		if (c != '%') {
+			shf_putc(c, shf);
+			nwritten++;
+			continue;
+		}
+		/*
+		 * This will accept flags/fields in any order - not
+		 * just the order specified in printf(3), but this is
+		 * the way _doprnt() seems to work (on bsd and sysV).
+		 * The only restriction is that the format character must
+		 * come last :-).
+		 */
+		flags = field = precision = 0;
+		for ( ; (c = *fmt++) ; ) {
+			switch (c) {
+			case '#':
+				flags |= FL_HASH;
+				continue;
+
+			case '+':
+				flags |= FL_PLUS;
+				continue;
+
+			case '-':
+				flags |= FL_RIGHT;
+				continue;
+
+			case ' ':
+				flags |= FL_BLANK;
+				continue;
+
+			case '0':
+				if (!(flags & FL_DOT))
+					flags |= FL_ZERO;
+				continue;
+
+			case '.':
+				flags |= FL_DOT;
+				precision = 0;
+				continue;
+
+			case '*':
+				tmp = va_arg(args, int);
+				if (flags & FL_DOT)
+					precision = tmp;
+				else if ((field = tmp) < 0) {
+					field = -field;
+					flags |= FL_RIGHT;
+				}
+				continue;
+
+			case 'l':
+				flags |= FL_LONG;
+				continue;
+
+			case 'h':
+				flags |= FL_SHORT;
+				continue;
+			}
+			if (ksh_isdigit(c)) {
+				tmp = c - '0';
+				while (c = *fmt++, ksh_isdigit(c))
+					tmp = tmp * 10 + c - '0';
+				--fmt;
+				if (tmp < 0)		/* overflow? */
+					tmp = 0;
+				if (flags & FL_DOT)
+					precision = tmp;
+				else
+					field = tmp;
+				continue;
+			}
+			break;
+		}
+
+		if (precision < 0)
+			precision = 0;
+
+		if (!c)		/* nasty format */
+			break;
+
+		if (c >= 'A' && c <= 'Z') {
+			flags |= FL_UPPER;
+			c = ksh_tolower(c);
+		}
+
+		switch (c) {
+		case 'p': /* pointer */
+			flags &= ~(FL_LONG | FL_SHORT);
+			flags |= (sizeof(char *) > sizeof(int)) ?
+			    /* hope it fits.. */ FL_LONG : 0;
+			/* aaahhh... */
+		case 'd':
+		case 'i':
+		case 'o':
+		case 'u':
+		case 'x':
+			flags |= FL_NUMBER;
+			cp = numbuf + sizeof(numbuf);
+			/*-
+			 * XXX any better way to do this?
+			 * XXX hopefully the compiler optimises this out
+			 *
+			 * For shorts, we want sign extend for %d but not
+			 * for %[oxu] - on 16 bit machines it doesn't matter.
+			 * Assumes C compiler has converted shorts to ints
+			 * before pushing them. XXX optimise this -tg
+			 */
+			if (flags & FL_LONG)
+				lnum = va_arg(args, unsigned long);
+			else if ((sizeof(int) < sizeof(long)) && (c == 'd'))
+				lnum = (long)va_arg(args, int);
+			else
+				lnum = va_arg(args, unsigned int);
+			switch (c) {
+			case 'd':
+			case 'i':
+				if (0 > (long)lnum) {
+					lnum = -(long)lnum;
+					tmp = 1;
+				} else
+					tmp = 0;
+				/* FALLTHROUGH */
+			case 'u':
+				do {
+					*--cp = lnum % 10 + '0';
+					lnum /= 10;
+				} while (lnum);
+
+				if (c != 'u') {
+					if (tmp)
+						*--cp = '-';
+					else if (flags & FL_PLUS)
+						*--cp = '+';
+					else if (flags & FL_BLANK)
+						*--cp = ' ';
+				}
+				break;
+
+			case 'o':
+				do {
+					*--cp = (lnum & 0x7) + '0';
+					lnum >>= 3;
+				} while (lnum);
+
+				if ((flags & FL_HASH) && *cp != '0')
+					*--cp = '0';
+				break;
+
+			case 'p':
+			case 'x': {
+				const char *digits = (flags & FL_UPPER) ?
+				    digits_uc : digits_lc;
+				do {
+					*--cp = digits[lnum & 0xf];
+					lnum >>= 4;
+				} while (lnum);
+
+				if (flags & FL_HASH) {
+					*--cp = (flags & FL_UPPER) ? 'X' : 'x';
+					*--cp = '0';
+				}
+			}
+			}
+			len = numbuf + sizeof(numbuf) - (s = cp);
+			if (flags & FL_DOT) {
+				if (precision > len) {
+					field = precision;
+					flags |= FL_ZERO;
+				} else
+					precision = len; /* no loss */
+			}
+			break;
+
+		case 's':
+			if (!(s = va_arg(args, const char *)))
+				s = "(null)";
+			len = utf_mbswidth(s);
+			break;
+
+		case 'c':
+			flags &= ~FL_DOT;
+			numbuf[0] = (char)(va_arg(args, int));
+			s = numbuf;
+			len = 1;
+			break;
+
+		case '%':
+		default:
+			numbuf[0] = c;
+			s = numbuf;
+			len = 1;
+			break;
+		}
+
+		/*
+		 * At this point s should point to a string that is to be
+		 * formatted, and len should be the length of the string.
+		 */
+		if (!(flags & FL_DOT) || len < precision)
+			precision = len;
+		if (field > precision) {
+			field -= precision;
+			if (!(flags & FL_RIGHT)) {
+				field = -field;
+				/* skip past sign or 0x when padding with 0 */
+				if ((flags & FL_ZERO) && (flags & FL_NUMBER)) {
+					if (*s == '+' || *s == '-' ||
+					    *s == ' ') {
+						shf_putc(*s, shf);
+						s++;
+						precision--;
+						nwritten++;
+					} else if (*s == '0') {
+						shf_putc(*s, shf);
+						s++;
+						nwritten++;
+						if (--precision > 0 &&
+						    (*s | 0x20) == 'x') {
+							shf_putc(*s, shf);
+							s++;
+							precision--;
+							nwritten++;
+						}
+					}
+					c = '0';
+				} else
+					c = flags & FL_ZERO ? '0' : ' ';
+				if (field < 0) {
+					nwritten += -field;
+					for ( ; field < 0 ; field++)
+						shf_putc(c, shf);
+				}
+			} else
+				c = ' ';
+		} else
+			field = 0;
+
+		if (precision > 0) {
+			const char *q;
+
+			nwritten += precision;
+			q = utf_skipcols(s, precision);
+			do {
+				shf_putc(*s, shf);
+			} while (++s < q);
+		}
+		if (field > 0) {
+			nwritten += field;
+			for ( ; field > 0 ; --field)
+				shf_putc(c, shf);
+		}
+	}
+
+	return (shf_error(shf) ? EOF : nwritten);
+}
+
+#ifdef MKSH_SMALL
+int
+shf_getc(struct shf *shf)
+{
+	return ((shf)->rnleft > 0 ? (shf)->rnleft--, *(shf)->rp++ :
+	    shf_getchar(shf));
+}
+
+int
+shf_putc(int c, struct shf *shf)
+{
+	return ((shf)->wnleft == 0 ? shf_putchar((c), (shf)) :
+	    ((shf)->wnleft--, *(shf)->wp++ = (c)));
+}
+#endif
diff --git a/mksh/src/syn.c b/mksh/src/syn.c
new file mode 100644
index 0000000..64b2867
--- /dev/null
+++ b/mksh/src/syn.c
@@ -0,0 +1,1004 @@
+/*	$OpenBSD: syn.c,v 1.28 2008/07/23 16:34:38 jaredy Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.49 2010/07/17 22:09:39 tg Exp $");
+
+struct nesting_state {
+	int start_token;	/* token than began nesting (eg, FOR) */
+	int start_line;		/* line nesting began on */
+};
+
+static void yyparse(void);
+static struct op *pipeline(int);
+static struct op *andor(void);
+static struct op *c_list(int);
+static struct ioword *synio(int);
+static struct op *nested(int, int, int);
+static struct op *get_command(int);
+static struct op *dogroup(void);
+static struct op *thenpart(void);
+static struct op *elsepart(void);
+static struct op *caselist(void);
+static struct op *casepart(int);
+static struct op *function_body(char *, bool);
+static char **wordlist(void);
+static struct op *block(int, struct op *, struct op *, char **);
+static struct op *newtp(int);
+static void syntaxerr(const char *) MKSH_A_NORETURN;
+static void nesting_push(struct nesting_state *, int);
+static void nesting_pop(struct nesting_state *);
+static int assign_command(char *);
+static int inalias(struct source *);
+static Test_op dbtestp_isa(Test_env *, Test_meta);
+static const char *dbtestp_getopnd(Test_env *, Test_op, bool);
+static int dbtestp_eval(Test_env *, Test_op, const char *,
+    const char *, bool);
+static void dbtestp_error(Test_env *, int, const char *) MKSH_A_NORETURN;
+
+static struct op *outtree;		/* yyparse output */
+static struct nesting_state nesting;	/* \n changed to ; */
+
+static int reject;		/* token(cf) gets symbol again */
+static int symbol;		/* yylex value */
+
+#define REJECT		(reject = 1)
+#define ACCEPT		(reject = 0)
+#define token(cf)	((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf)))
+#define tpeek(cf)	((reject) ? (symbol) : (REJECT, symbol = yylex(cf)))
+#define musthave(c,cf)	do { if (token(cf) != (c)) syntaxerr(NULL); } while (0)
+
+static void
+yyparse(void)
+{
+	int c;
+
+	ACCEPT;
+
+	outtree = c_list(source->type == SSTRING);
+	c = tpeek(0);
+	if (c == 0 && !outtree)
+		outtree = newtp(TEOF);
+	else if (c != '\n' && c != 0)
+		syntaxerr(NULL);
+}
+
+static struct op *
+pipeline(int cf)
+{
+	struct op *t, *p, *tl = NULL;
+
+	t = get_command(cf);
+	if (t != NULL) {
+		while (token(0) == '|') {
+			if ((p = get_command(CONTIN)) == NULL)
+				syntaxerr(NULL);
+			if (tl == NULL)
+				t = tl = block(TPIPE, t, p, NOWORDS);
+			else
+				tl = tl->right = block(TPIPE, tl->right, p, NOWORDS);
+		}
+		REJECT;
+	}
+	return (t);
+}
+
+static struct op *
+andor(void)
+{
+	struct op *t, *p;
+	int c;
+
+	t = pipeline(0);
+	if (t != NULL) {
+		while ((c = token(0)) == LOGAND || c == LOGOR) {
+			if ((p = pipeline(CONTIN)) == NULL)
+				syntaxerr(NULL);
+			t = block(c == LOGAND? TAND: TOR, t, p, NOWORDS);
+		}
+		REJECT;
+	}
+	return (t);
+}
+
+static struct op *
+c_list(int multi)
+{
+	struct op *t = NULL, *p, *tl = NULL;
+	int c, have_sep;
+
+	while (1) {
+		p = andor();
+		/* Token has always been read/rejected at this point, so
+		 * we don't worry about what flags to pass token()
+		 */
+		c = token(0);
+		have_sep = 1;
+		if (c == '\n' && (multi || inalias(source))) {
+			if (!p) /* ignore blank lines */
+				continue;
+		} else if (!p)
+			break;
+		else if (c == '&' || c == COPROC)
+			p = block(c == '&' ? TASYNC : TCOPROC,
+			    p, NOBLOCK, NOWORDS);
+		else if (c != ';')
+			have_sep = 0;
+		if (!t)
+			t = p;
+		else if (!tl)
+			t = tl = block(TLIST, t, p, NOWORDS);
+		else
+			tl = tl->right = block(TLIST, tl->right, p, NOWORDS);
+		if (!have_sep)
+			break;
+	}
+	REJECT;
+	return (t);
+}
+
+static struct ioword *
+synio(int cf)
+{
+	struct ioword *iop;
+	static struct ioword *nextiop = NULL;
+	bool ishere;
+
+	if (nextiop != NULL) {
+		iop = nextiop;
+		nextiop = NULL;
+		return (iop);
+	}
+
+	if (tpeek(cf) != REDIR)
+		return (NULL);
+	ACCEPT;
+	iop = yylval.iop;
+	ishere = (iop->flag&IOTYPE) == IOHERE;
+	musthave(LWORD, ishere ? HEREDELIM : 0);
+	if (ishere) {
+		iop->delim = yylval.cp;
+		if (*ident != 0) /* unquoted */
+			iop->flag |= IOEVAL;
+		if (herep > &heres[HERES - 1])
+			yyerror("too many <<s\n");
+		*herep++ = iop;
+	} else
+		iop->name = yylval.cp;
+
+	if (iop->flag & IOBASH) {
+		char *cp;
+
+		nextiop = alloc(sizeof(*iop), ATEMP);
+		nextiop->name = cp = alloc(5, ATEMP);
+
+		if (iop->unit > 9) {
+			*cp++ = CHAR;
+			*cp++ = '0' + (iop->unit / 10);
+		}
+		*cp++ = CHAR;
+		*cp++ = '0' + (iop->unit % 10);
+		*cp = EOS;
+
+		iop->flag &= ~IOBASH;
+		nextiop->unit = 2;
+		nextiop->flag = IODUP;
+		nextiop->delim = NULL;
+		nextiop->heredoc = NULL;
+	}
+	return (iop);
+}
+
+static struct op *
+nested(int type, int smark, int emark)
+{
+	struct op *t;
+	struct nesting_state old_nesting;
+
+	nesting_push(&old_nesting, smark);
+	t = c_list(true);
+	musthave(emark, KEYWORD|ALIAS);
+	nesting_pop(&old_nesting);
+	return (block(type, t, NOBLOCK, NOWORDS));
+}
+
+static struct op *
+get_command(int cf)
+{
+	struct op *t;
+	int c, iopn = 0, syniocf;
+	struct ioword *iop, **iops;
+	XPtrV args, vars;
+	struct nesting_state old_nesting;
+
+	iops = alloc((NUFILE + 1) * sizeof(struct ioword *), ATEMP);
+	XPinit(args, 16);
+	XPinit(vars, 16);
+
+	syniocf = KEYWORD|ALIAS;
+	switch (c = token(cf|KEYWORD|ALIAS|VARASN)) {
+	default:
+		REJECT;
+		afree(iops, ATEMP);
+		XPfree(args);
+		XPfree(vars);
+		return (NULL); /* empty line */
+
+	case LWORD:
+	case REDIR:
+		REJECT;
+		syniocf &= ~(KEYWORD|ALIAS);
+		t = newtp(TCOM);
+		t->lineno = source->line;
+		while (1) {
+			cf = (t->u.evalflags ? ARRAYVAR : 0) |
+			    (XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD);
+			switch (tpeek(cf)) {
+			case REDIR:
+				while ((iop = synio(cf)) != NULL) {
+					if (iopn >= NUFILE)
+						yyerror("too many redirections\n");
+					iops[iopn++] = iop;
+				}
+				break;
+
+			case LWORD:
+				ACCEPT;
+				/* the iopn == 0 and XPsize(vars) == 0 are
+				 * dubious but AT&T ksh acts this way
+				 */
+				if (iopn == 0 && XPsize(vars) == 0 &&
+				    XPsize(args) == 0 &&
+				    assign_command(ident))
+					t->u.evalflags = DOVACHECK;
+				if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
+				    is_wdvarassign(yylval.cp))
+					XPput(vars, yylval.cp);
+				else
+					XPput(args, yylval.cp);
+				break;
+
+			case '(':
+				/* Check for "> foo (echo hi)" which AT&T ksh
+				 * allows (not POSIX, but not disallowed)
+				 */
+				afree(t, ATEMP);
+				if (XPsize(args) == 0 && XPsize(vars) == 0) {
+					ACCEPT;
+					goto Subshell;
+				}
+#ifndef MKSH_SMALL
+				if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
+				    XPsize(vars) == 1 && is_wdvarassign(yylval.cp))
+					goto is_wdarrassign;
+#endif
+				/* Must be a function */
+				if (iopn != 0 || XPsize(args) != 1 ||
+				    XPsize(vars) != 0)
+					syntaxerr(NULL);
+				ACCEPT;
+				/*(*/
+				musthave(')', 0);
+				t = function_body(XPptrv(args)[0], false);
+				goto Leave;
+#ifndef MKSH_SMALL
+ is_wdarrassign:
+			{
+				static const char set_cmd0[] = {
+					CHAR, 'e', CHAR, 'v',
+					CHAR, 'a', CHAR, 'l', EOS
+				};
+				static const char set_cmd1[] = {
+					CHAR, 's', CHAR, 'e',
+					CHAR, 't', CHAR, ' ',
+					CHAR, '-', CHAR, 'A', EOS
+				};
+				static const char set_cmd2[] = {
+					CHAR, '-', CHAR, '-', EOS
+				};
+				char *tcp;
+				XPfree(vars);
+				XPinit(vars, 16);
+				/*
+				 * we know (or rather hope) that yylval.cp
+				 * contains a string "varname="
+				 */
+				tcp = wdcopy(yylval.cp, ATEMP);
+				tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
+				/* now make an array assignment command */
+				t = newtp(TCOM);
+				t->lineno = source->line;
+				ACCEPT;
+				XPput(args, wdcopy(set_cmd0, ATEMP));
+				XPput(args, wdcopy(set_cmd1, ATEMP));
+				XPput(args, tcp);
+				XPput(args, wdcopy(set_cmd2, ATEMP));
+				musthave(LWORD,LETARRAY);
+				XPput(args, yylval.cp);
+				break;
+			}
+#endif
+
+			default:
+				goto Leave;
+			}
+		}
+ Leave:
+		break;
+
+	case '(':
+ Subshell:
+		t = nested(TPAREN, '(', ')');
+		break;
+
+	case '{': /*}*/
+		t = nested(TBRACE, '{', '}');
+		break;
+
+	case MDPAREN: {
+		int lno;
+		static const char let_cmd[] = {
+			CHAR, 'l', CHAR, 'e',
+			CHAR, 't', EOS
+		};
+
+		/* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */
+		lno = source->line;
+		ACCEPT;
+		switch (token(LETEXPR)) {
+		case LWORD:
+			break;
+		case '(':	/* ) */
+			goto Subshell;
+		default:
+			syntaxerr(NULL);
+		}
+		t = newtp(TCOM);
+		t->lineno = lno;
+		XPput(args, wdcopy(let_cmd, ATEMP));
+		XPput(args, yylval.cp);
+		break;
+	}
+
+	case DBRACKET: /* [[ .. ]] */
+		/* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */
+		t = newtp(TDBRACKET);
+		ACCEPT;
+		{
+			Test_env te;
+
+			te.flags = TEF_DBRACKET;
+			te.pos.av = &args;
+			te.isa = dbtestp_isa;
+			te.getopnd = dbtestp_getopnd;
+			te.eval = dbtestp_eval;
+			te.error = dbtestp_error;
+
+			test_parse(&te);
+		}
+		break;
+
+	case FOR:
+	case SELECT:
+		t = newtp((c == FOR) ? TFOR : TSELECT);
+		musthave(LWORD, ARRAYVAR);
+		if (!is_wdvarname(yylval.cp, true))
+			yyerror("%s: bad identifier\n",
+			    c == FOR ? "for" : "select");
+		strdupx(t->str, ident, ATEMP);
+		nesting_push(&old_nesting, c);
+		t->vars = wordlist();
+		t->left = dogroup();
+		nesting_pop(&old_nesting);
+		break;
+
+	case WHILE:
+	case UNTIL:
+		nesting_push(&old_nesting, c);
+		t = newtp((c == WHILE) ? TWHILE : TUNTIL);
+		t->left = c_list(true);
+		t->right = dogroup();
+		nesting_pop(&old_nesting);
+		break;
+
+	case CASE:
+		t = newtp(TCASE);
+		musthave(LWORD, 0);
+		t->str = yylval.cp;
+		nesting_push(&old_nesting, c);
+		t->left = caselist();
+		nesting_pop(&old_nesting);
+		break;
+
+	case IF:
+		nesting_push(&old_nesting, c);
+		t = newtp(TIF);
+		t->left = c_list(true);
+		t->right = thenpart();
+		musthave(FI, KEYWORD|ALIAS);
+		nesting_pop(&old_nesting);
+		break;
+
+	case BANG:
+		syniocf &= ~(KEYWORD|ALIAS);
+		t = pipeline(0);
+		if (t == NULL)
+			syntaxerr(NULL);
+		t = block(TBANG, NOBLOCK, t, NOWORDS);
+		break;
+
+	case TIME:
+		syniocf &= ~(KEYWORD|ALIAS);
+		t = pipeline(0);
+		if (t) {
+			t->str = alloc(2, ATEMP);
+			t->str[0] = '\0';	/* TF_* flags */
+			t->str[1] = '\0';
+		}
+		t = block(TTIME, t, NOBLOCK, NOWORDS);
+		break;
+
+	case FUNCTION:
+		musthave(LWORD, 0);
+		t = function_body(yylval.cp, true);
+		break;
+	}
+
+	while ((iop = synio(syniocf)) != NULL) {
+		if (iopn >= NUFILE)
+			yyerror("too many redirections\n");
+		iops[iopn++] = iop;
+	}
+
+	if (iopn == 0) {
+		afree(iops, ATEMP);
+		t->ioact = NULL;
+	} else {
+		iops[iopn++] = NULL;
+		iops = aresize(iops, iopn * sizeof(struct ioword *), ATEMP);
+		t->ioact = iops;
+	}
+
+	if (t->type == TCOM || t->type == TDBRACKET) {
+		XPput(args, NULL);
+		t->args = (const char **)XPclose(args);
+		XPput(vars, NULL);
+		t->vars = (char **) XPclose(vars);
+	} else {
+		XPfree(args);
+		XPfree(vars);
+	}
+
+	return (t);
+}
+
+static struct op *
+dogroup(void)
+{
+	int c;
+	struct op *list;
+
+	c = token(CONTIN|KEYWORD|ALIAS);
+	/* A {...} can be used instead of do...done for for/select loops
+	 * but not for while/until loops - we don't need to check if it
+	 * is a while loop because it would have been parsed as part of
+	 * the conditional command list...
+	 */
+	if (c == DO)
+		c = DONE;
+	else if (c == '{')
+		c = '}';
+	else
+		syntaxerr(NULL);
+	list = c_list(true);
+	musthave(c, KEYWORD|ALIAS);
+	return (list);
+}
+
+static struct op *
+thenpart(void)
+{
+	struct op *t;
+
+	musthave(THEN, KEYWORD|ALIAS);
+	t = newtp(0);
+	t->left = c_list(true);
+	if (t->left == NULL)
+		syntaxerr(NULL);
+	t->right = elsepart();
+	return (t);
+}
+
+static struct op *
+elsepart(void)
+{
+	struct op *t;
+
+	switch (token(KEYWORD|ALIAS|VARASN)) {
+	case ELSE:
+		if ((t = c_list(true)) == NULL)
+			syntaxerr(NULL);
+		return (t);
+
+	case ELIF:
+		t = newtp(TELIF);
+		t->left = c_list(true);
+		t->right = thenpart();
+		return (t);
+
+	default:
+		REJECT;
+	}
+	return (NULL);
+}
+
+static struct op *
+caselist(void)
+{
+	struct op *t, *tl;
+	int c;
+
+	c = token(CONTIN|KEYWORD|ALIAS);
+	/* A {...} can be used instead of in...esac for case statements */
+	if (c == IN)
+		c = ESAC;
+	else if (c == '{')
+		c = '}';
+	else
+		syntaxerr(NULL);
+	t = tl = NULL;
+	while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { /* no ALIAS here */
+		struct op *tc = casepart(c);
+		if (tl == NULL)
+			t = tl = tc, tl->right = NULL;
+		else
+			tl->right = tc, tl = tc;
+	}
+	musthave(c, KEYWORD|ALIAS);
+	return (t);
+}
+
+static struct op *
+casepart(int endtok)
+{
+	struct op *t;
+	XPtrV ptns;
+
+	XPinit(ptns, 16);
+	t = newtp(TPAT);
+	/* no ALIAS here */
+	if (token(CONTIN | KEYWORD) != '(')
+		REJECT;
+	do {
+		musthave(LWORD, 0);
+		XPput(ptns, yylval.cp);
+	} while (token(0) == '|');
+	REJECT;
+	XPput(ptns, NULL);
+	t->vars = (char **) XPclose(ptns);
+	musthave(')', 0);
+
+	t->left = c_list(true);
+	/* Note: POSIX requires the ;; */
+	if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok)
+		musthave(BREAK, CONTIN|KEYWORD|ALIAS);
+	return (t);
+}
+
+static struct op *
+function_body(char *name,
+    bool ksh_func)		/* function foo { ... } vs foo() { .. } */
+{
+	char *sname, *p;
+	struct op *t;
+	bool old_func_parse;
+
+	sname = wdstrip(name, false, false);
+	/* Check for valid characters in name. POSIX and AT&T ksh93 say only
+	 * allow [a-zA-Z_0-9] but this allows more as old pdkshs have
+	 * allowed more (the following were never allowed:
+	 *	NUL TAB NL SP " $ & ' ( ) ; < = > \ ` |
+	 * C_QUOTE covers all but adds # * ? [ ]
+	 */
+	for (p = sname; *p; p++)
+		if (ctype(*p, C_QUOTE))
+			yyerror("%s: invalid function name\n", sname);
+
+	/* Note that POSIX allows only compound statements after foo(), sh and
+	 * AT&T ksh allow any command, go with the later since it shouldn't
+	 * break anything. However, for function foo, AT&T ksh only accepts
+	 * an open-brace.
+	 */
+	if (ksh_func) {
+		if (tpeek(CONTIN|KEYWORD|ALIAS) == '(' /* ) */) {
+			struct tbl *tp;
+
+			/* function foo () { */
+			ACCEPT;
+			musthave(')', 0);
+			/* degrade to POSIX function */
+			ksh_func = false;
+			if ((tp = ktsearch(&aliases, sname, hash(sname))))
+				ktdelete(tp);
+		}
+		musthave('{', CONTIN|KEYWORD|ALIAS); /* } */
+		REJECT;
+	}
+
+	t = newtp(TFUNCT);
+	t->str = sname;
+	t->u.ksh_func = ksh_func;
+	t->lineno = source->line;
+
+	old_func_parse = e->flags & EF_FUNC_PARSE;
+	e->flags |= EF_FUNC_PARSE;
+	if ((t->left = get_command(CONTIN)) == NULL) {
+		char *tv;
+		/*
+		 * Probably something like foo() followed by eof or ;.
+		 * This is accepted by sh and ksh88.
+		 * To make "typeset -f foo" work reliably (so its output can
+		 * be used as input), we pretend there is a colon here.
+		 */
+		t->left = newtp(TCOM);
+		t->left->args = alloc(2 * sizeof(char *), ATEMP);
+		t->left->args[0] = tv = alloc(3, ATEMP);
+		tv[0] = CHAR;
+		tv[1] = ':';
+		tv[2] = EOS;
+		t->left->args[1] = NULL;
+		t->left->vars = alloc(sizeof(char *), ATEMP);
+		t->left->vars[0] = NULL;
+		t->left->lineno = 1;
+	}
+	if (!old_func_parse)
+		e->flags &= ~EF_FUNC_PARSE;
+
+	return (t);
+}
+
+static char **
+wordlist(void)
+{
+	int c;
+	XPtrV args;
+
+	XPinit(args, 16);
+	/* POSIX does not do alias expansion here... */
+	if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) {
+		if (c != ';') /* non-POSIX, but AT&T ksh accepts a ; here */
+			REJECT;
+		return (NULL);
+	}
+	while ((c = token(0)) == LWORD)
+		XPput(args, yylval.cp);
+	if (c != '\n' && c != ';')
+		syntaxerr(NULL);
+	if (XPsize(args) == 0) {
+		XPfree(args);
+		return (NULL);
+	} else {
+		XPput(args, NULL);
+		return ((char **)XPclose(args));
+	}
+}
+
+/*
+ * supporting functions
+ */
+
+static struct op *
+block(int type, struct op *t1, struct op *t2, char **wp)
+{
+	struct op *t;
+
+	t = newtp(type);
+	t->left = t1;
+	t->right = t2;
+	t->vars = wp;
+	return (t);
+}
+
+const struct tokeninfo {
+	const char *name;
+	short val;
+	short reserved;
+} tokentab[] = {
+	/* Reserved words */
+	{ "if",		IF,	true },
+	{ "then",	THEN,	true },
+	{ "else",	ELSE,	true },
+	{ "elif",	ELIF,	true },
+	{ "fi",		FI,	true },
+	{ "case",	CASE,	true },
+	{ "esac",	ESAC,	true },
+	{ "for",	FOR,	true },
+	{ "select",	SELECT,	true },
+	{ "while",	WHILE,	true },
+	{ "until",	UNTIL,	true },
+	{ "do",		DO,	true },
+	{ "done",	DONE,	true },
+	{ "in",		IN,	true },
+	{ "function",	FUNCTION, true },
+	{ "time",	TIME,	true },
+	{ "{",		'{',	true },
+	{ "}",		'}',	true },
+	{ "!",		BANG,	true },
+	{ "[[",		DBRACKET, true },
+	/* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */
+	{ "&&",		LOGAND,	false },
+	{ "||",		LOGOR,	false },
+	{ ";;",		BREAK,	false },
+	{ "((",		MDPAREN, false },
+	{ "|&",		COPROC,	false },
+	/* and some special cases... */
+	{ "newline",	'\n',	false },
+	{ NULL,		0,	false }
+};
+
+void
+initkeywords(void)
+{
+	struct tokeninfo const *tt;
+	struct tbl *p;
+
+	ktinit(&keywords, APERM,
+	    /* must be 80% of 2^n (currently 20 keywords) */ 32);
+	for (tt = tokentab; tt->name; tt++) {
+		if (tt->reserved) {
+			p = ktenter(&keywords, tt->name, hash(tt->name));
+			p->flag |= DEFINED|ISSET;
+			p->type = CKEYWD;
+			p->val.i = tt->val;
+		}
+	}
+}
+
+static void
+syntaxerr(const char *what)
+{
+	char redir[6];	/* 2<<- is the longest redirection, I think */
+	const char *s;
+	struct tokeninfo const *tt;
+	int c;
+
+	if (!what)
+		what = "unexpected";
+	REJECT;
+	c = token(0);
+ Again:
+	switch (c) {
+	case 0:
+		if (nesting.start_token) {
+			c = nesting.start_token;
+			source->errline = nesting.start_line;
+			what = "unmatched";
+			goto Again;
+		}
+		/* don't quote the EOF */
+		yyerror("%s: unexpected EOF\n", T_synerr);
+		/* NOTREACHED */
+
+	case LWORD:
+		s = snptreef(NULL, 32, "%S", yylval.cp);
+		break;
+
+	case REDIR:
+		s = snptreef(redir, sizeof(redir), "%R", yylval.iop);
+		break;
+
+	default:
+		for (tt = tokentab; tt->name; tt++)
+			if (tt->val == c)
+			    break;
+		if (tt->name)
+			s = tt->name;
+		else {
+			if (c > 0 && c < 256) {
+				redir[0] = c;
+				redir[1] = '\0';
+			} else
+				shf_snprintf(redir, sizeof(redir),
+					"?%d", c);
+			s = redir;
+		}
+	}
+	yyerror("%s: '%s' %s\n", T_synerr, s, what);
+}
+
+static void
+nesting_push(struct nesting_state *save, int tok)
+{
+	*save = nesting;
+	nesting.start_token = tok;
+	nesting.start_line = source->line;
+}
+
+static void
+nesting_pop(struct nesting_state *saved)
+{
+	nesting = *saved;
+}
+
+static struct op *
+newtp(int type)
+{
+	struct op *t;
+
+	t = alloc(sizeof(struct op), ATEMP);
+	t->type = type;
+	t->u.evalflags = 0;
+	t->args = NULL;
+	t->vars = NULL;
+	t->ioact = NULL;
+	t->left = t->right = NULL;
+	t->str = NULL;
+	return (t);
+}
+
+struct op *
+compile(Source *s)
+{
+	nesting.start_token = 0;
+	nesting.start_line = 0;
+	herep = heres;
+	source = s;
+	yyparse();
+	return (outtree);
+}
+
+/* This kludge exists to take care of sh/AT&T ksh oddity in which
+ * the arguments of alias/export/readonly/typeset have no field
+ * splitting, file globbing, or (normal) tilde expansion done.
+ * AT&T ksh seems to do something similar to this since
+ *	$ touch a=a; typeset a=[ab]; echo "$a"
+ *	a=[ab]
+ *	$ x=typeset; $x a=[ab]; echo "$a"
+ *	a=a
+ *	$
+ */
+static int
+assign_command(char *s)
+{
+	if (!*s)
+		return (0);
+	return ((strcmp(s, "alias") == 0) ||
+	    (strcmp(s, "export") == 0) ||
+	    (strcmp(s, "readonly") == 0) ||
+	    (strcmp(s, T_typeset) == 0));
+}
+
+/* Check if we are in the middle of reading an alias */
+static int
+inalias(struct source *s)
+{
+	for (; s && s->type == SALIAS; s = s->next)
+		if (!(s->flags & SF_ALIASEND))
+			return (1);
+	return (0);
+}
+
+
+/* Order important - indexed by Test_meta values
+ * Note that ||, &&, ( and ) can't appear in as unquoted strings
+ * in normal shell input, so these can be interpreted unambiguously
+ * in the evaluation pass.
+ */
+static const char dbtest_or[] = { CHAR, '|', CHAR, '|', EOS };
+static const char dbtest_and[] = { CHAR, '&', CHAR, '&', EOS };
+static const char dbtest_not[] = { CHAR, '!', EOS };
+static const char dbtest_oparen[] = { CHAR, '(', EOS };
+static const char dbtest_cparen[] = { CHAR, ')', EOS };
+const char *const dbtest_tokens[] = {
+	dbtest_or, dbtest_and, dbtest_not,
+	dbtest_oparen, dbtest_cparen
+};
+const char db_close[] = { CHAR, ']', CHAR, ']', EOS };
+const char db_lthan[] = { CHAR, '<', EOS };
+const char db_gthan[] = { CHAR, '>', EOS };
+
+/*
+ * Test if the current token is a whatever. Accepts the current token if
+ * it is. Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static Test_op
+dbtestp_isa(Test_env *te, Test_meta meta)
+{
+	int c = tpeek(ARRAYVAR | (meta == TM_BINOP ? 0 : CONTIN));
+	int uqword;
+	char *save = NULL;
+	Test_op ret = TO_NONOP;
+
+	/* unquoted word? */
+	uqword = c == LWORD && *ident;
+
+	if (meta == TM_OR)
+		ret = c == LOGOR ? TO_NONNULL : TO_NONOP;
+	else if (meta == TM_AND)
+		ret = c == LOGAND ? TO_NONNULL : TO_NONOP;
+	else if (meta == TM_NOT)
+		ret = (uqword && !strcmp(yylval.cp,
+		    dbtest_tokens[(int)TM_NOT])) ? TO_NONNULL : TO_NONOP;
+	else if (meta == TM_OPAREN)
+		ret = c == '(' /*)*/ ? TO_NONNULL : TO_NONOP;
+	else if (meta == TM_CPAREN)
+		ret = c == /*(*/ ')' ? TO_NONNULL : TO_NONOP;
+	else if (meta == TM_UNOP || meta == TM_BINOP) {
+		if (meta == TM_BINOP && c == REDIR &&
+		    (yylval.iop->flag == IOREAD || yylval.iop->flag == IOWRITE)) {
+			ret = TO_NONNULL;
+			save = wdcopy(yylval.iop->flag == IOREAD ?
+			    db_lthan : db_gthan, ATEMP);
+		} else if (uqword && (ret = test_isop(meta, ident)))
+			save = yylval.cp;
+	} else /* meta == TM_END */
+		ret = (uqword && !strcmp(yylval.cp,
+		    db_close)) ? TO_NONNULL : TO_NONOP;
+	if (ret != TO_NONOP) {
+		ACCEPT;
+		if (meta < NELEM(dbtest_tokens))
+			save = wdcopy(dbtest_tokens[(int)meta], ATEMP);
+		if (save)
+			XPput(*te->pos.av, save);
+	}
+	return (ret);
+}
+
+static const char *
+dbtestp_getopnd(Test_env *te, Test_op op MKSH_A_UNUSED,
+    bool do_eval MKSH_A_UNUSED)
+{
+	int c = tpeek(ARRAYVAR);
+
+	if (c != LWORD)
+		return (NULL);
+
+	ACCEPT;
+	XPput(*te->pos.av, yylval.cp);
+
+	return (null);
+}
+
+static int
+dbtestp_eval(Test_env *te MKSH_A_UNUSED, Test_op op MKSH_A_UNUSED,
+    const char *opnd1 MKSH_A_UNUSED, const char *opnd2 MKSH_A_UNUSED,
+    bool do_eval MKSH_A_UNUSED)
+{
+	return (1);
+}
+
+static void
+dbtestp_error(Test_env *te, int offset, const char *msg)
+{
+	te->flags |= TEF_ERROR;
+
+	if (offset < 0) {
+		REJECT;
+		/* Kludgy to say the least... */
+		symbol = LWORD;
+		yylval.cp = *(XPptrv(*te->pos.av) + XPsize(*te->pos.av) +
+		    offset);
+	}
+	syntaxerr(msg);
+}
diff --git a/mksh/src/tree.c b/mksh/src/tree.c
new file mode 100644
index 0000000..aa861db
--- /dev/null
+++ b/mksh/src/tree.c
@@ -0,0 +1,716 @@
+/*	$OpenBSD: tree.c,v 1.19 2008/08/11 21:50:35 jaredy Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.30 2010/02/25 20:18:19 tg Exp $");
+
+#define INDENT	4
+
+#define tputc(c, shf) shf_putchar(c, shf);
+static void ptree(struct op *, int, struct shf *);
+static void pioact(struct shf *, int, struct ioword *);
+static void tputC(int, struct shf *);
+static void tputS(char *, struct shf *);
+static void vfptreef(struct shf *, int, const char *, va_list);
+static struct ioword **iocopy(struct ioword **, Area *);
+static void iofree(struct ioword **, Area *);
+
+/*
+ * print a command tree
+ */
+static void
+ptree(struct op *t, int indent, struct shf *shf)
+{
+	const char **w;
+	struct ioword **ioact;
+	struct op *t1;
+
+ Chain:
+	if (t == NULL)
+		return;
+	switch (t->type) {
+	case TCOM:
+		if (t->vars)
+			for (w = (const char **)t->vars; *w != NULL; )
+				fptreef(shf, indent, "%S ", *w++);
+		else
+			shf_puts("#no-vars# ", shf);
+		if (t->args)
+			for (w = t->args; *w != NULL; )
+				fptreef(shf, indent, "%S ", *w++);
+		else
+			shf_puts("#no-args# ", shf);
+		break;
+	case TEXEC:
+		t = t->left;
+		goto Chain;
+	case TPAREN:
+		fptreef(shf, indent + 2, "( %T) ", t->left);
+		break;
+	case TPIPE:
+		fptreef(shf, indent, "%T| ", t->left);
+		t = t->right;
+		goto Chain;
+	case TLIST:
+		fptreef(shf, indent, "%T%;", t->left);
+		t = t->right;
+		goto Chain;
+	case TOR:
+	case TAND:
+		fptreef(shf, indent, "%T%s %T",
+		    t->left, (t->type==TOR) ? "||" : "&&", t->right);
+		break;
+	case TBANG:
+		shf_puts("! ", shf);
+		t = t->right;
+		goto Chain;
+	case TDBRACKET: {
+		int i;
+
+		shf_puts("[[", shf);
+		for (i = 0; t->args[i]; i++)
+			fptreef(shf, indent, " %S", t->args[i]);
+		shf_puts(" ]] ", shf);
+		break;
+	}
+	case TSELECT:
+		fptreef(shf, indent, "select %s ", t->str);
+		/* FALLTHROUGH */
+	case TFOR:
+		if (t->type == TFOR)
+			fptreef(shf, indent, "for %s ", t->str);
+		if (t->vars != NULL) {
+			shf_puts("in ", shf);
+			for (w = (const char **)t->vars; *w; )
+				fptreef(shf, indent, "%S ", *w++);
+			fptreef(shf, indent, "%;");
+		}
+		fptreef(shf, indent + INDENT, "do%N%T", t->left);
+		fptreef(shf, indent, "%;done ");
+		break;
+	case TCASE:
+		fptreef(shf, indent, "case %S in", t->str);
+		for (t1 = t->left; t1 != NULL; t1 = t1->right) {
+			fptreef(shf, indent, "%N(");
+			for (w = (const char **)t1->vars; *w != NULL; w++)
+				fptreef(shf, indent, "%S%c", *w,
+				    (w[1] != NULL) ? '|' : ')');
+			fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left);
+		}
+		fptreef(shf, indent, "%Nesac ");
+		break;
+	case TIF:
+	case TELIF:
+		/* 3 == strlen("if ") */
+		fptreef(shf, indent + 3, "if %T", t->left);
+		for (;;) {
+			t = t->right;
+			if (t->left != NULL) {
+				fptreef(shf, indent, "%;");
+				fptreef(shf, indent + INDENT, "then%N%T",
+				    t->left);
+			}
+			if (t->right == NULL || t->right->type != TELIF)
+				break;
+			t = t->right;
+			fptreef(shf, indent, "%;");
+			/* 5 == strlen("elif ") */
+			fptreef(shf, indent + 5, "elif %T", t->left);
+		}
+		if (t->right != NULL) {
+			fptreef(shf, indent, "%;");
+			fptreef(shf, indent + INDENT, "else%;%T", t->right);
+		}
+		fptreef(shf, indent, "%;fi ");
+		break;
+	case TWHILE:
+	case TUNTIL:
+		/* 6 == strlen("while"/"until") */
+		fptreef(shf, indent + 6, "%s %T",
+		    (t->type==TWHILE) ? "while" : "until",
+		    t->left);
+		fptreef(shf, indent, "%;do");
+		fptreef(shf, indent + INDENT, "%;%T", t->right);
+		fptreef(shf, indent, "%;done ");
+		break;
+	case TBRACE:
+		fptreef(shf, indent + INDENT, "{%;%T", t->left);
+		fptreef(shf, indent, "%;} ");
+		break;
+	case TCOPROC:
+		fptreef(shf, indent, "%T|& ", t->left);
+		break;
+	case TASYNC:
+		fptreef(shf, indent, "%T& ", t->left);
+		break;
+	case TFUNCT:
+		fptreef(shf, indent,
+		    t->u.ksh_func ? "function %s %T" : "%s() %T",
+		    t->str, t->left);
+		break;
+	case TTIME:
+		fptreef(shf, indent, "time %T", t->left);
+		break;
+	default:
+		shf_puts("<botch>", shf);
+		break;
+	}
+	if ((ioact = t->ioact) != NULL) {
+		int	need_nl = 0;
+
+		while (*ioact != NULL)
+			pioact(shf, indent, *ioact++);
+		/* Print here documents after everything else... */
+		for (ioact = t->ioact; *ioact != NULL; ) {
+			struct ioword *iop = *ioact++;
+
+			/* heredoc is 0 when tracing (set -x) */
+			if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc &&
+			    /* iop->delim[1] == '<' means here string */
+			    (!iop->delim || iop->delim[1] != '<')) {
+				tputc('\n', shf);
+				shf_puts(iop->heredoc, shf);
+				fptreef(shf, indent, "%s",
+				    evalstr(iop->delim, 0));
+				need_nl = 1;
+			}
+		}
+		/* Last delimiter must be followed by a newline (this often
+		 * leads to an extra blank line, but its not worth worrying
+		 * about)
+		 */
+		if (need_nl)
+			tputc('\n', shf);
+	}
+}
+
+static void
+pioact(struct shf *shf, int indent, struct ioword *iop)
+{
+	int flag = iop->flag;
+	int type = flag & IOTYPE;
+	int expected;
+
+	expected = (type == IOREAD || type == IORDWR || type == IOHERE) ? 0 :
+	    (type == IOCAT || type == IOWRITE) ? 1 :
+	    (type == IODUP && (iop->unit == !(flag & IORDUP))) ? iop->unit :
+	    iop->unit + 1;
+	if (iop->unit != expected)
+		shf_fprintf(shf, "%d", iop->unit);
+
+	switch (type) {
+	case IOREAD:
+		shf_puts("< ", shf);
+		break;
+	case IOHERE:
+		shf_puts(flag & IOSKIP ? "<<-" : "<<", shf);
+		break;
+	case IOCAT:
+		shf_puts(">> ", shf);
+		break;
+	case IOWRITE:
+		shf_puts(flag & IOCLOB ? ">| " : "> ", shf);
+		break;
+	case IORDWR:
+		shf_puts("<> ", shf);
+		break;
+	case IODUP:
+		shf_puts(flag & IORDUP ? "<&" : ">&", shf);
+		break;
+	}
+	/* name/delim are 0 when printing syntax errors */
+	if (type == IOHERE) {
+		if (iop->delim)
+			fptreef(shf, indent, "%s%S ",
+			    /* here string */ iop->delim[1] == '<' ? "" : " ",
+			    iop->delim);
+		else
+			tputc(' ', shf);
+	} else if (iop->name)
+		fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ",
+		    iop->name);
+}
+
+
+/*
+ * variants of fputc, fputs for ptreef and snptreef
+ */
+static void
+tputC(int c, struct shf *shf)
+{
+	if ((c&0x60) == 0) {		/* C0|C1 */
+		tputc((c&0x80) ? '$' : '^', shf);
+		tputc(((c&0x7F)|0x40), shf);
+	} else if ((c&0x7F) == 0x7F) {	/* DEL */
+		tputc((c&0x80) ? '$' : '^', shf);
+		tputc('?', shf);
+	} else
+		tputc(c, shf);
+}
+
+static void
+tputS(char *wp, struct shf *shf)
+{
+	int c, quotelevel = 0;
+
+	/* problems:
+	 *	`...` -> $(...)
+	 *	'foo' -> "foo"
+	 * could change encoding to:
+	 *	OQUOTE ["'] ... CQUOTE ["']
+	 *	COMSUB [(`] ...\0	(handle $ ` \ and maybe " in `...` case)
+	 */
+	while (1)
+		switch (*wp++) {
+		case EOS:
+			return;
+		case ADELIM:
+		case CHAR:
+			tputC(*wp++, shf);
+			break;
+		case QCHAR:
+			c = *wp++;
+			if (!quotelevel || (c == '"' || c == '`' || c == '$'))
+				tputc('\\', shf);
+			tputC(c, shf);
+			break;
+		case COMSUB:
+			shf_puts("$(", shf);
+			while (*wp != 0)
+				tputC(*wp++, shf);
+			tputc(')', shf);
+			wp++;
+			break;
+		case EXPRSUB:
+			shf_puts("$((", shf);
+			while (*wp != 0)
+				tputC(*wp++, shf);
+			shf_puts("))", shf);
+			wp++;
+			break;
+		case OQUOTE:
+			quotelevel++;
+			tputc('"', shf);
+			break;
+		case CQUOTE:
+			if (quotelevel)
+				quotelevel--;
+			tputc('"', shf);
+			break;
+		case OSUBST:
+			tputc('$', shf);
+			if (*wp++ == '{')
+				tputc('{', shf);
+			while ((c = *wp++) != 0)
+				tputC(c, shf);
+			break;
+		case CSUBST:
+			if (*wp++ == '}')
+				tputc('}', shf);
+			break;
+		case OPAT:
+			tputc(*wp++, shf);
+			tputc('(', shf);
+			break;
+		case SPAT:
+			tputc('|', shf);
+			break;
+		case CPAT:
+			tputc(')', shf);
+			break;
+		}
+}
+
+/*
+ * this is the _only_ way to reliably handle
+ * variable args with an ANSI compiler
+ */
+/* VARARGS */
+int
+fptreef(struct shf *shf, int indent, const char *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+
+	vfptreef(shf, indent, fmt, va);
+	va_end(va);
+	return (0);
+}
+
+/* VARARGS */
+char *
+snptreef(char *s, int n, const char *fmt, ...)
+{
+	va_list va;
+	struct shf shf;
+
+	shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf);
+
+	va_start(va, fmt);
+	vfptreef(&shf, 0, fmt, va);
+	va_end(va);
+
+	return (shf_sclose(&shf)); /* null terminates */
+}
+
+static void
+vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
+{
+	int c;
+
+	while ((c = *fmt++)) {
+		if (c == '%') {
+			switch ((c = *fmt++)) {
+			case 'c':
+				tputc(va_arg(va, int), shf);
+				break;
+			case 's':
+				shf_puts(va_arg(va, char *), shf);
+				break;
+			case 'S':	/* word */
+				tputS(va_arg(va, char *), shf);
+				break;
+			case 'd':	/* decimal */
+				shf_fprintf(shf, "%d", va_arg(va, int));
+				break;
+			case 'u':	/* decimal */
+				shf_fprintf(shf, "%u", va_arg(va, unsigned int));
+				break;
+			case 'T':	/* format tree */
+				ptree(va_arg(va, struct op *), indent, shf);
+				break;
+			case ';':	/* newline or ; */
+			case 'N':	/* newline or space */
+				if (shf->flags & SHF_STRING) {
+					if (c == ';')
+						tputc(';', shf);
+					tputc(' ', shf);
+				} else {
+					int i;
+
+					tputc('\n', shf);
+					for (i = indent; i >= 8; i -= 8)
+						tputc('\t', shf);
+					for (; i > 0; --i)
+						tputc(' ', shf);
+				}
+				break;
+			case 'R':
+				pioact(shf, indent, va_arg(va, struct ioword *));
+				break;
+			default:
+				tputc(c, shf);
+				break;
+			}
+		} else
+			tputc(c, shf);
+	}
+}
+
+/*
+ * copy tree (for function definition)
+ */
+struct op *
+tcopy(struct op *t, Area *ap)
+{
+	struct op *r;
+	const char **tw;
+	char **rw;
+
+	if (t == NULL)
+		return (NULL);
+
+	r = alloc(sizeof(struct op), ap);
+
+	r->type = t->type;
+	r->u.evalflags = t->u.evalflags;
+
+	if (t->type == TCASE)
+		r->str = wdcopy(t->str, ap);
+	else
+		strdupx(r->str, t->str, ap);
+
+	if (t->vars == NULL)
+		r->vars = NULL;
+	else {
+		for (tw = (const char **)t->vars; *tw++ != NULL; )
+			;
+		rw = r->vars = alloc((tw - (const char **)t->vars + 1) *
+		    sizeof(*tw), ap);
+		for (tw = (const char **)t->vars; *tw != NULL; )
+			*rw++ = wdcopy(*tw++, ap);
+		*rw = NULL;
+	}
+
+	if (t->args == NULL)
+		r->args = NULL;
+	else {
+		for (tw = t->args; *tw++ != NULL; )
+			;
+		r->args = (const char **)(rw = alloc((tw - t->args + 1) *
+		    sizeof(*tw), ap));
+		for (tw = t->args; *tw != NULL; )
+			*rw++ = wdcopy(*tw++, ap);
+		*rw = NULL;
+	}
+
+	r->ioact = (t->ioact == NULL) ? NULL : iocopy(t->ioact, ap);
+
+	r->left = tcopy(t->left, ap);
+	r->right = tcopy(t->right, ap);
+	r->lineno = t->lineno;
+
+	return (r);
+}
+
+char *
+wdcopy(const char *wp, Area *ap)
+{
+	size_t len = wdscan(wp, EOS) - wp;
+	return (memcpy(alloc(len, ap), wp, len));
+}
+
+/* return the position of prefix c in wp plus 1 */
+const char *
+wdscan(const char *wp, int c)
+{
+	int nest = 0;
+
+	while (1)
+		switch (*wp++) {
+		case EOS:
+			return (wp);
+		case ADELIM:
+			if (c == ADELIM)
+				return (wp + 1);
+			/* FALLTHROUGH */
+		case CHAR:
+		case QCHAR:
+			wp++;
+			break;
+		case COMSUB:
+		case EXPRSUB:
+			while (*wp++ != 0)
+				;
+			break;
+		case OQUOTE:
+		case CQUOTE:
+			break;
+		case OSUBST:
+			nest++;
+			while (*wp++ != '\0')
+				;
+			break;
+		case CSUBST:
+			wp++;
+			if (c == CSUBST && nest == 0)
+				return (wp);
+			nest--;
+			break;
+		case OPAT:
+			nest++;
+			wp++;
+			break;
+		case SPAT:
+		case CPAT:
+			if (c == wp[-1] && nest == 0)
+				return (wp);
+			if (wp[-1] == CPAT)
+				nest--;
+			break;
+		default:
+			internal_warningf(
+			    "wdscan: unknown char 0x%x (carrying on)",
+			    wp[-1]);
+		}
+}
+
+/* return a copy of wp without any of the mark up characters and
+ * with quote characters (" ' \) stripped.
+ * (string is allocated from ATEMP)
+ */
+char *
+wdstrip(const char *wp, bool keepq, bool make_magic)
+{
+	struct shf shf;
+	int c;
+
+	shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf);
+
+	/* problems:
+	 *	`...` -> $(...)
+	 *	x${foo:-"hi"} -> x${foo:-hi}
+	 *	x${foo:-'hi'} -> x${foo:-hi} unless keepq
+	 */
+	while (1)
+		switch (*wp++) {
+		case EOS:
+			return (shf_sclose(&shf)); /* null terminates */
+		case ADELIM:
+		case CHAR:
+			c = *wp++;
+			if (make_magic && (ISMAGIC(c) || c == '[' || c == NOT ||
+			    c == '-' || c == ']' || c == '*' || c == '?'))
+				shf_putchar(MAGIC, &shf);
+			shf_putchar(c, &shf);
+			break;
+		case QCHAR:
+			c = *wp++;
+			if (keepq && (c == '"' || c == '`' || c == '$' || c == '\\'))
+				shf_putchar('\\', &shf);
+			shf_putchar(c, &shf);
+			break;
+		case COMSUB:
+			shf_puts("$(", &shf);
+			while (*wp != 0)
+				shf_putchar(*wp++, &shf);
+			shf_putchar(')', &shf);
+			break;
+		case EXPRSUB:
+			shf_puts("$((", &shf);
+			while (*wp != 0)
+				shf_putchar(*wp++, &shf);
+			shf_puts("))", &shf);
+			break;
+		case OQUOTE:
+			break;
+		case CQUOTE:
+			break;
+		case OSUBST:
+			shf_putchar('$', &shf);
+			if (*wp++ == '{')
+			    shf_putchar('{', &shf);
+			while ((c = *wp++) != 0)
+				shf_putchar(c, &shf);
+			break;
+		case CSUBST:
+			if (*wp++ == '}')
+				shf_putchar('}', &shf);
+			break;
+		case OPAT:
+			if (make_magic) {
+				shf_putchar(MAGIC, &shf);
+				shf_putchar(*wp++ | 0x80, &shf);
+			} else {
+				shf_putchar(*wp++, &shf);
+				shf_putchar('(', &shf);
+			}
+			break;
+		case SPAT:
+			if (make_magic)
+				shf_putchar(MAGIC, &shf);
+			shf_putchar('|', &shf);
+			break;
+		case CPAT:
+			if (make_magic)
+				shf_putchar(MAGIC, &shf);
+			shf_putchar(')', &shf);
+			break;
+		}
+}
+
+static struct ioword **
+iocopy(struct ioword **iow, Area *ap)
+{
+	struct ioword **ior;
+	int i;
+
+	for (ior = iow; *ior++ != NULL; )
+		;
+	ior = alloc((ior - iow + 1) * sizeof(struct ioword *), ap);
+
+	for (i = 0; iow[i] != NULL; i++) {
+		struct ioword *p, *q;
+
+		p = iow[i];
+		q = alloc(sizeof(struct ioword), ap);
+		ior[i] = q;
+		*q = *p;
+		if (p->name != NULL)
+			q->name = wdcopy(p->name, ap);
+		if (p->delim != NULL)
+			q->delim = wdcopy(p->delim, ap);
+		if (p->heredoc != NULL)
+			strdupx(q->heredoc, p->heredoc, ap);
+	}
+	ior[i] = NULL;
+
+	return (ior);
+}
+
+/*
+ * free tree (for function definition)
+ */
+void
+tfree(struct op *t, Area *ap)
+{
+	char **w;
+
+	if (t == NULL)
+		return;
+
+	if (t->str != NULL)
+		afree(t->str, ap);
+
+	if (t->vars != NULL) {
+		for (w = t->vars; *w != NULL; w++)
+			afree(*w, ap);
+		afree(t->vars, ap);
+	}
+
+	if (t->args != NULL) {
+		union mksh_ccphack cw;
+		/* XXX we assume the caller is right */
+		cw.ro = t->args;
+		for (w = cw.rw; *w != NULL; w++)
+			afree(*w, ap);
+		afree(t->args, ap);
+	}
+
+	if (t->ioact != NULL)
+		iofree(t->ioact, ap);
+
+	tfree(t->left, ap);
+	tfree(t->right, ap);
+
+	afree(t, ap);
+}
+
+static void
+iofree(struct ioword **iow, Area *ap)
+{
+	struct ioword **iop;
+	struct ioword *p;
+
+	for (iop = iow; (p = *iop++) != NULL; ) {
+		if (p->name != NULL)
+			afree(p->name, ap);
+		if (p->delim != NULL)
+			afree(p->delim, ap);
+		if (p->heredoc != NULL)
+			afree(p->heredoc, ap);
+		afree(p, ap);
+	}
+	afree(iow, ap);
+}
diff --git a/mksh/src/var.c b/mksh/src/var.c
new file mode 100644
index 0000000..4e9729e
--- /dev/null
+++ b/mksh/src/var.c
@@ -0,0 +1,1490 @@
+/*	$OpenBSD: var.c,v 1.34 2007/10/15 02:16:35 deraadt Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#include "sh.h"
+
+#if defined(__OpenBSD__)
+#include <sys/sysctl.h>
+#endif
+
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.110 2010/07/25 11:35:43 tg Exp $");
+
+/*
+ * Variables
+ *
+ * WARNING: unreadable code, needs a rewrite
+ *
+ * if (flag&INTEGER), val.i contains integer value, and type contains base.
+ * otherwise, (val.s + type) contains string value.
+ * if (flag&EXPORT), val.s contains "name=value" for E-Z exporting.
+ */
+static struct tbl vtemp;
+static struct table specials;
+static char *formatstr(struct tbl *, const char *);
+static void exportprep(struct tbl *, const char *);
+static int special(const char *);
+static void unspecial(const char *);
+static void getspec(struct tbl *);
+static void setspec(struct tbl *);
+static void unsetspec(struct tbl *);
+static int getint(struct tbl *, mksh_ari_t *, bool);
+static mksh_ari_t intval(struct tbl *);
+static struct tbl *arraysearch(struct tbl *, uint32_t);
+static const char *array_index_calc(const char *, bool *, uint32_t *);
+static uint32_t oaathash_update(register uint32_t, register const uint8_t *,
+    register size_t);
+static uint32_t oaathash_finalise(register uint32_t);
+
+uint8_t set_refflag = 0;
+
+/*
+ * create a new block for function calls and simple commands
+ * assume caller has allocated and set up e->loc
+ */
+void
+newblock(void)
+{
+	struct block *l;
+	static const char *empty[] = { null };
+
+	l = alloc(sizeof(struct block), ATEMP);
+	l->flags = 0;
+	ainit(&l->area); /* todo: could use e->area (l->area => l->areap) */
+	if (!e->loc) {
+		l->argc = 0;
+		l->argv = empty;
+	} else {
+		l->argc = e->loc->argc;
+		l->argv = e->loc->argv;
+	}
+	l->exit = l->error = NULL;
+	ktinit(&l->vars, &l->area, 0);
+	ktinit(&l->funs, &l->area, 0);
+	l->next = e->loc;
+	e->loc = l;
+}
+
+/*
+ * pop a block handling special variables
+ */
+void
+popblock(void)
+{
+	struct block *l = e->loc;
+	struct tbl *vp, **vpp = l->vars.tbls, *vq;
+	int i;
+
+	e->loc = l->next;	/* pop block */
+	for (i = l->vars.size; --i >= 0; )
+		if ((vp = *vpp++) != NULL && (vp->flag&SPECIAL)) {
+			if ((vq = global(vp->name))->flag & ISSET)
+				setspec(vq);
+			else
+				unsetspec(vq);
+		}
+	if (l->flags & BF_DOGETOPTS)
+		user_opt = l->getopts_state;
+	afreeall(&l->area);
+	afree(l, ATEMP);
+}
+
+/* called by main() to initialise variable data structures */
+#define VARSPEC_DEFNS
+#include "var_spec.h"
+
+enum var_specs {
+#define VARSPEC_ENUMS
+#include "var_spec.h"
+	V_MAX
+};
+
+static const char * const initvar_names[] = {
+#define VARSPEC_ITEMS
+#include "var_spec.h"
+};
+
+void
+initvar(void)
+{
+	int i = 0;
+	struct tbl *tp;
+
+	ktinit(&specials, APERM,
+	    /* must be 80% of 2^n (currently 12 specials) */ 16);
+	while (i < V_MAX - 1) {
+		tp = ktenter(&specials, initvar_names[i],
+		    hash(initvar_names[i]));
+		tp->flag = DEFINED|ISSET;
+		tp->type = ++i;
+	}
+}
+
+/* Used to calculate an array index for global()/local(). Sets *arrayp to
+ * true if this is an array, sets *valp to the array index, returns
+ * the basename of the array.
+ */
+static const char *
+array_index_calc(const char *n, bool *arrayp, uint32_t *valp)
+{
+	const char *p;
+	int len;
+	char *ap = NULL;
+
+	*arrayp = false;
+ redo_from_ref:
+	p = skip_varname(n, false);
+	if (!set_refflag && (p != n) && ksh_isalphx(n[0])) {
+		struct block *l = e->loc;
+		struct tbl *vp;
+		char *vn;
+		uint32_t h;
+
+		strndupx(vn, n, p - n, ATEMP);
+		h = hash(vn);
+		/* check if this is a reference */
+		do {
+			vp = ktsearch(&l->vars, vn, h);
+		} while (!vp && (l = l->next));
+		afree(vn, ATEMP);
+		if (vp && (vp->flag & (DEFINED|ASSOC|ARRAY)) ==
+		    (DEFINED|ASSOC)) {
+			char *cp;
+
+			/* gotcha! */
+			cp = shf_smprintf("%s%s", str_val(vp), p);
+			afree(ap, ATEMP);
+			n = ap = cp;
+			goto redo_from_ref;
+		}
+	}
+
+	if (p != n && *p == '[' && (len = array_ref_len(p))) {
+		char *sub, *tmp;
+		mksh_ari_t rval;
+
+		/* Calculate the value of the subscript */
+		*arrayp = true;
+		strndupx(tmp, p + 1, len - 2, ATEMP);
+		sub = substitute(tmp, 0);
+		afree(tmp, ATEMP);
+		strndupx(n, n, p - n, ATEMP);
+		evaluate(sub, &rval, KSH_UNWIND_ERROR, true);
+		*valp = (uint32_t)rval;
+		afree(sub, ATEMP);
+	}
+	return (n);
+}
+
+/*
+ * Search for variable, if not found create globally.
+ */
+struct tbl *
+global(const char *n)
+{
+	struct block *l = e->loc;
+	struct tbl *vp;
+	int c;
+	bool array;
+	uint32_t h, val;
+
+	/* Check to see if this is an array */
+	n = array_index_calc(n, &array, &val);
+	h = hash(n);
+	c = n[0];
+	if (!ksh_isalphx(c)) {
+		if (array)
+			errorf("bad substitution");
+		vp = &vtemp;
+		vp->flag = DEFINED;
+		vp->type = 0;
+		vp->areap = ATEMP;
+		*vp->name = c;
+		if (ksh_isdigit(c)) {
+			for (c = 0; ksh_isdigit(*n); n++)
+				c = c*10 + *n-'0';
+			if (c <= l->argc)
+				/* setstr can't fail here */
+				setstr(vp, l->argv[c], KSH_RETURN_ERROR);
+			vp->flag |= RDONLY;
+			return (vp);
+		}
+		vp->flag |= RDONLY;
+		if (n[1] != '\0')
+			return (vp);
+		vp->flag |= ISSET|INTEGER;
+		switch (c) {
+		case '$':
+			vp->val.i = kshpid;
+			break;
+		case '!':
+			/* If no job, expand to nothing */
+			if ((vp->val.i = j_async()) == 0)
+				vp->flag &= ~(ISSET|INTEGER);
+			break;
+		case '?':
+			vp->val.i = exstat;
+			break;
+		case '#':
+			vp->val.i = l->argc;
+			break;
+		case '-':
+			vp->flag &= ~INTEGER;
+			vp->val.s = getoptions();
+			break;
+		default:
+			vp->flag &= ~(ISSET|INTEGER);
+		}
+		return (vp);
+	}
+	for (l = e->loc; ; l = l->next) {
+		vp = ktsearch(&l->vars, n, h);
+		if (vp != NULL) {
+			if (array)
+				return (arraysearch(vp, val));
+			else
+				return (vp);
+		}
+		if (l->next == NULL)
+			break;
+	}
+	vp = ktenter(&l->vars, n, h);
+	if (array)
+		vp = arraysearch(vp, val);
+	vp->flag |= DEFINED;
+	if (special(n))
+		vp->flag |= SPECIAL;
+	return (vp);
+}
+
+/*
+ * Search for local variable, if not found create locally.
+ */
+struct tbl *
+local(const char *n, bool copy)
+{
+	struct block *l = e->loc;
+	struct tbl *vp;
+	bool array;
+	uint32_t h, val;
+
+	/* Check to see if this is an array */
+	n = array_index_calc(n, &array, &val);
+	h = hash(n);
+	if (!ksh_isalphx(*n)) {
+		vp = &vtemp;
+		vp->flag = DEFINED|RDONLY;
+		vp->type = 0;
+		vp->areap = ATEMP;
+		return (vp);
+	}
+	vp = ktenter(&l->vars, n, h);
+	if (copy && !(vp->flag & DEFINED)) {
+		struct block *ll = l;
+		struct tbl *vq = NULL;
+
+		while ((ll = ll->next) && !(vq = ktsearch(&ll->vars, n, h)))
+			;
+		if (vq) {
+			vp->flag |= vq->flag &
+			    (EXPORT | INTEGER | RDONLY | LJUST | RJUST |
+			    ZEROFIL | LCASEV | UCASEV_AL | INT_U | INT_L);
+			if (vq->flag & INTEGER)
+				vp->type = vq->type;
+			vp->u2.field = vq->u2.field;
+		}
+	}
+	if (array)
+		vp = arraysearch(vp, val);
+	vp->flag |= DEFINED;
+	if (special(n))
+		vp->flag |= SPECIAL;
+	return (vp);
+}
+
+/* get variable string value */
+char *
+str_val(struct tbl *vp)
+{
+	char *s;
+
+	if ((vp->flag&SPECIAL))
+		getspec(vp);
+	if (!(vp->flag&ISSET))
+		s = null;		/* special to dollar() */
+	else if (!(vp->flag&INTEGER))	/* string source */
+		s = vp->val.s + vp->type;
+	else {				/* integer source */
+		/* worst case number length is when base=2 */
+		/* 1 (minus) + 2 (base, up to 36) + 1 ('#') + number of bits
+		 * in the mksh_uari_t + 1 (NUL) */
+		char strbuf[1 + 2 + 1 + 8 * sizeof(mksh_uari_t) + 1];
+		const char *digits = (vp->flag & UCASEV_AL) ?
+		    digits_uc : digits_lc;
+		mksh_uari_t n;
+		int base;
+
+		s = strbuf + sizeof(strbuf);
+		if (vp->flag & INT_U)
+			n = vp->val.u;
+		else
+			n = (vp->val.i < 0) ? -vp->val.i : vp->val.i;
+		base = (vp->type == 0) ? 10 : vp->type;
+
+		if (base == 1) {
+			size_t sz = 1;
+
+			*(s = strbuf) = '1';
+			s[1] = '#';
+			if (!UTFMODE || ((n & 0xFF80) == 0xEF80))
+				/* OPTU-16 -> raw octet */
+				s[2] = n & 0xFF;
+			else
+				sz = utf_wctomb(s + 2, n);
+			s[2 + sz] = '\0';
+		} else {
+			*--s = '\0';
+			do {
+				*--s = digits[n % base];
+				n /= base;
+			} while (n != 0);
+			if (base != 10) {
+				*--s = '#';
+				*--s = digits[base % 10];
+				if (base >= 10)
+					*--s = digits[base / 10];
+			}
+			if (!(vp->flag & INT_U) && vp->val.i < 0)
+				*--s = '-';
+		}
+		if (vp->flag & (RJUST|LJUST)) /* case already dealt with */
+			s = formatstr(vp, s);
+		else
+			strdupx(s, s, ATEMP);
+	}
+	return (s);
+}
+
+/* get variable integer value, with error checking */
+static mksh_ari_t
+intval(struct tbl *vp)
+{
+	mksh_ari_t num;
+	int base;
+
+	base = getint(vp, &num, false);
+	if (base == -1)
+		/* XXX check calls - is error here ok by POSIX? */
+		errorf("%s: bad number", str_val(vp));
+	return (num);
+}
+
+/* set variable to string value */
+int
+setstr(struct tbl *vq, const char *s, int error_ok)
+{
+	char *salloc = NULL;
+	int no_ro_check = error_ok & 0x4;
+
+	error_ok &= ~0x4;
+	if ((vq->flag & RDONLY) && !no_ro_check) {
+		warningf(true, "%s: is read only", vq->name);
+		if (!error_ok)
+			errorfz();
+		return (0);
+	}
+	if (!(vq->flag&INTEGER)) { /* string dest */
+		if ((vq->flag&ALLOC)) {
+			/* debugging */
+			if (s >= vq->val.s &&
+			    s <= vq->val.s + strlen(vq->val.s))
+				internal_errorf(
+				    "setstr: %s=%s: assigning to self",
+				    vq->name, s);
+			afree(vq->val.s, vq->areap);
+		}
+		vq->flag &= ~(ISSET|ALLOC);
+		vq->type = 0;
+		if (s && (vq->flag & (UCASEV_AL|LCASEV|LJUST|RJUST)))
+			s = salloc = formatstr(vq, s);
+		if ((vq->flag&EXPORT))
+			exportprep(vq, s);
+		else {
+			strdupx(vq->val.s, s, vq->areap);
+			vq->flag |= ALLOC;
+		}
+	} else {		/* integer dest */
+		if (!v_evaluate(vq, s, error_ok, true))
+			return (0);
+	}
+	vq->flag |= ISSET;
+	if ((vq->flag&SPECIAL))
+		setspec(vq);
+	afree(salloc, ATEMP);
+	return (1);
+}
+
+/* set variable to integer */
+void
+setint(struct tbl *vq, mksh_ari_t n)
+{
+	if (!(vq->flag&INTEGER)) {
+		struct tbl *vp = &vtemp;
+		vp->flag = (ISSET|INTEGER);
+		vp->type = 0;
+		vp->areap = ATEMP;
+		vp->val.i = n;
+		/* setstr can't fail here */
+		setstr(vq, str_val(vp), KSH_RETURN_ERROR);
+	} else
+		vq->val.i = n;
+	vq->flag |= ISSET;
+	if ((vq->flag&SPECIAL))
+		setspec(vq);
+}
+
+static int
+getint(struct tbl *vp, mksh_ari_t *nump, bool arith)
+{
+	char *s;
+	int c, base, neg;
+	bool have_base = false;
+	mksh_ari_t num;
+
+	if (vp->flag&SPECIAL)
+		getspec(vp);
+	/* XXX is it possible for ISSET to be set and val.s to be 0? */
+	if (!(vp->flag&ISSET) || (!(vp->flag&INTEGER) && vp->val.s == NULL))
+		return (-1);
+	if (vp->flag&INTEGER) {
+		*nump = vp->val.i;
+		return (vp->type);
+	}
+	s = vp->val.s + vp->type;
+	if (s == NULL)	/* redundant given initial test */
+		s = null;
+	base = 10;
+	num = 0;
+	neg = 0;
+	if (arith && *s == '0' && *(s+1)) {
+		s++;
+		if (*s == 'x' || *s == 'X') {
+			s++;
+			base = 16;
+		} else if (vp->flag & ZEROFIL) {
+			while (*s == '0')
+				s++;
+		} else
+			base = 8;
+		have_base = true;
+	}
+	for (c = *s++; c ; c = *s++) {
+		if (c == '-') {
+			neg++;
+			continue;
+		} else if (c == '#') {
+			base = (int)num;
+			if (have_base || base < 1 || base > 36)
+				return (-1);
+			if (base == 1) {
+				unsigned int wc;
+
+				if (!UTFMODE)
+					wc = *(unsigned char *)s;
+				else if (utf_mbtowc(&wc, s) == (size_t)-1)
+					/* OPTU-8 -> OPTU-16 */
+					/*
+					 * (with a twist: 1#\uEF80 converts
+					 * the same as 1#\x80 does, thus is
+					 * not round-tripping correctly XXX)
+					 */
+					wc = 0xEF00 + *(unsigned char *)s;
+				*nump = (mksh_ari_t)wc;
+				return (1);
+			}
+			num = 0;
+			have_base = true;
+			continue;
+		} else if (ksh_isdigit(c))
+			c -= '0';
+		else if (ksh_islower(c))
+			c -= 'a' - 10;
+		else if (ksh_isupper(c))
+			c -= 'A' - 10;
+		else
+			return (-1);
+		if (c < 0 || c >= base)
+			return (-1);
+		num = num * base + c;
+	}
+	if (neg)
+		num = -num;
+	*nump = num;
+	return (base);
+}
+
+/* convert variable vq to integer variable, setting its value from vp
+ * (vq and vp may be the same)
+ */
+struct tbl *
+setint_v(struct tbl *vq, struct tbl *vp, bool arith)
+{
+	int base;
+	mksh_ari_t num;
+
+	if ((base = getint(vp, &num, arith)) == -1)
+		return (NULL);
+	if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) {
+		vq->flag &= ~ALLOC;
+		afree(vq->val.s, vq->areap);
+	}
+	vq->val.i = num;
+	if (vq->type == 0) /* default base */
+		vq->type = base;
+	vq->flag |= ISSET|INTEGER;
+	if (vq->flag&SPECIAL)
+		setspec(vq);
+	return (vq);
+}
+
+static char *
+formatstr(struct tbl *vp, const char *s)
+{
+	int olen, nlen;
+	char *p, *q;
+	size_t psiz;
+
+	olen = utf_mbswidth(s);
+
+	if (vp->flag & (RJUST|LJUST)) {
+		if (!vp->u2.field)	/* default field width */
+			vp->u2.field = olen;
+		nlen = vp->u2.field;
+	} else
+		nlen = olen;
+
+	p = alloc((psiz = nlen * /* MB_LEN_MAX */ 3 + 1), ATEMP);
+	if (vp->flag & (RJUST|LJUST)) {
+		int slen = olen, i = 0;
+
+		if (vp->flag & RJUST) {
+			const char *qq = s;
+			int n = 0;
+
+			while (i < slen)
+				i += utf_widthadj(qq, &qq);
+			/* strip trailing spaces (AT&T uses qq[-1] == ' ') */
+			while (qq > s && ksh_isspace(qq[-1])) {
+				--qq;
+				--slen;
+			}
+			if (vp->flag & ZEROFIL && vp->flag & INTEGER) {
+				if (s[1] == '#')
+					n = 2;
+				else if (s[2] == '#')
+					n = 3;
+				if (vp->u2.field <= n)
+					n = 0;
+			}
+			if (n) {
+				memcpy(p, s, n);
+				s += n;
+			}
+			while (slen > vp->u2.field)
+				slen -= utf_widthadj(s, &s);
+			if (vp->u2.field - slen)
+				memset(p + n, (vp->flag & ZEROFIL) ? '0' : ' ',
+				    vp->u2.field - slen);
+			slen -= n;
+			shf_snprintf(p + vp->u2.field - slen,
+			    psiz - (vp->u2.field - slen),
+			    "%.*s", slen, s);
+		} else {
+			/* strip leading spaces/zeros */
+			while (ksh_isspace(*s))
+				s++;
+			if (vp->flag & ZEROFIL)
+				while (*s == '0')
+					s++;
+			shf_snprintf(p, nlen + 1, "%-*.*s",
+				vp->u2.field, vp->u2.field, s);
+		}
+	} else
+		memcpy(p, s, strlen(s) + 1);
+
+	if (vp->flag & UCASEV_AL) {
+		for (q = p; *q; q++)
+			*q = ksh_toupper(*q);
+	} else if (vp->flag & LCASEV) {
+		for (q = p; *q; q++)
+			*q = ksh_tolower(*q);
+	}
+
+	return (p);
+}
+
+/*
+ * make vp->val.s be "name=value" for quick exporting.
+ */
+static void
+exportprep(struct tbl *vp, const char *val)
+{
+	char *xp;
+	char *op = (vp->flag&ALLOC) ? vp->val.s : NULL;
+	int namelen = strlen(vp->name);
+	int vallen = strlen(val) + 1;
+
+	vp->flag |= ALLOC;
+	xp = alloc(namelen + 1 + vallen, vp->areap);
+	memcpy(vp->val.s = xp, vp->name, namelen);
+	xp += namelen;
+	*xp++ = '=';
+	vp->type = xp - vp->val.s; /* offset to value */
+	memcpy(xp, val, vallen);
+	if (op != NULL)
+		afree(op, vp->areap);
+}
+
+/*
+ * lookup variable (according to (set&LOCAL)),
+ * set its attributes (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL,
+ * LCASEV, UCASEV_AL), and optionally set its value if an assignment.
+ */
+struct tbl *
+typeset(const char *var, Tflag set, Tflag clr, int field, int base)
+{
+	struct tbl *vp;
+	struct tbl *vpbase, *t;
+	char *tvar;
+	const char *val;
+	int len;
+
+	/* check for valid variable name, search for value */
+	val = skip_varname(var, false);
+	if (val == var)
+		return (NULL);
+	mkssert(var != NULL);
+	mkssert(*var != 0);
+	if (*val == '[') {
+		if (set_refflag)
+			errorf("%s: reference variable cannot be an array",
+			    var);
+		len = array_ref_len(val);
+		if (len == 0)
+			return (NULL);
+		/* IMPORT is only used when the shell starts up and is
+		 * setting up its environment. Allow only simple array
+		 * references at this time since parameter/command substitution
+		 * is preformed on the [expression] which would be a major
+		 * security hole.
+		 */
+		if (set & IMPORT) {
+			int i;
+			for (i = 1; i < len - 1; i++)
+				if (!ksh_isdigit(val[i]))
+					return (NULL);
+		}
+		val += len;
+	}
+	if (*val == '=')
+		strndupx(tvar, var, val++ - var, ATEMP);
+	else {
+		/* Importing from original environment: must have an = */
+		if (set & IMPORT)
+			return (NULL);
+		strdupx(tvar, var, ATEMP);
+		val = NULL;
+		/* handle foo[*] ⇒ foo (whole array) mapping for R39b */
+		len = strlen(tvar);
+		if (len > 3 && tvar[len-3] == '[' && tvar[len-2] == '*' &&
+		    tvar[len-1] == ']')
+			tvar[len-3] = '\0';
+	}
+
+	/* Prevent typeset from creating a local PATH/ENV/SHELL */
+	if (Flag(FRESTRICTED) && (strcmp(tvar, "PATH") == 0 ||
+	    strcmp(tvar, "ENV") == 0 || strcmp(tvar, "SHELL") == 0))
+		errorf("%s: restricted", tvar);
+
+	vp = (set&LOCAL) ? local(tvar, (set & LOCAL_COPY) ? true : false) :
+	    global(tvar);
+	if (set_refflag == 2 && (vp->flag & (ARRAY|ASSOC)) == ASSOC)
+		vp->flag &= ~ASSOC;
+	else if (set_refflag == 1) {
+		if (vp->flag & ARRAY) {
+			struct tbl *a, *tmp;
+
+			/* Free up entire array */
+			for (a = vp->u.array; a; ) {
+				tmp = a;
+				a = a->u.array;
+				if (tmp->flag & ALLOC)
+					afree(tmp->val.s, tmp->areap);
+				afree(tmp, tmp->areap);
+			}
+			vp->u.array = NULL;
+			vp->flag &= ~ARRAY;
+		}
+		vp->flag |= ASSOC;
+	}
+
+	set &= ~(LOCAL|LOCAL_COPY);
+
+	vpbase = (vp->flag & ARRAY) ? global(arrayname(var)) : vp;
+
+	/* only allow export flag to be set. AT&T ksh allows any attribute to
+	 * be changed which means it can be truncated or modified (-L/-R/-Z/-i)
+	 */
+	if ((vpbase->flag&RDONLY) &&
+	    (val || clr || (set & ~EXPORT)))
+		/* XXX check calls - is error here ok by POSIX? */
+		errorf("%s: is read only", tvar);
+	afree(tvar, ATEMP);
+
+	/* most calls are with set/clr == 0 */
+	if (set | clr) {
+		bool ok = true;
+
+		/* XXX if x[0] isn't set, there will be problems: need to have
+		 * one copy of attributes for arrays...
+		 */
+		for (t = vpbase; t; t = t->u.array) {
+			bool fake_assign;
+			char *s = NULL;
+			char *free_me = NULL;
+
+			fake_assign = (t->flag & ISSET) && (!val || t != vp) &&
+			    ((set & (UCASEV_AL|LCASEV|LJUST|RJUST|ZEROFIL)) ||
+			    ((t->flag & INTEGER) && (clr & INTEGER)) ||
+			    (!(t->flag & INTEGER) && (set & INTEGER)));
+			if (fake_assign) {
+				if (t->flag & INTEGER) {
+					s = str_val(t);
+					free_me = NULL;
+				} else {
+					s = t->val.s + t->type;
+					free_me = (t->flag & ALLOC) ? t->val.s :
+					    NULL;
+				}
+				t->flag &= ~ALLOC;
+			}
+			if (!(t->flag & INTEGER) && (set & INTEGER)) {
+				t->type = 0;
+				t->flag &= ~ALLOC;
+			}
+			t->flag = (t->flag | set) & ~clr;
+			/* Don't change base if assignment is to be done,
+			 * in case assignment fails.
+			 */
+			if ((set & INTEGER) && base > 0 && (!val || t != vp))
+				t->type = base;
+			if (set & (LJUST|RJUST|ZEROFIL))
+				t->u2.field = field;
+			if (fake_assign) {
+				if (!setstr(t, s, KSH_RETURN_ERROR)) {
+					/* Somewhat arbitrary action here:
+					 * zap contents of variable, but keep
+					 * the flag settings.
+					 */
+					ok = false;
+					if (t->flag & INTEGER)
+						t->flag &= ~ISSET;
+					else {
+						if (t->flag & ALLOC)
+							afree(t->val.s, t->areap);
+						t->flag &= ~(ISSET|ALLOC);
+						t->type = 0;
+					}
+				}
+				if (free_me)
+					afree(free_me, t->areap);
+			}
+		}
+		if (!ok)
+			errorfz();
+	}
+
+	if (val != NULL) {
+		if (vp->flag&INTEGER) {
+			/* do not zero base before assignment */
+			setstr(vp, val, KSH_UNWIND_ERROR | 0x4);
+			/* Done after assignment to override default */
+			if (base > 0)
+				vp->type = base;
+		} else
+			/* setstr can't fail (readonly check already done) */
+			setstr(vp, val, KSH_RETURN_ERROR | 0x4);
+	}
+
+	/* only x[0] is ever exported, so use vpbase */
+	if ((vpbase->flag&EXPORT) && !(vpbase->flag&INTEGER) &&
+	    vpbase->type == 0)
+		exportprep(vpbase, (vpbase->flag&ISSET) ? vpbase->val.s : null);
+
+	return (vp);
+}
+
+/**
+ * Unset a variable. The flags can be:
+ * |1	= tear down entire array
+ * |2	= keep attributes, only unset content
+ */
+void
+unset(struct tbl *vp, int flags)
+{
+	if (vp->flag & ALLOC)
+		afree(vp->val.s, vp->areap);
+	if ((vp->flag & ARRAY) && (flags & 1)) {
+		struct tbl *a, *tmp;
+
+		/* Free up entire array */
+		for (a = vp->u.array; a; ) {
+			tmp = a;
+			a = a->u.array;
+			if (tmp->flag & ALLOC)
+				afree(tmp->val.s, tmp->areap);
+			afree(tmp, tmp->areap);
+		}
+		vp->u.array = NULL;
+	}
+	if (flags & 2) {
+		vp->flag &= ~(ALLOC|ISSET);
+		return;
+	}
+	/* If foo[0] is being unset, the remainder of the array is kept... */
+	vp->flag &= SPECIAL | ((flags & 1) ? 0 : ARRAY|DEFINED);
+	if (vp->flag & SPECIAL)
+		unsetspec(vp);	/* responsible for 'unspecial'ing var */
+}
+
+/* return a pointer to the first char past a legal variable name (returns the
+ * argument if there is no legal name, returns a pointer to the terminating
+ * NUL if whole string is legal).
+ */
+const char *
+skip_varname(const char *s, int aok)
+{
+	int alen;
+
+	if (s && ksh_isalphx(*s)) {
+		while (*++s && ksh_isalnux(*s))
+			;
+		if (aok && *s == '[' && (alen = array_ref_len(s)))
+			s += alen;
+	}
+	return (s);
+}
+
+/* Return a pointer to the first character past any legal variable name */
+const char *
+skip_wdvarname(const char *s,
+    int aok)				/* skip array de-reference? */
+{
+	if (s[0] == CHAR && ksh_isalphx(s[1])) {
+		do {
+			s += 2;
+		} while (s[0] == CHAR && ksh_isalnux(s[1]));
+		if (aok && s[0] == CHAR && s[1] == '[') {
+			/* skip possible array de-reference */
+			const char *p = s;
+			char c;
+			int depth = 0;
+
+			while (1) {
+				if (p[0] != CHAR)
+					break;
+				c = p[1];
+				p += 2;
+				if (c == '[')
+					depth++;
+				else if (c == ']' && --depth == 0) {
+					s = p;
+					break;
+				}
+			}
+		}
+	}
+	return (s);
+}
+
+/* Check if coded string s is a variable name */
+int
+is_wdvarname(const char *s, int aok)
+{
+	const char *p = skip_wdvarname(s, aok);
+
+	return (p != s && p[0] == EOS);
+}
+
+/* Check if coded string s is a variable assignment */
+int
+is_wdvarassign(const char *s)
+{
+	const char *p = skip_wdvarname(s, true);
+
+	return (p != s && p[0] == CHAR && p[1] == '=');
+}
+
+/*
+ * Make the exported environment from the exported names in the dictionary.
+ */
+char **
+makenv(void)
+{
+	struct block *l;
+	XPtrV denv;
+	struct tbl *vp, **vpp;
+	int i;
+
+	XPinit(denv, 64);
+	for (l = e->loc; l != NULL; l = l->next)
+		for (vpp = l->vars.tbls, i = l->vars.size; --i >= 0; )
+			if ((vp = *vpp++) != NULL &&
+			    (vp->flag&(ISSET|EXPORT)) == (ISSET|EXPORT)) {
+				struct block *l2;
+				struct tbl *vp2;
+				uint32_t h = hash(vp->name);
+
+				/* unexport any redefined instances */
+				for (l2 = l->next; l2 != NULL; l2 = l2->next) {
+					vp2 = ktsearch(&l2->vars, vp->name, h);
+					if (vp2 != NULL)
+						vp2->flag &= ~EXPORT;
+				}
+				if ((vp->flag&INTEGER)) {
+					/* integer to string */
+					char *val;
+					val = str_val(vp);
+					vp->flag &= ~(INTEGER|RDONLY|SPECIAL);
+					/* setstr can't fail here */
+					setstr(vp, val, KSH_RETURN_ERROR);
+				}
+				XPput(denv, vp->val.s);
+			}
+	XPput(denv, NULL);
+	return ((char **)XPclose(denv));
+}
+
+/* Bob Jenkins' one-at-a-time hash */
+static uint32_t
+oaathash_update(register uint32_t h, register const uint8_t *cp,
+    register size_t n)
+{
+	while (n--) {
+		h += *cp++;
+		h += h << 10;
+		h ^= h >> 6;
+	}
+
+	return (h);
+}
+
+static uint32_t
+oaathash_finalise(register uint32_t h)
+{
+	h += h << 3;
+	h ^= h >> 11;
+	h += h << 15;
+
+	return (h);
+}
+
+uint32_t
+oaathash_full(register const uint8_t *bp)
+{
+	register uint32_t h = 0;
+	register uint8_t c;
+
+	while ((c = *bp++)) {
+		h += c;
+		h += h << 10;
+		h ^= h >> 6;
+	}
+
+	return (oaathash_finalise(h));
+}
+
+void
+change_random(const void *vp, size_t n)
+{
+	register uint32_t h = 0x100;
+#if defined(__OpenBSD__)
+	int mib[2];
+	uint8_t k[3];
+	size_t klen;
+#endif
+
+	kshstate_v.cr_dp = vp;
+	kshstate_v.cr_dsz = n;
+	gettimeofday(&kshstate_v.cr_tv, NULL);
+	h = oaathash_update(oaathash_update(h, (void *)&kshstate_v,
+	    sizeof(kshstate_v)), vp, n);
+	kshstate_v.lcg_state_ = oaathash_finalise(h);
+
+#if defined(__OpenBSD__)
+	/* OpenBSD, MirBSD: proper kernel entropy comes at zero cost */
+
+	mib[0] = CTL_KERN;
+	mib[1] = KERN_ARND;
+	klen = sizeof(k);
+	sysctl(mib, 2, k, &klen, &kshstate_v.lcg_state_,
+	    sizeof(kshstate_v.lcg_state_));
+	/* we ignore failures and take in k anyway */
+	h = oaathash_update(h, k, sizeof(k));
+	kshstate_v.lcg_state_ = oaathash_finalise(h);
+#elif defined(MKSH_A4PB)
+	/* forced by the user to use arc4random_pushb(3) • Cygwin? */
+	{
+		uint32_t prv;
+
+		prv = arc4random_pushb(&kshstate_v.lcg_state_,
+		    sizeof(kshstate_v.lcg_state_));
+		h = oaathash_update(h, &prv, sizeof(prv));
+	}
+	kshstate_v.lcg_state_ = oaathash_finalise(h);
+#endif
+}
+
+/*
+ * handle special variables with side effects - PATH, SECONDS.
+ */
+
+/* Test if name is a special parameter */
+static int
+special(const char *name)
+{
+	struct tbl *tp;
+
+	tp = ktsearch(&specials, name, hash(name));
+	return (tp && (tp->flag & ISSET) ? tp->type : V_NONE);
+}
+
+/* Make a variable non-special */
+static void
+unspecial(const char *name)
+{
+	struct tbl *tp;
+
+	tp = ktsearch(&specials, name, hash(name));
+	if (tp)
+		ktdelete(tp);
+}
+
+static time_t seconds;		/* time SECONDS last set */
+static int user_lineno;		/* what user set $LINENO to */
+
+static void
+getspec(struct tbl *vp)
+{
+	register mksh_ari_t i;
+	int st;
+
+	switch ((st = special(vp->name))) {
+	case V_SECONDS:
+		/*
+		 * On start up the value of SECONDS is used before
+		 * it has been set - don't do anything in this case
+		 * (see initcoms[] in main.c).
+		 */
+		if (vp->flag & ISSET) {
+			struct timeval tv;
+
+			gettimeofday(&tv, NULL);
+			i = tv.tv_sec - seconds;
+		} else
+			return;
+		break;
+	case V_RANDOM:
+		/*
+		 * this is the same Linear Congruential PRNG as Borland
+		 * C/C++ allegedly uses in its built-in rand() function
+		 */
+		i = ((kshstate_v.lcg_state_ =
+		    22695477 * kshstate_v.lcg_state_ + 1) >> 16) & 0x7FFF;
+		break;
+	case V_HISTSIZE:
+		i = histsize;
+		break;
+	case V_OPTIND:
+		i = user_opt.uoptind;
+		break;
+	case V_LINENO:
+		i = current_lineno + user_lineno;
+		break;
+	case V_COLUMNS:
+	case V_LINES:
+		/*
+		 * Do NOT export COLUMNS/LINES. Many applications
+		 * check COLUMNS/LINES before checking ws.ws_col/row,
+		 * so if the app is started with C/L in the environ
+		 * and the window is then resized, the app won't
+		 * see the change cause the environ doesn't change.
+		 */
+		change_winsz();
+		i = st == V_COLUMNS ? x_cols : x_lins;
+		break;
+	default:
+		/* do nothing, do not touch vp at all */
+		return;
+	}
+	vp->flag &= ~SPECIAL;
+	setint(vp, i);
+	vp->flag |= SPECIAL;
+}
+
+static void
+setspec(struct tbl *vp)
+{
+	mksh_ari_t i;
+	char *s;
+	int st;
+
+	switch ((st = special(vp->name))) {
+	case V_PATH:
+		if (path)
+			afree(path, APERM);
+		s = str_val(vp);
+		strdupx(path, s, APERM);
+		flushcom(1);	/* clear tracked aliases */
+		return;
+	case V_IFS:
+		setctypes(s = str_val(vp), C_IFS);
+		ifs0 = *s;
+		return;
+	case V_TMPDIR:
+		if (tmpdir) {
+			afree(tmpdir, APERM);
+			tmpdir = NULL;
+		}
+		/* Use tmpdir iff it is an absolute path, is writable and
+		 * searchable and is a directory...
+		 */
+		{
+			struct stat statb;
+
+			s = str_val(vp);
+			if (s[0] == '/' && access(s, W_OK|X_OK) == 0 &&
+			    stat(s, &statb) == 0 && S_ISDIR(statb.st_mode))
+				strdupx(tmpdir, s, APERM);
+		}
+		break;
+#if HAVE_PERSISTENT_HISTORY
+	case V_HISTFILE:
+		sethistfile(str_val(vp));
+		break;
+#endif
+	case V_TMOUT:
+		/* AT&T ksh seems to do this (only listen if integer) */
+		if (vp->flag & INTEGER)
+			ksh_tmout = vp->val.i >= 0 ? vp->val.i : 0;
+		break;
+
+	/* common sub-cases */
+	case V_OPTIND:
+	case V_HISTSIZE:
+	case V_COLUMNS:
+	case V_LINES:
+	case V_RANDOM:
+	case V_SECONDS:
+	case V_LINENO:
+		vp->flag &= ~SPECIAL;
+		i = intval(vp);
+		vp->flag |= SPECIAL;
+		break;
+	default:
+		/* do nothing, do not touch vp at all */
+		return;
+	}
+
+	/* process the singular parts of the common cases */
+
+	switch (st) {
+	case V_OPTIND:
+		getopts_reset((int)i);
+		break;
+	case V_HISTSIZE:
+		sethistsize((int)i);
+		break;
+	case V_COLUMNS:
+		if (i >= MIN_COLS)
+			x_cols = i;
+		break;
+	case V_LINES:
+		if (i >= MIN_LINS)
+			x_lins = i;
+		break;
+	case V_RANDOM:
+		/*
+		 * mksh R39d+ no longer has the traditional repeatability
+		 * of $RANDOM sequences, but always retains state
+		 */
+		change_random(&i, sizeof(i));
+		break;
+	case V_SECONDS:
+		{
+			struct timeval tv;
+
+			gettimeofday(&tv, NULL);
+			seconds = tv.tv_sec - i;
+		}
+		break;
+	case V_LINENO:
+		/* The -1 is because line numbering starts at 1. */
+		user_lineno = (unsigned int)i - current_lineno - 1;
+		break;
+	}
+}
+
+static void
+unsetspec(struct tbl *vp)
+{
+	switch (special(vp->name)) {
+	case V_PATH:
+		if (path)
+			afree(path, APERM);
+		strdupx(path, def_path, APERM);
+		flushcom(1);	/* clear tracked aliases */
+		break;
+	case V_IFS:
+		setctypes(" \t\n", C_IFS);
+		ifs0 = ' ';
+		break;
+	case V_TMPDIR:
+		/* should not become unspecial */
+		if (tmpdir) {
+			afree(tmpdir, APERM);
+			tmpdir = NULL;
+		}
+		break;
+	case V_LINENO:
+	case V_RANDOM:
+	case V_SECONDS:
+	case V_TMOUT:		/* AT&T ksh leaves previous value in place */
+		unspecial(vp->name);
+		break;
+
+	/*
+	 * AT&T ksh man page says OPTIND, OPTARG and _ lose special
+	 * meaning, but OPTARG does not (still set by getopts) and _ is
+	 * also still set in various places. Don't know what AT&T does
+	 * for HISTSIZE, HISTFILE. Unsetting these in AT&T ksh does not
+	 * loose the 'specialness': IFS, COLUMNS, PATH, TMPDIR
+	 */
+	}
+}
+
+/*
+ * Search for (and possibly create) a table entry starting with
+ * vp, indexed by val.
+ */
+static struct tbl *
+arraysearch(struct tbl *vp, uint32_t val)
+{
+	struct tbl *prev, *curr, *news;
+	size_t len;
+
+	vp->flag = (vp->flag | (ARRAY|DEFINED)) & ~ASSOC;
+	/* The table entry is always [0] */
+	if (val == 0)
+		return (vp);
+	prev = vp;
+	curr = vp->u.array;
+	while (curr && curr->ua.index < val) {
+		prev = curr;
+		curr = curr->u.array;
+	}
+	if (curr && curr->ua.index == val) {
+		if (curr->flag&ISSET)
+			return (curr);
+		news = curr;
+	} else
+		news = NULL;
+	len = strlen(vp->name) + 1;
+	if (!news) {
+		news = alloc(offsetof(struct tbl, name[0]) + len, vp->areap);
+		memcpy(news->name, vp->name, len);
+	}
+	news->flag = (vp->flag & ~(ALLOC|DEFINED|ISSET|SPECIAL)) | AINDEX;
+	news->type = vp->type;
+	news->areap = vp->areap;
+	news->u2.field = vp->u2.field;
+	news->ua.index = val;
+
+	if (curr != news) {		/* not reusing old array entry */
+		prev->u.array = news;
+		news->u.array = curr;
+	}
+	return (news);
+}
+
+/* Return the length of an array reference (eg, [1+2]) - cp is assumed
+ * to point to the open bracket. Returns 0 if there is no matching closing
+ * bracket.
+ */
+int
+array_ref_len(const char *cp)
+{
+	const char *s = cp;
+	int c;
+	int depth = 0;
+
+	while ((c = *s++) && (c != ']' || --depth))
+		if (c == '[')
+			depth++;
+	if (!c)
+		return (0);
+	return (s - cp);
+}
+
+/*
+ * Make a copy of the base of an array name
+ */
+char *
+arrayname(const char *str)
+{
+	const char *p;
+	char *rv;
+
+	if ((p = cstrchr(str, '[')) == 0)
+		/* Shouldn't happen, but why worry? */
+		strdupx(rv, str, ATEMP);
+	else
+		strndupx(rv, str, p - str, ATEMP);
+
+	return (rv);
+}
+
+/* set (or overwrite, if reset) the array variable var to the values in vals */
+mksh_uari_t
+set_array(const char *var, bool reset, const char **vals)
+{
+	struct tbl *vp, *vq;
+	mksh_uari_t i;
+	const char *ccp;
+#ifndef MKSH_SMALL
+	char *cp;
+	mksh_uari_t j;
+#endif
+
+	/* to get local array, use "typeset foo; set -A foo" */
+	vp = global(var);
+
+	/* Note: AT&T ksh allows set -A but not set +A of a read-only var */
+	if ((vp->flag&RDONLY))
+		errorf("%s: is read only", var);
+	/* This code is quite non-optimal */
+	if (reset)
+		/* trash existing values and attributes */
+		unset(vp, 1);
+	/* todo: would be nice for assignment to completely succeed or
+	 * completely fail. Only really effects integer arrays:
+	 * evaluation of some of vals[] may fail...
+	 */
+	i = 0;
+#ifndef MKSH_SMALL
+	j = 0;
+#else
+#define j i
+#endif
+	while ((ccp = vals[i])) {
+#ifndef MKSH_SMALL
+		if (*ccp == '[') {
+			int level = 0;
+
+			while (*ccp) {
+				if (*ccp == ']' && --level == 0)
+					break;
+				if (*ccp == '[')
+					++level;
+				++ccp;
+			}
+			if (*ccp == ']' && level == 0 && ccp[1] == '=') {
+				strndupx(cp, vals[i] + 1, ccp - (vals[i] + 1),
+				    ATEMP);
+				evaluate(substitute(cp, 0), (mksh_ari_t *)&j,
+				    KSH_UNWIND_ERROR, true);
+				afree(cp, ATEMP);
+				ccp += 2;
+			} else
+				ccp = vals[i];
+		}
+#endif
+
+		vq = arraysearch(vp, j);
+		/* would be nice to deal with errors here... (see above) */
+		setstr(vq, ccp, KSH_RETURN_ERROR);
+		i++;
+#ifndef MKSH_SMALL
+		j++;
+#endif
+	}
+
+	return (i);
+}
+
+void
+change_winsz(void)
+{
+	if (x_lins < 0) {
+		/* first time initialisation */
+#ifdef TIOCGWINSZ
+		if (tty_fd < 0)
+			/* non-FTALKING, try to get an fd anyway */
+			tty_init(false, false);
+#endif
+		x_cols = -1;
+	}
+
+#ifdef TIOCGWINSZ
+	/* check if window size has changed */
+	if (tty_fd >= 0) {
+		struct winsize ws;
+
+		if (ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) {
+			if (ws.ws_col)
+				x_cols = ws.ws_col;
+			if (ws.ws_row)
+				x_lins = ws.ws_row;
+		}
+	}
+#endif
+
+	/* bounds check for sane values, use defaults otherwise */
+	if (x_cols < MIN_COLS)
+		x_cols = 80;
+	if (x_lins < MIN_LINS)
+		x_lins = 24;
+
+#ifdef SIGWINCH
+	got_winch = 0;
+#endif
+}
+
+uint32_t
+evilhash(const char *s)
+{
+	register uint32_t h = 0x100;
+
+	h = oaathash_update(h, (void *)&kshstate_f, sizeof(kshstate_f));
+	kshstate_f.h = oaathash_full((const uint8_t *)s);
+	return (oaathash_finalise(oaathash_update(h,
+	    (void *)&kshstate_f.h, sizeof(kshstate_f.h))));
+}
diff --git a/mksh/src/var_spec.h b/mksh/src/var_spec.h
new file mode 100644
index 0000000..4035cc9
--- /dev/null
+++ b/mksh/src/var_spec.h
@@ -0,0 +1,39 @@
+#if defined(VARSPEC_DEFNS)
+__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.1 2009/09/26 03:40:03 tg Exp $");
+#define FN(name)			/* nothing */
+#elif defined(VARSPEC_ENUMS)
+#define FN(name)			V_##name,
+#define F0(name)			V_##name = 0,
+#elif defined(VARSPEC_ITEMS)
+#define F0(name)			/* nothing */
+#define FN(name)			#name,
+#endif
+
+#ifndef F0
+#define F0 FN
+#endif
+
+/* 0 is always V_NONE */
+F0(NONE)
+
+/* 1 and up are special variables */
+FN(COLUMNS)
+#if HAVE_PERSISTENT_HISTORY
+FN(HISTFILE)
+#endif
+FN(HISTSIZE)
+FN(IFS)
+FN(LINENO)
+FN(LINES)
+FN(OPTIND)
+FN(PATH)
+FN(RANDOM)
+FN(SECONDS)
+FN(TMOUT)
+FN(TMPDIR)
+
+#undef FN
+#undef F0
+#undef VARSPEC_DEFNS
+#undef VARSPEC_ENUMS
+#undef VARSPEC_ITEMS
diff --git a/netcfg/netcfg.c b/netcfg/netcfg.c
index fc9cf48..9cd883a 100644
--- a/netcfg/netcfg.c
+++ b/netcfg/netcfg.c
@@ -19,17 +19,13 @@
 #include <stdlib.h>
 #include <errno.h>
 #include <dirent.h>
+#include <netinet/ether.h>
+
+#include <netutils/ifc.h>
+#include <netutils/dhcp.h>
 
 static int verbose = 0;
 
-int ifc_init();
-void ifc_close();
-int ifc_up(char *iname);
-int ifc_down(char *iname);
-int ifc_remove_host_routes(char *iname);
-int ifc_remove_default_route(char *iname);
-int ifc_get_info(const char *name, unsigned *addr, unsigned *mask, unsigned *flags);
-int do_dhcp(char *iname);
 
 void die(const char *reason)
 {
@@ -37,16 +33,12 @@
     exit(1);
 }
 
-const char *ipaddr(unsigned addr)
+const char *ipaddr(in_addr_t addr)
 {
-    static char buf[32];
-    
-    sprintf(buf,"%d.%d.%d.%d", 
-            addr & 255,
-            ((addr >> 8) & 255),
-            ((addr >> 16) & 255), 
-            (addr >> 24));
-    return buf;
+    struct in_addr in_addr;
+
+    in_addr.s_addr = addr;
+    return inet_ntoa(in_addr);
 }
 
 void usage(void)
@@ -86,6 +78,15 @@
     return 0;
 }
 
+int set_hwaddr(const char *name, const char *asc) {
+    struct ether_addr *addr = ether_aton(asc);
+    if (!addr) {
+        printf("Failed to parse '%s'\n", asc);
+        return -1;
+    }
+    return ifc_set_hwaddr(name, addr->ether_addr_octet);
+}
+
 struct 
 {
     const char *name;
@@ -97,6 +98,7 @@
     { "down",   1, ifc_down },
     { "flhosts",  1, ifc_remove_host_routes },
     { "deldefault", 1, ifc_remove_default_route },
+    { "hwaddr", 2, set_hwaddr },
     { 0, 0, 0 },
 };
 
diff --git a/nexus/DhcpClient.cpp b/nexus/DhcpClient.cpp
index a5654d2..713059d 100644
--- a/nexus/DhcpClient.cpp
+++ b/nexus/DhcpClient.cpp
@@ -27,35 +27,15 @@
 
 #include <sysutils/ServiceManager.h>
 
+#include <netutils/ifc.h>
+#include <netutils/dhcp.h>
+
 #include "DhcpClient.h"
 #include "DhcpState.h"
 #include "DhcpListener.h"
 #include "IDhcpEventHandlers.h"
 #include "Controller.h"
 
-extern "C" {
-int ifc_disable(const char *ifname);
-int ifc_add_host_route(const char *ifname, uint32_t addr);
-int ifc_remove_host_routes(const char *ifname);
-int ifc_set_default_route(const char *ifname, uint32_t gateway);
-int ifc_get_default_route(const char *ifname);
-int ifc_remove_default_route(const char *ifname);
-int ifc_reset_connections(const char *ifname);
-int ifc_configure(const char *ifname, in_addr_t ipaddr, in_addr_t netmask, in_addr_t gateway, in_addr_t dns1, in_addr_t dns2);
-
-int dhcp_do_request(const char *ifname,
-                    in_addr_t *ipaddr,
-                    in_addr_t *gateway,
-                    in_addr_t *mask,
-                    in_addr_t *dns1,
-                    in_addr_t *dns2,
-                    in_addr_t *server,
-                    uint32_t  *lease);
-int dhcp_stop(const char *ifname);
-int dhcp_release_lease(const char *ifname);
-char *dhcp_get_errmsg();
-}
-
 DhcpClient::DhcpClient(IDhcpEventHandlers *handlers) :
             mState(DhcpState::INIT), mHandlers(handlers) {
     mServiceManager = new ServiceManager();
diff --git a/patch.txt b/patch.txt
new file mode 100644
index 0000000..258965d
--- /dev/null
+++ b/patch.txt
@@ -0,0 +1,16 @@
+diff --git a/init/util.c b/init/util.c
+index 4d98cc2..0667593 100755
+--- a/init/util.c
++++ b/init/util.c
+@@ -657,8 +657,9 @@ static void get_hardware_name(void)
+         if (x) {
+             x += 2;
+             n = 0;
+-            while (*x && !isspace(*x)) {
+-                hardware[n++] = tolower(*x);
++            while (*x && *x != '\n') {
++                if (!isspace(*x))
++                    hardware[n++] = tolower(*x);
+                 x++;
+                 if (n == 31) break;
+             }
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 329be7f..1a9e06f 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -7,7 +7,11 @@
 	etc/dbus.conf \
 	etc/hosts
 
-ifeq ($(TARGET_PRODUCT),generic)
+ifeq ($(TARGET_PRODUCT),full)
+copy_from += etc/vold.fstab
+endif
+
+ifeq ($(TARGET_PRODUCT),full_x86)
 copy_from += etc/vold.fstab
 endif
 
diff --git a/rootdir/etc/init.goldfish.sh b/rootdir/etc/init.goldfish.sh
index 5ff0a3a..cfa2c82 100755
--- a/rootdir/etc/init.goldfish.sh
+++ b/rootdir/etc/init.goldfish.sh
@@ -45,3 +45,13 @@
 # this line doesn't really do anything useful. however without it the
 # previous setprop doesn't seem to apply for some really odd reason
 setprop ro.qemu.init.completed 1
+
+# set up the second interface (for inter-emulator connections)
+# if required
+my_ip=`getprop net.shared_net_ip`
+case "$my_ip" in
+    "")
+    ;;
+    *) ifconfig eth1 "$my_ip" netmask 255.255.255.0 up
+    ;;
+esac
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 8227273..90c017b 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -176,6 +176,11 @@
     chown root root /cache/lost+found
     chmod 0770 /cache/lost+found
 
+    # create data/drm directory
+    mkdir /data/drm 0774 drm drm
+    chown drm drm /data/drm
+    chmod 0774 /data/drm
+
 on boot
 # basic network init
     ifup lo
@@ -315,6 +320,7 @@
 
 service netd /system/bin/netd
     socket netd stream 0660 root system
+    socket dnsproxyd stream 0660 root inet
 
 service debuggerd /system/bin/debuggerd
 
@@ -331,6 +337,13 @@
     onrestart restart media
     onrestart restart netd
 
+service drm /system/bin/drmserver
+    user drm
+    group system root inet
+
+service drmio /system/bin/drmioserver
+    user drmio
+
 service media /system/bin/mediaserver
     user media
     group system audio camera graphics inet net_bt net_bt_admin net_raw
diff --git a/sh/Android.mk b/sh/Android.mk
index b5e5c38..dcd13d8 100644
--- a/sh/Android.mk
+++ b/sh/Android.mk
@@ -29,7 +29,8 @@
 	bltin/echo.c \
 	init.c
 
-LOCAL_MODULE:= sh
+LOCAL_MODULE:= ash
+LOCAL_MODULE_TAGS:= shell_ash
 
 LOCAL_CFLAGS += -DSHELL -DWITH_LINENOISE
 
@@ -51,3 +52,19 @@
 	sh ./mkinit.sh $(PRIVATE_SRC_FILES) 
 
 include $(BUILD_EXECUTABLE)
+
+
+# create /system/bin/sh symlink to $(TARGET_SHELL)
+# not the optimal place for this, but a fitting one
+
+OUTSYSTEMBINSH := $(TARGET_OUT)/bin/sh
+LOCAL_MODULE := systembinsh
+$(OUTSYSTEMBINSH): | $(TARGET_SHELL)
+$(OUTSYSTEMBINSH): LOCAL_MODULE := $(LOCAL_MODULE)
+$(OUTSYSTEMBINSH):
+	@echo "Symlink: $@ -> $(TARGET_SHELL)"
+	@rm -rf $@
+	$(hide) ln -sf $(TARGET_SHELL) $@
+
+ALL_DEFAULT_INSTALLED_MODULES += $(OUTSYSTEMBINSH)
+ALL_MODULES.$(LOCAL_MODULE).INSTALLED += $(OUTSYSTEMBINSH)
diff --git a/toolbox/chmod.c b/toolbox/chmod.c
index 31a53bf..2a524e9 100644
--- a/toolbox/chmod.c
+++ b/toolbox/chmod.c
@@ -4,17 +4,74 @@
 #include <sys/types.h>
 #include <dirent.h>
 #include <errno.h>
+#include <sys/limits.h>
+#include <sys/stat.h>
 
 #include <unistd.h>
 #include <time.h>
 
+void recurse_chmod(char* path, int mode)
+{
+    struct dirent *dp;
+    DIR *dir = opendir(path);
+    if (dir == NULL) {
+        // not a directory, carry on
+        return;
+    }
+    char *subpath = malloc(sizeof(char)*PATH_MAX);
+    int pathlen = strlen(path);
+
+    while ((dp = readdir(dir)) != NULL) {
+        if (strcmp(dp->d_name, ".") == 0 ||
+            strcmp(dp->d_name, "..") == 0) continue;
+
+        if (strlen(dp->d_name) + pathlen + 2/*NUL and slash*/ > PATH_MAX) {
+            fprintf(stderr, "Invalid path specified: too long\n");
+            exit(1);
+        }
+
+        strcpy(subpath, path);
+        strcat(subpath, "/");
+        strcat(subpath, dp->d_name);
+
+        if (chmod(subpath, mode) < 0) {
+            fprintf(stderr, "Unable to chmod %s: %s\n", subpath, strerror(errno));
+            exit(1);
+        }
+
+        recurse_chmod(subpath, mode);
+    }
+    free(subpath);
+    closedir(dir);
+}
+
+static int usage()
+{
+    fprintf(stderr, "Usage: chmod [OPTION] <MODE> <FILE>\n");
+    fprintf(stderr, "  -R, --recursive         change files and directories recursively\n");
+    fprintf(stderr, "  --help                  display this help and exit\n");
+
+    return 10;
+}
+
 int chmod_main(int argc, char **argv)
 {
     int i;
 
-    if (argc < 3) {
-        fprintf(stderr, "Usage: chmod <MODE> <FILE>\n");
-        return 10;
+    if (argc < 3 || strcmp(argv[1], "--help") == 0) {
+        return usage();
+    }
+
+    int recursive = (strcmp(argv[1], "-R") == 0 ||
+                     strcmp(argv[1], "--recursive") == 0) ? 1 : 0;
+
+    if (recursive && argc < 4) {
+        return usage();
+    }
+
+    if (recursive) {
+        argc--;
+        argv++;
     }
 
     int mode = 0;
@@ -29,11 +86,15 @@
         }
         s++;
     }
+
     for (i = 2; i < argc; i++) {
         if (chmod(argv[i], mode) < 0) {
             fprintf(stderr, "Unable to chmod %s: %s\n", argv[i], strerror(errno));
             return 10;
         }
+        if (recursive) {
+            recurse_chmod(argv[i], mode);
+        }
     }
     return 0;
 }
diff --git a/toolbox/insmod.c b/toolbox/insmod.c
index 44b9847..756a64b 100644
--- a/toolbox/insmod.c
+++ b/toolbox/insmod.c
@@ -77,7 +77,6 @@
 			memcpy(ptr, argv[i], len);
 			ptr += len;
 			*ptr++ = ' ';
-			*ptr++ = '\0';
 		}
 		*(ptr - 1) = '\0';
 	}
diff --git a/toolbox/ls.c b/toolbox/ls.c
index 8799514..962bf47 100644
--- a/toolbox/ls.c
+++ b/toolbox/ls.c
@@ -13,6 +13,130 @@
 #include <grp.h>
 
 #include <linux/kdev_t.h>
+#include <limits.h>
+
+// dynamic arrays
+typedef struct {
+    int count;
+    int capacity;
+    void** items;
+} dynarray_t;
+
+#define DYNARRAY_INITIALIZER  { 0, 0, NULL }
+
+static void dynarray_init( dynarray_t *a )
+{
+    a->count = a->capacity = 0;
+    a->items = NULL;
+}
+
+static void dynarray_reserve_more( dynarray_t *a, int count )
+{
+    int old_cap = a->capacity;
+    int new_cap = old_cap;
+    const int max_cap = INT_MAX/sizeof(void*);
+    void** new_items;
+    int new_count = a->count + count;
+
+    if (count <= 0)
+        return;
+
+    if (count > max_cap - a->count)
+        abort();
+
+    new_count = a->count + count;
+
+    while (new_cap < new_count) {
+        old_cap = new_cap;
+        new_cap += (new_cap >> 2) + 4;
+        if (new_cap < old_cap || new_cap > max_cap) {
+            new_cap = max_cap;
+        }
+    }
+    new_items = realloc(a->items, new_cap*sizeof(void*));
+    if (new_items == NULL)
+        abort();
+
+    a->items = new_items;
+    a->capacity = new_cap;
+}
+
+static void dynarray_append( dynarray_t *a, void* item )
+{
+    if (a->count >= a->capacity)
+        dynarray_reserve_more(a, 1);
+
+    a->items[a->count++] = item;
+}
+
+static void dynarray_done( dynarray_t *a )
+{
+    free(a->items);
+    a->items = NULL;
+    a->count = a->capacity = 0;
+}
+
+#define DYNARRAY_FOREACH_TYPE(_array,_item_type,_item,_stmnt) \
+    do { \
+        int _nn_##__LINE__ = 0; \
+        for (;_nn_##__LINE__ < (_array)->count; ++ _nn_##__LINE__) { \
+            _item_type _item = (_item_type)(_array)->items[_nn_##__LINE__]; \
+            _stmnt; \
+        } \
+    } while (0)
+
+#define DYNARRAY_FOREACH(_array,_item,_stmnt) \
+    DYNARRAY_FOREACH_TYPE(_array,void *,_item,_stmnt)
+
+// string arrays
+
+typedef dynarray_t  strlist_t;
+
+#define  STRLIST_INITIALIZER  DYNARRAY_INITIALIZER
+
+#define  STRLIST_FOREACH(_list,_string,_stmnt) \
+    DYNARRAY_FOREACH_TYPE(_list,char *,_string,_stmnt)
+
+static void strlist_init( strlist_t *list )
+{
+    dynarray_init(list);
+}
+
+static void strlist_append_b( strlist_t *list, const void* str, size_t  slen )
+{
+    char *copy = malloc(slen+1);
+    memcpy(copy, str, slen);
+    copy[slen] = '\0';
+    dynarray_append(list, copy);
+}
+
+static void strlist_append_dup( strlist_t *list, const char *str)
+{
+    strlist_append_b(list, str, strlen(str));
+}
+
+static void strlist_done( strlist_t *list )
+{
+    STRLIST_FOREACH(list, string, free(string));
+    dynarray_done(list);
+}
+
+static int strlist_compare_strings(const void* a, const void* b)
+{
+    const char *sa = *(const char **)a;
+    const char *sb = *(const char **)b;
+    return strcmp(sa, sb);
+}
+
+static void strlist_sort( strlist_t *list )
+{
+    if (list->count > 0) {
+        qsort(list->items, 
+              (size_t)list->count,
+              sizeof(void*),
+              strlist_compare_strings);
+    }
+}
 
 // bits for flags argument
 #define LIST_LONG           (1 << 0)
@@ -233,7 +357,8 @@
     char tmp[4096];
     DIR *d;
     struct dirent *de;
-
+    strlist_t  files = STRLIST_INITIALIZER;
+    
     d = opendir(name);
     if(d == 0) {
         fprintf(stderr, "opendir failed, %s\n", strerror(errno));
@@ -248,10 +373,16 @@
         if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue;
         if(de->d_name[0] == '.' && (flags & LIST_ALL) == 0) continue;
 
-        listfile(name, de->d_name, flags);
+        strlist_append_dup(&files, de->d_name);
     }
 
+    strlist_sort(&files);
+    STRLIST_FOREACH(&files, filename, listfile(name, filename, flags));
+    strlist_done(&files);
+
     if (flags & LIST_RECURSIVE) {
+        strlist_t subdirs = STRLIST_INITIALIZER;
+
         rewinddir(d);
 
         while ((de = readdir(d)) != 0) {
@@ -284,10 +415,15 @@
             }
 
             if (S_ISDIR(s.st_mode)) {
-                printf("\n%s:\n", tmp);
-                listdir(tmp, flags);
+                strlist_append_dup(&subdirs, tmp);
             }
         }
+        strlist_sort(&subdirs);
+        STRLIST_FOREACH(&subdirs, path, {
+            printf("\n%s:\n", path);
+            listdir(path, flags);
+        });
+        strlist_done(&subdirs);
     }
 
     closedir(d);
@@ -331,27 +467,40 @@
     if(argc > 1) {
         int i;
         int err = 0;
+        strlist_t  files = STRLIST_INITIALIZER;
 
         for (i = 1; i < argc; i++) {
-            if (!strcmp(argv[i], "-l")) {
-                flags |= LIST_LONG;
-            } else if (!strcmp(argv[i], "-s")) {
-                flags |= LIST_SIZE;
-            } else if (!strcmp(argv[i], "-a")) {
-                flags |= LIST_ALL;
-            } else if (!strcmp(argv[i], "-R")) {
-                flags |= LIST_RECURSIVE;
-            } else if (!strcmp(argv[i], "-d")) {
-                flags |= LIST_DIRECTORIES;
-            } else {
-                listed++;
-                if(listpath(argv[i], flags) != 0) {
-                    err = EXIT_FAILURE;
+            if (argv[i][0] == '-') {
+                /* an option ? */
+                const char *arg = argv[i]+1;
+                while (arg[0]) {
+                    switch (arg[0]) {
+                    case 'l': flags |= LIST_LONG; break;
+                    case 's': flags |= LIST_SIZE; break;
+                    case 'R': flags |= LIST_RECURSIVE; break;
+                    case 'd': flags |= LIST_DIRECTORIES; break;
+                    case 'a': flags |= LIST_ALL; break;
+                    default:
+                        fprintf(stderr, "%s: Unknown option '-%c'. Aborting.\n", "ls", arg[0]);
+                        exit(1);
+                    }
+                    arg++;
                 }
+            } else {
+                /* not an option ? */
+                strlist_append_dup(&files, argv[i]);
             }
         }
 
-        if (listed  > 0) return err;
+        if (files.count > 0) {
+            STRLIST_FOREACH(&files, path, {
+                if (listpath(path, flags) != 0) {
+                    err = EXIT_FAILURE;
+                }
+            });
+            strlist_done(&files);
+            return err;
+        }
     }
     
     // list working directory if no files or directories were specified    
diff --git a/toolbox/mkdir.c b/toolbox/mkdir.c
index 121adab..656970a 100644
--- a/toolbox/mkdir.c
+++ b/toolbox/mkdir.c
@@ -2,10 +2,14 @@
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
+#include <sys/limits.h>
+#include <sys/stat.h>
 
 static int usage()
 {
-    fprintf(stderr,"mkdir <target>\n");
+    fprintf(stderr,"mkdir [OPTION] <target>\n");
+    fprintf(stderr,"    --help           display usage and exit\n");
+    fprintf(stderr,"    -p, --parents    create parent directories as needed\n");
     return -1;
 }
 
@@ -13,15 +17,60 @@
 {
     int symbolic = 0;
     int ret;
-    if(argc < 2) return usage();
+    if(argc < 2 || strcmp(argv[1], "--help") == 0) {
+        return usage();
+    }
+
+    int recursive = (strcmp(argv[1], "-p") == 0 ||
+                     strcmp(argv[1], "--parents") == 0) ? 1 : 0;
+
+    if(recursive && argc < 3) {
+        // -p specified without a path
+        return usage();
+    }
+
+    if(recursive) {
+        argc--;
+        argv++;
+    }
+
+    char currpath[PATH_MAX], *pathpiece;
+    struct stat st;
 
     while(argc > 1) {
         argc--;
         argv++;
-        ret = mkdir(argv[0], 0777);
-        if(ret < 0) {
-            fprintf(stderr, "mkdir failed for %s, %s\n", argv[0], strerror(errno));
-            return ret;
+        if(recursive) {
+            // reset path
+            strcpy(currpath, "");
+            // create the pieces of the path along the way
+            pathpiece = strtok(argv[0], "/");
+            if(argv[0][0] == '/') {
+                // prepend / if needed
+                strcat(currpath, "/");
+            }
+            while(pathpiece != NULL) {
+                if(strlen(currpath) + strlen(pathpiece) + 2/*NUL and slash*/ > PATH_MAX) {
+                    fprintf(stderr, "Invalid path specified: too long\n");
+                    return 1;
+                }
+                strcat(currpath, pathpiece);
+                strcat(currpath, "/");
+                if(stat(currpath, &st) != 0) {
+                    ret = mkdir(currpath, 0777);
+                    if(ret < 0) {
+                        fprintf(stderr, "mkdir failed for %s, %s\n", currpath, strerror(errno));
+                        return ret;
+                    }
+                }
+                pathpiece = strtok(NULL, "/");
+            }
+        } else {
+            ret = mkdir(argv[0], 0777);
+            if(ret < 0) {
+                fprintf(stderr, "mkdir failed for %s, %s\n", argv[0], strerror(errno));
+                return ret;
+            }
         }
     }
     
diff --git a/toolbox/route.c b/toolbox/route.c
index 107e48a..3e10014 100644
--- a/toolbox/route.c
+++ b/toolbox/route.c
@@ -80,14 +80,24 @@
 
         /* route add -net 192.168.1.2 netmask 255.255.255.0 gw 192.168.1.1 */
         if (argc > 7 && !strcmp(argv[2], "-net") &&
-            !strcmp(argv[4], "netmask") && !strcmp(argv[6], "gw")) {
-            rt.rt_flags = RTF_UP | RTF_GATEWAY;
-            if (set_address(argv[3], &rt.rt_dst) &&
-                set_address(argv[5], &rt.rt_genmask) &&
-                set_address(argv[7], &rt.rt_gateway)) {
-                errno = 0;
+                !strcmp(argv[4], "netmask")) {
+            if (!strcmp(argv[6], "gw")) {
+                rt.rt_flags = RTF_UP | RTF_GATEWAY;
+                if (set_address(argv[3], &rt.rt_dst) &&
+                    set_address(argv[5], &rt.rt_genmask) &&
+                    set_address(argv[7], &rt.rt_gateway)) {
+                    errno = 0;
+                }
+                goto apply;
+            } else if (!strcmp(argv[6], "dev")) {
+                rt.rt_flags = RTF_UP;
+                rt.rt_dev = argv[7];
+                if (set_address(argv[3], &rt.rt_dst) &&
+                    set_address(argv[5], &rt.rt_genmask)) {
+                    errno = 0;
+                }
+                goto apply;
             }
-            goto apply;
         }
     }