aboutsummaryrefslogtreecommitdiff
path: root/input.c
diff options
context:
space:
mode:
Diffstat (limited to 'input.c')
-rw-r--r--input.c825
1 files changed, 825 insertions, 0 deletions
diff --git a/input.c b/input.c
new file mode 100644
index 00000000..663ef2a3
--- /dev/null
+++ b/input.c
@@ -0,0 +1,825 @@
+/* $Id: input.c,v 1.1.1.1 2007-07-09 19:03:45 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+size_t input_sequence(
+ u_char *, size_t, u_char *, u_char *, uint16_t **, u_int *);
+int input_control(
+ u_char **, size_t *, struct buffer *, struct screen *, u_char);
+int input_pair_private(
+ u_char **, size_t *, struct buffer *, struct screen *, u_char);
+int input_pair_standard(
+ u_char **, size_t *, struct buffer *, struct screen *, u_char);
+int input_pair_control(
+ u_char **, size_t *, struct buffer *, struct screen *, u_char);
+int input_control_sequence(
+ u_char **, size_t *, struct buffer *, struct screen *);
+int input_check_one(uint16_t *, u_int, uint16_t *, uint16_t);
+int input_check_one2(
+ uint16_t *, u_int, uint16_t *, uint16_t, uint16_t, uint16_t);
+int input_check_two(
+ uint16_t *, u_int, uint16_t *, uint16_t, uint16_t *, uint16_t);
+
+struct input_key {
+ int key;
+ const char *string;
+};
+
+struct input_key input_keys[] = {
+ { KEYC_BACKSPACE, "" },
+ { KEYC_DC, "[3~" },
+ { KEYC_DOWN, "OB" },
+ { KEYC_F1, "OP" },
+ { KEYC_F10, "[21~" },
+ { KEYC_F11, "[23~" },
+ { KEYC_F12, "[24~" },
+ { KEYC_F2, "OQ" },
+ { KEYC_F3, "OR" },
+ { KEYC_F4, "OS" },
+ { KEYC_F5, "[15~" },
+ { KEYC_F6, "[17~" },
+ { KEYC_F7, "[18~" },
+ { KEYC_F8, "[19~" },
+ { KEYC_F9, "[20~" },
+ { KEYC_HOME, "[1~" },
+ { KEYC_IC, "[2~" },
+ { KEYC_LEFT, "OD" },
+ { KEYC_LL, "[4~" },
+ { KEYC_NPAGE, "[6~" },
+ { KEYC_PPAGE, "[5~" },
+ { KEYC_RIGHT, "OC" },
+ { KEYC_UP, "OA" }
+};
+
+/*
+ * This parses CSI escape sequences into a code and a block of uint16_t
+ * arguments. buf must be the next byte after the \e[ and len the remaining
+ * data.
+ */
+size_t
+input_sequence(u_char *buf, size_t len,
+ u_char *code, u_char *private, uint16_t **argv, u_int *argc)
+{
+ char ch;
+ u_char *ptr, *saved;
+ const char *errstr;
+
+ *code = 0;
+
+ *argc = 0;
+ *argv = NULL;
+
+ if (len == 0)
+ return (0);
+ saved = buf;
+
+ /*
+ * 0x3c (<) to 0x3f (?) mark private sequences when appear as the first
+ * character.
+ */
+ *private = '\0';
+ if (*buf >= '<' && *buf <= '?') {
+ *private = *buf;
+ buf++; len--;
+ } else if (*buf < '0' || *buf > ';')
+ goto complete;
+
+ while (len > 0) {
+ /*
+ * Every parameter substring is bytes from 0x30 (0) to 0x3a (:),
+ * terminated by 0x3b (;). 0x3a is an internal seperator.
+ */
+
+ /* Find the end of the substring. */
+ ptr = buf;
+ while (len != 0 && *ptr >= '0' && *ptr <= '9') {
+ ptr++;
+ len--;
+ }
+ if (len == 0)
+ break;
+
+ /* An 0x3a is unsupported. */
+ if (*ptr == ':')
+ goto invalid;
+
+ /* Create a new argument. */
+ (*argc)++;
+ *argv = xrealloc(*argv, *argc, sizeof **argv);
+
+ /* Fill in argument value. */
+ errstr = NULL;
+ if (ptr == buf)
+ (*argv)[*argc - 1] = UINT16_MAX;
+ else {
+ ch = *ptr; *ptr = '\0';
+ (*argv)[*argc - 1] =
+ strtonum(buf, 0, UINT16_MAX - 1, &errstr);
+ *ptr = ch;
+ }
+ buf = ptr;
+
+ /* If the conversion had errors, abort now. */
+ if (errstr != NULL)
+ goto invalid;
+
+ /* Break for any non-terminator. */
+ if (*buf != ';')
+ goto complete;
+ buf++; len--;
+ }
+ if (len == 0)
+ goto incomplete;
+
+complete:
+ /* Valid final characters are 0x40 (@) to 0x7e (~). */
+ if (*buf < '@' || *buf > '~')
+ goto invalid;
+
+ *code = *buf;
+ return (buf - saved + 1);
+
+invalid:
+ if (*argv != NULL) {
+ xfree(*argv);
+ *argv = NULL;
+ }
+ *argc = 0;
+
+ /* Invalid. Consume until a valid terminator. */
+ while (len > 0) {
+ if (*buf >= '@' && *buf <= '~')
+ break;
+ buf++; len--;
+ }
+ if (len == 0)
+ goto incomplete;
+
+ *code = '\0';
+ return (buf - saved + 1);
+
+incomplete:
+ if (*argv != NULL) {
+ xfree(*argv);
+ *argv = NULL;
+ }
+ *argc = 0;
+
+ *code = '\0';
+ return (0);
+}
+
+/* Translate a key code into an output key sequence. */
+void
+input_key(struct buffer *b, int key)
+{
+ struct input_key *ak;
+ u_int i;
+
+ log_debug("writing key %d", key);
+ if (key != KEYC_NONE && key >= 0) {
+ input_store8(b, key);
+ return;
+ }
+
+ for (i = 0; i < (sizeof input_keys / sizeof input_keys[0]); i++) {
+ ak = input_keys + i;
+ if (ak->key == key) {
+ log_debug("found key %d: \"%s\"", key, ak->string);
+ buffer_write(b, ak->string, strlen(ak->string));
+ return;
+ }
+ }
+}
+
+/*
+ * Parse a block of data and normalise escape sequences into a \e, a single
+ * character code and the correct number of arguments. This includes adding
+ * missing arguments and default values, and enforcing limits. Returns the
+ * number of bytes consumed. The screen is updated with the data and used
+ * to fill in current cursor positions and sizes.
+ */
+size_t
+input_parse(u_char *buf, size_t len, struct buffer *b, struct screen *s)
+{
+ u_char *saved, ch;
+ size_t size;
+ FILE *f;
+
+ saved = buf;
+
+ if (debug_level > 1) {
+ f = fopen("tmux-in.log", "a+");
+ fwrite(buf, len, 1, f);
+ fclose(f);
+ }
+
+ while (len > 0) {
+ ch = *buf++; len--;
+
+ /* Handle control characters. */
+ if (ch != '\e') {
+ if (ch < ' ') {
+ if (input_control(&buf, &len, b, s, ch) == 1) {
+ *--buf = ch;
+ break;
+ }
+ } else {
+ log_debug("character: %c (%hhu)", ch, ch);
+ screen_character(s, ch);
+ input_store8(b, ch);
+ }
+ continue;
+ }
+ if (len == 0) {
+ *--buf = '\e';
+ break;
+ }
+
+ /* Read the first character. */
+ ch = *buf++; len--;
+
+ /* Ignore delete. */
+ if (ch == '\177') {
+ if (len == 0) {
+ *--buf = '\e';
+ break;
+ }
+ ch = *buf++; len--;
+ }
+
+ /* Interpret C0 immediately. */
+ if (ch < ' ') {
+ if (input_control(&buf, &len, b, s, ch) == 1) {
+ *--buf = ch;
+ break;
+ }
+
+ if (len == 0) {
+ *--buf = '\e';
+ break;
+ }
+ ch = *buf++; len--;
+ }
+
+ /*
+ * Save used size to work out how much to pass to
+ * screen_sequence later.
+ */
+ size = BUFFER_USED(b);
+
+ /* Skip until the end of intermediate strings. */
+ if (ch >= ' ' && ch <= '/') {
+ while (len != 0) {
+ if (ch >= 0x30 && ch <= 0x3f)
+ break;
+ if (ch >= 0x40 && ch <= 0x5f)
+ break;
+ ch = *buf++; len--;
+ }
+ continue;
+ }
+
+ /* Handle two-character sequences. */
+ if (ch >= '0' && ch <= '?') {
+ if (input_pair_private(&buf, &len, b, s, ch) == 1)
+ goto incomplete;
+ goto next;
+ }
+ if (ch >= '`' && ch <= '~') {
+ if (input_pair_standard(&buf, &len, b, s, ch) == 1)
+ goto incomplete;
+ goto next;
+ }
+ if (ch >= '@' && ch <= '_' && ch != '[') {
+ if (input_pair_control(&buf, &len, b, s, ch) == 1)
+ goto incomplete;
+ goto next;
+ }
+
+ /* If not CSI at this point, invalid. */
+ if (ch != '[')
+ continue;
+
+ if (input_control_sequence(&buf, &len, b, s) == 1)
+ goto incomplete;
+
+ next:
+ size = BUFFER_USED(b) - size;
+ log_debug("output is %zu bytes", size);
+ if (size > 0) /* XXX only one command? */
+ screen_sequence(s, BUFFER_IN(b) - size);
+ log_debug("remaining data %zu bytes", len);
+ }
+
+ return (buf - saved);
+
+incomplete:
+ *--buf = ch;
+ *--buf = '\e';
+ return (buf - saved);
+}
+
+/* Handle single control characters. */
+int
+input_control(unused u_char **buf, unused size_t *len,
+ struct buffer *b, struct screen *s, u_char ch)
+{
+ switch (ch) {
+ case '\0': /* NUL */
+ break;
+ case '\n': /* LF */
+ case '\r': /* CR */
+ case '\010': /* BS */
+ log_debug("control: %hhu", ch);
+ screen_character(s, ch);
+ input_store8(b, ch);
+ break;
+ default:
+ log_debug("unknown control: %c (%hhu)", ch, ch);
+ break;
+ }
+
+ return (0);
+}
+
+/* Translate a private two-character sequence. */
+int
+input_pair_private(unused u_char **buf, unused size_t *len,
+ unused struct buffer *b, unused struct screen *s, unused u_char ch)
+{
+ log_debug("private2: %c (%hhu)", ch, ch);
+
+ switch (ch) {
+ case '=': /* DECKPAM */
+ input_store_zero(b, CODE_KKEYPADON);
+ break;
+ case '>': /* DECKPNM*/
+ input_store_zero(b, CODE_KKEYPADOFF);
+ break;
+ default:
+ log_debug("unknown private2: %c (%hhu)", ch, ch);
+ break;
+ }
+
+ return (0);
+}
+
+/* Translate a standard two-character sequence. */
+int
+input_pair_standard(unused u_char **buf, unused size_t *len,
+ unused struct buffer *b, unused struct screen *s, u_char ch)
+{
+ log_debug("unknown standard2: %c (%hhu)", ch, ch);
+
+ return (0);
+}
+
+/* Translate a control two-character sequence. */
+int
+input_pair_control(u_char **buf, size_t *len,
+ struct buffer *b, unused struct screen *s, u_char ch)
+{
+ u_char *ptr;
+ size_t size;
+
+ log_debug("control2: %c (%hhu)", ch, ch);
+
+ switch (ch) {
+ case ']': /* window title */
+ if (*len < 3)
+ return (1);
+ ch = *(*buf)++; (*len)--;
+
+ /*
+ * Search MAXTITLELEN + 1 to allow space for the ;. The
+ * \007 is also included, but space is needed for a \0 so
+ * it doesn't need to be compensated for.
+ */
+ size = *len > MAXTITLELEN + 1 ? MAXTITLELEN + 1 : *len;
+ if ((ptr = memchr(*buf, '\007', size)) == NULL) {
+ log_debug("title not found in %zu bytes", size);
+ if (*len >= MAXTITLELEN + 1)
+ break;
+ (*buf)--; (*len)++;
+ return (1);
+ }
+ size = ptr - *buf;
+
+ /* A zero size means no ;, just skip the \007 and return. */
+ if (size == 0) {
+ (*buf)++; (*len)--;
+ break;
+ }
+
+ /* Set the title if appropriate. */
+ if (**buf == ';' && (ch == '0' || ch == '1')) {
+ log_debug("title found, length %zu bytes: %.*s",
+ size - 1, (int) size - 1, *buf + 1);
+ input_store_one(b, CODE_TITLE, size - 1);
+ buffer_write(b, *buf + 1, size - 1);
+ }
+
+ /* Skip the title; add one for the \007. */
+ (*buf) += size + 1;
+ (*len) -= size + 1;
+ break;
+ case 'M': /* RI */
+ input_store_one(b, CODE_CURSORUPSCROLL, 1);
+ break;
+ default:
+ log_debug("unknown control2: %c (%hhu)", ch, ch);
+ break;
+ }
+
+ return (0);
+}
+
+/* Translate a control sequence. */
+int
+input_control_sequence(
+ u_char **buf, size_t *len, struct buffer *b, struct screen *s)
+{
+ u_char code, private;
+ size_t used;
+ uint16_t *argv, ua, ub;
+ u_int argc, i;
+
+ used = input_sequence(*buf, *len, &code, &private, &argv, &argc);
+ if (used == 0) /* incomplete */
+ return (1);
+
+ (*buf) += used;
+ (*len) -= used;
+
+ if (code == '\0') /* invalid */
+ return (-1);
+
+ log_debug(
+ "sequence: %c (%hhu) [%c] [cx %u, cy %u, sx %u, sy %u]: %u", code,
+ code, private, s->cx + 1, s->cy + 1, s->sx + 1, s->sy + 1, argc);
+ for (i = 0; i < argc; i++)
+ log_debug("\targument %u: %u", i, argv[i]);
+
+ switch (code) {
+ case 'A': /* CUU */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_CURSORUP, ua);
+ break;
+ case 'B': /* CUD */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_CURSORDOWN, ua);
+ break;
+ case 'C': /* CUF */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_CURSORRIGHT, ua);
+ break;
+ case 'D': /* CUB */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_CURSORLEFT, ua);
+ break;
+ case 'P': /* DCH */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_DELETECHARACTER, ua);
+ break;
+ case 'M': /* DL */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_DELETELINE, ua);
+ break;
+ case '@': /* ICH */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_INSERTCHARACTER, ua);
+ break;
+ case 'L': /* IL */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_one(b, CODE_INSERTLINE, ua);
+ break;
+ case 'd': /* VPA */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_two(b, CODE_CURSORMOVE, ua, s->cx + 1);
+ break;
+ case 'G': /* HPA */
+ if (private != '\0')
+ break;
+ if (input_check_one(argv, argc, &ua, 1) != 0)
+ break;
+ if (ua == 0)
+ break;
+ input_store_two(b, CODE_CURSORMOVE, s->cy + 1, ua);
+ break;
+ case 'H': /* CUP */
+ case 'f': /* HVP */
+ if (private != '\0')
+ break;
+ if (input_check_two(argv, argc, &ua, 1, &ub, 1) != 0)
+ break;
+ if (ua == 0 || ub == 0)
+ break;
+ input_store_two(b, CODE_CURSORMOVE, ua, ub);
+ break;
+ case 'J': /* ED */
+ if (private != '\0')
+ break;
+ if (input_check_one2(argv, argc, &ua, 0, 0, 2) != 0)
+ break;
+ switch (ua) {
+ case 0:
+ input_store_zero(b, CODE_CLEARENDOFSCREEN);
+ break;
+ case 2:
+ input_store_zero(b, CODE_CLEARSCREEN);
+ break;
+ }
+ break;
+ case 'K': /* EL */
+ if (private != '\0')
+ break;
+ if (input_check_one2(argv, argc, &ua, 0, 0, 2) != 0)
+ break;
+ switch (ua) {
+ case 0:
+ input_store_zero(b, CODE_CLEARENDOFLINE);
+ break;
+ case 1:
+ input_store_zero(b, CODE_CLEARSTARTOFLINE);
+ break;
+ case 2:
+ input_store_zero(b, CODE_CLEARLINE);
+ break;
+ }
+ break;
+ case 'h': /* SM */
+ if (input_check_one(argv, argc, &ua, 0) != 0)
+ break;
+ switch (private) {
+ case '?':
+ switch (ua) {
+ case 1: /* GATM */
+ input_store_zero(b, CODE_KCURSORON);
+ break;
+ case 25: /* TCEM */
+ input_store_zero(b, CODE_CURSORON);
+ break;
+ default:
+ log_debug("unknown SM [%d]: %u", private, ua);
+ }
+ break;
+ case '\0':
+ switch (ua) {
+ case 4: /* IRM */
+ input_store_zero(b, CODE_INSERTON);
+ break;
+ case 34:
+ /* Cursor high visibility not supported. */
+ break;
+ default:
+ log_debug("unknown SM [%d]: %u", private, ua);
+ break;
+ }
+ break;
+ }
+ break;
+ case 'l': /* RM */
+ if (input_check_one(argv, argc, &ua, 0) != 0)
+ break;
+ switch (private) {
+ case '?':
+ switch (ua) {
+ case 1: /* GATM */
+ input_store_zero(b, CODE_KCURSOROFF);
+ break;
+ case 25: /* TCEM */
+ input_store_zero(b, CODE_CURSOROFF);
+ break;
+ default:
+ log_debug("unknown RM [%d]: %u", private, ua);
+ }
+ break;
+ case '\0':
+ switch (ua) {
+ case 4: /* IRM */
+ input_store_zero(b, CODE_INSERTOFF);
+ break;
+ case 34:
+ /* Cursor high visibility not supported. */
+ break;
+ default:
+ log_debug("unknown RM [%d]: %u", private, ua);
+ break;
+ }
+ break;
+ }
+ break;
+ case 'r': /* DECSTBM */
+ if (private != '\0')
+ break;
+ if (input_check_two(argv, argc,
+ &ua, s->ry_upper + 1, &ub, s->ry_lower + 1) != 0)
+ break;
+ if (ua == 0 || ub == 0 || ub < ua)
+ break;
+ input_store_two(b, CODE_SCROLLREGION, ua, ub);
+ break;
+ case 'm': /* SGR */
+ input_store_zero(b, CODE_ATTRIBUTES);
+ if (argc == 0) {
+ input_store16(b, 1);
+ input_store16(b, 0);
+ } else {
+ input_store16(b, argc);
+ for (i = 0; i < argc; i++) {
+ if (argv[i] == UINT16_MAX)
+ argv[i] = 0;
+ input_store16(b, argv[i]);
+ }
+ }
+ break;
+ default:
+ log_debug("unknown sequence: %c (%hhu)", code, code);
+ break;
+ }
+
+ if (argv != NULL) {
+ xfree(argv);
+ argv = NULL;
+ }
+
+ return (0);
+}
+
+/* Check for one argument. */
+int
+input_check_one(uint16_t *argv, u_int argc, uint16_t *a, uint16_t ad)
+{
+ *a = ad;
+ if (argc == 1) {
+ if (argv[0] != UINT16_MAX)
+ *a = argv[0];
+ } else if (argc != 0)
+ return (-1);
+ return (0);
+}
+
+/* Check for one argument with limits. */
+int
+input_check_one2(uint16_t *argv, u_int argc,
+ uint16_t *a, uint16_t ad, uint16_t al, uint16_t au)
+{
+ *a = ad;
+ if (argc == 1) {
+ if (argv[0] != UINT16_MAX)
+ *a = argv[0];
+ } else if (argc != 0)
+ return (-1);
+ if (*a < al || *a > au)
+ return (-1);
+ return (0);
+}
+
+/* Check for two arguments. */
+int
+input_check_two(uint16_t *argv, u_int argc,
+ uint16_t *a, uint16_t ad, uint16_t *b, uint16_t bd)
+{
+ *a = ad;
+ *b = bd;
+ if (argc == 1) {
+ if (argv[0] != UINT16_MAX)
+ *a = argv[0];
+ } else if (argc == 2) {
+ if (argv[0] != UINT16_MAX)
+ *a = argv[0];
+ if (argv[1] != UINT16_MAX)
+ *b = argv[1];
+ } else if (argc != 0)
+ return (-1);
+ return (0);
+}
+
+/* Store a code without arguments. */
+void
+input_store_zero(struct buffer *b, u_char code)
+{
+ input_store8(b, '\e');
+ input_store8(b, code);
+}
+
+/* Store a code with a single argument. */
+void
+input_store_one(struct buffer *b, u_char code, uint16_t ua)
+{
+ input_store8(b, '\e');
+ input_store8(b, code);
+ input_store16(b, ua);
+}
+
+/* Store a code with two arguments. */
+void
+input_store_two(struct buffer *b, u_char code, uint16_t ua, uint16_t ub)
+{
+ input_store8(b, '\e');
+ input_store8(b, code);
+ input_store16(b, ua);
+ input_store16(b, ub);
+}
+
+/* Write an 8-bit quantity to a buffer. */
+void
+input_store8(struct buffer *b, uint8_t n)
+{
+ buffer_write(b, &n, sizeof n);
+}
+
+/* Write a 16-bit argument to a buffer. */
+void
+input_store16(struct buffer *b, uint16_t n)
+{
+ buffer_write(b, &n, sizeof n);
+}
+
+/* Extract an 8-bit quantity from a buffer. */
+uint8_t
+input_extract8(struct buffer *b)
+{
+ uint8_t n;
+
+ buffer_read(b, &n, sizeof n);
+ return (n);
+}
+
+/* Extract a 16-bit argument from a pointer. */
+uint16_t
+input_extract16(struct buffer *b)
+{
+ uint16_t n;
+
+ buffer_read(b, &n, sizeof n);
+ return (n);
+}