summaryrefslogtreecommitdiff
path: root/console/input.c
diff options
context:
space:
mode:
authorMarcus Brinkmann <marcus@gnu.org>2002-06-05 02:00:32 +0000
committerMarcus Brinkmann <marcus@gnu.org>2002-06-05 02:00:32 +0000
commitcb387b25d318025c67c422d509253d824a7764f1 (patch)
tree671b4093bcce8ffe84524a4b52be00fb762274f6 /console/input.c
parent94d1c5884949b21303e66b80c00c1690ec0d9d60 (diff)
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.
Diffstat (limited to 'console/input.c')
-rw-r--r--console/input.c277
1 files changed, 277 insertions, 0 deletions
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);
+}