summaryrefslogtreecommitdiff
path: root/usr.sbin/btctl/parse.y
diff options
context:
space:
mode:
authorUwe Stuehler <uwe@cvs.openbsd.org>2008-11-24 23:34:43 +0000
committerUwe Stuehler <uwe@cvs.openbsd.org>2008-11-24 23:34:43 +0000
commitb76ea0f34f6d26d8711d402b22cc4aa21d8bac6c (patch)
treeb8b93e3d3e731a3dd6da39f8b778856970f8b23f /usr.sbin/btctl/parse.y
parentb6679679f13b731ba42b47d62e0c1c9c441021fe (diff)
Bluetooth daemon and contrl utility, one for all, work in progress
Diffstat (limited to 'usr.sbin/btctl/parse.y')
-rw-r--r--usr.sbin/btctl/parse.y602
1 files changed, 602 insertions, 0 deletions
diff --git a/usr.sbin/btctl/parse.y b/usr.sbin/btctl/parse.y
new file mode 100644
index 00000000000..b9278fd1724
--- /dev/null
+++ b/usr.sbin/btctl/parse.y
@@ -0,0 +1,602 @@
+/* $OpenBSD: parse.y,v 1.1 2008/11/24 23:34:41 uwe Exp $ */
+
+/*
+ * Copyright (c) 2008 Uwe Stuehler <uwe@openbsd.org>
+ * Copyright (c) 2003 Can Erkin Acar
+ * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * 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 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/limits.h>
+#include <sys/queue.h>
+
+#include <dev/bluetooth/btdev.h>
+
+#include <bluetooth.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "btctl.h"
+#include "btd.h"
+
+TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
+
+static struct file {
+ TAILQ_ENTRY(file) entry;
+ FILE *stream;
+ char *name;
+ int lineno;
+ int errors;
+} *file, *topfile;
+
+struct file *pushfile(const char *);
+int popfile(void);
+int yyparse(void);
+int yylex(void);
+int yyerror(const char *, ...);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(int);
+int lungetc(int);
+int findeol(void);
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ bdaddr_t bdaddr;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+int ctl_sock;
+
+int exec_stmt(enum btctl_stmt_type, void *, size_t);
+void must_read(void *, size_t);
+void must_write(const void *, size_t);
+
+#define strbufcpy(_what, _from, _to) do { \
+ if (_from == NULL) \
+ memset(_to, 0, sizeof(_to)); \
+ else if (strlen(_from) <= sizeof(_to)) \
+ strncpy(_to, _from, sizeof(_to)); \
+ else { \
+ yyerror(_what " too long"); \
+ YYERROR; \
+ } \
+} while (0)
+
+%}
+
+%token INTERFACE NAME DISABLED
+%token ATTACH TYPE HID HSET NONE HF PIN
+%token ERROR
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.bdaddr> address
+%type <v.string> name_opt
+%type <v.number> disabled_opt
+%type <v.number> type_opt
+%type <v.string> pin_opt
+%%
+
+grammar
+: /* empty */
+| grammar '\n'
+| grammar main '\n'
+| grammar error '\n'
+{
+ file->errors++;
+}
+;
+
+main
+: INTERFACE address name_opt disabled_opt
+{
+ btctl_interface_stmt stmt;
+
+ bdaddr_copy(&stmt.addr, &$2);
+ strbufcpy("interface name", $3, stmt.name);
+ stmt.flags = $4 ? BTCTL_INTERFACE_DISABLED : 0;
+
+ switch (exec_stmt(BTCTL_INTERFACE_STMT, &stmt, sizeof(stmt))) {
+ case 0: break;
+ case EEXIST:
+ yyerror("interface %s is already defined",
+ bdaddr_any(&$2) ? "*" : bt_ntoa(&$2, NULL));
+ YYERROR;
+ default:
+ yyerror("could not add interface");
+ YYERROR;
+ }
+}
+| ATTACH address type_opt pin_opt
+{
+ btctl_attach_stmt stmt;
+
+ bdaddr_copy(&stmt.addr, &$2);
+ stmt.type = $3;
+ strbufcpy("PIN code", $4, stmt.pin);
+
+ switch (exec_stmt(BTCTL_ATTACH_STMT, &stmt, sizeof(stmt))) {
+ case 0: break;
+ case EEXIST:
+ yyerror("device %s is already defined",
+ bdaddr_any(&$2) ? "*" : bt_ntoa(&$2, NULL));
+ YYERROR;
+ default:
+ yyerror("could not add device");
+ YYERROR;
+ }
+}
+;
+
+name_opt
+: /* empty */
+{ $$ = NULL; }
+| NAME STRING
+{ $$ = $2; }
+;
+
+disabled_opt
+: /* empty */
+{ $$ = 0; }
+| DISABLED
+{ $$ = 1; }
+;
+
+type_opt
+: /* empty */
+{ $$ = BTDEV_NONE; }
+| TYPE NONE
+{ $$ = BTDEV_NONE; }
+| TYPE HID
+{ $$ = BTDEV_HID; }
+| TYPE HSET
+{ $$ = BTDEV_HSET; }
+| TYPE HF
+{ $$ = BTDEV_HF; }
+;
+
+pin_opt
+: /* empty */
+{ $$ = NULL; }
+| PIN STRING
+{
+ if (($$ = calloc(HCI_PIN_SIZE, sizeof(uint8_t))) == NULL)
+ fatal("pin_opt calloc");
+
+ strlcpy($$, $2, HCI_PIN_SIZE);
+ free($2);
+}
+;
+
+address
+: STRING
+{
+ if (strcmp($1, "*")) {
+ bt_aton($1, &$$);
+
+ if (bdaddr_any(&$$)) {
+ /* 0:0:0:0:0:0 could be misinterpreted */
+ yyerror("invalid address '%s'", $1);
+ free($1);
+ YYERROR;
+ }
+
+ free($1);
+ } else
+ bdaddr_copy(&$$, BDADDR_ANY);
+}
+;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+ char *nfmt;
+
+ file->errors++;
+ va_start(ap, fmt);
+ if (asprintf(&nfmt, "%s:%d: %s", file->name, yylval.lineno, fmt) == -1)
+ fatalx("yyerror asprintf");
+ vlog(LOG_CRIT, nfmt, ap);
+ va_end(ap);
+ free(nfmt);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ { "attach", ATTACH},
+ { "disabled", DISABLED},
+ { "hf", HF},
+ { "hid", HID},
+ { "hset", HSET},
+ { "interface", INTERFACE},
+ { "name", NAME},
+ { "none", NONE},
+ { "pin", PIN},
+ { "type", TYPE}
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+char *parsebuf;
+int parseindex;
+char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ if (quotec) {
+ if ((c = getc(file->stream)) == EOF) {
+ yyerror("reached end of file while parsing "
+ "quoted string");
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ return (quotec);
+ }
+ return (c);
+ }
+
+ while ((c = getc(file->stream)) == '\\') {
+ next = getc(file->stream);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+
+ while (c == EOF) {
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ c = getc(file->stream);
+ }
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ if (pushback_index)
+ c = pushback_buffer[--pushback_index];
+ else
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ char buf[8096];
+ char *p;
+ int quotec, next, c;
+ int token;
+
+ p = buf;
+ while ((c = lgetc(0)) == ' ' || c == '\t')
+ ; /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#')
+ while ((c = lgetc(0)) != '\n' && c != EOF)
+ ; /* nothing */
+
+ switch (c) {
+ case '\'':
+ case '"':
+ quotec = c;
+ while (1) {
+ if ((c = lgetc(quotec)) == EOF)
+ return (0);
+ if (c == '\n') {
+ file->lineno++;
+ continue;
+ } else if (c == '\\') {
+ if ((next = lgetc(quotec)) == EOF)
+ return (0);
+ if (next == quotec || c == ' ' || c == '\t')
+ c = next;
+ else if (next == '\n')
+ continue;
+ else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = (char)c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ fatal("yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && isdigit(c));
+ lungetc(c);
+ if (p == buf + 1 && buf[0] == '-')
+ goto nodigits;
+ if (c == EOF || allowed_to_end_number(c)) {
+ const char *errstr = NULL;
+
+ *p = '\0';
+ yylval.v.number = strtonum(buf, LLONG_MIN,
+ LLONG_MAX, &errstr);
+ if (errstr) {
+ yyerror("\"%s\" invalid number: %s",
+ buf, errstr);
+ return (findeol());
+ }
+ return (NUMBER);
+ } else {
+nodigits:
+ while (p > buf + 1)
+ lungetc(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && x != '<' && x != '>' && \
+ x != '!' && x != '=' && x != '/' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_' || c == '*') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ fatal("yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+struct file *
+pushfile(const char *name)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL ||
+ (nfile->name = strdup(name)) == NULL) {
+ log_warn("malloc");
+ return (NULL);
+ }
+ if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ log_warn("%s", nfile->name);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+ nfile->lineno = 1;
+ TAILQ_INSERT_TAIL(&files, nfile, entry);
+ return (nfile);
+}
+
+int
+popfile(void)
+{
+ struct file *prev;
+
+ if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+ prev->errors += file->errors;
+
+ TAILQ_REMOVE(&files, file, entry);
+ fclose(file->stream);
+ free(file->name);
+ free(file);
+ file = prev;
+ return (file ? 0 : EOF);
+}
+
+int
+parse_config(const char *filename, int fd)
+{
+ enum btctl_stmt_type stmt;
+ int errors = 0;
+
+ ctl_sock = fd;
+
+ if ((errno = exec_stmt(BTCTL_CONFIG, NULL, 0)) != 0) {
+ fatal("BTCTL_CONFIG");
+ return -1;
+ }
+
+ if ((file = pushfile(filename)) == NULL)
+ return (-1);
+
+ topfile = file;
+ yyparse();
+ errors = file->errors;
+ popfile();
+
+ stmt = errors ? BTCTL_ROLLBACK : BTCTL_COMMIT;
+ if ((errno = exec_stmt(stmt, NULL, 0)) != 0)
+ fatal(errors ? "BTCTL_ROLLBACK" : "BTCTL_CONFIG");
+
+ return (errors ? -1 : 0);
+}
+
+int
+exec_stmt(enum btctl_stmt_type stmt_type, void *data, size_t size)
+{
+ int err;
+
+ must_write(&stmt_type, sizeof(stmt_type));
+ must_write(data, size);
+ must_read(&err, sizeof(err));
+
+ return err;
+}
+
+/* Read data with the assertion that it all must come through, or
+ * else abort the process. Based on atomicio() from openssh. */
+void
+must_read(void *buf, size_t n)
+{
+ char *s = buf;
+ ssize_t res, pos = 0;
+
+ while (n > pos) {
+ res = read(ctl_sock, s + pos, n - pos);
+ switch (res) {
+ case -1:
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ /* FALLTHROUGH */
+ case 0:
+ fatalx("short read");
+ default:
+ pos += res;
+ }
+ }
+}
+
+/* Write data with the assertion that it all has to be written, or
+ * else abort the process. Based on atomicio() from openssh. */
+void
+must_write(const void *buf, size_t n)
+{
+ const char *s = buf;
+ ssize_t res, pos = 0;
+
+ while (n > pos) {
+ res = write(ctl_sock, s + pos, n - pos);
+ switch (res) {
+ case -1:
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ /* FALLTHROUGH */
+ case 0:
+ fatalx("short write");
+ default:
+ pos += res;
+ }
+ }
+}