summaryrefslogtreecommitdiff
path: root/console
diff options
context:
space:
mode:
Diffstat (limited to 'console')
-rw-r--r--console/Makefile47
-rw-r--r--console/README.UTF8143
-rw-r--r--console/console.c2093
-rw-r--r--console/display.c2138
-rw-r--r--console/display.h84
-rw-r--r--console/hurd.ti164
-rw-r--r--console/input.c277
-rw-r--r--console/input.h58
-rw-r--r--console/motd.UTF84
-rw-r--r--console/mutations.h27
-rw-r--r--console/pager.c221
-rw-r--r--console/pager.h47
-rw-r--r--console/priv.h37
13 files changed, 5340 insertions, 0 deletions
diff --git a/console/Makefile b/console/Makefile
new file mode 100644
index 00000000..3eb4252e
--- /dev/null
+++ b/console/Makefile
@@ -0,0 +1,47 @@
+#
+# Copyright (C) 2002, 2012 Free Software Foundation, Inc.
+# Written by Marcus Brinkmann.
+#
+# This file is part of the GNU Hurd.
+#
+# The GNU Hurd is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2, or (at
+# your option) any later version.
+#
+# The GNU Hurd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+
+dir := console
+makemode := server
+
+target = console
+
+SRCS = console.c display.c pager.c input.c
+LCLHDRS = display.h pager.h input.h priv.h mutations.h
+DIST_FILES = hurd.ti motd.UTF8
+
+MIGSTUBS = notifyServer.o tioctlServer.o fs_notifyUser.o
+
+HURDLIBS = netfs fshelp iohelp pager threads ports ihash shouldbeinlibc
+OBJS = $(sort $(SRCS:.c=.o) $(MIGSTUBS))
+
+MIGSFLAGS += -imacros $(srcdir)/mutations.h
+
+# This is the old monolithic version of the console server.
+#SRCS = main.c vcons.c focus.c vga-display.c vga.c dynafont.c bdf.c
+#LCLHDRS = focus.h input-drv.h vcons.h display-drv.h vga.h vga-hw.h \
+# dynafont.h bdf.h mutations.h priv.h
+#
+#HURDLIBS = trivfs fshelp iohelp threads ports ihash shouldbeinlibc
+#OBJS = $(subst .c,.o,$(SRCS)) tioctlServer.o
+#
+#MIGSFLAGS += -imacros $(srcdir)/mutations.h
+
+include ../Makeconf
diff --git a/console/README.UTF8 b/console/README.UTF8
new file mode 100644
index 00000000..91901e1b
--- /dev/null
+++ b/console/README.UTF8
@@ -0,0 +1,143 @@
+
+The console server supports any encoding supported by iconv, but uses
+Unicode internally. The default encoding is ISO8859-1, another useful
+variant is UTF-8. To configure the console server to use UTF-8 you
+have to use the `--encoding' argument:
+
+# settrans -fg /dev/vcs /hurd/console --encoding=UTF-8
+
+If you actually try this, you will notice two problems:
+
+1. You can not enter the letters in your locale, because the keyboard
+doesn't have the right layout. Keyboard maps come later. For now,
+you have to help yourself with the direct input with RightAlt. Maybe
+I will put a simple compose key feature in the pc_kbd driver, so that
+some western locales can be used more easily.
+
+2. If you bother to look up the unicode hex code and enter it with
+AltGr, the font can not display it! If you are using the ncursesw
+driver, do you use it while you are logged in from a working UTF-8
+terminal? If not, then this is your problem. An ncurses driver for
+non-UTF-8 terminals is on the TODO list. But if you use the VGA
+driver, you need to load a different font.
+
+This is because by default, the vga driver just reads the VGA card
+memory and takes the font that is stored there. This font has a
+limited characterset (256 characters, many graphical symbols among
+that), so you won't get more than a few western characters with that.
+
+Unicode support
+===============
+
+But you want it all. You want to read Middle Old English. You want
+to read Thai. Your Korean spam. Georgian script. Hebrew. And you
+can have it.
+
+You need a Unicode font. There are good ones provided by Markus Kuhn,
+the UCS fonts. Get them here:
+
+http://www.cl.cam.ac.uk/~mgk25/download/ucs-fonts.tar.gz
+
+(See also the web page at http://www.cl.cam.ac.uk/~mgk25/ucs-fonts.html).
+
+Now, load the font by providing it with the --font option to the vga
+driver. I suggest only the 8x13 and the 9x15 fonts, but feel free to
+try others, too. Note that the VGA text mode can not really display 9
+pixel wide characters. But as most characters have the ninth column
+empty, and the VGA text mode can display an empty column between two
+adjacent character cells, this trick allows us to display most of the
+9x15 font correctly. So you won't notice a difference until you come
+to very broad characters or special symbols, where you will see that
+the last column is cut off. (BTW, I wrote the dynafont code carefully
+to still support horizontal line graphic characters properly in 9
+pixel wide fonts. This is done by exploiting some special modes in
+the VGA hardware. This is why in 512 (256) glyph mode and 9 pixel
+wide fonts, you are limited to 448 (224) normal characters: 64 (32)
+slots are reserved for the horizontal line graphic characters so they
+are drawn continuously.)
+
+So, try the following:
+
+# console -d vga --font 8x13.bdf -d pc_kbd -d generic_speaker /dev/vcs
+
+or
+
+# console -d vga --font 9x15.bdf -d pc_kbd -d generic_speaker /dev/vcs
+
+If you are satisfied, copy your default font to
+/lib/hurd/fonts/vga-system.bdf, where it will be picked up
+automatically in favor to the graphic card's font.
+
+More about fonts
+================
+
+While we are talking about fonts, try also the 8x13O font with
+--font-italic and 8x13B or 9x15B font with --font-bold. You can save
+them in /lib/hurd/fonts/vga-system-bold.bdf and
+/lib/hurd/fonts/vga-system-italic.bdf, too.
+
+To activate those fonts on your virtual console, try the following:
+
+# echo `tput sitm`Hello slanted world.`tput ritm`
+
+and
+
+# echo `tput gsbom`Hello bold world.`tput grbom`
+
+I hope you like what you see. Imagine this in emacs font-lock mode.
+
+
+Unicode, finally
+================
+
+There are a few more steps necessary to make your Unicode environment
+ready:
+
+Add a Unicode locale to /etc/locale.gen, and generate the locale
+information for that! For example, I am living in Germany, and
+normally use de_DE with the encoding ISO8859-1. My Unicode locale is
+de_DE.UTF-8, so I am adding that to /etc/locale.gen:
+
+de_DE.UTF-8 UTF-8
+
+and rerun locale-gen:
+
+# locale-gen
+
+See also /share/i18n/SUPPORTED. You can also do this more conveniently with
+
+# dpkg-reconfigure locales
+
+Once you generated this, make it your default locale:
+
+# export LANG=de_DE.UTF-8
+
+If you have also loaded the unicode font above, you are set up. Try
+for example to view the examples/ files in the ucs-fonts package with
+less.
+
+# less fonts/examples/UTF_8-demo.txt
+
+You should see most of that file with the 9x15 font (a bit less with
+the 8x13 font).
+
+You should be able to do the above process with other encodings than
+UTF-8. But you should _always_ use a Unicode font, because the
+console client uses Unicode internally for everything.
+
+Application specific notes
+==========================
+
+If you enter unicode characters at the shell, libreadline loses track
+of the number of characters displayed (it is not aware of multi-byte
+encodings like UTF-8). This is fixed in readline 4.3 (which is not
+yet in Debian).
+
+If you use mutt, install mutt-utf8. For lynx, edit /etc/lynx.cfg,
+making sure that CHARACTER_SET is set to utf-8.
+
+If you use other applications, try to search with google for
+"application-name utf8" or "application-name unicode". Often you find
+what you need. The issues are the same for the GNU/Hurd and GNU/Linux
+systems, so most of the information can be shared, except how to setup
+the system console to support Unicode, of course.
diff --git a/console/console.c b/console/console.c
new file mode 100644
index 00000000..0b1f42c4
--- /dev/null
+++ b/console/console.c
@@ -0,0 +1,2093 @@
+/* console.c -- A console server.
+
+ Copyright (C) 1997, 1999, 2002, 2003, 2007, 2008, 2010
+ Free Software Foundation, Inc.
+
+ Written by Miles Bader and Marcus Brinkmann.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <locale.h>
+
+#include <argp.h>
+#include <argz.h>
+#include <error.h>
+#include <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <rwlock.h>
+#include <maptime.h>
+#include <cthreads.h>
+
+#include <version.h>
+
+#include <mach.h>
+#include <hurd/netfs.h>
+#include <hurd/ioctl_types.h>
+/* We include console.h for the color numbers. */
+#include <hurd/console.h>
+
+#include "display.h"
+#include "input.h"
+
+#include "fs_notify_U.h"
+
+const char *argp_program_version = STANDARD_HURD_VERSION (console);
+
+char *netfs_server_name = "console";
+char *netfs_server_version = HURD_VERSION;
+int netfs_maxsymlinks = 16; /* Arbitrary. */
+
+/* Handy source of time. */
+volatile struct mapped_time_value *console_maptime;
+
+#define DEFAULT_ENCODING "ISO-8859-1"
+#define DEFAULT_INTENSITY CONS_ATTR_INTENSITY_NORMAL
+#define DEFAULT_UNDERLINED 0
+#define DEFAULT_BLINKING 0
+#define DEFAULT_REVERSED 0
+#define DEFAULT_CONCEALED 0
+#define DEFAULT_WIDTH 80
+#define DEFAULT_HEIGHT 25
+#define DEFAULT_LINES 50
+/* Stringification of a macro. */
+#define STRX(s) #s
+#define STR(s) STRX(s)
+
+/* For the help output. */
+#define DEFAULT_ATTRIBUTE_NAME "normal"
+#define DEFAULT_FOREGROUND CONS_COLOR_WHITE
+/* For the help output. */
+#define DEFAULT_FOREGROUND_NAME "white"
+#define DEFAULT_BACKGROUND CONS_COLOR_BLACK
+/* For the help output. */
+#define DEFAULT_BACKGROUND_NAME "black"
+
+
+/* A handle for a console device. */
+typedef struct cons *cons_t;
+
+/* A handle for a virtual console device. */
+typedef struct vcons *vcons_t;
+
+struct vcons
+{
+ /* Protected by cons->lock. */
+ vcons_t next;
+ vcons_t prev;
+ /* We acquire one reference per netnode. */
+ int refcnt;
+
+ /* The following members remain constant over the lifetime of the
+ object and accesses don't need to be locked. */
+ int id;
+ char *name;
+ cons_t cons;
+ display_t display;
+ input_t input;
+
+ struct mutex lock;
+ /* Nodes in the filesystem referring to this virtual console. */
+ struct node *dir_node;
+ struct node *cons_node;
+ struct node *disp_node;
+ struct node *inpt_node;
+};
+
+/* Pending directory modification requests. */
+struct modreq
+{
+ mach_port_t port;
+ struct modreq *next;
+};
+
+struct cons
+{
+ /* The lock protects the console, all virtual consoles contained in
+ it and the reference counters. It also locks the configuration
+ parameters. */
+ struct mutex lock;
+ vcons_t vcons_list;
+ /* The encoding. */
+ char *encoding;
+ /* Default attributes. */
+ conchar_attr_t attribute;
+
+ /* Requester of directory modification notifications. */
+ struct modreq *dirmod_reqs;
+ unsigned int dirmod_tick;
+
+ struct node *node;
+ mach_port_t underlying;
+ /* A template for the stat information of all nodes. */
+ struct stat stat_template;
+
+ /* The amount of lines, width and height. */
+ unsigned int lines;
+ unsigned int width;
+ unsigned int height;
+};
+
+
+/* Requires CONS to be locked. */
+static void
+cons_notice_dirchange (cons_t cons, dir_changed_type_t type, char *name)
+{
+ error_t err;
+ struct modreq **preq = &cons->dirmod_reqs;
+
+ cons->dirmod_tick++;
+ while (*preq)
+ {
+ struct modreq *req = *preq;
+
+ err = dir_changed (req->port, cons->dirmod_tick, type, name);
+ if (err && err != MACH_SEND_TIMEOUT)
+ {
+ /* Remove notify port. */
+ *preq = req->next;
+ mach_port_deallocate (mach_task_self (), req->port);
+ free (req);
+ }
+ else
+ preq = &req->next;
+ }
+}
+
+
+/* Lookup the virtual console with number ID in the console CONS,
+ acquire a reference for it, and return it in R_VCONS. If CREATE is
+ true, the virtual console will be created if it doesn't exist yet.
+ If CREATE is true, and ID 0, the first free virtual console id is
+ used. */
+error_t
+vcons_lookup (cons_t cons, int id, int create, vcons_t *r_vcons)
+{
+ error_t err;
+ vcons_t previous_vcons = 0;
+ vcons_t vcons;
+
+ if (!id && !create)
+ return EINVAL;
+
+ mutex_lock (&cons->lock);
+ if (id)
+ {
+ if (cons->vcons_list && cons->vcons_list->id <= id)
+ {
+ previous_vcons = cons->vcons_list;
+ while (previous_vcons->next && previous_vcons->next->id <= id)
+ previous_vcons = previous_vcons->next;
+ if (previous_vcons->id == id)
+ {
+ previous_vcons->refcnt++;
+ mutex_unlock (&cons->lock);
+ *r_vcons = previous_vcons;
+ return 0;
+ }
+ }
+ else if (!create)
+ {
+ mutex_unlock (&cons->lock);
+ return ESRCH;
+ }
+ }
+ else
+ {
+ id = 1;
+ if (cons->vcons_list && cons->vcons_list->id == 1)
+ {
+ previous_vcons = cons->vcons_list;
+ while (previous_vcons && previous_vcons->id == id)
+ {
+ id++;
+ previous_vcons = previous_vcons->next;
+ }
+ }
+ }
+
+ vcons = calloc (1, sizeof (struct vcons));
+ if (!vcons)
+ {
+ mutex_unlock (&cons->lock);
+ return ENOMEM;
+ }
+ vcons->cons = cons;
+ vcons->refcnt = 1;
+ vcons->id = id;
+ asprintf (&vcons->name, "%i", id);
+ /* XXX Error checking. */
+
+ mutex_init (&vcons->lock);
+ err = display_create (&vcons->display, cons->encoding ?: DEFAULT_ENCODING,
+ cons->attribute, cons->lines, cons->width,
+ cons->height);
+ if (err)
+ {
+ free (vcons->name);
+ free (vcons);
+ mutex_unlock (&cons->lock);
+ return err;
+ }
+
+ err = input_create (&vcons->input, cons->encoding ?: DEFAULT_ENCODING);
+ if (err)
+ {
+ display_destroy (vcons->display);
+ free (vcons->name);
+ free (vcons);
+ mutex_unlock (&cons->lock);
+ return err;
+ }
+
+ /* Insert the virtual console into the doubly linked list. */
+ if (previous_vcons)
+ {
+ vcons->prev = previous_vcons;
+ if (previous_vcons->next)
+ {
+ previous_vcons->next->prev = vcons;
+ vcons->next = previous_vcons->next;
+ }
+ previous_vcons->next = vcons;
+ }
+ else
+ {
+ if (cons->vcons_list)
+ {
+ cons->vcons_list->prev = vcons;
+ vcons->next = cons->vcons_list;
+ }
+ cons->vcons_list = vcons;
+ }
+ cons_notice_dirchange (cons, DIR_CHANGED_NEW, vcons->name);
+
+ mutex_unlock (&cons->lock);
+ *r_vcons = vcons;
+ return 0;
+}
+
+/* Acquire an additional reference to the virtual console VCONS. */
+void
+vcons_ref (vcons_t vcons)
+{
+ cons_t cons = vcons->cons;
+
+ mutex_lock (&cons->lock);
+ vcons->refcnt++;
+ mutex_unlock (&cons->lock);
+}
+
+/* Release a reference to the virtual console VCONS. If this was the
+ last reference the virtual console is destroyed. */
+void
+vcons_release (vcons_t vcons)
+{
+ cons_t cons = vcons->cons;
+
+ mutex_lock (&cons->lock);
+ if (!--vcons->refcnt)
+ {
+ /* As we keep a reference for all input focus groups pointing to
+ the virtual console, and a reference for the active console,
+ we know that without references, this virtual console is
+ neither active nor used by any input group. */
+
+ if (vcons->prev)
+ vcons->prev->next = vcons->next;
+ else
+ cons->vcons_list = vcons->next;
+ if (vcons->next)
+ vcons->next->prev = vcons->prev;
+
+ cons_notice_dirchange (cons, DIR_CHANGED_UNLINK, vcons->name);
+
+ /* XXX Destroy the state. */
+ display_destroy (vcons->display);
+ input_destroy (vcons->input);
+ free (vcons->name);
+ free (vcons);
+ }
+ mutex_unlock (&cons->lock);
+}
+
+struct netnode
+{
+ /* The root node points to the console object. */
+ cons_t cons;
+
+ /* All other nodes point to the virtual console object. */
+ vcons_t vcons;
+};
+
+typedef enum
+ {
+ VCONS_NODE_DIR = 0,
+ VCONS_NODE_CONSOLE,
+ VCONS_NODE_DISPLAY,
+ VCONS_NODE_INPUT
+ } vcons_node_type;
+
+/* Make a new virtual node. Always consumes the ports. */
+static error_t
+new_node (struct node **np, vcons_t vcons, vcons_node_type type)
+{
+ struct netnode *nn = calloc (1, sizeof *nn);
+ if (nn == 0)
+ return ENOMEM;
+
+ nn->vcons = vcons;
+ *np = netfs_make_node (nn);
+ if (*np == 0)
+ {
+ free (nn);
+ return ENOMEM;
+ }
+ (*np)->nn_stat = vcons->cons->stat_template;
+ (*np)->nn_translated = 0;
+
+ switch (type)
+ {
+ case VCONS_NODE_DIR:
+ (*np)->nn_stat.st_ino = vcons->id << 2;
+ (*np)->nn_stat.st_mode |= S_IFDIR;
+ (*np)->nn_stat.st_size = 0;
+ break;
+ case VCONS_NODE_CONSOLE:
+ (*np)->nn_stat.st_ino = (vcons->id << 2) + 1;
+ (*np)->nn_stat.st_mode |= S_IFCHR; /* Don't set nn_translated! */
+ (*np)->nn_stat.st_mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
+ (*np)->nn_stat.st_size = 0;
+ break;
+ case VCONS_NODE_DISPLAY:
+ (*np)->nn_stat.st_ino = (vcons->id << 2) + 2;
+ (*np)->nn_stat.st_mode |= S_IFREG;
+ (*np)->nn_stat.st_mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
+ (*np)->nn_stat.st_size = display_get_size (vcons->display);
+ break;
+ case VCONS_NODE_INPUT:
+ (*np)->nn_stat.st_ino = (vcons->id << 2) + 3;
+ (*np)->nn_stat.st_mode |= S_IFIFO;
+ (*np)->nn_stat.st_mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
+ (*np)->nn_stat.st_size = 0;
+ break;
+ }
+
+ /* If the underlying node isn't a directory, propagate read permission to
+ execute permission since we need that for lookups. */
+ if (! S_ISDIR (vcons->cons->stat_template.st_mode)
+ && S_ISDIR ((*np)->nn_stat.st_mode))
+ {
+ if (vcons->cons->stat_template.st_mode & S_IRUSR)
+ (*np)->nn_stat.st_mode |= S_IXUSR;
+ if (vcons->cons->stat_template.st_mode & S_IRGRP)
+ (*np)->nn_stat.st_mode |= S_IXGRP;
+ if (vcons->cons->stat_template.st_mode & S_IROTH)
+ (*np)->nn_stat.st_mode |= S_IXOTH;
+ }
+
+ fshelp_touch (&(*np)->nn_stat, TOUCH_ATIME|TOUCH_MTIME|TOUCH_CTIME,
+ console_maptime);
+
+ return 0;
+}
+
+
+/* Node management. */
+
+/* Node NP has no more references; free all its associated
+ storage. */
+void
+netfs_node_norefs (struct node *np)
+{
+ vcons_t vcons = np->nn->vcons;
+
+ /* The root node does never go away. */
+ assert (!np->nn->cons && np->nn->vcons);
+
+ /* Avoid deadlock. */
+ spin_unlock (&netfs_node_refcnt_lock);
+
+ /* Find the back reference to ourself in the virtual console
+ structure, and delete it. */
+ mutex_lock (&vcons->lock);
+ spin_lock (&netfs_node_refcnt_lock);
+ if (np->references)
+ {
+ /* Someone else got a reference while we were attempting to go
+ away. This can happen in netfs_attempt_lookup. In this
+ case, just unlock the node and do nothing else. */
+ mutex_unlock (&vcons->lock);
+ mutex_unlock (&np->lock);
+ return;
+ }
+ if (np == vcons->dir_node)
+ vcons->dir_node = 0;
+ else if (np == vcons->cons_node)
+ vcons->cons_node = 0;
+ else if (np == vcons->disp_node)
+ vcons->disp_node = 0;
+ else
+ {
+ assert (np == vcons->inpt_node);
+ vcons->inpt_node = 0;
+ }
+ mutex_unlock (&vcons->lock);
+
+ /* Release our reference. */
+ vcons_release (vcons);
+
+ free (np->nn);
+ free (np);
+}
+
+/* Attempt to create a file named NAME in DIR for USER with MODE. Set
+ *NODE to the new node upon return. On any error, clear *NODE.
+ *NODE should be locked on success; no matter what, unlock DIR
+ before returning. */
+error_t
+netfs_attempt_create_file (struct iouser *user, struct node *dir,
+ char *name, mode_t mode, struct node **np)
+{
+ /* We create virtual consoles dynamically on the fly, so there is no
+ need for an explicit create operation. */
+ *np = 0;
+ mutex_unlock (&dir->lock);
+ return EOPNOTSUPP;
+}
+
+/* Node NODE is being opened by USER, with FLAGS. NEWNODE is nonzero
+ if we just created this node. Return an error if we should not
+ permit the open to complete because of a permission
+ restriction. */
+error_t
+netfs_check_open_permissions (struct iouser *user, struct node *node,
+ int flags, int newnode)
+{
+ error_t err = 0;
+ if (flags & O_READ)
+ err = fshelp_access (&node->nn_stat, S_IREAD, user);
+ if (!err && (flags & O_WRITE))
+ err = fshelp_access (&node->nn_stat, S_IWRITE, user);
+ if (!err && (flags & O_EXEC))
+ err = fshelp_access (&node->nn_stat, S_IEXEC, user);
+ return err;
+}
+
+/* This should attempt a utimes call for the user specified by CRED on node
+ NODE, to change the atime to ATIME and the mtime to MTIME. */
+error_t
+netfs_attempt_utimes (struct iouser *cred, struct node *node,
+ struct timespec *atime, struct timespec *mtime)
+{
+ error_t err = fshelp_isowner (&node->nn_stat, cred);
+ int flags = TOUCH_CTIME;
+
+ if (! err)
+ {
+ if (mtime)
+ node->nn_stat.st_mtim = *mtime;
+ else
+ flags |= TOUCH_MTIME;
+
+ if (atime)
+ node->nn_stat.st_atim = *atime;
+ else
+ flags |= TOUCH_ATIME;
+
+ fshelp_touch (&node->nn_stat, flags, console_maptime);
+ }
+ return err;
+}
+
+/* Return the valid access types (bitwise OR of O_READ, O_WRITE, and O_EXEC)
+ in *TYPES for file NODE and user CRED. */
+error_t
+netfs_report_access (struct iouser *cred, struct node *node, int *types)
+{
+ *types = 0;
+ if (fshelp_access (&node->nn_stat, S_IREAD, cred) == 0)
+ *types |= O_READ;
+ if (fshelp_access (&node->nn_stat, S_IWRITE, cred) == 0)
+ *types |= O_WRITE;
+ if (fshelp_access (&node->nn_stat, S_IEXEC, cred) == 0)
+ *types |= O_EXEC;
+ return 0;
+}
+
+/* Make sure that NP->nn_stat is filled with the most current
+ information. CRED identifies the user responsible for the
+ operation. NP is locked. */
+error_t
+netfs_validate_stat (struct node *np, struct iouser *cred)
+{
+ /* We are always uptodate. */
+ return 0;
+}
+
+/* This should sync the file NODE completely to disk, for the user
+ CRED. If WAIT is set, return only after sync is completely
+ finished. */
+error_t
+netfs_attempt_sync (struct iouser *cred, struct node *np, int wait)
+{
+ return 0;
+}
+
+
+/* Directory management. */
+
+/* Lookup NAME in DIR for USER; set *NODE to the found name upon
+ return. If the name was not found, then return ENOENT. On any
+ error, clear *NODE. (*NODE, if found, should be locked, this call
+ should unlock DIR no matter what.) */
+error_t
+netfs_attempt_lookup (struct iouser *user, struct node *dir,
+ char *name, struct node **node)
+{
+ error_t err;
+
+ *node = 0;
+ err = fshelp_access (&dir->nn_stat, S_IEXEC, user);
+ if (err)
+ goto out;
+
+ if (strcmp (name, ".") == 0)
+ {
+ /* Current directory -- just add an additional reference to DIR
+ and return it. */
+ netfs_nref (dir);
+ *node = dir;
+ goto out;
+ }
+
+ if (strcmp (name, "..") == 0)
+ {
+ /* Parent directory -- if this is the root directory, return
+ EAGAIN. Otherwise return the root node, because we know
+ that our hierarchy is only one level deep. */
+
+ if (dir->nn->cons)
+ err = EAGAIN;
+ else
+ {
+ netfs_nref (netfs_root_node);
+ *node = netfs_root_node;
+ }
+ goto out;
+ }
+
+ if (dir->nn->cons)
+ {
+ /* This is the root directory. Look up the desired virtual
+ console, creating it on the fly if necessary. */
+ vcons_t vcons;
+ int release_vcons = 0;
+ char *tail = NULL;
+ int id;
+ errno = 0;
+ id = strtol (name, &tail, 0);
+ if ((tail && *tail) || errno)
+ {
+ err = ENOENT;
+ goto out;
+ }
+ err = vcons_lookup (dir->nn->cons, id, 1, &vcons);
+ if (err == ESRCH || err == EINVAL)
+ err = ENOENT;
+ if (err)
+ goto out;
+
+ mutex_lock (&vcons->lock);
+ if (vcons->dir_node)
+ {
+ /* We already have a directory node for this virtual
+ console. Use that, acquire a reference for it, and drop
+ our extra reference to the virtual console. */
+ *node = vcons->dir_node;
+ netfs_nref (*node);
+ release_vcons = 1;
+ }
+ else
+ {
+ /* Create a new directory node, connsuming the reference to
+ the virtual console. */
+ err = new_node (node, vcons, VCONS_NODE_DIR);
+ if (!err)
+ vcons->dir_node = *node;
+ else
+ release_vcons = 1;
+ }
+ mutex_unlock (&vcons->lock);
+ if (release_vcons)
+ vcons_release (vcons);
+ }
+ else
+ {
+ /* This is a virtual console directory node. */
+ vcons_t vcons = dir->nn->vcons;
+ int ref_vcons = 0;
+ assert (dir == vcons->dir_node);
+
+ if (!strcmp (name, "console"))
+ {
+ mutex_lock (&vcons->lock);
+ if (vcons->cons_node)
+ {
+ *node = vcons->cons_node;
+ netfs_nref (*node);
+ }
+ else
+ {
+ err = new_node (node, vcons, VCONS_NODE_CONSOLE);
+ if (!err)
+ {
+ vcons->cons_node = *node;
+ ref_vcons = 1;
+ }
+ }
+ mutex_unlock (&vcons->lock);
+ }
+ else if (!strcmp (name, "display"))
+ {
+ mutex_lock (&vcons->lock);
+ if (vcons->disp_node)
+ {
+ *node = vcons->disp_node;
+ netfs_nref (*node);
+ }
+ else
+ {
+ err = new_node (node, vcons, VCONS_NODE_DISPLAY);
+ if (!err)
+ {
+ vcons->disp_node = *node;
+ ref_vcons = 1;
+ }
+ }
+ mutex_unlock (&vcons->lock);
+ }
+ else if (!strcmp (name, "input"))
+ {
+ mutex_lock (&vcons->lock);
+ if (vcons->inpt_node)
+ {
+ *node = vcons->inpt_node;
+ netfs_nref (*node);
+ }
+ else
+ {
+ err = new_node (node, vcons, VCONS_NODE_INPUT);
+ if (!err)
+ {
+ vcons->inpt_node = *node;
+ ref_vcons = 1;
+ }
+ }
+ mutex_unlock (&vcons->lock);
+ }
+ else
+ err = ENOENT;
+
+ if (ref_vcons)
+ vcons_ref (vcons);
+ }
+
+ if (!err)
+ fshelp_touch (&dir->nn_stat, TOUCH_ATIME, console_maptime);
+
+ out:
+ mutex_unlock (&dir->lock);
+ if (err)
+ *node = 0;
+ else
+ mutex_lock (&(*node)->lock);
+
+ return err;
+}
+
+/* Returned directory entries are aligned to blocks this many bytes long.
+ Must be a power of two. */
+#define DIRENT_ALIGN 4
+#define DIRENT_NAME_OFFS offsetof (struct dirent, d_name)
+/* Length is structure before the name + the name + '\0', all
+ padded to a four-byte alignment. */
+#define DIRENT_LEN(name_len) \
+ ((DIRENT_NAME_OFFS + (name_len) + 1 + (DIRENT_ALIGN - 1)) \
+ & ~(DIRENT_ALIGN - 1))
+
+/* Implement the netfs_get_dirents callback as described in
+ <hurd/netfs.h>. */
+error_t
+netfs_get_dirents (struct iouser *cred, struct node *dir,
+ int first_entry, int num_entries, char **data,
+ mach_msg_type_number_t *data_len,
+ vm_size_t max_data_len, int *data_entries)
+{
+ error_t err;
+ int count = 0;
+ size_t size = 0; /* Total size of our return block. */
+ struct vcons *first_vcons = NULL;
+ struct vcons *vcons;
+
+ /* Add the length of a directory entry for NAME to SIZE and return true,
+ unless it would overflow MAX_DATA_LEN or NUM_ENTRIES, in which case
+ return false. */
+ int bump_size (const char *name)
+ {
+ if (num_entries == -1 || count < num_entries)
+ {
+ size_t new_size = size + DIRENT_LEN (strlen (name));
+ if (max_data_len > 0 && new_size > max_data_len)
+ return 0;
+ size = new_size;
+ count++;
+ return 1;
+ }
+ else
+ return 0;
+ }
+
+ if (!dir->nn->cons && !(dir == dir->nn->vcons->dir_node))
+ return ENOTDIR;
+
+ if (dir->nn->cons)
+ {
+ mutex_lock (&dir->nn->cons->lock);
+
+ /* Find the first entry. */
+ for (first_vcons = dir->nn->cons->vcons_list, count = 2;
+ first_vcons && first_entry > count;
+ first_vcons = first_vcons->next)
+ count++;
+
+ count = 0;
+ }
+
+ /* Make space for the `.' and `..' entries. */
+ if (first_entry == 0)
+ bump_size (".");
+ if (first_entry <= 1)
+ bump_size ("..");
+
+ if (dir->nn->cons)
+ {
+ /* See how much space we need for the result. */
+ for (vcons = first_vcons; vcons; vcons = vcons->next)
+ if (!bump_size (vcons->name))
+ break;
+ }
+ else
+ {
+ if (first_entry <= 2)
+ bump_size ("console");
+ if (first_entry <= 3)
+ bump_size ("display");
+ if (first_entry <= 4)
+ bump_size ("input");
+ }
+
+ /* Allocate it. */
+ *data = mmap (0, size, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0);
+ err = ((void *) *data == (void *) -1) ? errno : 0;
+
+ if (! err)
+ /* Copy out the result. */
+ {
+ char *p = *data;
+
+ int add_dir_entry (const char *name, ino_t fileno, int type)
+ {
+ if (num_entries == -1 || count < num_entries)
+ {
+ struct dirent hdr;
+ size_t name_len = strlen (name);
+ size_t sz = DIRENT_LEN (name_len);
+
+ if (sz > size)
+ return 0;
+ else
+ size -= sz;
+
+ hdr.d_fileno = fileno;
+ hdr.d_reclen = sz;
+ hdr.d_type = type;
+ hdr.d_namlen = name_len;
+
+ memcpy (p, &hdr, DIRENT_NAME_OFFS);
+ strcpy (p + DIRENT_NAME_OFFS, name);
+ p += sz;
+
+ count++;
+
+ return 1;
+ }
+ else
+ return 0;
+ }
+
+ *data_len = size;
+ *data_entries = count;
+
+ count = 0;
+
+ if (dir->nn->cons)
+ {
+ /* Add `.' and `..' entries. */
+ if (first_entry == 0)
+ add_dir_entry (".", 2, DT_DIR);
+ if (first_entry <= 1)
+ add_dir_entry ("..", 2, DT_DIR);
+
+ /* Fill in the real directory entries. */
+ for (vcons = first_vcons; vcons; vcons = vcons->next)
+ if (!add_dir_entry (vcons->name,
+ vcons->id << 2, DT_DIR))
+ break;
+ }
+ else
+ {
+ /* Add `.' and `..' entries. */
+ if (first_entry == 0)
+ add_dir_entry (".", dir->nn_stat.st_ino, DT_DIR);
+ if (first_entry <= 1)
+ add_dir_entry ("..", 2, DT_DIR);
+
+ if (first_entry <= 2)
+ add_dir_entry ("console", (dir->nn->vcons->id << 2) + 1, DT_REG);
+ if (first_entry <= 3)
+ add_dir_entry ("display", (dir->nn->vcons->id << 2) + 2, DT_REG);
+ if (first_entry <= 4)
+ add_dir_entry ("input", (dir->nn->vcons->id << 3) + 2, DT_FIFO);
+ }
+ }
+
+ if (dir->nn->cons)
+ mutex_unlock(&dir->nn->cons->lock);
+
+ fshelp_touch (&dir->nn_stat, TOUCH_ATIME, console_maptime);
+ return err;
+}
+
+/* This should sync the entire remote filesystem. If WAIT is set, return
+ only after sync is completely finished. */
+error_t
+netfs_attempt_syncfs (struct iouser *cred, int wait)
+{
+ return 0;
+}
+
+/* This should attempt a chmod call for the user specified by CRED on node
+ NODE, to change the owner to UID and the group to GID. */
+error_t
+netfs_attempt_chown (struct iouser *cred, struct node *node,
+ uid_t uid, uid_t gid)
+{
+ cons_t cons = node->nn->cons;
+ vcons_t vcons;
+ error_t err;
+
+ if (!cons)
+ return EOPNOTSUPP;
+
+ err = file_chown (cons->underlying, uid, gid);
+ if (err)
+ return err;
+
+ /* Change NODE's owner. */
+ node->nn_stat.st_uid = uid;
+ node->nn_stat.st_gid = gid;
+
+ mutex_lock (&cons->lock);
+ cons->stat_template.st_uid = uid;
+ cons->stat_template.st_gid = gid;
+
+ /* Change the owner of each leaf node. */
+ for (vcons = cons->vcons_list; vcons; vcons = vcons->next)
+ {
+ if (vcons->dir_node)
+ {
+ vcons->dir_node->nn_stat.st_uid = uid;
+ vcons->dir_node->nn_stat.st_gid = gid;
+ }
+ if (vcons->cons_node)
+ {
+ vcons->cons_node->nn_stat.st_uid = uid;
+ vcons->cons_node->nn_stat.st_gid = gid;
+ }
+ if (vcons->disp_node)
+ {
+ vcons->disp_node->nn_stat.st_uid = uid;
+ vcons->disp_node->nn_stat.st_gid = gid;
+ }
+ if (vcons->inpt_node)
+ {
+ vcons->inpt_node->nn_stat.st_uid = uid;
+ vcons->inpt_node->nn_stat.st_gid = gid;
+ }
+ }
+ mutex_unlock (&cons->lock);
+ fshelp_touch (&node->nn_stat, TOUCH_CTIME, console_maptime);
+ return err;
+}
+
+/* This should attempt a chauthor call for the user specified by CRED on node
+ NODE, to change the author to AUTHOR. */
+error_t
+netfs_attempt_chauthor (struct iouser *cred, struct node *node, uid_t author)
+{
+ cons_t cons = node->nn->cons;
+ vcons_t vcons;
+ error_t err;
+
+ if (!cons)
+ return EOPNOTSUPP;
+
+ err = file_chauthor (cons->underlying, author);
+ if (err)
+ return err;
+
+ /* Change NODE's author. */
+ node->nn_stat.st_author = author;
+
+ mutex_lock (&cons->lock);
+ cons->stat_template.st_author = author;
+
+ /* Change the author of each leaf node. */
+ for (vcons = cons->vcons_list; vcons; vcons = vcons->next)
+ {
+ if (vcons->dir_node)
+ vcons->dir_node->nn_stat.st_author = author;
+ if (vcons->cons_node)
+ vcons->cons_node->nn_stat.st_author = author;
+ if (vcons->disp_node)
+ vcons->disp_node->nn_stat.st_author = author;
+ if (vcons->inpt_node)
+ vcons->inpt_node->nn_stat.st_author = author;
+ }
+ mutex_unlock (&cons->lock);
+ fshelp_touch (&node->nn_stat, TOUCH_CTIME, console_maptime);
+ return err;
+}
+
+/* This should attempt a chmod call for the user specified by CRED on node
+ NODE, to change the mode to MODE. Unlike the normal Unix and Hurd meaning
+ of chmod, this function is also used to attempt to change files into other
+ types. If such a transition is attempted which is impossible, then return
+ EOPNOTSUPP. */
+error_t
+netfs_attempt_chmod (struct iouser *cred, struct node *node, mode_t mode)
+{
+ error_t err;
+
+ mode &= ~S_ITRANS;
+ if ((mode & S_IFMT) == 0)
+ mode |= (node->nn_stat.st_mode & S_IFMT);
+
+ if (!node->nn->cons || ((mode & S_IFMT) != (node->nn_stat.st_mode & S_IFMT)))
+ return EOPNOTSUPP;
+
+ err = file_chmod (node->nn->cons->underlying, mode & ~S_IFMT);
+ if (err)
+ return err;
+
+ node->nn_stat.st_mode = mode;
+ fshelp_touch (&node->nn_stat, TOUCH_CTIME, console_maptime);
+ return err;
+}
+
+
+/* The user must define this function. Attempt to turn locked node NP
+ (user CRED) into a symlink with target NAME. */
+error_t
+netfs_attempt_mksymlink (struct iouser *cred, struct node *np, char *name)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_mkdev (struct iouser *cred, struct node *np,
+ mode_t type, dev_t indexes)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_chflags (struct iouser *cred, struct node *np, int flags)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_set_size (struct iouser *cred, struct node *np, off_t size)
+{
+ vcons_t vcons = np->nn->vcons;
+
+ if (!vcons || np == vcons->dir_node
+ || np == vcons->disp_node)
+ return EOPNOTSUPP;
+
+ assert (np == vcons->cons_node || np == vcons->inpt_node);
+ return 0;
+}
+
+error_t
+netfs_attempt_statfs (struct iouser *cred, struct node *np, struct statfs *st)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_mkdir (struct iouser *user, struct node *dir,
+ char *name, mode_t mode)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_unlink (struct iouser *user, struct node *dir, char *name)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_rename (struct iouser *user, struct node *fromdir,
+ char *fromname, struct node *todir,
+ char *toname, int excl)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_rmdir (struct iouser *user,
+ struct node *dir, char *name)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_link (struct iouser *user, struct node *dir,
+ struct node *file, char *name, int excl)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_mkfile (struct iouser *user, struct node *dir,
+ mode_t mode, struct node **np)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_readlink (struct iouser *user, struct node *np, char *buf)
+{
+ return EOPNOTSUPP;
+}
+
+error_t
+netfs_attempt_read (struct iouser *cred, struct node *np,
+ off_t offset, size_t *len, void *data)
+{
+ error_t err = 0;
+ vcons_t vcons = np->nn->vcons;
+ if (!vcons || np == vcons->dir_node
+ || np == vcons->inpt_node)
+ return EOPNOTSUPP;
+
+ mutex_unlock (&np->lock);
+ if (np == vcons->cons_node)
+ {
+ ssize_t amt = input_dequeue (vcons->input,
+ /* cred->po->openstat & O_NONBLOCK */ 0,
+ data, *len);
+ if (amt == -1)
+ err = errno;
+ else
+ *len = amt;
+ }
+ else
+ {
+ /* Pass display content to caller. */
+ ssize_t amt = *len;
+ assert (np == vcons->disp_node);
+
+ if (offset + amt > np->nn_stat.st_size)
+ amt = np->nn_stat.st_size - offset;
+ if (amt < 0)
+ amt = 0;
+ else
+ amt = display_read (vcons->display,
+ /* cred->po->openstat & O_NONBLOCK */ 0,
+ offset, data, amt);
+ if (amt == -1)
+ err = errno;
+ else
+ *len = amt;
+ }
+ mutex_lock (&np->lock);
+ return err;
+}
+
+error_t
+netfs_attempt_write (struct iouser *cred, struct node *np,
+ off_t offset, size_t *len, void *data)
+{
+ error_t err = 0;
+ vcons_t vcons = np->nn->vcons;
+ if (!vcons || np == vcons->dir_node
+ || np == vcons->disp_node)
+ return EOPNOTSUPP;
+
+ mutex_unlock (&np->lock);
+ if (np == vcons->cons_node)
+ {
+ /* The term server is writing to the console device. Feed the
+ data into the screen matrix display. */
+ int amt = display_output (vcons->display,
+ /* cred->po->openstat & O_NONBLOCK */ 0,
+ data, *len);
+ if (amt == -1)
+ err = errno;
+ else
+ *len = amt;
+ }
+ else
+ {
+ int amt;
+ /* The input driver is writing to the input device. Feed the
+ data into the input queue. */
+ assert (np == vcons->inpt_node);
+
+ amt = input_enqueue (vcons->input,
+ /* cred->po->openstat & O_NONBLOCK */ 1,
+ data, *len);
+ if (amt == -1)
+ err = errno;
+ else
+ *len = amt;
+ }
+ mutex_lock (&np->lock);
+ return err;
+}
+
+
+/* Implement io_map as described in <hurd/io.defs>. */
+kern_return_t
+netfs_S_io_map (struct protid *cred,
+ memory_object_t *rdobj,
+ mach_msg_type_name_t *rdtype,
+ memory_object_t *wrobj,
+ mach_msg_type_name_t *wrtype)
+{
+ int flags;
+ struct node *np;
+ vcons_t vcons;
+
+ if (!cred)
+ return EOPNOTSUPP;
+
+ np = cred->po->np;
+ vcons = np->nn->vcons;
+ if (!vcons || np != vcons->disp_node)
+ return EOPNOTSUPP;
+
+ *wrobj = *rdobj = MACH_PORT_NULL;
+
+ flags = cred->po->openstat & (O_READ | O_WRITE);
+
+ mutex_lock (&np->lock);
+ switch (flags)
+ {
+ case O_READ | O_WRITE:
+ *wrobj = *rdobj = display_get_filemap (vcons->display,
+ VM_PROT_READ | VM_PROT_WRITE);
+ if (*wrobj == MACH_PORT_NULL)
+ goto error;
+ mach_port_mod_refs (mach_task_self (), *rdobj, MACH_PORT_RIGHT_SEND, 1);
+ break;
+ case O_READ:
+ *rdobj = display_get_filemap (vcons->display, VM_PROT_READ);
+ if (*rdobj == MACH_PORT_NULL)
+ goto error;
+ break;
+ case O_WRITE:
+ *wrobj = display_get_filemap (vcons->display, VM_PROT_WRITE);
+ if (*wrobj == MACH_PORT_NULL)
+ goto error;
+ break;
+ }
+ mutex_unlock (&np->lock);
+
+ *rdtype = MACH_MSG_TYPE_MOVE_SEND;
+ *wrtype = MACH_MSG_TYPE_MOVE_SEND;
+
+ return 0;
+
+ error:
+ mutex_unlock (&np->lock);
+ return errno;
+}
+
+
+kern_return_t
+netfs_S_dir_notice_changes (struct protid *cred, mach_port_t notify)
+{
+ error_t err;
+ cons_t cons;
+ struct modreq **preq;
+ struct modreq *req;
+
+ if (!cred)
+ return EOPNOTSUPP;
+
+ cons = cred->po->np->nn->cons;
+ if (!cons)
+ return EOPNOTSUPP;
+
+ mutex_lock (&cons->lock);
+ /* We have to prevent that we accumulate dead-names in the
+ notification list. They are cleaned up in cons_notice_dirchange,
+ but that is not called often enough, so we also clean them up
+ here. This way, the maximum of dead-names will never exceed the
+ prior maximum of active clients. The better way would be to
+ request dead-name notifications, XXX. */
+ preq = &cons->dirmod_reqs;
+
+ while (*preq)
+ {
+ mach_port_type_t type;
+ req = *preq;
+
+ err = mach_port_type (mach_task_self (), req->port, &type);
+ if (!err && type == MACH_PORT_TYPE_DEAD_NAME)
+ {
+ /* Remove notify port. */
+ *preq = req->next;
+ mach_port_deallocate (mach_task_self (), req->port);
+ free (req);
+ }
+ else
+ preq = &req->next;
+ }
+
+ err = dir_changed (notify, cons->dirmod_tick, DIR_CHANGED_NULL, "");
+ if (err)
+ {
+ mutex_unlock (&cons->lock);
+ return err;
+ }
+ req = malloc (sizeof (struct modreq));
+ if (!req)
+ {
+ mutex_unlock (&cons->lock);
+ return errno;
+ }
+ req->port = notify;
+ req->next = cons->dirmod_reqs;
+ cons->dirmod_reqs = req;
+ mutex_unlock (&cons->lock);
+ return 0;
+}
+
+kern_return_t
+netfs_S_file_notice_changes (struct protid *cred, mach_port_t notify)
+{
+ struct node *np;
+ vcons_t vcons;
+
+ if (!cred)
+ return EOPNOTSUPP;
+
+ np = cred->po->np;
+ vcons = np->nn->vcons;
+ if (!vcons || np != vcons->disp_node)
+ return EOPNOTSUPP;
+
+ return display_notice_changes (vcons->display, notify);
+}
+
+
+static const char *color_names[CONS_COLOR_MAX + 1] =
+ {
+ [CONS_COLOR_BLACK] = "black",
+ [CONS_COLOR_RED] = "red",
+ [CONS_COLOR_GREEN] = "green",
+ [CONS_COLOR_YELLOW] = "yellow",
+ [CONS_COLOR_BLUE] = "blue",
+ [CONS_COLOR_MAGENTA] = "magenta",
+ [CONS_COLOR_CYAN] = "cyan",
+ [CONS_COLOR_WHITE] = "white"
+ };
+
+static const struct argp_option options[] =
+{
+ { "foreground", 'f', "COLOR", 0, "Set foreground color to"
+ " COLOR (default `" DEFAULT_FOREGROUND_NAME "')" },
+ { "background", 'b', "COLOR", 0, "Set background color to"
+ " COLOR (default `" DEFAULT_BACKGROUND_NAME "')" },
+ { "attribute", 'a', "ATTR[,...]", 0, "Set further default attributes"
+ " (default `" DEFAULT_ATTRIBUTE_NAME "')" },
+ { "encoding", 'e', "NAME", 0, "Set encoding of virtual consoles to"
+ " NAME (default `" DEFAULT_ENCODING "')" },
+ { "width", 'w', "WIDTH", 0, "Set width to WIDTH (default `"
+ STR(DEFAULT_WIDTH) "')" },
+ { "height", 'h', "HEIGHT", 0, "Set height to HEIGHT (default `"
+ STR(DEFAULT_HEIGHT) "')" },
+ { "lines", 'l', "LINES", 0, "Set amount of scrollback lines to LINES "
+ "(default `" STR(DEFAULT_LINES) "')" },
+ {0}
+};
+
+static error_t
+parse_color (const char *name, int *number)
+{
+ if (isdigit (*name))
+ {
+ long int nr;
+ char *tail;
+
+ errno = 0;
+
+ nr = strtol (name, &tail, 0);
+ if (errno || *tail || nr < 0 || nr > CONS_COLOR_MAX)
+ return EINVAL;
+ *number = nr;
+ return 0;
+ }
+ else
+ {
+ int i;
+ for (i = 0; i <= CONS_COLOR_MAX; i++)
+ if (!strcmp (color_names[i], name))
+ {
+ *number = i;
+ return 0;
+ }
+ return EINVAL;
+ }
+}
+
+static error_t
+parse_attributes (const char *name, conchar_attr_t *attr)
+{
+ while (*name)
+ {
+ int value = 1;
+
+ if (!strncmp (name, "not-", 4))
+ {
+ value = 0;
+ name += 4;
+ }
+
+ if (!strncmp (name, "normal", 6))
+ {
+ name += 6;
+ if (value != 1)
+ return EINVAL;
+ attr->intensity = CONS_ATTR_INTENSITY_NORMAL;
+ }
+ else if (!strncmp (name, "bright", 6))
+ {
+ name += 6;
+ if (value != 1)
+ return EINVAL;
+ attr->intensity = CONS_ATTR_INTENSITY_BOLD;
+ }
+ else if (!strncmp (name, "dim", 3))
+ {
+ name += 3;
+ if (value != 1)
+ return EINVAL;
+ attr->intensity = CONS_ATTR_INTENSITY_DIM;
+ }
+ else if (!strncmp (name, "underlined", 10))
+ {
+ name += 10;
+ attr->underlined = value;
+ }
+ else if (!strncmp (name, "blinking", 8))
+ {
+ name += 8;
+ attr->blinking = value;
+ }
+ else if (!strncmp (name, "concealed", 9))
+ {
+ name += 9;
+ attr->concealed = value;
+ }
+ else if (!strncmp (name, "italic", 6))
+ {
+ name += 6;
+ attr->italic = value;
+ }
+ else if (!strncmp (name, "bold", 4))
+ {
+ name += 4;
+ attr->bold = value;
+ }
+ else
+ return EINVAL;
+
+ if (name[0] == ',')
+ name++;
+ else if (name[0] != '\0')
+ return EINVAL;
+ }
+ return 0;
+}
+
+static error_t
+parse_opt (int opt, char *arg, struct argp_state *state)
+{
+ cons_t cons = state->input ?: netfs_root_node->nn->cons;
+ error_t err;
+ int color = 0;
+ char *tail;
+
+ switch (opt)
+ {
+ default:
+ return ARGP_ERR_UNKNOWN;
+ case ARGP_KEY_SUCCESS:
+ case ARGP_KEY_ERROR:
+ break;
+
+ case ARGP_KEY_INIT:
+ mutex_lock (&cons->lock);
+ break;
+
+ case ARGP_KEY_FINI:
+ mutex_unlock (&cons->lock);
+ break;
+
+ case 'f':
+ err = parse_color (arg, &color);
+ cons->attribute.fgcol = color;
+ if (err)
+ argp_error (state, "Invalid color name: %s", arg);
+ break;
+
+ case 'b':
+ err = parse_color (arg, &color);
+ cons->attribute.bgcol = color;
+ if (err)
+ argp_error (state, "Invalid color name: %s", arg);
+ break;
+
+ case 'a':
+ err = parse_attributes (arg, &cons->attribute);
+ if (err)
+ argp_error (state, "Invalid attribute specifier: %s", arg);
+ break;
+
+ case 'l':
+ errno = 0;
+ cons->lines = strtoul (arg, &tail, 0);
+ if (tail == NULL || tail == arg || *tail != '\0')
+ argp_error (state, "LINES is not a number: %s", arg);
+ if (errno)
+ argp_error (state, "Overflow in argument LINES %s", arg);
+ break;
+
+ case 'w':
+ errno = 0;
+ cons->width = strtoul (arg, &tail, 0);
+ if (tail == NULL || tail == arg || *tail != '\0')
+ argp_error (state, "WIDTH is not a number: %s", arg);
+ if (errno)
+ argp_error (state, "Overflow in argument WIDTH %s", arg);
+ break;
+
+ case 'h':
+ errno = 0;
+ cons->height = strtoul (arg, &tail, 0);
+ if (tail == NULL || tail == arg || *tail != '\0')
+ argp_error (state, "HEIGHT is not a number: %s", arg);
+ if (errno)
+ argp_error (state, "Overflow in argument HEIGHT %s", arg);
+ break;
+
+ case 'e':
+ /* XXX Check validity of encoding. Can we perform all necessary
+ conversions? */
+ {
+ char *new = strdup (arg);
+ if (!new)
+ return ENOMEM;
+ if (cons->encoding)
+ free (cons->encoding);
+ cons->encoding = new;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* Return an argz string describing the current options. Fill *ARGZ
+ with a pointer to newly malloced storage holding the list and *LEN
+ to the length of that storage. */
+error_t
+netfs_append_args (char **argz, size_t *argz_len)
+{
+ error_t err = 0;
+ cons_t cons = netfs_root_node->nn->cons;
+ /* The longest possible is 61 characters long:
+ "normal,not-underlined,not-blinking,not-reversed,not-concealed". */
+ char attr_str[80] = "--attribute=";
+ char *attr = &attr_str[12];
+ char *attrp = attr;
+
+ if (cons->encoding && strcmp (cons->encoding, DEFAULT_ENCODING))
+ {
+ char *buf;
+ if (asprintf (&buf, "--encoding=%s", cons->encoding) < 0)
+ err = ENOMEM;
+ else
+ err = argz_add (argz, argz_len, buf);
+
+ }
+ if (!err && cons->attribute.fgcol != DEFAULT_FOREGROUND)
+ {
+ char *buf;
+ if (asprintf (&buf, "--foreground=%s",
+ color_names[cons->attribute.fgcol]) < 0)
+ err = ENOMEM;
+ else
+ err = argz_add (argz, argz_len, buf);
+
+ }
+ if (!err && cons->attribute.bgcol != DEFAULT_BACKGROUND)
+ {
+ char *buf;
+ if (asprintf (&buf, "--background=%s",
+ color_names[cons->attribute.bgcol]) < 0)
+ err = ENOMEM;
+ else
+ err = argz_add (argz, argz_len, buf);
+ }
+ if (!err && cons->lines != DEFAULT_LINES)
+ {
+ char *buf;
+ if (asprintf (&buf, "--lines=%d", cons->lines) < 0)
+ err = ENOMEM;
+ else
+ err = argz_add (argz, argz_len, buf);
+ }
+ if (!err && cons->width != DEFAULT_WIDTH)
+ {
+ char *buf;
+ if (asprintf (&buf, "--width=%d", cons->lines) < 0)
+ err = ENOMEM;
+ else
+ err = argz_add (argz, argz_len, buf);
+ }
+ if (!err && cons->height != DEFAULT_HEIGHT)
+ {
+ char *buf;
+ if (asprintf (&buf, "--height=%d", cons->height) < 0)
+ err = ENOMEM;
+ else
+ err = argz_add (argz, argz_len, buf);
+ }
+ if (!err && cons->attribute.intensity != DEFAULT_INTENSITY)
+ {
+ if (attrp != attr)
+ *(attrp++) = ',';
+ switch (cons->attribute.intensity)
+ {
+ case CONS_ATTR_INTENSITY_NORMAL:
+ attrp = stpcpy (attrp, "normal");
+ break;
+ case CONS_ATTR_INTENSITY_BOLD:
+ attrp = stpcpy (attrp, "bold");
+ break;
+ case CONS_ATTR_INTENSITY_DIM:
+ attrp = stpcpy (attrp, "dim");
+ break;
+ }
+ }
+ if (!err && cons->attribute.underlined != DEFAULT_UNDERLINED)
+ {
+ if (attrp != attr)
+ *(attrp++) = ',';
+ if (!cons->attribute.underlined)
+ attrp = stpcpy (attrp, "not-");
+ attrp = stpcpy (attrp, "underlined");
+ }
+ if (!err && cons->attribute.blinking != DEFAULT_BLINKING)
+ {
+ if (attrp != attr)
+ *(attrp++) = ',';
+ if (!cons->attribute.blinking)
+ attrp = stpcpy (attrp, "not-");
+ attrp = stpcpy (attrp, "blinking");
+ }
+ if (!err && cons->attribute.reversed != DEFAULT_REVERSED)
+ {
+ if (attrp != attr)
+ *(attrp++) = ',';
+ if (!cons->attribute.reversed)
+ attrp = stpcpy (attrp, "not-");
+ attrp = stpcpy (attrp, "reversed");
+ }
+ if (!err && cons->attribute.concealed != DEFAULT_CONCEALED)
+ {
+ if (attrp != attr)
+ *(attrp++) = ',';
+ if (!cons->attribute.concealed)
+ attrp = stpcpy (attrp, "not-");
+ attrp = stpcpy (attrp, "concealed");
+ }
+ if (!err && attrp != attr)
+ err = argz_add (argz, argz_len, attr_str);
+
+ return err;
+}
+
+
+kern_return_t
+S_tioctl_tiocflush (struct protid *cred, int queue_selector)
+{
+ struct node *np;
+ vcons_t vcons;
+
+ if (!cred)
+ return EOPNOTSUPP;
+ if (!(cred->po->openstat & (O_READ | O_WRITE)))
+ return EBADF;
+
+ np = cred->po->np;
+ vcons = np->nn->vcons;
+ if (!vcons || np != vcons->cons_node)
+ return EOPNOTSUPP;
+
+ if (!queue_selector)
+ queue_selector = O_READ | O_WRITE;
+
+ if (queue_selector & O_READ)
+ input_flush (vcons->input);
+ if (queue_selector & O_WRITE)
+ display_discard_output (vcons->display);
+
+ return 0;
+}
+
+kern_return_t
+S_tioctl_tiocgwinsz (struct protid *cred, struct winsize *size)
+{
+ struct node *np;
+ vcons_t vcons;
+
+ if (!cred)
+ return EOPNOTSUPP;
+
+ np = cred->po->np;
+ vcons = np->nn->vcons;
+ if (!vcons || np != vcons->cons_node)
+ return EOPNOTSUPP;
+
+ display_getsize (vcons->display, size);
+ return 0;
+}
+
+kern_return_t
+S_tioctl_tiocstart (struct protid *cred)
+{
+ struct node *np;
+ vcons_t vcons;
+
+ if (!cred)
+ return EOPNOTSUPP;
+ if (!(cred->po->openstat & (O_READ | O_WRITE)))
+ return EBADF;
+
+ np = cred->po->np;
+ vcons = np->nn->vcons;
+ if (!vcons || np != vcons->cons_node)
+ return EOPNOTSUPP;
+
+ display_start_output (vcons->display);
+ return 0;
+}
+
+kern_return_t
+S_tioctl_tiocstop (struct protid *cred)
+{
+ struct node *np;
+ vcons_t vcons;
+
+ if (!cred)
+ return EOPNOTSUPP;
+ if (!(cred->po->openstat & (O_READ | O_WRITE)))
+ return EBADF;
+
+ np = cred->po->np;
+ vcons = np->nn->vcons;
+ if (!vcons || np != vcons->cons_node)
+ return EOPNOTSUPP;
+
+ display_stop_output (vcons->display);
+ return 0;
+}
+
+
+kern_return_t
+S_tioctl_tiocoutq (struct protid *cred, int *queue_size)
+{
+ struct node *np;
+ vcons_t vcons;
+
+ if (!cred)
+ return EOPNOTSUPP;
+ if (!(cred->po->openstat & (O_READ | O_WRITE)))
+ return EBADF;
+
+ np = cred->po->np;
+ vcons = np->nn->vcons;
+ if (!vcons || np != vcons->cons_node)
+ return EOPNOTSUPP;
+
+ *queue_size = display_pending_output (vcons->display);
+ return 0;
+}
+
+kern_return_t
+S_tioctl_tiocspgrp (struct protid *cred, int pgrp)
+{
+ struct node *np;
+ vcons_t vcons;
+
+ if (!cred)
+ return EOPNOTSUPP;
+ if (!(cred->po->openstat & (O_READ | O_WRITE)))
+ return EBADF;
+
+ np = cred->po->np;
+ vcons = np->nn->vcons;
+ if (!vcons || np != vcons->cons_node)
+ return EOPNOTSUPP;
+
+ display_set_owner (vcons->display, -pgrp);
+ return 0;
+}
+
+kern_return_t
+S_tioctl_tiocgpgrp (struct protid *cred, int *pgrp)
+{
+ error_t err;
+ struct node *np;
+ vcons_t vcons;
+
+ if (!cred)
+ return EOPNOTSUPP;
+ if (!(cred->po->openstat & (O_READ | O_WRITE)))
+ return EBADF;
+
+ np = cred->po->np;
+ vcons = np->nn->vcons;
+ if (!vcons || np != vcons->cons_node)
+ return EOPNOTSUPP;
+
+ err = display_get_owner (vcons->display, pgrp);
+ if (!err)
+ *pgrp = -*pgrp;
+
+ return err;
+}
+
+kern_return_t
+S_tioctl_tiocmodg (io_t port, int *state)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocmods (io_t port, int state)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocexcl (io_t port)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocnxcl (io_t port)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocgeta (io_t port, tcflag_t *modes, cc_t *ccs, speed_t *speeds)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocseta (io_t port, tcflag_t *modes, cc_t *ccs, speed_t *speeds)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocsetaw (io_t port, tcflag_t *modes, cc_t *ccs, speed_t *speeds)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocsetaf (io_t port, tcflag_t *modes, cc_t *ccs,
+ speed_t *speeds)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocgetd (io_t port, int *disc)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocsetd (io_t port, int disc)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocdrain (io_t port)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocmget (io_t port, int *bits)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocmset (io_t port, int bits)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocsig (io_t port, int sig)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocext (io_t port, int mode)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocucntl (io_t port, int mode)
+
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocswinsz (struct protid *cred, struct winsize size)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocremote (struct protid *cred, int how)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocmbic (struct protid *cred, int bits)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocmbis (struct protid *cred, int bits)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocpkt (struct protid *cred, int mode)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocsti (struct protid *cred, char c)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tioccdtr (struct protid *cred)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocsdtr (struct protid *cred)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tioccbrk (struct protid *cred)
+{
+ return EOPNOTSUPP;
+}
+
+kern_return_t
+S_tioctl_tiocsbrk (struct protid *cred)
+{
+ return EOPNOTSUPP;
+}
+
+
+int
+console_demuxer (mach_msg_header_t *inp,
+ mach_msg_header_t *outp)
+{
+ extern int netfs_demuxer (mach_msg_header_t *inp, mach_msg_header_t *outp);
+ extern int tioctl_server (mach_msg_header_t *inp, mach_msg_header_t *outp);
+
+ return (netfs_demuxer (inp, outp)
+ || tioctl_server (inp, outp));
+}
+
+const struct argp netfs_std_runtime_argp =
+ { options, parse_opt, NULL,
+ "A translator that provides virtual consoles." };
+
+int
+main (int argc, char **argv)
+{
+ error_t err;
+ mach_port_t bootstrap;
+ struct stat ul_stat;
+ cons_t cons;
+ struct netnode root_nn = { vcons: 0 };
+
+ cons = malloc (sizeof (struct cons));
+ if (!cons)
+ error (1, ENOMEM, "Cannot create console structure");
+ mutex_init (&cons->lock);
+ cons->encoding = NULL;
+ cons->width = DEFAULT_WIDTH;
+ cons->height = DEFAULT_HEIGHT;
+ cons->lines = DEFAULT_LINES;
+ cons->attribute.intensity = DEFAULT_INTENSITY;
+ cons->attribute.underlined = DEFAULT_UNDERLINED;
+ cons->attribute.blinking = DEFAULT_BLINKING;
+ cons->attribute.reversed = DEFAULT_REVERSED;
+ cons->attribute.concealed = DEFAULT_CONCEALED;
+ cons->attribute.fgcol = DEFAULT_FOREGROUND;
+ cons->attribute.bgcol = DEFAULT_BACKGROUND;
+ cons->vcons_list = NULL;
+ cons->dirmod_reqs = NULL;
+ cons->dirmod_tick = 0;
+ root_nn.cons = cons;
+
+ /* Parse our command line arguments. */
+ argp_parse (&netfs_std_runtime_argp, argc, argv, 0, 0, cons);
+
+ setlocale (LC_CTYPE, "C.UTF-8");
+
+ task_get_bootstrap_port (mach_task_self (), &bootstrap);
+
+ netfs_init ();
+
+ display_init ();
+
+ /* Create the root node (some attributes initialized below). */
+ netfs_root_node = netfs_make_node (&root_nn);
+ if (! netfs_root_node)
+ error (2, ENOMEM, "Cannot create root node");
+
+ err = maptime_map (0, 0, &console_maptime);
+ if (err)
+ error (3, err, "Cannot map time");
+
+ cons->node = netfs_root_node;
+ cons->underlying = netfs_startup (bootstrap, O_READ);
+ if (cons->underlying == MACH_PORT_NULL)
+ error (4, 0, "Cannot get underlying node");
+
+ err = io_stat (cons->underlying, &ul_stat);
+ if (err)
+ error (5, err, "Cannot stat underlying node");
+
+ /* CONS.stat_template contains some fields that are inherited by all
+ nodes we create. */
+ cons->stat_template.st_uid = ul_stat.st_uid;
+ cons->stat_template.st_gid = ul_stat.st_gid;
+ cons->stat_template.st_author = ul_stat.st_author;
+ cons->stat_template.st_mode = (ul_stat.st_mode & ~S_IFMT & ~S_ITRANS);
+ cons->stat_template.st_fsid = getpid ();
+ cons->stat_template.st_nlink = 1;
+ cons->stat_template.st_fstype = FSTYPE_MISC;
+
+ /* Initialize the root node's stat information. */
+ netfs_root_node->nn_stat = cons->stat_template;
+ netfs_root_node->nn_stat.st_ino = 2;
+ netfs_root_node->nn_stat.st_mode |= S_IFDIR;
+ netfs_root_node->nn_translated = 0;
+
+ /* If the underlying node isn't a directory, propagate read permission to
+ execute permission since we need that for lookups. */
+ if (! S_ISDIR (ul_stat.st_mode))
+ {
+ if (ul_stat.st_mode & S_IRUSR)
+ netfs_root_node->nn_stat.st_mode |= S_IXUSR;
+ if (ul_stat.st_mode & S_IRGRP)
+ netfs_root_node->nn_stat.st_mode |= S_IXGRP;
+ if (ul_stat.st_mode & S_IROTH)
+ netfs_root_node->nn_stat.st_mode |= S_IXOTH;
+ }
+
+ fshelp_touch (&netfs_root_node->nn_stat, TOUCH_ATIME|TOUCH_MTIME|TOUCH_CTIME,
+ console_maptime);
+
+ do
+ {
+ ports_manage_port_operations_multithread (netfs_port_bucket,
+ console_demuxer,
+ 1000 * 60 * 2,
+ 1000 * 60 * 10,
+ 0);
+ err = netfs_shutdown (0);
+ }
+ while (err);
+
+ exit (err);
+}
diff --git a/console/display.c b/console/display.c
new file mode 100644
index 00000000..e807c50f
--- /dev/null
+++ b/console/display.c
@@ -0,0 +1,2138 @@
+/* display.c - The display component of a virtual console.
+ Copyright (C) 1999 Kalle Olavi Niemitalo (emu.c from colortext 0.3).
+ Copyright (C) 2002, 2003, 2010 Free Software Foundation, Inc.
+ Written by Marcus Brinkmann and Kalle Olavi Niemitalo.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#include <stddef.h>
+#include <errno.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <iconv.h>
+#include <argp.h>
+#include <string.h>
+#include <assert.h>
+#include <error.h>
+
+#include <cthreads.h>
+
+#include <hurd.h>
+#include <hurd/ports.h>
+#include <hurd/console.h>
+
+#ifndef __STDC_ISO_10646__
+#error It is required that wchar_t is UCS-4.
+#endif
+
+#include "display.h"
+#include "pager.h"
+
+
+struct changes
+{
+ uint32_t flags;
+ struct
+ {
+ uint32_t col;
+ uint32_t row;
+ uint32_t status;
+ } cursor;
+ struct
+ {
+ uint32_t cur_line;
+ uint32_t scr_lines;
+ } screen;
+
+ uint32_t bell_audible;
+ uint32_t bell_visible;
+
+ off_t start;
+ off_t end;
+
+#define DISPLAY_CHANGE_CURSOR_POS 0x0001
+#define DISPLAY_CHANGE_CURSOR_STATUS 0x0002
+#define DISPLAY_CHANGE_SCREEN_CUR_LINE 0x0004
+#define DISPLAY_CHANGE_SCREEN_SCR_LINES 0x0008
+#define DISPLAY_CHANGE_BELL_AUDIBLE 0x0010
+#define DISPLAY_CHANGE_BELL_VISIBLE 0x0020
+#define DISPLAY_CHANGE_FLAGS 0x0030
+#define DISPLAY_CHANGE_MATRIX 0x0040
+ unsigned int which;
+};
+
+struct cursor
+{
+ uint32_t saved_x;
+ uint32_t saved_y;
+};
+typedef struct cursor *cursor_t;
+
+struct scrolling_region
+{
+ uint32_t top;
+ uint32_t bottom;
+};
+
+struct parse
+{
+ /* The parsing state of output characters, needed to handle escape
+ character sequences. */
+ enum
+ {
+ STATE_NORMAL = 0,
+ /* An escape character has just been parsed. */
+ STATE_ESC,
+ STATE_ESC_BRACKET_INIT,
+ STATE_ESC_BRACKET,
+ STATE_ESC_BRACKET_QUESTION,
+ STATE_ESC_BRACKET_RIGHT_ANGLE
+ } state;
+
+ /* How many parameters an escape sequence may have. */
+#define PARSE_MAX_PARAMS 10
+ int params[PARSE_MAX_PARAMS];
+ int nparams;
+};
+typedef struct parse *parse_t;
+
+struct output
+{
+ /* The state of the conversion of output characters. */
+ iconv_t cd;
+ /* The output queue holds the characters that are to be outputted.
+ The conversion routine might refuse to handle some incomplete
+ multi-byte or composed character at the end of the buffer, so we
+ have to keep them around. */
+ int stopped;
+ struct condition resumed;
+ char *buffer;
+ size_t allocated;
+ size_t size;
+
+ /* The parsing state of output characters. */
+ struct parse parse;
+};
+typedef struct output *output_t;
+
+struct attr
+{
+ conchar_attr_t attr_def;
+ conchar_attr_t current;
+ /* True if in alternate character set (ASCII graphic) mode. */
+ unsigned int altchar;
+};
+typedef struct attr *attr_t;
+
+/* Pending directory and file modification requests. */
+struct modreq
+{
+ mach_port_t port;
+ struct modreq *next;
+ /* If the port should have been notified, but it was blocking, we
+ set this. */
+ int pending;
+};
+
+/* For each display, a notification port is created to which the
+ kernel sends message accepted notifications. */
+struct notify
+{
+ struct port_info pi;
+ struct display *display;
+};
+
+struct display
+{
+ /* The lock for the virtual console display structure. */
+ struct mutex lock;
+
+ /* Indicates if OWNER_ID is initialized. */
+ int has_owner;
+ /* Specifies the ID of the process that should receive the WINCH
+ signal for this virtual console. */
+ int owner_id;
+
+ /* The pending changes. */
+ struct changes changes;
+
+ /* The state of the virtual console. */
+ /* The saved cursor position. */
+ struct cursor cursor;
+ /* The output queue and parser state. */
+ struct output output;
+ /* The current video attributes. */
+ struct attr attr;
+ /* Non-zero if we are in insert mode. */
+ int insert_mode;
+ /* Scrolling region. */
+ struct scrolling_region csr;
+
+ struct cons_display *user;
+
+ /* The pager for the USER member. */
+ struct user_pager user_pager;
+
+ /* A list of ports to send file change notifications to. */
+ struct modreq *filemod_reqs;
+ /* Those ports which currently have a pending notification. */
+ struct modreq *filemod_reqs_pending;
+ /* The notify port. */
+ struct notify *notify_port;
+};
+
+
+/* The bucket and class for notification messages. */
+static struct port_bucket *notify_bucket;
+static struct port_class *notify_class;
+
+#define msgh_request_port msgh_remote_port
+#define msgh_reply_port msgh_local_port
+
+/* SimpleRoutine file_changed */
+kern_return_t
+nowait_file_changed (mach_port_t notify_port, natural_t tickno,
+ file_changed_type_t change,
+ off_t start, off_t end, mach_port_t notify)
+{
+ typedef struct
+ {
+ mach_msg_header_t Head;
+ mach_msg_type_t ticknoType;
+ natural_t tickno;
+ mach_msg_type_t changeType;
+ file_changed_type_t change;
+ mach_msg_type_t startType;
+ loff_t start;
+ mach_msg_type_t endType;
+ loff_t end;
+ } Request;
+ union
+ {
+ Request In;
+ } Mess;
+ register Request *InP = &Mess.In;
+
+ static const mach_msg_type_t ticknoType = {
+ /* msgt_name = */ 2,
+ /* msgt_size = */ 32,
+ /* msgt_number = */ 1,
+ /* msgt_inline = */ TRUE,
+ /* msgt_longform = */ FALSE,
+ /* msgt_deallocate = */ FALSE,
+ /* msgt_unused = */ 0
+ };
+
+ static const mach_msg_type_t changeType = {
+ /* msgt_name = */ 2,
+ /* msgt_size = */ 32,
+ /* msgt_number = */ 1,
+ /* msgt_inline = */ TRUE,
+ /* msgt_longform = */ FALSE,
+ /* msgt_deallocate = */ FALSE,
+ /* msgt_unused = */ 0
+ };
+
+ static const mach_msg_type_t startType = {
+ /* msgt_name = */ 11,
+ /* msgt_size = */ 64,
+ /* msgt_number = */ 1,
+ /* msgt_inline = */ TRUE,
+ /* msgt_longform = */ FALSE,
+ /* msgt_deallocate = */ FALSE,
+ /* msgt_unused = */ 0
+ };
+
+ static const mach_msg_type_t endType = {
+ /* msgt_name = */ 11,
+ /* msgt_size = */ 64,
+ /* msgt_number = */ 1,
+ /* msgt_inline = */ TRUE,
+ /* msgt_longform = */ FALSE,
+ /* msgt_deallocate = */ FALSE,
+ /* msgt_unused = */ 0
+ };
+
+ InP->ticknoType = ticknoType;
+ InP->tickno = tickno;
+ InP->changeType = changeType;
+ InP->change = change;
+ InP->startType = startType;
+ InP->start = start;
+ InP->endType = endType;
+ InP->end = end;
+
+ InP->Head.msgh_bits = MACH_MSGH_BITS(19, 0);
+ /* msgh_size passed as argument. */
+ InP->Head.msgh_request_port = notify_port;
+ InP->Head.msgh_reply_port = MACH_PORT_NULL;
+ InP->Head.msgh_seqno = 0;
+ InP->Head.msgh_id = 20501;
+
+ if (notify == MACH_PORT_NULL)
+ return mach_msg (&InP->Head, MACH_SEND_MSG | MACH_MSG_OPTION_NONE,
+ 64, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
+ MACH_PORT_NULL);
+ else
+ return mach_msg (&InP->Head, MACH_SEND_MSG | MACH_SEND_NOTIFY,
+ 64, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
+ notify);
+}
+
+/* Free the list of modification requests MR */
+static void
+free_modreqs (struct modreq *mr)
+{
+ struct modreq *tmp;
+ for (; mr; mr = tmp)
+ {
+ mach_port_t old;
+ /* Cancel the dead-name notification. */
+ mach_port_request_notification (mach_task_self (), mr->port,
+ MACH_NOTIFY_DEAD_NAME, 0,
+ MACH_PORT_NULL,
+ MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);
+ mach_port_deallocate (mach_task_self (), old);
+
+ /* Deallocate the user's port. */
+ mach_port_deallocate (mach_task_self (), mr->port);
+ tmp = mr->next;
+ free (mr);
+ }
+}
+
+/* A port deleted notification is generated when we deallocate the
+ user's notify port before it is dead. */
+error_t
+do_mach_notify_port_deleted (mach_port_t notify, mach_port_t name)
+{
+ /* As we cancel the dead-name notification before deallocating the
+ port, this should not happen. */
+ assert (0);
+}
+
+/* We request dead name notifications for the user ports. */
+error_t
+do_mach_notify_dead_name (mach_port_t notify, mach_port_t dead_name)
+{
+ struct notify *notify_port = ports_lookup_port (notify_bucket,
+ notify, notify_class);
+ struct display *display;
+ struct modreq **preq;
+ struct modreq *req;
+
+ if (!notify_port)
+ return EOPNOTSUPP;
+
+ display = notify_port->display;
+ mutex_lock (&display->lock);
+
+ /* Find request in pending queue. */
+ preq = &display->filemod_reqs_pending;
+ while (*preq && (*preq)->port != dead_name)
+ preq = &(*preq)->next;
+ if (! *preq)
+ {
+ /* Find request in queue. */
+ preq = &display->filemod_reqs;
+ while (*preq && (*preq)->port != dead_name)
+ preq = &(*preq)->next;
+ }
+
+ if (*preq)
+ {
+ req = *preq;
+ *preq = req->next;
+
+ mach_port_deallocate (mach_task_self (), req->port);
+ free (req);
+ }
+ mutex_unlock (&display->lock);
+
+ /* Drop gratuitous extra reference that the notification creates. */
+ mach_port_deallocate (mach_task_self (), dead_name);
+
+ return 0;
+}
+
+void do_mach_notify_port_destroyed (void) { assert (0); }
+
+error_t
+do_mach_notify_no_senders (mach_port_t port, mach_port_mscount_t count)
+{
+ return ports_do_mach_notify_no_senders (port, count);
+}
+
+kern_return_t
+do_mach_notify_send_once (mach_port_t notify)
+{
+ return 0;
+}
+
+kern_return_t
+do_mach_notify_msg_accepted (mach_port_t notify, mach_port_t send)
+{
+ struct notify *notify_port = ports_lookup_port (notify_bucket,
+ notify, notify_class);
+ struct display *display;
+ struct modreq **preq;
+ struct modreq *req;
+
+ if (!notify_port)
+ return EOPNOTSUPP;
+
+ /* If we deallocated the send right in display_destroy before the
+ notification was created. We have nothing to do in this
+ case. */
+ if (!send)
+ {
+ assert(0);
+ ports_port_deref (notify_port);
+ return 0;
+ }
+
+ display = notify_port->display;
+ mutex_lock (&display->lock);
+ /* Find request in pending queue. */
+ preq = &display->filemod_reqs_pending;
+ while (*preq && (*preq)->port != send)
+ preq = &(*preq)->next;
+ /* If we don't find the request, it was destroyed in
+ display_destroy. In this case, there is nothing left to do
+ here. */
+ if (! *preq)
+ {
+ assert(0);
+ mutex_unlock (&display->lock);
+ ports_port_deref (notify_port);
+ return 0;
+ }
+ req = *preq;
+
+ if (req->pending)
+ {
+ error_t err;
+ /* A request was desired while we were blocking. Send it now
+ and stay in pending queue. */
+ req->pending = 0;
+ err = nowait_file_changed (req->port, 0, FILE_CHANGED_WRITE, -1, -1,
+ notify);
+ if (err && err != MACH_SEND_WILL_NOTIFY)
+ {
+ mach_port_t old;
+ *preq = req->next;
+ mutex_unlock (&display->lock);
+
+ /* Cancel the dead-name notification. */
+ mach_port_request_notification (mach_task_self (), req->port,
+ MACH_NOTIFY_DEAD_NAME, 0,
+ MACH_PORT_NULL,
+ MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);
+ mach_port_deallocate (mach_task_self (), old);
+
+ mach_port_deallocate (mach_task_self (), req->port);
+ free (req);
+ ports_port_deref (notify_port);
+ return err;
+ }
+ if (err == MACH_SEND_WILL_NOTIFY)
+ {
+ mutex_unlock (&display->lock);
+ return 0;
+ }
+ /* The message was successfully queued, fall through. */
+ }
+ /* Remove request from pending queue. */
+ *preq = req->next;
+ /* Insert request into active queue. */
+ req->next = display->filemod_reqs;
+ display->filemod_reqs = req;
+ mutex_unlock (&display->lock);
+ ports_port_deref (notify_port);
+ return 0;
+}
+
+/* A top-level function for the notification thread that just services
+ notification messages. */
+static void
+service_notifications (any_t arg)
+{
+ struct port_bucket *notify_bucket = arg;
+ extern int notify_server (mach_msg_header_t *inp, mach_msg_header_t *outp);
+
+ for (;;)
+ ports_manage_port_operations_one_thread (notify_bucket,
+ notify_server,
+ 1000 * 60 * 10);
+}
+
+error_t
+display_notice_changes (display_t display, mach_port_t notify)
+{
+ error_t err;
+ struct modreq *req;
+ mach_port_t notify_port;
+ mach_port_t old;
+
+ mutex_lock (&display->lock);
+ err = nowait_file_changed (notify, 0, FILE_CHANGED_NULL, 0, 0,
+ MACH_PORT_NULL);
+ if (err)
+ {
+ mutex_unlock (&display->lock);
+ return err;
+ }
+
+ req = malloc (sizeof (struct modreq));
+ if (!req)
+ {
+ mutex_unlock (&display->lock);
+ return errno;
+ }
+
+ notify_port = ports_get_right (display->notify_port);
+
+ /* Request dead-name notification for the user's port. */
+ err = mach_port_request_notification (mach_task_self (), notify,
+ MACH_NOTIFY_DEAD_NAME, 0,
+ notify_port,
+ MACH_MSG_TYPE_MAKE_SEND_ONCE, &old);
+ if (err)
+ {
+ free (req);
+ mutex_unlock (&display->lock);
+ return err;
+ }
+ assert (old == MACH_PORT_NULL);
+
+ req->port = notify;
+ req->pending = 0;
+ req->next = display->filemod_reqs;
+ display->filemod_reqs = req;
+ mutex_unlock (&display->lock);
+ return 0;
+}
+
+/* Requires DISPLAY to be locked. */
+static void
+display_notice_filechange (display_t display)
+{
+ error_t err;
+ struct modreq *req = display->filemod_reqs_pending;
+ struct modreq **preq = &display->filemod_reqs;
+ mach_port_t notify_port = ports_get_right (display->notify_port);
+
+ while (req)
+ {
+ req->pending = 1;
+ req = req->next;
+ }
+
+ while (*preq)
+ {
+ req = *preq;
+
+ err = nowait_file_changed (req->port, 0, FILE_CHANGED_WRITE, -1, -1,
+ notify_port);
+ if (err)
+ {
+ /* Remove notify port. */
+ *preq = req->next;
+
+ if (err == MACH_SEND_WILL_NOTIFY)
+ {
+ req->next = display->filemod_reqs_pending;
+ display->filemod_reqs_pending = req;
+ }
+ else
+ {
+ mach_port_t old;
+
+ /* Cancel the dead-name notification. */
+ mach_port_request_notification (mach_task_self (), req->port,
+ MACH_NOTIFY_DEAD_NAME, 0,
+ MACH_PORT_NULL, 0, &old);
+ mach_port_deallocate (mach_task_self (), old);
+ mach_port_deallocate (mach_task_self (), req->port);
+ free (req);
+ }
+ }
+ else
+ preq = &req->next;
+ }
+}
+
+static void
+display_flush_filechange (display_t display, unsigned int type)
+{
+ struct cons_display *user = display->user;
+ cons_change_t *next = &user->changes._buffer[user->changes.written
+ % _CONS_CHANGES_LENGTH];
+ int notify = 0;
+ int bump_written = 0;
+
+ if (type & DISPLAY_CHANGE_MATRIX
+ && display->changes.which & DISPLAY_CHANGE_MATRIX)
+ {
+ notify = 1;
+ next->matrix.start = display->changes.start;
+ next->matrix.end = display->changes.end;
+ user->changes.written++;
+ next = &user->changes._buffer[user->changes.written
+ % _CONS_CHANGES_LENGTH];
+ display->changes.which &= ~DISPLAY_CHANGE_MATRIX;
+ }
+
+ memset (next, 0, sizeof (cons_change_t));
+ next->what.not_matrix = 1;
+
+ if (type & DISPLAY_CHANGE_CURSOR_POS
+ && display->changes.which & DISPLAY_CHANGE_CURSOR_POS
+ && (display->changes.cursor.col != user->cursor.col
+ || display->changes.cursor.row != user->cursor.row))
+ {
+ notify = 1;
+ next->what.cursor_pos = 1;
+ bump_written = 1;
+ display->changes.which &= ~DISPLAY_CHANGE_CURSOR_POS;
+ }
+
+ if (type & DISPLAY_CHANGE_CURSOR_STATUS
+ && display->changes.which & DISPLAY_CHANGE_CURSOR_STATUS
+ && display->changes.cursor.status != user->cursor.status)
+ {
+ notify = 1;
+ next->what.cursor_status = 1;
+ bump_written = 1;
+ display->changes.which &= ~DISPLAY_CHANGE_CURSOR_STATUS;
+ }
+
+ if (type & DISPLAY_CHANGE_SCREEN_CUR_LINE
+ && display->changes.which & DISPLAY_CHANGE_SCREEN_CUR_LINE
+ && display->changes.screen.cur_line != user->screen.cur_line)
+ {
+ notify = 1;
+ next->what.screen_cur_line = 1;
+ bump_written = 1;
+ display->changes.which &= ~DISPLAY_CHANGE_SCREEN_CUR_LINE;
+ }
+
+ if (type & DISPLAY_CHANGE_SCREEN_SCR_LINES
+ && display->changes.which & DISPLAY_CHANGE_SCREEN_SCR_LINES
+ && display->changes.screen.scr_lines != user->screen.scr_lines)
+ {
+ notify = 1;
+ next->what.screen_scr_lines = 1;
+ bump_written = 1;
+ display->changes.which &= ~DISPLAY_CHANGE_SCREEN_SCR_LINES;
+ }
+
+ if (type & DISPLAY_CHANGE_BELL_AUDIBLE
+ && display->changes.which & DISPLAY_CHANGE_BELL_AUDIBLE
+ && display->changes.bell_audible != user->bell.audible)
+ {
+ notify = 1;
+ next->what.bell_audible = 1;
+ bump_written = 1;
+ display->changes.which &= ~DISPLAY_CHANGE_BELL_AUDIBLE;
+ }
+
+ if (type & DISPLAY_CHANGE_BELL_VISIBLE
+ && display->changes.which & DISPLAY_CHANGE_BELL_VISIBLE
+ && display->changes.bell_visible != user->bell.visible)
+ {
+ notify = 1;
+ next->what.bell_visible = 1;
+ bump_written = 1;
+ display->changes.which &= ~DISPLAY_CHANGE_BELL_VISIBLE;
+ }
+
+ if (type & DISPLAY_CHANGE_FLAGS
+ && display->changes.which & DISPLAY_CHANGE_FLAGS
+ && display->changes.flags != user->flags)
+ {
+ notify = 1;
+ next->what.flags = 1;
+ bump_written = 1;
+ display->changes.which &= ~DISPLAY_CHANGE_FLAGS;
+ }
+
+ if (bump_written)
+ user->changes.written++;
+ if (notify)
+ display_notice_filechange (display);
+}
+
+/* Record a change in the matrix ringbuffer. */
+static void
+display_record_filechange (display_t display, off_t start, off_t end)
+{
+ if (!(display->changes.which & DISPLAY_CHANGE_MATRIX))
+ {
+ display->changes.start = start;
+ display->changes.end = end;
+ display->changes.which |= DISPLAY_CHANGE_MATRIX;
+ }
+ else
+ {
+ off_t size = display->user->screen.width * display->user->screen.lines;
+ off_t rotate = display->changes.start;
+ off_t old_end = display->changes.end;
+ int disjunct = 0;
+
+ /* First rotate the buffer to reduce the number of cases. */
+ old_end -= rotate;
+ if (old_end < 0)
+ old_end += size;
+ start -= rotate;
+ if (start < 0)
+ start += size;
+ end -= rotate;
+ if (end < 0)
+ end += size;
+
+ /* Now the old region starts at 0 and ends at OLD_END. Try to
+ merge in the new region if it overlaps or touches the old
+ one. */
+ if (start <= end)
+ {
+ if (start <= old_end + 1)
+ {
+ start = 0;
+ if (old_end > end)
+ end = old_end;
+ }
+ else
+ {
+ if (end == size - 1)
+ end = old_end;
+ else
+ disjunct = 1;
+ }
+ }
+ else
+ {
+ if (start <= old_end + 1)
+ {
+ start = 0;
+ end = size - 1;
+ }
+ else
+ {
+ if (old_end > end)
+ end = old_end;
+ }
+ }
+ /* Now reverse the rotation. */
+ start += rotate;
+ if (start >= size)
+ start -= size;
+ end += rotate;
+ if (end >= size)
+ end -= size;
+
+ if (disjunct)
+ {
+ /* The regions are disjunct, so we have to flush the old
+ changes. */
+ display_flush_filechange (display, DISPLAY_CHANGE_MATRIX);
+ display->changes.which |= DISPLAY_CHANGE_MATRIX;
+ }
+ display->changes.start = start;
+ display->changes.end = end;
+ }
+}
+
+
+static void
+conchar_memset (conchar_t *conchar, wchar_t chr, conchar_attr_t attr,
+ size_t size)
+{
+ int i;
+
+ for (i = 0; i < size; i++)
+ {
+ conchar->chr = chr;
+ conchar->attr = attr;
+ conchar++;
+ }
+}
+
+
+static error_t
+user_create (display_t display, uint32_t width, uint32_t height,
+ uint32_t lines, wchar_t chr, conchar_attr_t attr)
+{
+ error_t err;
+ struct cons_display *user;
+ int npages = (round_page (sizeof (struct cons_display) + sizeof (conchar_t)
+ * width * lines)) / vm_page_size;
+
+ err = user_pager_create (&display->user_pager, npages, &display->user);
+ if (err)
+ return err;
+
+ user = display->user;
+ user->magic = CONS_MAGIC;
+ user->version = CONS_VERSION_MAJ << CONS_VERSION_MAJ_SHIFT | CONS_VERSION_AGE;
+ user->changes.buffer = offsetof (struct cons_display, changes._buffer)
+ / sizeof (uint32_t);
+ user->changes.length = _CONS_CHANGES_LENGTH;
+ user->screen.width = width;
+ user->screen.height = height;
+ user->screen.lines = lines;
+ user->screen.cur_line = 0;
+ user->screen.scr_lines = 0;
+ user->screen.matrix = sizeof (struct cons_display) / sizeof (uint32_t);
+ user->cursor.col = 0;
+ user->cursor.row = 0;
+ user->cursor.status = CONS_CURSOR_NORMAL;
+ conchar_memset (user->_matrix, chr, attr,
+ user->screen.width * user->screen.lines);
+ return 0;
+}
+
+static void
+user_destroy (display_t display)
+{
+ user_pager_destroy (&display->user_pager, display->user);
+}
+
+
+static void
+screen_fill (display_t display, size_t col1, size_t row1, size_t col2,
+ size_t row2, wchar_t chr, conchar_attr_t attr)
+{
+ struct cons_display *user = display->user;
+ off_t start = ((user->screen.cur_line % user->screen.lines) + row1)
+ * user->screen.width + col1;
+ off_t end = ((user->screen.cur_line % user->screen.lines) + row2)
+ * user->screen.width + col2;
+ off_t size = user->screen.width * user->screen.lines;
+
+ if (start >= size && end >= size)
+ {
+ start -= size;
+ end -= size;
+ }
+
+ if (end < size)
+ {
+ conchar_memset (user->_matrix + start, chr, attr, end - start + 1);
+ display_record_filechange (display, start, end);
+ }
+ else
+ {
+ conchar_memset (user->_matrix + start, chr, attr, size - start);
+ conchar_memset (user->_matrix, chr, attr, end - size + 1);
+ display_record_filechange (display, start, end - size);
+ }
+}
+
+static void
+screen_shift_left (display_t display, size_t col1, size_t row1, size_t col2,
+ size_t row2, size_t shift, wchar_t chr, conchar_attr_t attr)
+{
+ struct cons_display *user = display->user;
+ off_t start = ((user->screen.cur_line % user->screen.lines) + row1)
+ * user->screen.width + col1;
+ off_t end = ((user->screen.cur_line % user->screen.lines) + row2)
+ * user->screen.width + col2;
+ off_t size = user->screen.width * user->screen.lines;
+
+ if (start >= size && end >= size)
+ {
+ start -= size;
+ end -= size;
+ }
+
+ if (start + shift <= end)
+ {
+ /* Use a loop to copy the data. Using wmemmove and wmemset on
+ the chunks is tiresome, as there are many cases. */
+ off_t src = start + shift;
+ off_t dst = start;
+
+ while (src <= end)
+ user->_matrix[dst++ % size] = user->_matrix[src++ % size];
+ while (dst <= end)
+ {
+ user->_matrix[dst % size].chr = chr;
+ user->_matrix[dst++ % size].attr = attr;
+ }
+
+ display_record_filechange (display, start, end);
+ }
+ else
+ screen_fill (display, col1, row1, col2, row2, chr, attr);
+}
+
+static void
+screen_shift_right (display_t display, size_t col1, size_t row1, size_t col2,
+ size_t row2, size_t shift,
+ wchar_t chr, conchar_attr_t attr)
+{
+ struct cons_display *user = display->user;
+ off_t start = ((user->screen.cur_line % user->screen.lines) + row1)
+ * user->screen.width + col1;
+ off_t end = ((user->screen.cur_line % user->screen.lines) + row2)
+ * user->screen.width + col2;
+ off_t size = user->screen.width * user->screen.lines;
+
+ if (start >= size && end >= size)
+ {
+ start -= size;
+ end -= size;
+ }
+
+ if (start + shift <= end)
+ {
+ /* Use a loop to copy the data. Using wmemmove and wmemset on
+ the chunks is tiresome, as there are many cases. */
+ off_t src = end - shift;
+ off_t dst = end;
+
+ while (src >= start)
+ user->_matrix[dst-- % size] = user->_matrix[src-- % size];
+ while (dst >= start)
+ {
+ user->_matrix[dst % size].chr = chr;
+ user->_matrix[dst-- % size].attr = attr;
+ }
+
+ display_record_filechange (display, start, end);
+ }
+ else
+ screen_fill (display, col1, row1, col2, row2, chr, attr);
+}
+
+
+static error_t
+output_init (output_t output, const char *encoding)
+{
+ condition_init (&output->resumed);
+ output->stopped = 0;
+ output->buffer = NULL;
+ output->allocated = 0;
+ output->size = 0;
+
+ /* WCHAR_T happens to be UCS-4 on the GNU system. */
+ output->cd = iconv_open ("WCHAR_T", encoding);
+ if (output->cd == (iconv_t) -1)
+ return errno;
+ return 0;
+}
+
+static void
+output_deinit (output_t output)
+{
+ iconv_close (output->cd);
+}
+
+
+static void
+handle_esc_bracket_hl (display_t display, int code, int flag)
+{
+ switch (code)
+ {
+ case 4: /* ECMA-48 <SMIR>, <RMIR>. */
+ /* Insert mode: <smir>, <rmir>. */
+ display->insert_mode = flag ? 1 : 0;
+ break;
+ case 34:
+ /* Cursor standout: <cnorm>, <cvvis>. */
+ if (flag)
+ display->user->cursor.status = CONS_CURSOR_NORMAL;
+ else
+ display->user->cursor.status = CONS_CURSOR_VERY_VISIBLE;
+ /* XXX Flag cursor status change. */
+ break;
+ }
+}
+
+static void
+handle_esc_bracket_m (attr_t attr, int code)
+{
+ switch (code)
+ {
+ case 0:
+ /* All attributes off: <sgr0>. */
+ attr->current = attr->attr_def;
+ attr->altchar = 0;
+ break;
+ case 1:
+ /* Bold on: <bold>. */
+ attr->current.intensity = CONS_ATTR_INTENSITY_BOLD;
+ break;
+ case 2:
+ /* Dim on: <dim>. */
+ attr->current.intensity = CONS_ATTR_INTENSITY_DIM;
+ break;
+ case 3:
+ /* Italic on: <sitm>. */
+ attr->current.italic = 1;
+ break;
+ case 4:
+ /* Underline on: <smul>. */
+ attr->current.underlined = 1;
+ break;
+ case 5:
+ /* (Slow) blink on: <blink>. */
+ attr->current.blinking = 1;
+ break;
+ case 7:
+ /* Reverse video on: <rev>, <smso>. */
+ attr->current.reversed = 1;
+ break;
+ case 8:
+ /* Concealed on: <invis>. */
+ attr->current.concealed = 1;
+ break;
+ case 10:
+ /* Alternate character set mode off: <rmacs>. */
+ attr->altchar = 0;
+ break;
+ case 11:
+ /* Alternate character set mode on: <smacs>. */
+ attr->altchar = 1;
+ break;
+ case 21:
+ /* Normal intensity (switch off bright). */
+ attr->current.intensity = CONS_ATTR_INTENSITY_NORMAL;
+ break;
+ case 22:
+ /* Normal intensity (switch off dim). */
+ attr->current.intensity = CONS_ATTR_INTENSITY_NORMAL;
+ break;
+ case 23:
+ /* Italic off: <ritm>. */
+ attr->current.italic = 0;
+ break;
+ case 24:
+ /* Underline off: <rmul>. */
+ attr->current.underlined = 0;
+ break;
+ case 25:
+ /* Blink off. */
+ attr->current.blinking = 0;
+ break;
+ case 27:
+ /* Reverse video off: <rmso>. */
+ attr->current.reversed = 0;
+ break;
+ case 28:
+ /* Concealed off. */
+ attr->current.concealed = 0;
+ break;
+ case 30 ... 37:
+ /* Set foreground color: <setaf>. */
+ attr->current.fgcol = code - 30;
+ break;
+ case 39:
+ /* Default foreground color; ANSI?. */
+ attr->current.fgcol = attr->attr_def.fgcol;
+ break;
+ case 40 ... 47:
+ /* Set background color: <setab>. */
+ attr->current.bgcol = code - 40;
+ break;
+ case 49:
+ /* Default background color; ANSI?. */
+ attr->current.bgcol = attr->attr_def.bgcol;
+ break;
+ }
+}
+
+static
+void limit_cursor (display_t display)
+{
+ struct cons_display *user = display->user;
+
+ if (user->cursor.col >= user->screen.width)
+ user->cursor.col = user->screen.width - 1;
+ else if (user->cursor.col < 0)
+ user->cursor.col = 0;
+
+ if (user->cursor.row >= user->screen.height)
+ user->cursor.row = user->screen.height - 1;
+ else if (user->cursor.row < 0)
+ user->cursor.row = 0;
+
+ /* XXX Flag cursor change. */
+}
+
+
+static void
+linefeed (display_t display)
+{
+ struct cons_display *user = display->user;
+
+ if (display->csr.top == 0 && display->csr.bottom >= user->screen.height - 1)
+ {
+ /* No scrolling region active, do the normal scrolling activity. */
+ if (user->cursor.row < user->screen.height - 1)
+ {
+ user->cursor.row++;
+ /* XXX Flag cursor update. */
+ }
+ else
+ {
+ user->screen.cur_line++;
+
+ screen_fill (display, 0, user->screen.height - 1,
+ user->screen.width - 1, user->screen.height - 1,
+ L' ', display->attr.current);
+ if (user->screen.scr_lines <
+ user->screen.lines - user->screen.height)
+ user->screen.scr_lines++;
+ /* XXX Flag current line change. */
+ /* XXX Possibly flag change of length of scroll back buffer. */
+ }
+ }
+ else
+ {
+ /* With an active scrolling region, never actually scroll. Just
+ shift the scrolling region if necessary. */
+ if (user->cursor.row != display->csr.bottom
+ && user->cursor.row < user->screen.height - 1)
+ {
+ user->cursor.row++;
+ /* XXX Flag cursor update. */
+ }
+ else if (user->cursor.row == display->csr.bottom)
+ screen_shift_left (display, 0, display->csr.top,
+ user->screen.width - 1, display->csr.bottom,
+ user->screen.width,
+ L' ', display->attr.current);
+ }
+}
+
+static void
+horizontal_tab (display_t display)
+{
+ struct cons_display *user = display->user;
+
+ user->cursor.col = (user->cursor.col | 7) + 1;
+ if (user->cursor.col >= user->screen.width)
+ {
+ user->cursor.col = 0;
+ linefeed (display);
+ }
+ /* XXX Flag cursor update. */
+}
+
+static void
+handle_esc_bracket (display_t display, char op)
+{
+ struct cons_display *user = display->user;
+ parse_t parse = &display->output.parse;
+ int i;
+
+ switch (op)
+ {
+ case 'H': /* ECMA-48 <CUP>. */
+ case 'f': /* ECMA-48 <HVP>. */
+ /* Cursor position: <cup>. */
+ user->cursor.col = (parse->params[1] ?: 1) - 1;
+ user->cursor.row = (parse->params[0] ?: 1) - 1;
+ limit_cursor (display);
+ break;
+ case 'G': /* ECMA-48 <CHA>. */
+ case '`': /* ECMA-48 <HPA>. */
+ case '\'': /* VT100. */
+ /* Horizontal cursor position: <hpa>. */
+ user->cursor.col = (parse->params[0] ?: 1) - 1;
+ limit_cursor (display);
+ break;
+ case 'a': /* ECMA-48 <HPR>. */
+ /* Horizontal cursor position relative. */
+ user->cursor.col += (parse->params[1] ?: 1) - 1;
+ limit_cursor (display);
+ break;
+ case 'd': /* ECMA-48 <VPA>. */
+ /* Vertical cursor position: <vpa>. */
+ user->cursor.row = (parse->params[0] ?: 1) - 1;
+ limit_cursor (display);
+ break;
+ case 'F': /* ECMA-48 <CPL>. */
+ /* Beginning of previous line. */
+ user->cursor.col = 0;
+ /* Fall through. */
+ case 'A': /* ECMA-48 <CUU>. */
+ case 'k': /* ECMA-48 <VPB>. */
+ /* Cursor up: <cuu>, <cuu1>. */
+ user->cursor.row -= (parse->params[0] ?: 1);
+ limit_cursor (display);
+ break;
+ case 'E': /* ECMA-48 <CNL>. */
+ /* Beginning of next line. */
+ user->cursor.col = 0;
+ /* Fall through. */
+ case 'B': /* ECMA-48 <CUD>. */
+ case 'e': /* ECMA-48 <VPR>. */
+ /* Cursor down: <cud1>, <cud>. */
+ user->cursor.row += (parse->params[0] ?: 1);
+ limit_cursor (display);
+ break;
+ case 'C': /* ECMA-48 <CUF>. */
+ /* Cursor right: <cuf1>, <cuf>. */
+ user->cursor.col += (parse->params[0] ?: 1);
+ limit_cursor (display);
+ break;
+ case 'D': /* ECMA-48 <CUB>. */
+ /* Cursor left: <cub>, <cub1>. */
+ user->cursor.col -= (parse->params[0] ?: 1);
+ limit_cursor (display);
+ break;
+ case 'l':
+ /* Reset mode. */
+ for (i = 0; i < parse->nparams; i++)
+ handle_esc_bracket_hl (display, parse->params[i], 0);
+ break;
+ case 'h':
+ /* Set mode. */
+ for (i = 0; i < parse->nparams; i++)
+ handle_esc_bracket_hl (display, parse->params[i], 1);
+ break;
+ case 'm': /* ECMA-48 <SGR>. */
+ for (i = 0; i < parse->nparams; i++)
+ handle_esc_bracket_m (&display->attr, parse->params[i]);
+ break;
+ case 'J': /* ECMA-48 <ED>. */
+ switch (parse->params[0])
+ {
+ case 0:
+ /* Clear to end of screen: <ed>. */
+ screen_fill (display, user->cursor.col, user->cursor.row,
+ user->screen.width - 1, user->screen.height - 1,
+ L' ', display->attr.current);
+ break;
+ case 1:
+ /* Clear to beginning of screen. */
+ screen_fill (display, 0, 0,
+ user->cursor.col, user->cursor.row,
+ L' ', display->attr.current);
+ break;
+ case 2:
+ /* Clear entire screen. */
+ screen_fill (display, 0, 0,
+ user->screen.width - 1, user->screen.height - 1,
+ L' ', display->attr.current);
+ break;
+ }
+ break;
+ case 'K': /* ECMA-48 <EL>. */
+ switch (parse->params[0])
+ {
+ case 0:
+ /* Clear to end of line: <el>. */
+ screen_fill (display, user->cursor.col, user->cursor.row,
+ user->screen.width - 1, user->cursor.row,
+ L' ', display->attr.current);
+ break;
+ case 1:
+ /* Clear to beginning of line: <el1>. */
+ screen_fill (display, 0, user->cursor.row,
+ user->cursor.col, user->cursor.row,
+ L' ', display->attr.current);
+ break;
+ case 2:
+ /* Clear entire line. */
+ screen_fill (display, 0, user->cursor.row,
+ user->screen.width - 1, user->cursor.row,
+ L' ', display->attr.current);
+ break;
+ }
+ break;
+ case 'L': /* ECMA-48 <IL>. */
+ /* Insert line(s): <il1>, <il>. */
+ screen_shift_right (display, 0, user->cursor.row,
+ user->screen.width - 1,
+ (user->cursor.row <= display->csr.bottom)
+ ? display->csr.bottom : user->screen.height - 1,
+ (parse->params[0] ?: 1) * user->screen.width,
+ L' ', display->attr.current);
+ break;
+ case 'M': /* ECMA-48 <DL>. */
+ /* Delete line(s): <dl1>, <dl>. */
+ screen_shift_left (display, 0, user->cursor.row,
+ user->screen.width - 1,
+ (user->cursor.row <= display->csr.bottom)
+ ? display->csr.bottom : user->screen.height - 1,
+ (parse->params[0] ?: 1) * user->screen.width,
+ L' ', display->attr.current);
+ break;
+ case '@': /* ECMA-48 <ICH>. */
+ /* Insert character(s): <ich1>, <ich>. */
+ screen_shift_right (display, user->cursor.col, user->cursor.row,
+ user->screen.width - 1, user->cursor.row,
+ parse->params[0] ?: 1,
+ L' ', display->attr.current);
+ break;
+ case 'P': /* ECMA-48 <DCH>. */
+ /* Delete character(s): <dch1>, <dch>. */
+ screen_shift_left (display, user->cursor.col, user->cursor.row,
+ user->screen.width - 1, user->cursor.row,
+ parse->params[0] ?: 1,
+ L' ', display->attr.current);
+ break;
+ case 'r': /* VT100: Set scrolling region. */
+ if (!parse->params[1])
+ {
+ display->csr.top = 0;
+ display->csr.bottom = user->screen.height - 1;
+ }
+ else
+ {
+ if (parse->params[1] <= user->screen.height
+ && parse->params[0] < parse->params[1])
+ {
+ display->csr.top = parse->params[0] ? parse->params[0] - 1 : 0;
+ display->csr.bottom = parse->params[1] - 1;
+ user->cursor.col = 0;
+ user->cursor.row = 0;
+ /* XXX Flag cursor change. */
+ }
+ }
+ break;
+ case 'S': /* ECMA-48 <SU>. */
+ /* Scroll up: <ind>, <indn>. */
+ screen_shift_left (display, 0, display->csr.top,
+ user->screen.width - 1, display->csr.bottom,
+ (parse->params[0] ?: 1) * user->screen.width,
+ L' ', display->attr.current);
+ break;
+ case 'T': /* ECMA-48 <SD>. */
+ /* Scroll down: <ri>, <rin>. */
+ screen_shift_right (display, 0, display->csr.top,
+ user->screen.width - 1, display->csr.bottom,
+ (parse->params[0] ?: 1) * user->screen.width,
+ L' ', display->attr.current);
+ break;
+ case 'X': /* ECMA-48 <ECH>. */
+ /* Erase character(s): <ech>. */
+ {
+ int col = user->cursor.col;
+ if (parse->params[0] - 1 > 0)
+ col += parse->params[0] - 1;
+ if (col > user->screen.width - 1)
+ col = user->screen.width - 1;
+
+ screen_fill (display, user->cursor.col, user->cursor.row,
+ col, user->cursor.row, L' ', display->attr.current);
+ }
+ break;
+ case 'I': /* ECMA-48 <CHT>. */
+ /* Horizontal tab. */
+ if (!parse->params[0])
+ parse->params[0] = 1;
+ while (parse->params[0]--)
+ horizontal_tab (display);
+ break;
+ case 'Z': /* ECMA-48 <CBT>. */
+ /* Cursor backward tabulation: <cbt>. */
+ if (parse->params[0] > user->screen.height * (user->screen.width / 8))
+ {
+ user->cursor.col = 0;
+ user->cursor.row = 0;
+ }
+ else
+ {
+ int i = parse->params[0] ?: 1;
+
+ while (i--)
+ {
+ if (user->cursor.col == 0)
+ {
+ if (user->cursor.row == 0)
+ break;
+ else
+ {
+ user->cursor.col = user->screen.width - 1;
+ user->cursor.row--;
+ }
+ }
+ else
+ user->cursor.col--;
+ user->cursor.col &= ~7;
+ }
+ }
+ }
+}
+
+
+static void
+handle_esc_bracket_question_hl (display_t display, int code, int flag)
+{
+ switch (code)
+ {
+ case 25:
+ /* Cursor invisibility: <civis>, <cnorm>. */
+ if (flag)
+ display->user->cursor.status = CONS_CURSOR_NORMAL;
+ else
+ display->user->cursor.status = CONS_CURSOR_INVISIBLE;
+ /* XXX Flag cursor status change. */
+ break;
+ case 1000:
+ /* XTerm mouse tracking. */
+ if (flag)
+ display->user->flags |= CONS_FLAGS_TRACK_MOUSE;
+ else
+ display->user->flags &= ~CONS_FLAGS_TRACK_MOUSE;
+ /* XXX Flag flags change. */
+ break;
+ }
+}
+
+
+static void
+handle_esc_bracket_question (display_t display, char op)
+{
+ parse_t parse = &display->output.parse;
+
+ int i;
+ switch (op)
+ {
+ case 'l':
+ /* Reset mode. */
+ for (i = 0; i < parse->nparams; ++i)
+ handle_esc_bracket_question_hl (display, parse->params[i], 0);
+ break;
+ case 'h':
+ /* Set mode. */
+ for (i = 0; i < parse->nparams; ++i)
+ handle_esc_bracket_question_hl (display, parse->params[i], 1);
+ break;
+ }
+}
+
+
+static void
+handle_esc_bracket_right_angle_hl (display_t display, int code, int flag)
+{
+ switch (code)
+ {
+ case 1:
+ /* Bold: <gsbom>, <grbom>. This is a GNU extension. */
+ if (flag)
+ display->attr.current.bold = 1;
+ else
+ display->attr.current.bold = 0;
+ break;
+ }
+}
+
+
+static void
+handle_esc_bracket_right_angle (display_t display, char op)
+{
+ parse_t parse = &display->output.parse;
+
+ int i;
+ switch (op)
+ {
+ case 'l':
+ /* Reset mode. */
+ for (i = 0; i < parse->nparams; ++i)
+ handle_esc_bracket_right_angle_hl (display, parse->params[i], 0);
+ break;
+ case 'h':
+ /* Set mode. */
+ for (i = 0; i < parse->nparams; ++i)
+ handle_esc_bracket_right_angle_hl (display, parse->params[i], 1);
+ break;
+ }
+}
+
+
+static wchar_t
+altchar_to_ucs4 (wchar_t chr)
+{
+ /* Alternative character set frobbing. */
+ switch (chr)
+ {
+ case L'+':
+ return CONS_CHAR_RARROW;
+ case L',':
+ return CONS_CHAR_LARROW;
+ case L'-':
+ return CONS_CHAR_UARROW;
+ case L'.':
+ return CONS_CHAR_DARROW;
+ case L'0':
+ return CONS_CHAR_BLOCK;
+ case L'I':
+ return CONS_CHAR_LANTERN;
+ case L'`':
+ return CONS_CHAR_DIAMOND;
+ case L'a':
+ return CONS_CHAR_CKBOARD;
+ case L'f':
+ return CONS_CHAR_DEGREE;
+ case L'g':
+ return CONS_CHAR_PLMINUS;
+ case L'h':
+ return CONS_CHAR_BOARD;
+ case L'j':
+ return CONS_CHAR_LRCORNER;
+ case L'k':
+ return CONS_CHAR_URCORNER;
+ case L'l':
+ return CONS_CHAR_ULCORNER;
+ case L'm':
+ return CONS_CHAR_LLCORNER;
+ case L'n':
+ return CONS_CHAR_PLUS;
+ case L'o':
+ return CONS_CHAR_S1;
+ case L'p':
+ return CONS_CHAR_S3;
+ case L'q':
+ return CONS_CHAR_HLINE;
+ case L'r':
+ return CONS_CHAR_S7;
+ case L's':
+ return CONS_CHAR_S9;
+ case L't':
+ return CONS_CHAR_LTEE;
+ case L'u':
+ return CONS_CHAR_RTEE;
+ case L'v':
+ return CONS_CHAR_BTEE;
+ case L'w':
+ return CONS_CHAR_TTEE;
+ case L'x':
+ return CONS_CHAR_VLINE;
+ case L'y':
+ return CONS_CHAR_LEQUAL;
+ case L'z':
+ return CONS_CHAR_GEQUAL;
+ case L'{':
+ return CONS_CHAR_PI;
+ case L'|':
+ return CONS_CHAR_NEQUAL;
+ case L'}':
+ return CONS_CHAR_STERLING;
+ case L'~':
+ return CONS_CHAR_BULLET;
+ default:
+ return chr;
+ }
+}
+
+/* Display must be locked. */
+static void
+display_output_one (display_t display, wchar_t chr)
+{
+ struct cons_display *user = display->user;
+ parse_t parse = &display->output.parse;
+
+ switch (parse->state)
+ {
+ case STATE_NORMAL:
+ switch (chr)
+ {
+ case L'\r':
+ /* Carriage return: <cr>. */
+ if (user->cursor.col)
+ {
+ user->cursor.col = 0;
+ /* XXX Flag cursor update. */
+ }
+ break;
+ case L'\n':
+ /* Line feed. */
+ linefeed (display);
+ break;
+ case L'\b':
+ /* Backspace. */
+ if (user->cursor.col > 0 || user->cursor.row > 0)
+ {
+ if (user->cursor.col > 0)
+ user->cursor.col--;
+ else
+ {
+ /* This implements the <bw> functionality. */
+ user->cursor.col = user->screen.width - 1;
+ user->cursor.row--;
+ }
+ /* XXX Flag cursor update. */
+ }
+ break;
+ case L'\t':
+ /* Horizontal tab: <ht> */
+ horizontal_tab (display);
+ break;
+ case L'\033':
+ parse->state = STATE_ESC;
+ break;
+ case L'\0':
+ /* Padding character: <pad>. */
+ break;
+ case L'\a':
+ /* Audible bell. */
+ user->bell.audible++;
+ break;
+ default:
+ {
+ int line;
+ int idx;
+
+ if (user->cursor.col >= user->screen.width)
+ {
+ user->cursor.col = 0;
+ linefeed (display);
+ }
+
+ line = (user->screen.cur_line + user->cursor.row)
+ % user->screen.lines;
+ idx = line * user->screen.width + user->cursor.col;
+ int width, i;
+
+ width = wcwidth (chr);
+ if (width < 0)
+ width = 1;
+
+ if (display->insert_mode
+ && user->cursor.col < user->screen.width - width)
+ {
+ /* If in insert mode, do the same as <ich1>. */
+ screen_shift_right (display, user->cursor.col,
+ user->cursor.row,
+ user->screen.width - 1, user->cursor.row,
+ width, L' ', display->attr.current);
+ }
+
+ if (display->attr.altchar)
+ chr = altchar_to_ucs4 (chr);
+
+ for (i = 0; i < width; i++)
+ {
+ if (user->cursor.col >= user->screen.width)
+ break;
+ user->_matrix[idx+i].chr = chr;
+ user->_matrix[idx+i].attr = display->attr.current;
+ user->cursor.col++;
+ chr |= CONS_WCHAR_CONTINUED;
+ }
+
+ if (i > 0)
+ display_record_filechange (display, idx, idx + i - 1);
+
+ if (user->cursor.col > user->screen.width)
+ {
+ user->cursor.col = 0;
+ linefeed (display);
+ }
+ }
+ break;
+ }
+ break;
+
+ case STATE_ESC:
+ parse->state = STATE_NORMAL;
+ switch (chr)
+ {
+ case L'[':
+ parse->state = STATE_ESC_BRACKET_INIT;
+ break;
+ case L'M': /* ECMA-48 <RIS>. */
+ /* Reset: <rs2>. */
+ display->attr.current = display->attr.attr_def;
+ display->attr.altchar = 0;
+ display->insert_mode = 0;
+ display->csr.top = 0;
+ display->csr.bottom = user->screen.height - 1;
+ user->cursor.status = CONS_CURSOR_NORMAL;
+ /* Fall through. */
+ case L'c':
+ /* Clear screen and home cursor: <clear>. */
+ screen_fill (display, 0, 0,
+ user->screen.width - 1, user->screen.height - 1,
+ L' ', display->attr.current);
+ user->cursor.col = user->cursor.row = 0;
+ /* XXX Flag cursor change. */
+ break;
+ case L'E': /* ECMA-48 <NEL>. */
+ /* Newline. */
+ user->cursor.col = 0;
+ linefeed (display);
+ break;
+ case L'7': /* VT100: Save cursor and attributes. */
+ /* Save cursor position: <sc>. */
+ display->cursor.saved_x = user->cursor.col;
+ display->cursor.saved_y = user->cursor.row;
+ break;
+ case L'8': /* VT100: Restore cursor and attributes. */
+ /* Restore cursor position: <rc>. */
+ user->cursor.col = display->cursor.saved_x;
+ user->cursor.row = display->cursor.saved_y;
+ /* In case the screen was larger before: */
+ limit_cursor (display);
+ break;
+ case L'g':
+ /* Visible bell. */
+ user->bell.visible++;
+ break;
+ default:
+ /* Unsupported escape sequence. */
+ break;
+ }
+ break;
+
+ case STATE_ESC_BRACKET_INIT:
+ memset (&parse->params, 0, sizeof parse->params);
+ parse->nparams = 0;
+ if (chr == '?')
+ {
+ parse->state = STATE_ESC_BRACKET_QUESTION;
+ break; /* Consume the question mark. */
+ }
+ else if (chr == '>')
+ {
+ parse->state = STATE_ESC_BRACKET_RIGHT_ANGLE;
+ break; /* Consume the right angle. */
+ }
+ else
+ parse->state = STATE_ESC_BRACKET;
+ /* Fall through. */
+ case STATE_ESC_BRACKET:
+ case STATE_ESC_BRACKET_QUESTION:
+ case STATE_ESC_BRACKET_RIGHT_ANGLE:
+ if (chr >= '0' && chr <= '9')
+ parse->params[parse->nparams]
+ = parse->params[parse->nparams]*10 + chr - '0';
+ else if (chr == ';')
+ {
+ if (++(parse->nparams) >= PARSE_MAX_PARAMS)
+ parse->state = STATE_NORMAL; /* too many */
+ }
+ else
+ {
+ parse->nparams++;
+ if (parse->state == STATE_ESC_BRACKET)
+ handle_esc_bracket (display, chr);
+ else if (parse->state == STATE_ESC_BRACKET_RIGHT_ANGLE)
+ handle_esc_bracket_right_angle (display, chr);
+ else
+ handle_esc_bracket_question (display, chr);
+ parse->state = STATE_NORMAL;
+ }
+ break;
+ default:
+ abort ();
+ }
+}
+
+/* Output LENGTH bytes starting from BUFFER in the system encoding.
+ Set BUFFER and LENGTH to the new values. The exact semantics are
+ just as in the iconv interface. */
+static error_t
+display_output_some (display_t display, char **buffer, size_t *length)
+{
+#define CONV_OUTBUF_SIZE 256
+ error_t err = 0;
+
+ display->changes.cursor.col = display->user->cursor.col;
+ display->changes.cursor.row = display->user->cursor.row;
+ display->changes.cursor.status = display->user->cursor.status;
+ display->changes.screen.cur_line = display->user->screen.cur_line;
+ display->changes.screen.scr_lines = display->user->screen.scr_lines;
+ display->changes.bell_audible = display->user->bell.audible;
+ display->changes.bell_visible = display->user->bell.visible;
+ display->changes.flags = display->user->flags;
+ display->changes.which = ~DISPLAY_CHANGE_MATRIX;
+
+ while (!err && *length > 0)
+ {
+ size_t nconv;
+ wchar_t outbuf[CONV_OUTBUF_SIZE];
+ char *outptr = (char *) outbuf;
+ size_t outsize = CONV_OUTBUF_SIZE * sizeof (wchar_t);
+ error_t saved_err;
+ int i;
+
+ nconv = iconv (display->output.cd, buffer, length, &outptr, &outsize);
+ saved_err = errno;
+
+ /* First process all successfully converted characters. */
+ for (i = 0; i < CONV_OUTBUF_SIZE - outsize / sizeof (wchar_t); i++)
+ display_output_one (display, outbuf[i]);
+
+ if (nconv == (size_t) -1)
+ {
+ /* Conversion is not completed, look for recoverable
+ errors. */
+#define UNICODE_REPLACEMENT_CHARACTER ((wchar_t) 0xfffd)
+ if (saved_err == EILSEQ)
+ {
+ assert (*length);
+ (*length)--;
+ (*buffer)++;
+ display_output_one (display, UNICODE_REPLACEMENT_CHARACTER);
+ }
+ else if (saved_err == EINVAL)
+ /* This is only an unfinished byte sequence at the end of
+ the input buffer. */
+ break;
+ else if (saved_err != E2BIG)
+ err = saved_err;
+ }
+ }
+
+ display_flush_filechange (display, ~0);
+ return err;
+}
+
+
+/* Forward declaration. */
+void display_destroy_complete (void *pi);
+
+void
+display_init (void)
+{
+ user_pager_init ();
+
+ /* Create the notify bucket, and start to serve notifications. */
+ notify_bucket = ports_create_bucket ();
+ if (! notify_bucket)
+ error (5, errno, "Cannot create notify bucket");
+ notify_class = ports_create_class (display_destroy_complete, NULL);
+ if (! notify_class)
+ error (5, errno, "Cannot create notify class");
+
+ cthread_detach (cthread_fork ((cthread_fn_t) service_notifications,
+ (any_t) notify_bucket));
+}
+
+
+/* Create a new virtual console display, with the system encoding
+ being ENCODING. */
+error_t
+display_create (display_t *r_display, const char *encoding,
+ conchar_attr_t def_attr, unsigned int lines,
+ unsigned int width, unsigned int height)
+{
+ error_t err = 0;
+ display_t display;
+
+ *r_display = NULL;
+ display = calloc (1, sizeof *display);
+ if (!display)
+ return ENOMEM;
+
+ err = ports_create_port (notify_class, notify_bucket, sizeof (struct notify),
+ &display->notify_port);
+ if (err)
+ {
+ free (display);
+ return err;
+ }
+ display->notify_port->display = display;
+
+ mutex_init (&display->lock);
+ display->attr.attr_def = def_attr;
+ display->attr.current = display->attr.attr_def;
+ display->csr.bottom = height - 1;
+
+ err = user_create (display, width, height, lines, L' ',
+ display->attr.current);
+ if (err)
+ {
+ ports_destroy_right (display->notify_port);
+ free (display);
+ return err;
+ }
+
+ err = output_init (&display->output, encoding);
+ if (err)
+ {
+ user_destroy (display);
+ ports_destroy_right (display->notify_port);
+ free (display);
+ }
+ *r_display = display;
+ return err;
+}
+
+
+/* Destroy the display DISPLAY. */
+void
+display_destroy (display_t display)
+{
+ mutex_lock (&display->lock);
+ if (display->filemod_reqs_pending)
+ {
+ free_modreqs (display->filemod_reqs_pending);
+ display->filemod_reqs_pending = NULL;
+ }
+ if (display->filemod_reqs)
+ {
+ free_modreqs (display->filemod_reqs);
+ display->filemod_reqs = NULL;
+ }
+ ports_destroy_right (display->notify_port);
+ output_deinit (&display->output);
+ user_destroy (display);
+ mutex_unlock (&display->lock);
+
+ /* We can not free the display structure here, because it might
+ still be needed by pending modification requests when msg
+ accepted notifications are handled. So we have to wait until all
+ notifications have arrived and the notify port is completely
+ deallocated, which will invoke display_destroy_complete
+ below. */
+}
+
+
+/* Complete destruction of the display DISPLAY. */
+void
+display_destroy_complete (void *pi)
+{
+ struct display *display = ((struct notify *) pi)->display;
+ free (display);
+}
+
+
+/* Return the dimension of the display in bytes. */
+off_t
+display_get_size (display_t display)
+{
+ return sizeof (struct cons_display)
+ + (sizeof (conchar_t) * display->user->screen.width
+ * display->user->screen.lines);
+}
+
+
+/* Return the dimensions of the display DISPLAY in *WINSIZE. */
+void
+display_getsize (display_t display, struct winsize *winsize)
+{
+ mutex_lock (&display->lock);
+ winsize->ws_row = display->user->screen.height;
+ winsize->ws_col = display->user->screen.width;
+ winsize->ws_xpixel = 0;
+ winsize->ws_ypixel = 0;
+ mutex_unlock (&display->lock);
+}
+
+
+/* Set the owner of the display DISPLAY to PID. The owner receives
+ the SIGWINCH signal when the terminal size changes. */
+error_t
+display_set_owner (display_t display, pid_t pid)
+{
+ mutex_lock (&display->lock);
+ display->has_owner = 1;
+ display->owner_id = pid;
+ mutex_unlock (&display->lock);
+ return 0;
+}
+
+
+/* Return the owner of the display DISPLAY in PID. If there is no
+ owner, return ENOTTY. */
+error_t
+display_get_owner (display_t display, pid_t *pid)
+{
+ error_t err = 0;
+ mutex_lock (&display->lock);
+ if (!display->has_owner)
+ err = ENOTTY;
+ else
+ *pid = display->owner_id;
+ mutex_unlock (&display->lock);
+ return err;
+}
+
+/* Output DATALEN characters from the buffer DATA on display DISPLAY.
+ The DATA must be supplied in the system encoding configured for
+ DISPLAY. The function returns the amount of bytes written (might
+ be smaller than DATALEN) or -1 and the error number in errno. If
+ NONBLOCK is not zero, return with -1 and set errno to EWOULDBLOCK
+ if operation would block for a long time. */
+ssize_t
+display_output (display_t display, int nonblock, char *data, size_t datalen)
+{
+ output_t output = &display->output;
+ error_t err;
+ char *buffer;
+ size_t buffer_size;
+ ssize_t amount;
+
+ error_t ensure_output_buffer_size (size_t new_size)
+ {
+ /* Must be a power of two. */
+#define OUTPUT_ALLOCSIZE 32
+
+ if (output->allocated < new_size)
+ {
+ char *new_buffer;
+ new_size = (new_size + OUTPUT_ALLOCSIZE - 1)
+ & ~(OUTPUT_ALLOCSIZE - 1);
+ new_buffer = realloc (output->buffer, new_size);
+ if (!new_buffer)
+ return ENOMEM;
+ output->buffer = new_buffer;
+ output->allocated = new_size;
+ }
+ return 0;
+ }
+
+ mutex_lock (&display->lock);
+ while (output->stopped)
+ {
+ if (nonblock)
+ {
+ mutex_unlock (&display->lock);
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ if (hurd_condition_wait (&output->resumed, &display->lock))
+ {
+ mutex_unlock (&display->lock);
+ errno = EINTR;
+ return -1;
+ }
+ }
+
+ if (output->size)
+ {
+ err = ensure_output_buffer_size (output->size + datalen);
+ if (err)
+ {
+ mutex_unlock (&display->lock);
+ errno = ENOMEM;
+ return -1;
+ }
+ buffer = output->buffer;
+ buffer_size = output->size;
+ memcpy (buffer + buffer_size, data, datalen);
+ buffer_size += datalen;
+ }
+ else
+ {
+ buffer = data;
+ buffer_size = datalen;
+ }
+ amount = buffer_size;
+ err = display_output_some (display, &buffer, &buffer_size);
+ amount -= buffer_size;
+
+ if (err && !amount)
+ {
+ mutex_unlock (&display->lock);
+ errno = err;
+ return err;
+ }
+ if (buffer_size)
+ {
+ /* If we used the caller's buffer DATA, the remaining bytes
+ might not fit in our internal output buffer. In this case we
+ can reallocate the buffer in VCONS without needing to update
+ OUTPUT (as it points into DATA). */
+ err = ensure_output_buffer_size (buffer_size);
+ if (err)
+ {
+ mutex_unlock (&display->lock);
+ return err;
+ }
+ memmove (output->buffer, buffer, buffer_size);
+ }
+ output->size = buffer_size;
+ amount += buffer_size;
+
+ mutex_unlock (&display->lock);
+ return amount;
+}
+
+
+ssize_t
+display_read (display_t display, int nonblock, off_t off,
+ char *data, size_t len)
+{
+ mutex_lock (&display->lock);
+ memcpy (data, ((char *) display->user) + off, len);
+ mutex_unlock (&display->lock);
+ return len;
+}
+
+
+/* Resume the output on the display DISPLAY. */
+void
+display_start_output (display_t display)
+{
+ mutex_lock (&display->lock);
+ if (display->output.stopped)
+ {
+ display->output.stopped = 0;
+ condition_broadcast (&display->output.resumed);
+ }
+ display->changes.flags = display->user->flags;
+ display->changes.which = DISPLAY_CHANGE_FLAGS;
+ display->user->flags &= ~CONS_FLAGS_SCROLL_LOCK;
+ display_flush_filechange (display, DISPLAY_CHANGE_FLAGS);
+ mutex_unlock (&display->lock);
+}
+
+
+/* Stop all output on the display DISPLAY. */
+void
+display_stop_output (display_t display)
+{
+ mutex_lock (&display->lock);
+ display->output.stopped = 1;
+ display->changes.flags = display->user->flags;
+ display->changes.which = DISPLAY_CHANGE_FLAGS;
+ display->user->flags |= CONS_FLAGS_SCROLL_LOCK;
+ display_flush_filechange (display, DISPLAY_CHANGE_FLAGS);
+ mutex_unlock (&display->lock);
+}
+
+
+/* Return the number of pending output bytes for DISPLAY. */
+size_t
+display_pending_output (display_t display)
+{
+ int output_size;
+ mutex_lock (&display->lock);
+ output_size = display->output.size;
+ mutex_unlock (&display->lock);
+ return output_size;
+}
+
+
+/* Flush the output buffer, discarding all pending data. */
+void
+display_discard_output (display_t display)
+{
+ mutex_lock (&display->lock);
+ display->output.size = 0;
+ mutex_unlock (&display->lock);
+}
+
+
+mach_port_t
+display_get_filemap (display_t display, vm_prot_t prot)
+{
+ mach_port_t memobj;
+ mutex_lock (&display->lock);
+ memobj = user_pager_get_filemap (&display->user_pager, prot);
+ mutex_unlock (&display->lock);
+ return memobj;
+}
diff --git a/console/display.h b/console/display.h
new file mode 100644
index 00000000..d64cf69e
--- /dev/null
+++ b/console/display.h
@@ -0,0 +1,84 @@
+/* display.h - Interface to the display component of a virtual console.
+ Copyright (C) 2002, 2003 Free Software Foundation, Inc.
+ Written by Marcus Brinkmann.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#ifndef DISPLAY_H
+#define DISPLAY_H
+
+#include <sys/ioctl.h>
+
+struct display;
+typedef struct display *display_t;
+
+void display_init (void);
+
+/* Create a new virtual console display, with the system encoding
+ being ENCODING and the default colors being FOREGROUND and BACKGROUND. */
+error_t
+display_create (display_t *r_display, const char *encoding,
+ conchar_attr_t def_attr, unsigned int lines,
+ unsigned int width, unsigned int height);
+
+
+/* Destroy the display DISPLAY. */
+void display_destroy (display_t display);
+
+/* Return the dimension of the display in bytes. */
+off_t display_get_size (display_t display);
+
+/* Return the dimensions of the display DISPLAY in *WINSIZE. */
+void display_getsize (display_t display, struct winsize *winsize);
+
+/* Set the owner of the display DISPLAY to PID. The owner receives
+ the SIGWINCH signal when the terminal size changes. */
+error_t display_set_owner (display_t display, pid_t pid);
+
+/* Return the owner of the display DISPLAY in PID. If there is no
+ owner, return ENOTTY. */
+error_t display_get_owner (display_t display, pid_t *pid);
+
+/* Output DATALEN characters from the buffer DATA on display DISPLAY.
+ The DATA must be supplied in the system encoding configured for
+ DISPLAY. The function returns the amount of bytes written (might
+ be smaller than DATALEN) or -1 and the error number in errno. If
+ NONBLOCK is not zero, return with -1 and set errno to EWOULDBLOCK
+ if operation would block for a long time. */
+ssize_t display_output (display_t display, int nonblock, char *data,
+ size_t datalen);
+
+mach_port_t display_get_filemap (display_t display, vm_prot_t prot);
+
+ssize_t display_read (display_t display, int nonblock, off_t off,
+ char *data, size_t len);
+
+error_t display_notice_changes (display_t display, mach_port_t notify);
+
+/* Resume the output on the display DISPLAY. */
+void display_start_output (display_t display);
+
+/* Stop all output on the display DISPLAY. */
+void display_stop_output (display_t display);
+
+/* Return the number of pending output bytes for DISPLAY. */
+size_t display_pending_output (display_t display);
+
+/* Flush the output buffer, discarding all pending data. */
+void display_discard_output (display_t display);
+
+#endif /* DISPLAY_H */
diff --git a/console/hurd.ti b/console/hurd.ti
new file mode 100644
index 00000000..2508482a
--- /dev/null
+++ b/console/hurd.ti
@@ -0,0 +1,164 @@
+hurd|The GNU Hurd console server,
+# Over-all properties.
+# We use 8-bit characters
+ km,
+# Although we don't do XON/XOFF, we don't want padding characters.
+ xon,
+# Hard reset.
+ rs1=\EM,
+
+# Note about compatibility to vt100: We don't specify <xenl>, as we
+# don't have the eat_newline_glitch. We don't support setting or
+# removing tab stops (hts/tbc).
+
+# Cursor related capabilities.
+
+# Moving the cursor.
+# We have automatic margins.
+ am,
+# We wrap around the left edge.
+ bw,
+# Carriage return and newline.
+ cr=^M, nel=^M^J,
+# Move cursor to home position (to position P1, P2).
+ home=\E[H, cup=\E[%i%p1%d;%p2%dH,
+# Move cursor one character (P1 characters) backwards.
+# We use ^H instead \E[D for cub1, as only ^H implements <bw> and it
+# is one byte instead three.
+ cub1=^H, cub=\E[%p1%dD,
+# Move cursor one line (P1 lines) downwards.
+ cud1=\E[B, cud=\E[%p1%dB,
+# Move cursor one character (P1 characters) forwards.
+ cuf1=\E[C, cuf=\E[%p1%dC,
+# Move cursor one line (P1 lines) upwards.
+ cuu1=\E[A, cuu=\E[%p1%dA,
+# Set horizontal (vertical) cursor position to P1.
+ hpa=\E[%i%p1%dG, vpa=\E[%i%p1%dd,
+# Save (restore) cursor position.
+ sc=\E7, rc=\E8,
+# Set the scrolling region to lines P1 to P2.
+ csr=\E[%i%p1%d;%p2%dr,
+
+# Modifying cursor attributes.
+# Make cursor invisible, very visible or normal.
+ civis=\E[?25l, cvvis=\E[34l, cnorm=\E[?25h,
+
+# Tabulator stops.
+# We have tabulator stops every eight rows.
+ it#8,
+# Move cursor to next tabulator stop.
+ ht=^I,
+# Move cursor to previous tabulator stop.
+ cbt=\E[Z,
+# XXX When we implement this.
+# Set tab stop in the current column of every row.
+# hts=\EH,
+# Delete all tab stops.
+# tbc=\E[3g,
+
+
+# Screen editing capabilities.
+# Clear screen.
+ clear=\Ec,
+# Clear to end of screen.
+ ed=\E[J,
+# Clear to end (beginning) of line.
+ el=\E[K, el1=\E[1K,
+
+# Insert one character (P1 characters).
+# <ich1> not included because we have insert mode.
+# ich1=\E[@,
+ ich=\E[%p1%d@,
+# Enter (leave) insert mode.
+ smir=\E[4h, rmir=\E[4l,
+# It is save to move when in insert mode.
+ mir,
+# Delete one character (P1 characters).
+ dch1=\E[P, dch=\E[%p1%dP,
+# Erase the next N characters.
+ ech=\E[%p1%dX,
+# Insert one line (P1 lines).
+ il1=\E[L, il=\E[%p1%dL,
+# Delete one line (P1 lines).
+ dl1=\E[M, dl=\E[%p1%dM,
+# Scroll the whole screen one line (P1 lines) upwards. We don't use
+# ^J, because this could put things into the scrollback buffer.
+ ind=\E[S, indn=\E[%p1%dS,
+# Scroll the whole screen one line (P1 lines) downwards.
+ rin=\E[%p1%dT, ri=\E[T,
+
+
+# Bell capabilities.
+# Audible bell.
+ bel=^G,
+# Flash the screen (visible bell).
+ flash=\Eg,
+
+
+# Keycodes for special keys.
+# Backspace key.
+ kbs=\177,
+# Keycode for left, down, right and up arrow key.
+ kcub1=\EOD, kcud1=\EOB, kcuf1=\EOC, kcuu1=\EOA,
+# Keycodes for function keys.
+ kf1=\EOP, kf2=\EOQ, kf3=\EOR, kf4=\EOS, kf5=\E[15~,
+ kf6=\E[17~, kf7=\E[18~, kf8=\E[19~, kf9=\E[20~,
+ kf10=\E[21~, kf11=\E[23~, kf12=\E[24~, kf13=\E[25~,
+ kf14=\E[26~, kf15=\E[28~, kf16=\E[29~, kf17=\E[31~,
+ kf18=\E[32~, kf19=\E[33~, kf20=\E[34~,
+# Keycode for backtab key.
+ kcbt=\E[Z,
+# Keycode for suspend key.
+ kspd=^Z,
+# Keycode for home (insert, delete, end) key.
+ khome=\E[1~, kich1=\E[2~, kdch1=\E[3~, kend=\E[4~,
+# Keycode for previous (next) page key.
+ kpp=\E[5~, knp=\E[6~,
+# Keycode for center of keypad area.
+ kb2=\E[G,
+# Mouse event has occurred.
+ kmous=\E[M,
+
+# Text attribute capabilities.
+ acsc=++\,\,--..00ii``aaffgghhjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
+
+# Color support.
+# We erase the screen with the current background color.
+ bce,
+# Number of colors and color pairs at the same time.
+ colors#8,
+ pairs#64,
+# Video attributes colliding with color.
+# ORed: A_STANDOUT 1, A_UNDERLINE 2, A_REVERSE 4, A_BLINK 8, A_DIM 16,
+# A_BOLD 32, A_INVIS 64
+# We don't define this as we do our own display optimization,
+# depending on the display driver. Alternatively, we could provide
+# different terminfo entries.
+# ncv#18,
+# Set background (foreground) color.
+ setab=\E[4%p1%dm, setaf=\E[3%p1%dm,
+# Set default color pair to its original value.
+ op=\E[39;49m,
+
+# Video attributes.
+# Overstrikes are erasable with a blank.
+ eo,
+# It is save to move when in standout mode.
+ msgr,
+# Enable dim (blinking, bold, invisible, reverse) attribute.
+ dim=\E[2m, blink=\E[5m, bold=\E[1m, invis=\E[8m, rev=\E[7m,
+# Enable (disable) standout mode.
+ smso=\E[7m, rmso=\E[27m,
+# Enable (disable) underline mode.
+ smul=\E[4m, rmul=\E[24m,
+# Enable (disable) italic mode.
+ sitm=\E[3m, ritm=\E[23m,
+# Enable (disable) real bold (not intensity bright) mode. This is a
+# GNU extension.
+ gsbom=\E[>1h, grbom=\E[>1l,
+# Enable (disable) alternative character set.
+ smacs=\E[11m, rmacs=\E[10m,
+# Set all attributes.
+ sgr=\E[0%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p5%t;2%;%?%p6%t;1%;%?%p7%t;8%;%?%p9%t;11%;m,
+# Reset all attributes.
+ sgr0=\E[0m,
diff --git a/console/input.c b/console/input.c
new file mode 100644
index 00000000..14879d07
--- /dev/null
+++ b/console/input.c
@@ -0,0 +1,277 @@
+/* input.c - Input component of a virtual console.
+ Copyright (C) 2002 Free Software Foundation, Inc.
+ Written by Marcus Brinkmann.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#include <iconv.h>
+#include <error.h>
+#include <string.h>
+#include <errno.h>
+#include <malloc.h>
+#include <sys/types.h>
+
+#include <cthreads.h>
+
+#include "input.h"
+
+struct input
+{
+ struct mutex lock;
+
+ struct condition data_available;
+ struct condition space_available;
+#define INPUT_QUEUE_SIZE 300
+ char buffer[INPUT_QUEUE_SIZE];
+ int full;
+ size_t size;
+
+ /* The state of the conversion of input characters. */
+ iconv_t cd;
+ /* The conversion routine might refuse to handle some incomplete
+ multi-byte or composed character at the end of the buffer, so we
+ have to keep them around. */
+ char *cd_buffer;
+ size_t cd_size;
+ size_t cd_allocated;
+};
+
+/* Create a new virtual console input queue, with the system encoding
+ being ENCODING. */
+error_t input_create (input_t *r_input, const char *encoding)
+{
+ input_t input = calloc (1, sizeof *input);
+ if (!input)
+ return ENOMEM;
+
+ mutex_init (&input->lock);
+ condition_init (&input->data_available);
+ condition_init (&input->space_available);
+
+ input->cd = iconv_open (encoding, "UTF-8");
+ if (input->cd == (iconv_t) -1)
+ {
+ free (input);
+ return errno;
+ }
+
+ *r_input = input;
+ return 0;
+}
+
+/* Destroy the input queue INPUT. */
+void input_destroy (input_t input)
+{
+ iconv_close (input->cd);
+ free (input);
+}
+
+/* Enter DATALEN characters from the buffer DATA into the input queue
+ INPUT. The DATA must be supplied in UTF-8 encoding (XXX UCS-4
+ would be nice, too, but it requires knowledge of endianess). The
+ function returns the amount of bytes written (might be smaller than
+ DATALEN) or -1 and the error number in errno. If NONBLOCK is not
+ zero, return with -1 and set errno to EWOULDBLOCK if operation
+ would block for a long time. */
+ssize_t input_enqueue (input_t input, int nonblock, char *data,
+ size_t datalen)
+{
+ error_t err = 0;
+ int was_empty;
+ int enqueued = 0;
+ char *buffer;
+ size_t buffer_size;
+ ssize_t amount;
+ size_t nconv;
+ char *outbuf;
+ size_t outbuf_size;
+
+ error_t ensure_cd_buffer_size (size_t new_size)
+ {
+ /* Must be a power of two. */
+#define CD_ALLOCSIZE 32
+
+ if (input->cd_allocated < new_size)
+ {
+ char *new_buffer;
+ new_size = (new_size + CD_ALLOCSIZE - 1)
+ & ~(CD_ALLOCSIZE - 1);
+ new_buffer = realloc (input->cd_buffer, new_size);
+ if (!new_buffer)
+ return ENOMEM;
+ input->cd_buffer = new_buffer;
+ input->cd_allocated = new_size;
+ }
+ return 0;
+ }
+
+ mutex_lock (&input->lock);
+ was_empty = !input->size;
+
+ while (datalen)
+ {
+ /* Make sure we are ready for writing (or at least can make a
+ honest attempt at it). */
+ while (input->full)
+ {
+ if (nonblock)
+ {
+ err = EWOULDBLOCK;
+ goto out;
+ }
+ if (hurd_condition_wait (&input->space_available, &input->lock))
+ {
+ err = EINTR;
+ goto out;
+ }
+ was_empty = !input->size;
+ }
+
+ /* Prepare the input buffer for iconv. */
+ if (input->cd_size)
+ {
+ err = ensure_cd_buffer_size (input->cd_size + datalen);
+ if (err)
+ goto out;
+ buffer = input->cd_buffer;
+ buffer_size = input->cd_size;
+ memcpy (buffer + buffer_size, data, datalen);
+ buffer_size += datalen;
+ }
+ else
+ {
+ buffer = data;
+ buffer_size = datalen;
+ }
+ /* Prepare output buffer for iconv. */
+ outbuf = &input->buffer[input->size];
+ outbuf_size = INPUT_QUEUE_SIZE - input->size;
+
+ amount = buffer_size;
+ nconv = iconv (input->cd, &buffer, &buffer_size, &outbuf, &outbuf_size);
+ amount -= buffer_size;
+
+ /* Calculate buffer progress. */
+ enqueued += amount;
+ data = buffer;
+ datalen = buffer_size;
+ input->size = INPUT_QUEUE_SIZE - outbuf_size;
+
+ if (nconv == (size_t) -1)
+ {
+ if (errno == E2BIG)
+ {
+ /* There is not enough space for more data in the outbuf
+ buffer. Mark the buffer as full, awake waiting
+ readers and go to sleep (above). */
+ input->full = 1;
+ if (was_empty)
+ condition_broadcast (&input->data_available);
+ /* Prevent calling condition_broadcast again if nonblock. */
+ was_empty = 0;
+ }
+ else
+ break;
+ }
+ }
+
+ /* XXX What should be done with invalid characters etc? */
+ if (errno == EINVAL && datalen)
+ {
+ /* The conversion stopped because of an incomplete byte sequence
+ at the end of the buffer. */
+ /* If we used the caller's buffer DATA, the remaining bytes
+ might not fit in our internal output buffer. In this case we
+ can reallocate the buffer in INPUT without needing to update
+ CD_BUFFER (as it points into DATA). */
+ err = ensure_cd_buffer_size (datalen);
+ if (err)
+ {
+ mutex_unlock (&input->lock);
+ errno = err;
+ return enqueued ?: -1;
+ }
+ memmove (input->cd_buffer, data, datalen);
+ }
+
+ out:
+ if (enqueued)
+ {
+ if (was_empty)
+ condition_broadcast (&input->data_available);
+ }
+ else
+ errno = err;
+ mutex_unlock (&input->lock);
+ return enqueued ?: -1;
+}
+
+/* Remove DATALEN characters from the input queue and put them in the
+ buffer DATA. The data will be supplied in the local encoding. The
+ function returns the amount of bytes removed (might be smaller than
+ DATALEN) or -1 and the error number in errno. If NONBLOCK is not
+ zero, return with -1 and set errno to EWOULDBLOCK if operation
+ would block for a long time. */
+ssize_t input_dequeue (input_t input, int nonblock, char *data,
+ size_t datalen)
+{
+ size_t amount = datalen;
+
+ mutex_lock (&input->lock);
+ while (!input->size)
+ {
+ if (nonblock)
+ {
+ mutex_unlock (&input->lock);
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ if (hurd_condition_wait (&input->data_available, &input->lock))
+ {
+ mutex_unlock (&input->lock);
+ errno = EINTR;
+ return -1;
+ }
+ }
+
+ if (amount > input->size)
+ amount = input->size;
+ memcpy (data, input->buffer, amount);
+ memmove (input->buffer, input->buffer + amount, input->size - amount);
+ input->size -= amount;
+ if (amount && input->full)
+ {
+ input->full = 0;
+ condition_broadcast (&input->space_available);
+ }
+ mutex_unlock (&input->lock);
+ return amount;
+}
+
+
+/* Flush the input buffer, discarding all pending data. */
+void input_flush (input_t input)
+{
+ mutex_lock (&input->lock);
+ input->size = 0;
+ if (input->full)
+ {
+ input->full = 0;
+ condition_broadcast (&input->space_available);
+ }
+ mutex_unlock (&input->lock);
+}
diff --git a/console/input.h b/console/input.h
new file mode 100644
index 00000000..47de35e2
--- /dev/null
+++ b/console/input.h
@@ -0,0 +1,58 @@
+/* input.h - Interface to the input component of a virtual console.
+ Copyright (C) 2002 Free Software Foundation, Inc.
+ Written by Marcus Brinkmann.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#ifndef INPUT_H
+#define INPUT_H
+
+#include <errno.h>
+
+struct input;
+typedef struct input *input_t;
+
+/* Create a new virtual console input queue, with the system encoding
+ being ENCODING. */
+error_t input_create (input_t *r_input, const char *encoding);
+
+/* Destroy the input queue INPUT. */
+void input_destroy (input_t input);
+
+/* Enter DATALEN characters from the buffer DATA into the input queue
+ INPUT. The DATA must be supplied in UTF-8 encoding (XXX UCS-4
+ would be nice, too, but it requires knowledge of endianess). The
+ function returns the amount of bytes written (might be smaller than
+ DATALEN) or -1 and the error number in errno. If NONBLOCK is not
+ zero, return with -1 and set errno to EWOULDBLOCK if operation
+ would block for a long time. */
+ssize_t input_enqueue (input_t input, int nonblock, char *data,
+ size_t datalen);
+
+/* Remove DATALEN characters from the input queue and put them in the
+ buffer DATA. The data will be supplied in the local encoding. The
+ function returns the amount of bytes removed (might be smaller than
+ DATALEN) or -1 and the error number in errno. If NONBLOCK is not
+ zero, return with -1 and set errno to EWOULDBLOCK if operation
+ would block for a long time. */
+ssize_t input_dequeue (input_t input, int nonblock, char *data,
+ size_t datalen);
+
+/* Flush the input buffer, discarding all pending data. */
+void input_flush (input_t input);
+
+#endif /* INPUT_H */
diff --git a/console/motd.UTF8 b/console/motd.UTF8
new file mode 100644
index 00000000..40a104be
--- /dev/null
+++ b/console/motd.UTF8
@@ -0,0 +1,4 @@
+
+
+ This is the GNU Hurd. Welcome.
+
diff --git a/console/mutations.h b/console/mutations.h
new file mode 100644
index 00000000..a8183fc0
--- /dev/null
+++ b/console/mutations.h
@@ -0,0 +1,27 @@
+/* mutations.h - Automagic type transformation for MiG interfaces.
+ Copyright (C) 2002 Free Software Foundation, Inc.
+ Written by Marcus Brinkmann.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+/* Only CPP macro definitions should go in this file. */
+
+#define IO_INTRAN protid_t begin_using_protid_port (io_t)
+#define IO_DESTRUCTOR end_using_protid_port (protid_t)
+
+#define TIOCTL_IMPORTS import "priv.h";
+
diff --git a/console/pager.c b/console/pager.c
new file mode 100644
index 00000000..092a8820
--- /dev/null
+++ b/console/pager.c
@@ -0,0 +1,221 @@
+/* pager.c - The pager for the display component of a virtual console.
+ Copyright (C) 2002 Free Software Foundation, Inc.
+ Written by Marcus Brinkmann.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#include <errno.h>
+#include <assert.h>
+#include <error.h>
+
+#include <sys/mman.h>
+#include <cthreads.h>
+
+#include <hurd.h>
+#include <hurd/pager.h>
+#include <hurd/console.h>
+
+#include "pager.h"
+
+
+struct user_pager_info
+{
+ size_t memobj_npages;
+ vm_address_t memobj_pages[0];
+};
+
+
+/* We need a separate bucket for the pager ports. */
+static struct port_bucket *pager_bucket;
+
+
+/* Implement the pager_clear_user_data callback from the pager library. */
+void
+pager_clear_user_data (struct user_pager_info *upi)
+{
+ int idx;
+
+ for (idx = 0; idx < upi->memobj_npages; idx++)
+ if (upi->memobj_pages[idx])
+ vm_deallocate (mach_task_self (), upi->memobj_pages[idx], vm_page_size);
+ free (upi);
+}
+
+
+error_t
+pager_read_page (struct user_pager_info *upi, vm_offset_t page,
+ vm_address_t *buf, int *writelock)
+{
+ /* XXX clients should get a read only object. */
+ *writelock = 0;
+
+ if (upi->memobj_pages[page / vm_page_size] != (vm_address_t) NULL)
+ {
+ *buf = upi->memobj_pages[page / vm_page_size];
+ upi->memobj_pages[page / vm_page_size] = (vm_address_t) NULL;
+ }
+ else
+ *buf = (vm_address_t) mmap (0, vm_page_size, PROT_READ|PROT_WRITE,
+ MAP_ANON, 0, 0);
+ return 0;
+}
+
+
+error_t
+pager_write_page (struct user_pager_info *upi, vm_offset_t page,
+ vm_address_t buf)
+{
+ assert (upi->memobj_pages[page / vm_page_size] == (vm_address_t) NULL);
+ upi->memobj_pages[page / vm_page_size] = buf;
+ return 0;
+}
+
+
+error_t
+pager_unlock_page (struct user_pager_info *pager,
+ vm_offset_t address)
+{
+ assert (!"unlocking requested on unlocked page");
+ return 0;
+}
+
+
+/* Tell how big the file is. */
+error_t
+pager_report_extent (struct user_pager_info *upi,
+ vm_address_t *offset,
+ vm_size_t *size)
+{
+ *offset = 0;
+ *size = upi->memobj_npages * vm_page_size;
+ return 0;
+}
+
+
+void
+pager_dropweak (struct user_pager_info *upi)
+{
+}
+
+
+/* A top-level function for the paging thread that just services paging
+ requests. */
+static void
+service_paging_requests (any_t arg)
+{
+ struct port_bucket *pager_bucket = arg;
+ for (;;)
+ ports_manage_port_operations_multithread (pager_bucket,
+ pager_demuxer,
+ 1000 * 60 * 2,
+ 1000 * 60 * 10, 0);
+}
+
+
+/* Initialize the pager for the display component. */
+void
+user_pager_init (void)
+{
+ /* Create the pager bucket, and start to serve paging requests. */
+ pager_bucket = ports_create_bucket ();
+ if (! pager_bucket)
+ error (5, errno, "Cannot create pager bucket");
+
+ /* Make a thread to service paging requests. */
+ cthread_detach (cthread_fork ((cthread_fn_t) service_paging_requests,
+ (any_t) pager_bucket));
+}
+
+
+/* Create a new pager in USER_PAGER with NPAGES pages, and return a
+ mapping to the memory in *USER. */
+error_t
+user_pager_create (struct user_pager *user_pager, unsigned int npages,
+ struct cons_display **user)
+{
+ error_t err;
+ struct user_pager_info *upi;
+
+ upi = calloc (1, sizeof (struct user_pager_info)
+ + sizeof (vm_address_t) * npages);
+ if (!upi)
+ return errno;
+
+ upi->memobj_npages = npages;
+
+ /* XXX Are the values 1 and MEMORY_OBJECT_COPY_DELAY correct? */
+ user_pager->pager = pager_create (upi, pager_bucket,
+ 1, MEMORY_OBJECT_COPY_DELAY);
+ if (!user_pager->pager)
+ {
+ free (upi);
+ return errno;
+ }
+ user_pager->memobj = pager_get_port (user_pager->pager);
+ ports_port_deref (user_pager->pager);
+
+ mach_port_insert_right (mach_task_self (), user_pager->memobj,
+ user_pager->memobj, MACH_MSG_TYPE_MAKE_SEND);
+
+ err = vm_map (mach_task_self (),
+ (vm_address_t *) user,
+ (vm_size_t) npages * vm_page_size,
+ (vm_address_t) 0,
+ 1 /* ! (flags & MAP_FIXED) */,
+ user_pager->memobj, 0 /* (vm_offset_t) offset */,
+ 0 /* ! (flags & MAP_SHARED) */,
+ VM_PROT_READ | VM_PROT_WRITE,
+ VM_PROT_READ | VM_PROT_WRITE,
+ VM_INHERIT_NONE);
+ if (err)
+ {
+ /* UPI will be cleaned up by libpager. */
+ mach_port_deallocate (mach_task_self (), user_pager->memobj);
+ return err;
+ }
+
+ return 0;
+}
+
+
+/* Destroy the pager USER_PAGER and the mapping at USER. */
+void
+user_pager_destroy (struct user_pager *user_pager, struct cons_display *user)
+{
+ /* The pager will be deallocated by libpager. */
+ vm_deallocate (mach_task_self (), (vm_offset_t) user,
+ pager_get_upi (user_pager->pager)->memobj_npages
+ * vm_page_size);
+ mach_port_deallocate (mach_task_self (), user_pager->memobj);
+}
+
+
+/* Allocate a reference for the memory object backing the pager
+ USER_PAGER with protection PROT and return it. */
+mach_port_t
+user_pager_get_filemap (struct user_pager *user_pager, vm_prot_t prot)
+{
+ error_t err;
+
+ /* Add a reference for each call, the caller will deallocate it. */
+ err = mach_port_mod_refs (mach_task_self (), user_pager->memobj,
+ MACH_PORT_RIGHT_SEND, +1);
+ assert_perror (err);
+
+ return user_pager->memobj;
+}
+
diff --git a/console/pager.h b/console/pager.h
new file mode 100644
index 00000000..974db961
--- /dev/null
+++ b/console/pager.h
@@ -0,0 +1,47 @@
+/* pager.h - Interface to the pager for display component of a virtual console.
+ Copyright (C) 2002 Free Software Foundation, Inc.
+ Written by Marcus Brinkmann.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#ifndef PAGER_H
+#define PAGER_H
+
+struct user_pager
+{
+ struct pager *pager;
+ memory_object_t memobj;
+};
+
+/* Initialize the pager for the display component. */
+void user_pager_init (void);
+
+/* Create a new pager in USER_PAGER with NPAGES pages, and return a
+ mapping to the memory in *USER. */
+error_t user_pager_create (struct user_pager *user_pager, unsigned int npages,
+ struct cons_display **user);
+
+/* Destroy the pager USER_PAGER and the mapping at USER. */
+void user_pager_destroy (struct user_pager *user_pager,
+ struct cons_display *user);
+
+/* Allocate a reference for the memory object backing the pager
+ USER_PAGER with protection PROT and return it. */
+mach_port_t user_pager_get_filemap (struct user_pager *user_pager,
+ vm_prot_t prot);
+
+#endif /* PAGER_H_ */
diff --git a/console/priv.h b/console/priv.h
new file mode 100644
index 00000000..c03bbc46
--- /dev/null
+++ b/console/priv.h
@@ -0,0 +1,37 @@
+/*
+ Copyright (C) 1995, 1996, 2001, 2007 Free Software Foundation, Inc.
+
+ Written by Michael I. Bushnell, p/BSG.
+
+ This file is part of the GNU Hurd.
+
+ The GNU Hurd is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ The GNU Hurd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
+
+#include <hurd/hurd_types.h>
+
+#include <hurd/netfs.h>
+
+static inline struct protid * __attribute__ ((unused))
+begin_using_protid_port (file_t port)
+{
+ return ports_lookup_port (netfs_port_bucket, port, netfs_protid_class);
+}
+
+static inline void __attribute__ ((unused))
+end_using_protid_port (struct protid *cred)
+{
+ if (cred)
+ ports_port_deref (cred);
+}