diff options
-rw-r--r-- | console/ChangeLog | 13 | ||||
-rw-r--r-- | console/Makefile | 20 | ||||
-rw-r--r-- | console/console.c | 1171 | ||||
-rw-r--r-- | console/console.h | 35 | ||||
-rw-r--r-- | console/display.c | 524 | ||||
-rw-r--r-- | console/display.h | 39 | ||||
-rw-r--r-- | console/input.c | 277 | ||||
-rw-r--r-- | console/input.h | 58 |
8 files changed, 1983 insertions, 154 deletions
diff --git a/console/ChangeLog b/console/ChangeLog index 513802c6..5a14f195 100644 --- a/console/ChangeLog +++ b/console/ChangeLog @@ -1,5 +1,18 @@ 2002-06-05 Marcus Brinkmann <marcus@gnu.org> + * input.h: New file. + * input.c: Likewise. + * console.h: Likewise. + * console.c: Likewise. + * display.h: New development version. + * display.c: Likewise. + * Makefile (SRCS): Replace with files for new console server. + (LCLHDRS): Likewise. + (HURDLIBS): Likewise. + (OBJS): Likewise. + +2002-06-05 Marcus Brinkmann <marcus@gnu.org> + * input.h: Renamed to ... * input-drv.h: ... this. * focus.c: Include "input-drv.h" instead "input.h". diff --git a/console/Makefile b/console/Makefile index a5de1e49..2e227269 100644 --- a/console/Makefile +++ b/console/Makefile @@ -22,13 +22,21 @@ dir := console makemode := server target = console -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 +SRCS = console.c display.c input.c +LCLHDRS = console.h display.h input.h -MIGSFLAGS += -imacros $(srcdir)/mutations.h +HURDLIBS = netfs fshelp iohelp threads ports ihash shouldbeinlibc +OBJS = $(subst .c,.o,$(SRCS)) + +# 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/console.c b/console/console.c new file mode 100644 index 00000000..57e93de6 --- /dev/null +++ b/console/console.c @@ -0,0 +1,1171 @@ +/* console.c -- A console server. + Copyright (C) 1997, 1999, 2002 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 <hurd/netfs.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> + +#include <argp.h> +#include <error.h> +#include <string.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <cthreads.h> +#include <mcheck.h> + +#include <version.h> + +#include "console.h" +#include "display.h" +#include "input.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. */ + + +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; +}; + +struct cons +{ + /* The lock protects the console, all virtual consoles contained in + it and the reference counters. It also locks the + configuration. */ + struct mutex lock; + vcons_t vcons_list; + /* The encoding. */ + const char *encoding; + + struct node *node; + mach_port_t underlying; + /* A template for the stat information of all nodes. */ + struct stat stat_template; +} mycons; +cons_t cons = &mycons; + +/* 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 (&vcons->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); + if (err) + { + free (vcons->name); + free (vcons); + mutex_unlock (&vcons->cons->lock); + return err; + } + + err = input_create (&vcons->input, cons->encoding); + if (err) + { + display_destroy (vcons->display); + free (vcons->name); + free (vcons); + mutex_unlock (&vcons->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; + } + 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; + + /* 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 = 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 = 2000 * sizeof (wchar_t); /* XXX */ + 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 (cons->stat_template.st_mode) + && S_ISDIR ((*np)->nn_stat.st_mode)) + { + if (cons->stat_template.st_mode & S_IRUSR) + (*np)->nn_stat.st_mode |= S_IXUSR; + if (cons->stat_template.st_mode & S_IRGRP) + (*np)->nn_stat.st_mode |= S_IXGRP; + if (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_mtime = mtime->tv_sec; + node->nn_stat.st_mtime_usec = mtime->tv_nsec / 1000; + } + else + flags |= TOUCH_MTIME; + + if (atime) + { + node->nn_stat.st_atime = atime->tv_sec; + node->nn_stat.st_atime_usec = atime->tv_nsec / 1000; + } + 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, *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; + mutex_unlock (&dir->nn->cons->lock); + } + 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); + } + } + + 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; + assert (np == vcons->disp_node); + amt = display_read (vcons->display, + /* cred->po->openstat & O_NONBLOCK */ 0, + offset, data, *len); + 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; +} + + +int +main (int argc, char **argv) +{ + error_t err; + mach_port_t bootstrap; + struct stat ul_stat; + struct netnode root_nn = { cons: cons, vcons: 0 }; + + mtrace(); + struct argp argp + = { NULL, NULL, NULL, + "A translator that provides virtual console displays." }; + + /* Parse our command line arguments (all none of them). */ + argp_parse (&argp, argc, argv, 0, 0, 0); + + task_get_bootstrap_port (mach_task_self (), &bootstrap); + netfs_init (); + + /* Create the root node (some attributes initialized below). */ + netfs_root_node = netfs_make_node (&root_nn); + if (! netfs_root_node) + error (5, ENOMEM, "Cannot create root node"); + + err = maptime_map (0, 0, &console_maptime); + if (err) + error (6, err, "Cannot map time"); + + mutex_init (&cons->lock); + cons->encoding = "ISO-8859-1"; + cons->node = netfs_root_node; + cons->underlying = netfs_startup (bootstrap, O_READ); + if (cons->underlying == MACH_PORT_NULL) + error (5, err, "Cannot get underlying node"); + + err = io_stat (cons->underlying, &ul_stat); + if (err) + error (6, 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); + + netfs_server_loop (); /* Never returns. */ + + /*NOTREACHED*/ + return 0; +} diff --git a/console/console.h b/console/console.h new file mode 100644 index 00000000..3642bfe4 --- /dev/null +++ b/console/console.h @@ -0,0 +1,35 @@ +/* console.h -- Interfaces for the console server. + Copyright (C) 2002 Free Software Foundation, Inc. + Written by 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 */ + +#ifndef CONSOLE_H +#define CONSOLE_H + +#include <hurd/netfs.h> +#include <rwlock.h> +#include <maptime.h> + +/* Handy source of time. */ +volatile struct mapped_time_value *console_maptime; + +/* A handle for a console device. */ +typedef struct cons *cons_t; + +/* A handle for a virtual console device. */ +typedef struct vcons *vcons_t; + +#endif /* CONSOLE_H */ diff --git a/console/display.c b/console/display.c index 363c9c09..d2c0b590 100644 --- a/console/display.c +++ b/console/display.c @@ -57,6 +57,20 @@ struct screen }; typedef struct screen *screen_t; +struct cursor +{ + /* The visibility of the cursor. */ + int status; +#define CURSOR_INVISIBLE 1 +#define CURSOR_STANDOUT 2 + + size_t x; + size_t y; + size_t saved_x; + size_t saved_y; +}; +typedef struct cursor *cursor_t; + struct parse { /* The parsing state of output characters, needed to handle escape @@ -76,47 +90,53 @@ struct parse int params[PARSE_MAX_PARAMS]; int nparams; }; +typedef struct parse *parse_t; -struct cursor +struct output { - /* The visibility of the cursor. */ - int status; -#define CURSOR_INVISIBLE 1 -#define CURSOR_STANDOUT 2 + /* 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; - size_t x; - size_t y; - size_t saved_x; - size_t saved_y; +struct attr +{ + /* Current attribute. */ + char current; + int fg; + int bg; + int def_fg; + int def_bg; + int reverse : 1; + int bold : 1; + int blink : 1; + int invisible : 1; + int dim : 1; + int underline : 1; }; +typedef struct attr *attr_t; struct display { /* The lock for the virtual console display structure. */ struct mutex lock; - /* The state of the conversion of output characters. */ - iconv_t cd; - - struct parse parse; struct screen screen; struct cursor cursor; - - struct - { - /* Current attribute. */ - char current; - int fg; - int bg; - int def_fg; - int def_bg; - int reverse : 1; - int bold : 1; - int blink : 1; - int invisible : 1; - int dim : 1; - int underline : 1; - } attr; + struct output output; + struct attr attr; /* Indicates if OWNER_ID is initialized. */ int has_owner; @@ -202,7 +222,7 @@ static void screen_scroll_left (screen_t screen, size_t x, size_t y, size_t w, size_t h, int amt, wchar_t chr, char attr) { - int y; + int i; wchar_t *matrixp = screen->matrix + y * screen->width + x; if (amt < 0) @@ -210,9 +230,9 @@ screen_scroll_left (screen_t screen, size_t x, size_t y, size_t w, size_t h, if (amt > w) amt = w; - for (y = 0; y < y + h; y++) + for (i = 0; i < y + h; i++) { - wmemmov (matrixp, matrixp + amt, w - amt); + wmemmove (matrixp, matrixp + amt, w - amt); matrixp += screen->width; } screen_fill (screen, x + w - amt, y, amt, h, chr, attr); @@ -222,7 +242,7 @@ static void screen_scroll_right (screen_t screen, size_t x, size_t y, size_t w, size_t h, int amt, wchar_t chr, char attr) { - int y; + int i; wchar_t *matrixp = screen->matrix + y * screen->width + x; if (amt < 0) @@ -230,152 +250,131 @@ screen_scroll_right (screen_t screen, size_t x, size_t y, size_t w, size_t h, if (amt > w) amt = w; - for (y = 0; y < y + h; y++) + for (i = 0; i < y + h; i++) { - wmemmov (matrixp + amt, matrixp, w - amt); + wmemmove (matrixp + amt, matrixp, w - amt); matrixp += screen->width; } screen_fill (screen, x, y, amt, h, chr, attr); } -/* Create a new virtual console display, with the system encoding - being ENCODING. */ -error_t -display_create (display_t *r_display, const char *encoding) +static error_t +output_init (output_t output, const char *encoding) { - error_t err = 0; - display_t display; - - *r_display = NULL; - display = calloc (1, sizeof *display); - if (!display) - return ENOMEM; - - mutex_init (&display->lock); - err = screen_init (&display->screen); - if (err) - { - free (display); - return err; - } + 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. */ - display->cd = iconv_open ("WCHAR_T", encoding); - if (display->cd == (iconv_t) -1) - { - err = errno; - screen_deinit (&display->screen); - free (display); - } - *r_display = display; - return err; + output->cd = iconv_open ("WCHAR_T", encoding); + if (output->cd == (iconv_t) -1) + return errno; + return 0; } - -/* Destroy the display DISPLAY. */ -void -display_destroy (display_t display) +static void +output_deinit (output_t output) { - iconv_close (display->cd); - screen_deinit (&display->screen); - free (display); + iconv_close (output->cd); } static void -handle_esc_bracket_hl (display_t display, int code, int flag) +handle_esc_bracket_hl (cursor_t cursor, int code, int flag) { switch (code) { case 34: /* Cursor standout: <cnorm>, <cvvis>. */ if (flag) - display->cursor.status |= CURSOR_STANDOUT; + cursor->status |= CURSOR_STANDOUT; else - display->cursor.status &= ~CURSOR_STANDOUT; + cursor->status &= ~CURSOR_STANDOUT; /* XXX Flag cursor status change. */ break; } } static void -handle_esc_bracket_m (display_t display, int code) +handle_esc_bracket_m (attr_t attr, int code) { switch (code) { case 0: /* All attributes off: <sgr0>. */ - display->attr.fg = display->attr.def_fg; - display->attr.bg = display->attr.def_bg; - display->attr.reverse = display->attr.bold = display->attr.blink - = display->attr.invisible = display->attr.dim - = display->attr.underline = 0; + attr->fg = attr->def_fg; + attr->bg = attr->def_bg; + attr->reverse = attr->bold = attr->blink + = attr->invisible = attr->dim + = attr->underline = 0; /* Cursor attributes aren't text attributes. */ break; case 1: /* Bold on: <bold>. */ - display->attr.bold = 1; + attr->bold = 1; break; case 2: /* Dim on: <dim>. */ - display->attr.dim = 1; + attr->dim = 1; break; case 4: /* Underline on: <smul>. */ - display->attr.underline = 1; + attr->underline = 1; break; case 5: /* Blink on: <blink>. */ - display->attr.blink = 1; + attr->blink = 1; break; case 7: /* Reverse video on: <rev>, <smso>. */ - display->attr.reverse = 1; + attr->reverse = 1; break; case 8: /* Concealed on: <invis>. */ - display->attr.invisible = 1; + attr->invisible = 1; break; case 21: /* Bold Off. */ - display->attr.bold = 0; + attr->bold = 0; break; case 22: /* Dim off. */ - display->attr.dim = 0; + attr->dim = 0; break; case 24: /* Underline off: <rmul>. */ - display->attr.underline = 0; + attr->underline = 0; break; case 25: /* Blink off. */ - display->attr.blink = 0; + attr->blink = 0; break; case 27: /* Reverse video off: <rmso>. */ - display->attr.reverse = 0; + attr->reverse = 0; break; case 28: /* Concealed off. */ - display->attr.invisible = 0; + attr->invisible = 0; break; case 30 ... 37: /* Set foreground color: <setaf>. */ - display->attr.fg = code - 30; + attr->fg = code - 30; break; case 39: /* Default foreground color; ANSI?. */ - display->attr.fg = display->attr.def_fg; + attr->fg = attr->def_fg; break; case 40 ... 47: /* Set background color: <setab>. */ - display->attr.bg = code - 40; + attr->bg = code - 40; break; case 49: /* Default background color; ANSI?. */ - display->attr.bg = display->attr.def_bg; + attr->bg = attr->def_bg; break; } /* XXX */ @@ -385,6 +384,7 @@ handle_esc_bracket_m (display_t display, int code) static void handle_esc_bracket (display_t display, char op) { + parse_t parse = &display->output.parse; int i; static void limit_cursor (void) @@ -407,13 +407,13 @@ handle_esc_bracket (display_t display, char op) case 'H': case 'f': /* Cursor position: <cup>. */ - display->cursor.x = display->parse.params[1] - 1; - display->cursor.y = display->parse.params[0] - 1; + display->cursor.x = parse->params[1] - 1; + display->cursor.y = parse->params[0] - 1; limit_cursor (); break; case 'G': /* Horizontal position: <hpa>. */ - display->cursor.x = display->parse.params[0] - 1; + display->cursor.x = parse->params[0] - 1; limit_cursor (); break; case 'F': @@ -422,7 +422,7 @@ handle_esc_bracket (display_t display, char op) /* fall through */ case 'A': /* Cursor up: <cuu>, <cuu1>. */ - display->cursor.y -= (display->parse.params[0] ?: 1); + display->cursor.y -= (parse->params[0] ?: 1); limit_cursor (); break; case 'E': @@ -431,17 +431,17 @@ handle_esc_bracket (display_t display, char op) /* Fall through. */ case 'B': /* Cursor down: <cud1>, <cud>. */ - display->cursor.y += (display->parse.params[0] ?: 1); + display->cursor.y += (parse->params[0] ?: 1); limit_cursor (); break; case 'C': /* Cursor right: <cuf1>, <cuf>. */ - display->cursor.x += (display->parse.params[0] ?: 1); + display->cursor.x += (parse->params[0] ?: 1); limit_cursor (); break; case 'D': /* Cursor left: <cub>, <cub1>. */ - display->cursor.x -= (display->parse.params[0] ?: 1); + display->cursor.x -= (parse->params[0] ?: 1); limit_cursor (); break; case 's': @@ -458,20 +458,20 @@ handle_esc_bracket (display_t display, char op) break; case 'h': /* Reset mode. */ - for (i = 0; i < display->parse.nparams; i++) - handle_esc_bracket_hl (display, display->parse.params[i], 0); + for (i = 0; i < parse->nparams; i++) + handle_esc_bracket_hl (&display->cursor, parse->params[i], 0); break; case 'l': /* Set mode. */ - for (i = 0; i < display->parse.nparams; i++) - handle_esc_bracket_hl (display, display->parse.params[i], 1); + for (i = 0; i < parse->nparams; i++) + handle_esc_bracket_hl (&display->cursor, parse->params[i], 1); break; case 'm': - for (i = 0; i < display->parse.nparams; i++) - handle_esc_bracket_m (display, display->parse.params[i]); + for (i = 0; i < parse->nparams; i++) + handle_esc_bracket_m (&display->attr, parse->params[i]); break; case 'J': - switch (display->parse.params[0]) + switch (parse->params[0]) { case 0: /* Clear to end of screen: <ed>. */ @@ -501,7 +501,7 @@ handle_esc_bracket (display_t display, char op) } break; case 'K': - switch (display->parse.params[0]) + switch (parse->params[0]) { case 0: /* Clear to end of line: <el>. */ @@ -528,7 +528,7 @@ handle_esc_bracket (display_t display, char op) screen_scroll_down (&display->screen, 0, display->cursor.y, display->screen.width, display->screen.height - display->cursor.y, - display->parse.params[0] ?: 1, + parse->params[0] ?: 1, L' ', display->attr.current); break; case 'M': @@ -536,7 +536,7 @@ handle_esc_bracket (display_t display, char op) screen_scroll_up (&display->screen, 0, display->cursor.y, display->screen.width, display->screen.height - display->cursor.y, - display->parse.params[0] ?: 1, + parse->params[0] ?: 1, L' ', display->attr.current); break; case '@': @@ -544,7 +544,7 @@ handle_esc_bracket (display_t display, char op) screen_scroll_right (&display->screen, display->cursor.x, display->cursor.y, display->screen.width - display->cursor.x, 1, - display->parse.params[0] ?: 1, + parse->params[0] ?: 1, L' ', display->attr.current); break; case 'P': @@ -552,27 +552,27 @@ handle_esc_bracket (display_t display, char op) screen_scroll_left (&display->screen, display->cursor.x, display->cursor.y, display->screen.width - display->cursor.x, 1, - display->parse.params[0] ?: 1, + parse->params[0] ?: 1, L' ', display->attr.current); break; case 'S': /* Scroll up: <ind>, <indn>. */ screen_scroll_up (&display->screen, 0, 0, display->screen.width, display->screen.height, - display->parse.params[0] ?: 1, + parse->params[0] ?: 1, L' ', display->attr.current); break; case 'T': /* Scroll down: <ri>, <rin>. */ screen_scroll_down (&display->screen, 0, 0, display->screen.width, display->screen.height, - display->parse.params[0] ?: 1, + parse->params[0] ?: 1, L' ', display->attr.current); break; case 'X': /* Erase character(s): <ech>. */ screen_fill (&display->screen, display->cursor.x, display->cursor.y, - display->parse.params[0] ?: 1, 1, + parse->params[0] ?: 1, 1, L' ', display->attr.current); break; } @@ -598,18 +598,20 @@ handle_esc_bracket_question_hl (display_t display, int code, int flag) static void handle_esc_bracket_question (display_t display, char op) { + parse_t parse = &display->output.parse; + int i; switch (op) { case 'h': /* Reset mode. */ - for (i = 0; i < display->parse.nparams; ++i) - handle_esc_bracket_question_hl (display, display->parse.params[i], 0); + for (i = 0; i < parse->nparams; ++i) + handle_esc_bracket_question_hl (display, parse->params[i], 0); break; case 'l': /* Set mode. */ - for (i = 0; i < display->parse.nparams; ++i) - handle_esc_bracket_question_hl (display, display->parse.params[i], 1); + for (i = 0; i < parse->nparams; ++i) + handle_esc_bracket_question_hl (display, parse->params[i], 1); break; } } @@ -618,6 +620,8 @@ handle_esc_bracket_question (display_t display, char op) static void display_output_one (display_t display, wchar_t chr) { + parse_t parse = &display->output.parse; + void newline (void) { if (display->cursor.y < display->screen.height - 1) @@ -643,7 +647,7 @@ display_output_one (display_t display, wchar_t chr) } } - switch (display->parse.state) + switch (parse->state) { case STATE_NORMAL: switch (chr) @@ -687,7 +691,7 @@ display_output_one (display_t display, wchar_t chr) /* XXX Flag cursor update. */ break; case L'\033': - display->parse.state = STATE_ESC; + parse->state = STATE_ESC; break; case L'\0': /* Padding character: <pad>. */ @@ -702,7 +706,7 @@ display_output_one (display_t display, wchar_t chr) + display->cursor.x] = chr; display->cursor.x++; - if (display->cursor.x == display->screen.height) + if (display->cursor.x == display->screen.width) { display->cursor.x = 0; newline (); @@ -716,7 +720,7 @@ display_output_one (display_t display, wchar_t chr) switch (chr) { case L'[': - display->parse.state = STATE_ESC_BRACKET_INIT; + parse->state = STATE_ESC_BRACKET_INIT; break; case L'c': /* Clear screen and home cursor: <clear>. */ @@ -725,44 +729,44 @@ display_output_one (display_t display, wchar_t chr) L' ', display->attr.current); display->cursor.x = display->cursor.y = 0; /* XXX Flag cursor change. */ - display->parse.state = STATE_NORMAL; + parse->state = STATE_NORMAL; break; default: /* Unsupported escape sequence. */ - display->parse.state = STATE_NORMAL; + parse->state = STATE_NORMAL; break; } break; case STATE_ESC_BRACKET_INIT: - memset (&display->parse.params, 0, sizeof display->parse.params); - display->parse.nparams = 0; + memset (&parse->params, 0, sizeof parse->params); + parse->nparams = 0; if (chr == '?') { - display->parse.state = STATE_ESC_BRACKET_QUESTION; + parse->state = STATE_ESC_BRACKET_QUESTION; break; /* Consume the question mark. */ } else - display->parse.state = STATE_ESC_BRACKET; + parse->state = STATE_ESC_BRACKET; /* Fall through. */ case STATE_ESC_BRACKET: case STATE_ESC_BRACKET_QUESTION: if (chr >= '0' && chr <= '9') - display->parse.params[display->parse.nparams] - = display->parse.params[display->parse.nparams]*10 + chr - '0'; + parse->params[parse->nparams] + = parse->params[parse->nparams]*10 + chr - '0'; else if (chr == ';') { - if (++(display->parse.nparams) >= PARSE_MAX_PARAMS) - display->parse.state = STATE_NORMAL; /* too many */ + if (++(parse->nparams) >= PARSE_MAX_PARAMS) + parse->state = STATE_NORMAL; /* too many */ } else { - display->parse.nparams++; - if (display->parse.state == STATE_ESC_BRACKET) + parse->nparams++; + if (parse->state == STATE_ESC_BRACKET) handle_esc_bracket (display, chr); else handle_esc_bracket_question (display, chr); - display->parse.state = STATE_NORMAL; + parse->state = STATE_NORMAL; } break; default: @@ -770,31 +774,29 @@ display_output_one (display_t display, wchar_t chr) } } - /* 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. */ -error_t -display_output (display_t display, char **buffer, size_t *length) +static error_t +display_output_some (display_t display, char **buffer, size_t *length) { #define CONV_OUTBUF_SIZE 256 error_t err = 0; - mutex_lock (&display->lock); while (!err && *length > 0) { size_t nconv; wchar_t outbuf[CONV_OUTBUF_SIZE]; char *outptr = (char *) outbuf; - size_t outsize = CONV_OUTBUF_SIZE; + size_t outsize = CONV_OUTBUF_SIZE * sizeof (wchar_t); error_t saved_err; int i; - nconv = iconv (display->cd, buffer, length, &outptr, &outsize); + 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; i++) + for (i = 0; i < CONV_OUTBUF_SIZE - outsize / sizeof (wchar_t); i++) display_output_one (display, outbuf[i]); if (nconv == (size_t) -1) @@ -808,11 +810,52 @@ display_output (display_t display, char **buffer, size_t *length) err = saved_err; } } - mutex_unlock (&display->lock); return err; } - +/* Create a new virtual console display, with the system encoding + being ENCODING. */ +error_t +display_create (display_t *r_display, const char *encoding) +{ + error_t err = 0; + display_t display; + + *r_display = NULL; + display = calloc (1, sizeof *display); + if (!display) + return ENOMEM; + + mutex_init (&display->lock); + err = screen_init (&display->screen); + if (err) + { + free (display); + return err; + } + + err = output_init (&display->output, encoding); + if (err) + { + screen_deinit (&display->screen); + free (display); + } + *r_display = display; + return err; +} + + +/* Destroy the display DISPLAY. */ +void +display_destroy (display_t display) +{ + output_deinit (&display->output); + screen_deinit (&display->screen); + free (display); +} + + +/* Return the dimensions of the display DISPLAY in *WINSIZE. */ void display_getsize (display_t display, struct winsize *winsize) { @@ -823,3 +866,190 @@ display_getsize (display_t display, struct winsize *winsize) 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; + } + /* XXX What should be done with invalid characters etc? */ + 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); + if (off + len > 2000 * sizeof(wchar_t)) + len = 2000 * sizeof(wchar_t) - off; + memcpy (data, (char *) display->screen.matrix + 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); + } + 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; + 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); +} diff --git a/console/display.h b/console/display.h index 0043fb3e..6762e1b7 100644 --- a/console/display.h +++ b/console/display.h @@ -26,9 +26,46 @@ struct display; typedef struct display *display_t; +/* Create a new virtual console display, with the system encoding + being ENCODING. */ error_t display_create (display_t *r_display, const char *encoding); + +/* Destroy the display DISPLAY. */ void display_destroy (display_t display); -error_t display_output (display_t display, char **buffer, size_t *length); + +/* 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); + +ssize_t display_read (display_t display, int nonblock, off_t off, + char *data, size_t len); + +/* 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/input.c b/console/input.c new file mode 100644 index 00000000..e27d30c8 --- /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 ("UTF-8", encoding); + 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; + 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 */ |