commit - 7a89af2b85e5db320ff2be364cb6c0cc0473663d
commit + 423f02f5dbaa7b68b446483dc25e22b00db8a07e
blob - 024927cfcf8dc33f61b3656e18d9a92471c0f326
blob + ad3f4c31452cba5bba00a25368429af6966e770a
--- TODO
+++ TODO
user and not by root.
- tweak iounit so it's always lower than the choosen msize
+
+ - handle message bigger than MAX_IMSGSIZE - IMSG_HEADER_SIZE. One
+ way to do that would be to *not* use asynchroonus imsgs in client.c
+ but synchronous I/O: this way, once a message has been processed,
+ we can just receive the next in the same function (i.e. twrite) and
+ go ahead.
blob - d3484c718bade31e04b234150c8dd206f4ac75d2
blob + ea9b319fd27f64cd69cd2b5366f4fae9b549471e
--- client.c
+++ client.c
#include "sandbox.h"
#include "utils.h"
+/*
+ * XXX: atm is difficult to accept messages bigger than MAX_IMSGSIZE
+ * minus IMSG_HEADER_SIZE, we need something to split messages into
+ * chunks and receive them one by the other.
+ *
+ * CLIENT_MSIZE is thus the maximum message size we can handle now.
+ */
+#define CLIENT_MSIZE (MAX_IMSGSIZE - IMSG_HEADER_SIZE)
+
#define DEBUG_PACKETS 0
/* straight outta /src/usr.bin/ssh/scp.c */
((sizeof(type) == 4 && (val) > INT32_MAX) || \
(sizeof(type) == 8 && (val) > INT64_MAX) || \
(sizeof(type) != 4 && sizeof(type) != 8))
-
-struct qid {
- uint64_t path;
- uint32_t vers;
- uint8_t type;
-};
STAILQ_HEAD(dirhead, dir) dirs;
struct dir {
/* version matched */
handshaked = 1;
- msize = MIN(msize, MSIZE9P);
+ msize = MIN(msize, CLIENT_MSIZE);
client_send_listener(IMSG_MSIZE, &msize, sizeof(msize));
np_version(hdr->tag, msize, VERSION9P);
return;
}
if (TYPE_OVERFLOW(off_t, off)) {
- log_warnx("unexpected size_t size");
+ log_warnx("unexpected off_t size");
np_error(hdr->tag, "invalid offset");
return;
}
if (off == 0 && f->offset != 0) {
rewinddir(f->d);
f->offset = 0;
+ evbuffer_drain(f->evb, EVBUFFER_LENGTH(f->evb));
}
if (off != f->offset) {
blob - 6b3f71d033c44d0f05037b8cebd54c1c3e79580e
blob + c218227956175b3d089238ca1934f43f54e80105
--- ftp.c
+++ ftp.c
#include <sys/types.h>
#include <sys/socket.h>
+#include <assert.h>
#include <netdb.h>
#include <limits.h>
#include <stdio.h>
#include <tls.h>
#include <unistd.h>
-#if HAVE_READLINE
+#if HAVE_LIBREADLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
#include "9pclib.h"
#include "kamid.h"
#include "utils.h"
struct tls_config *tlsconf;
struct tls *ctx;
int sock;
+struct evbuffer *buf;
+struct evbuffer *dirbuf;
+uint32_t msize;
+int bell;
#define PWDFID 0
-#if HAVE_READLINE
+#define ASSERT_EMPTYBUF() assert(EVBUFFER_LENGTH(buf) == 0)
+
+#if HAVE_LIBREADLINE
static char *
read_line(const char *prompt)
{
size_t linesize = 0;
ssize_t linelen;
+ printf("%s", prompt);
+ fflush(stdout);
+
linelen = getline(&line, &linesize, stdin);
if (linelen == -1)
return NULL;
}
static void
+do_send(void)
+{
+ ssize_t r;
+
+ while (EVBUFFER_LENGTH(evb) != 0) {
+ r = tls_write(ctx, EVBUFFER_DATA(evb), EVBUFFER_LENGTH(evb));
+ switch (r) {
+ case TLS_WANT_POLLIN:
+ case TLS_WANT_POLLOUT:
+ continue;
+ case -1:
+ errx(1, "tls: %s", tls_error(ctx));
+ default:
+ evbuffer_drain(evb, r);
+ }
+ }
+}
+
+static void
+mustread(void *d, size_t len)
+{
+ ssize_t r;
+
+ while (len != 0) {
+ switch (r = tls_read(ctx, d, len)) {
+ case TLS_WANT_POLLIN:
+ case TLS_WANT_POLLOUT:
+ continue;
+ case -1:
+ errx(1, "tls: %s", tls_error(ctx));
+ default:
+ d += r;
+ len -= r;
+ }
+ }
+}
+
+static void
+recv_msg(void)
+{
+ uint32_t len;
+ ssize_t r;
+ char tmp[BUFSIZ];
+
+ mustread(&len, sizeof(len));
+ len = le32toh(len);
+ if (len < HEADERSIZE)
+ errx(1, "read message of invalid length %d", len);
+
+ len -= 4; /* skip the length just read */
+
+ while (len != 0) {
+ switch (r = tls_read(ctx, tmp, sizeof(tmp))) {
+ case TLS_WANT_POLLIN:
+ case TLS_WANT_POLLOUT:
+ continue;
+ case -1:
+ errx(1, "tls: %s", tls_error(ctx));
+ default:
+ len -= r;
+ evbuffer_add(buf, tmp, r);
+ }
+ }
+}
+
+static uint64_t
+np_read64(struct evbuffer *buf)
+{
+ uint64_t n;
+
+ evbuffer_remove(buf, &n, sizeof(n));
+ return le64toh(n);
+}
+
+static uint32_t
+np_read32(struct evbuffer *buf)
+{
+ uint32_t n;
+
+ evbuffer_remove(buf, &n, sizeof(n));
+ return le32toh(n);
+}
+
+static uint16_t
+np_read16(struct evbuffer *buf)
+{
+ uint16_t n;
+
+ evbuffer_remove(buf, &n, sizeof(n));
+ return le16toh(n);
+}
+
+static uint16_t
+np_read8(struct evbuffer *buf)
+{
+ uint8_t n;
+
+ evbuffer_remove(buf, &n, sizeof(n));
+ return n;
+}
+
+static char *
+np_readstr(struct evbuffer *buf)
+{
+ uint16_t len;
+ char *str;
+
+ len = np_read16(buf);
+ assert(EVBUFFER_LENGTH(buf) >= len);
+
+ if ((str = calloc(1, len+1)) == NULL)
+ err(1, "calloc");
+ evbuffer_remove(buf, str, len);
+ return str;
+}
+
+static void
+np_read_qid(struct evbuffer *buf, struct qid *qid)
+{
+ assert(EVBUFFER_LENGTH(buf) >= QIDSIZE);
+
+ qid->type = np_read8(buf);
+ qid->vers = np_read32(buf);
+ qid->path = np_read64(buf);
+}
+
+static void
+expect(uint8_t type)
+{
+ uint8_t t;
+
+ t = np_read8(buf);
+ if (t == type)
+ return;
+
+ if (t == Terror) {
+ char *err;
+
+ err = np_readstr(buf);
+ errx(1, "expected %s, got error %s",
+ pp_msg_type(type), err);
+ }
+
+ errx(1, "expected %s, got msg type %s",
+ pp_msg_type(type), pp_msg_type(t));
+}
+
+static void
+expect2(uint8_t type, uint16_t tag)
+{
+ uint16_t t;
+
+ expect(type);
+
+ t = np_read16(buf);
+ if (t == tag)
+ return;
+
+ errx(1, "expected tag 0x%x, got 0x%x", tag, t);
+}
+
+static void
do_version(void)
{
+ char *version;
+
tversion(VERSION9P, MSIZE9P);
- /* TODO: get reply */
+ do_send();
+ recv_msg();
+ expect2(Rversion, NOTAG);
+
+ msize = np_read32(buf);
+ version = np_readstr(buf);
+
+ if (msize > MSIZE9P)
+ errx(1, "got unexpected msize: %d", msize);
+ if (strcmp(version, VERSION9P))
+ errx(1, "unexpected 9p version: %s", version);
+
+ free(version);
+ ASSERT_EMPTYBUF();
}
static void
do_attach(const char *path)
{
+ const char *user;
+ struct qid qid;
+
if (path == NULL)
path = "/";
+ if ((user = getenv("USER")) == NULL)
+ user = "flan";
- /* TODO: do attach */
+ tattach(PWDFID, NOFID, user, path);
+ do_send();
+ recv_msg();
+ expect2(Rattach, iota_tag);
+ np_read_qid(buf, &qid);
+
+ ASSERT_EMPTYBUF();
+}
+
+static uint32_t
+do_open(uint32_t fid, uint8_t mode)
+{
+ struct qid qid;
+ uint32_t iounit;
+
+ topen(fid, mode);
+ do_send();
+ recv_msg();
+ expect2(Ropen, iota_tag);
+
+ np_read_qid(buf, &qid);
+ iounit = np_read32(buf);
+
+ ASSERT_EMPTYBUF();
+
+ return iounit;
}
static void
+do_clunk(uint32_t fid)
+{
+ tclunk(fid);
+ do_send();
+ recv_msg();
+ expect2(Rclunk, iota_tag);
+
+ ASSERT_EMPTYBUF();
+}
+
+static void
+dup_fid(int fid, int nfid)
+{
+ uint16_t nwqid;
+
+ twalk(fid, nfid, NULL, 0);
+ do_send();
+ recv_msg();
+ expect2(Rwalk, iota_tag);
+
+ nwqid = np_read16(buf);
+ assert(nwqid == 0);
+
+ ASSERT_EMPTYBUF();
+}
+
+static void
do_connect(const char *connspec, const char *path)
{
int handshake;
free(host);
}
+static void
+cmd_bell(int argc, const char **argv)
+{
+ if (argc == 0) {
+ bell = !bell;
+ if (bell)
+ puts("bell mode enabled");
+ else
+ puts("bell mode disabled");
+ return;
+ }
+
+ if (argc != 1)
+ goto usage;
+
+ if (!strcmp(*argv, "on")) {
+ bell = 1;
+ puts("bell mode enabled");
+ return;
+ }
+
+ if (!strcmp(*argv, "off")) {
+ bell = 0;
+ puts("bell mode disabled");
+ return;
+ }
+
+usage:
+ printf("bell [on | off]\n");
+}
+
+static void
+cmd_bye(int argc, const char **argv)
+{
+ log_warnx("bye\n");
+ exit(0);
+}
+
+static void
+cmd_ls(int argc, const char **argv)
+{
+ uint64_t off = 0;
+ uint32_t len;
+
+ if (argc != 0) {
+ printf("ls don't take arguments (yet)\n");
+ return;
+ }
+
+ dup_fid(PWDFID, 1);
+ do_open(1, KOREAD);
+
+ evbuffer_drain(dirbuf, EVBUFFER_LENGTH(dirbuf));
+
+ for (;;) {
+ tread(1, off, BUFSIZ);
+ do_send();
+ recv_msg();
+ expect2(Rread, iota_tag);
+
+ len = np_read32(buf);
+ if (len == 0)
+ break;
+
+ evbuffer_add_buffer(dirbuf, buf);
+ off += len;
+
+ ASSERT_EMPTYBUF();
+ }
+
+ while (EVBUFFER_LENGTH(dirbuf) != 0) {
+ struct qid qid;
+ uint64_t len;
+ uint16_t size;
+ char *name;
+
+ size = np_read16(dirbuf);
+ assert(size <= EVBUFFER_LENGTH(dirbuf));
+
+ np_read16(dirbuf); /* skip type */
+ np_read32(dirbuf); /* skip dev */
+
+ np_read_qid(dirbuf, &qid);
+ printf("%s ", pp_qid_type(qid.type));
+
+ np_read32(dirbuf); /* skip mode */
+ np_read32(dirbuf); /* skip atime */
+ np_read32(dirbuf); /* skip mtime */
+
+ len = np_read64(dirbuf);
+ printf("%llu ", (unsigned long long)len);
+
+ name = np_readstr(dirbuf);
+ printf("%s\n", name);
+ free(name);
+
+ free(np_readstr(dirbuf)); /* skip uid */
+ free(np_readstr(dirbuf)); /* skip gid */
+ free(np_readstr(dirbuf)); /* skip muid */
+ }
+
+ do_clunk(1);
+}
+
+static void
+cmd_verbose(int argc, const char **argv)
+{
+ if (argc == 0) {
+ log_setverbose(!log_getverbose());
+ if (log_getverbose())
+ puts("verbose mode enabled");
+ else
+ puts("verbose mode disabled");
+ return;
+ }
+
+ if (argc != 1)
+ goto usage;
+
+ if (!strcmp(*argv, "on")) {
+ log_setverbose(1);
+ puts("verbose mode enabled");
+ return;
+ }
+
+ if (!strcmp(*argv, "off")) {
+ log_setverbose(0);
+ puts("verbose mode disabled");
+ return;
+ }
+
+usage:
+ printf("verbose [on | off]\n");
+}
+
+static void
+excmd(int argc, const char **argv)
+{
+ struct cmd {
+ const char *name;
+ void (*fn)(int, const char **);
+ } cmds[] = {
+ {"bell", cmd_bell},
+ {"bye", cmd_bye},
+ {"ls", cmd_ls},
+ {"quit", cmd_bye},
+ {"verbose", cmd_verbose},
+ };
+ size_t i;
+
+ if (argc == 0)
+ return;
+ for (i = 0; i < nitems(cmds); ++i) {
+ if (!strcmp(cmds[i].name, *argv)) {
+ cmds[i].fn(argc-1, argv+1);
+ return;
+ }
+ }
+
+ log_warnx("unknown command %s", *argv);
+}
+
int
main(int argc, char **argv)
{
int ch;
log_init(1, LOG_DAEMON);
- log_setverbose(1);
+ log_setverbose(0);
log_procinit(getprogname());
while ((ch = getopt(argc, argv, "C:cK:")) != -1) {
if ((evb = evbuffer_new()) == NULL)
fatal("evbuffer_new");
+ if ((buf = evbuffer_new()) == NULL)
+ fatal("evbuffer_new");
+
+ if ((dirbuf = evbuffer_new()) == NULL)
+ fatal("evbuferr_new");
+
do_connect(argv[0], argv[1]);
+ /* cmd_ls(0, NULL); */
+
for (;;) {
- char *line;
+ int argc = 0;
+ char *line, *argv[16] = {0}, **ap;
- if ((line = read_line("ftp> ")) == NULL)
+ if ((line = read_line("kamiftp> ")) == NULL)
break;
- printf("read: %s\n", line);
+
+ for (argc = 0, ap = argv; ap < &argv[15] &&
+ (*ap = strsep(&line, " \t")) != NULL;) {
+ if (**ap != '\0')
+ ap++, argc++;
+ }
+ excmd(argc, (const char **)argv);
+
+ if (bell) {
+ printf("\a");
+ fflush(stdout);
+ }
+
+ free(line);
}
printf("\n");
blob - 9fa99a3c7244b99f835976cae7bb1dc48e7cdfda
blob + 9675e6ac175f820b3eec23853740698a562b017b
--- kamid.h
+++ kamid.h
uint16_t tag;
};
+struct qid {
+ uint64_t path;
+ uint32_t vers;
+ uint8_t type;
+};
+
/* useful constants */
#define HEADERSIZE (4 + 1 + 2)
#define VERSION9P "9P2000"
blob - 1b88cb698ca30ff9bb277d2af0697b6fa3075734
blob + bbb82ddbd18431c0c4cc0cc05d9e9273e5bf4d26
--- kamiftp.1
+++ kamiftp.1
.Ar key
to be used during the TLS handshake.
.El
+.Pp
+The following commands are recognized by
+.Nm :
+.Bl -tag -width Ds
+.It Ic bell Oo Cm on | off Oc
+Request terminal to sound a bell after each command.
+Without arguments toggle the current state.
+.It Ic bye
+Terminate the session.
+Synomym of
+.Ic quit .
+.It Ic ls
+List the file in the current directory
+.It Ic quit
+Terminate the session.
+Synomym of
+.Ic bye .
+.It Ic verbose Oo Cm on | off Oc
+Print verbose information.
+Without arguments toggle the current state.
+.El
.Sh SEE ALSO
-.Xr 9p 7
+.Xr 9p 7 ,
.Xr kamid 8
.Sh AUTHORS
The
blob - 6c7171faab707741aba6595e903666e9a77964eb
blob + 504329156629b6374f7a18342d97b66bfbb16659
--- kamirepl.c
+++ kamirepl.c
static void excmd_write(const char **, int);
static void excmd(const char **, int);
-static const char *pp_qid_type(uint8_t);
static void pp_qid(const uint8_t *, uint32_t);
static void pp_msg(uint32_t, uint8_t, uint16_t, const uint8_t *);
static void handle_9p(const uint8_t *, size_t);
}
log_warnx("Unknown command %s", *argv);
-}
-
-static const char *
-pp_qid_type(uint8_t type)
-{
- switch (type) {
- case QTDIR: return "dir";
- case QTAPPEND: return "append-only";
- case QTEXCL: return "exclusive";
- case QTMOUNT: return "mounted-channel";
- case QTAUTH: return "authentication";
- case QTTMP: return "non-backed-up";
- case QTSYMLINK: return "symlink";
- case QTFILE: return "file";
- }
-
- return "unknown";
}
static void
blob - 1e9b8c93e6bd2a5ee92763875fd80ccd05987ef8
blob + 6da049b0758a48894158408f942e91823baa3ade
--- ninepscript.5
+++ ninepscript.5
scope.
.It comparison
The syntax is
-.Ql Ar expression Cm == Ar expression
+.Bd -literal -offset Ds
+.Ar expression Cm == Ar expression
+.Ar expression Cm <= Ar expression
+.Ed
+.Pp
and yields a true value if the two expressions are considered to be
+respectively
.Sq equal
-or a false value otherwise.
+or
+.Sq lesser equal ,
+a false value otherwise.
Two values are equal if they are both number and represent the same
value
.Pq regardless of the size
.Ar procedure
with the given
.Ar arguments .
-.It Ic assert Ar expression
+.It Ic assert Ar comparison
Evaluate
-.Ar expression
+.Ar comparison
and if it not yields a true-ish value terminate the current running
test and mark it as failed.
Multiple assertion can be done in one single
block using the following syntax:
.Bd -literal -offset Ds
.Ic assert (
- expression_1
- expression_2
+ comparison_1
+ comparison_2
...
- expression_n
+ comparison_n
)
.Ed
.Pp
Note that newlines are mandatory after every
-.Ar expression
+.Ar comparison
in this case.
.It Ic should-fail Ar expression Op : Ar reason
Evaluate
blob - e856da804353a10ea38daeea0e7396e071ecbc58
blob + 3b22364359c6f834886ffda7912b580339df0524
--- np.y
+++ np.y
* interested in checking all the possibilities here.
*/
cexpr : literal | varref | funcall | faccess ;
-check : cexpr '=' '=' cexpr { $$ = op_cmp_eq($1, $4); } ;
+check : cexpr '=' '=' cexpr { $$ = op_cmp_eq($1, $4); }
+ | cexpr '<' '=' cexpr { $$ = op_cmp_leq($1, $4); }
+ ;
expr : literal | funcall | varref | check | cast | faccess | vargs ;
blob - 68fd63cc1792b8a8158fe5a817c2794afa5ff875
blob + c5b25c870e154f06143d64be5bab83c66694c253
--- regress/lib.9ps
+++ regress/lib.9ps
assert (
m.type == Rversion
m.tag == notag
- m.msize == msize
+ m.msize <= msize
# m.version == version
)
blob - 08f401c4930cd4e4cf94ab0ca4ece3cff6e04a93
blob + 8c80fde28a266bf7c9947cfd555b284fd9d5f801
--- regress/misc-suite.9ps
+++ regress/misc-suite.9ps
assert (
m.type == Rversion
m.tag == notag
- m.msize == msize
+ m.msize <= msize
)
fid1 = 0
blob - 4fb5bc0910430077c25c774212922762678ad98f
blob + 8b480c2ff8bd568c4adadc410eed846e8f7696d2
--- script.c
+++ script.c
free_op_rec(op->v.cast.expr);
break;
case OP_CMP_EQ:
- free_op_rec(op->v.cmp_eq.a);
- free_op_rec(op->v.cmp_eq.b);
+ case OP_CMP_LEQ:
+ free_op_rec(op->v.bin_cmp.a);
+ free_op_rec(op->v.bin_cmp.b);
break;
case OP_FACCESS:
free_op_rec(op->v.faccess.expr);
struct op *op;
op = newop(OP_CMP_EQ);
- op->v.cmp_eq.a = a;
- op->v.cmp_eq.b = b;
+ op->v.bin_cmp.a = a;
+ op->v.bin_cmp.b = b;
+
+ return op;
+}
+
+struct op *
+op_cmp_leq(struct op *a, struct op *b)
+{
+ struct op *op;
+ op = newop(OP_CMP_LEQ);
+ op->v.bin_cmp.a = a;
+ op->v.bin_cmp.b = b;
+
return op;
}
case V_SYM:
return !strcmp(a->v.str, b->v.str);
}
+
+ return 0;
+}
+int
+val_leq(struct value *a, struct value *b)
+{
+ if (val_isnum(a) && val_isnum(b))
+ return val_tonum(a) <= val_tonum(b);
return 0;
}
}
break;
case OP_CMP_EQ:
- pp_op(op->v.cmp_eq.a);
+ pp_op(op->v.bin_cmp.a);
printf(" == ");
- pp_op(op->v.cmp_eq.b);
+ pp_op(op->v.bin_cmp.b);
break;
+ case OP_CMP_LEQ:
+ pp_op(op->v.bin_cmp.a);
+ printf(" <= ");
+ pp_op(op->v.bin_cmp.b);
+ break;
case OP_FACCESS:
pp_op(op->v.faccess.expr);
printf(".%s", op->v.faccess.field);
break;
case OP_CMP_EQ:
- if ((ret = eval(op->v.cmp_eq.a)) != EVAL_OK)
+ if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK)
return ret;
- if ((ret = eval(op->v.cmp_eq.b)) != EVAL_OK)
+ if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK)
return ret;
popv(&b);
popv(&a);
pushbool(val_eq(&a, &b));
+ break;
+
+ case OP_CMP_LEQ:
+ if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK)
+ return ret;
+ if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK)
+ return ret;
+ popv(&b);
+ popv(&a);
+ pushbool(val_leq(&a, &b));
break;
case OP_FACCESS:
blob - 8e094a0a1f2b051994a39fe747d5803f68d7bb85
blob + f7b6a07ae2b5f8ecc35bea64130cf0b6af2af2c0
--- script.h
+++ script.h
OP_VAR,
OP_CAST,
OP_CMP_EQ,
+ OP_CMP_LEQ,
OP_FACCESS,
OP_SFAIL,
OP_VARGS,
struct {
struct op *a;
struct op *b;
- } cmp_eq;
+ } bin_cmp;
struct {
struct op *expr;
char *field;
struct op *op_lit_str(char *);
struct op *op_lit_num(uint64_t);
struct op *op_cmp_eq(struct op *, struct op *);
+struct op *op_cmp_leq(struct op *, struct op *);
struct op *op_cast(struct op *, int);
struct op *op_faccess(struct op *, char *);
struct op *op_sfail(struct op *, char *);
int val_isnum(struct value *);
int64_t val_tonum(struct value *);
int val_eq(struct value *, struct value *);
+int val_leq(struct value *, struct value *);
int val_cast(struct value *, int);
int val_faccess(struct value *, const char *, struct value *);
void pp_op(struct op *);
blob - 066fdb3d2905942ccc7328d712194ff6a03fc1a8
blob + 50b83e74a8d469e6fa1c0906a70fe5fd7fd61ca9
--- utils.c
+++ utils.c
}
}
+const char *
+pp_qid_type(uint8_t type)
+{
+ switch (type) {
+ case QTDIR: return "dir";
+ case QTAPPEND: return "append-only";
+ case QTEXCL: return "exclusive";
+ case QTMOUNT: return "mounted-channel";
+ case QTAUTH: return "authentication";
+ case QTTMP: return "non-backed-up";
+ case QTSYMLINK: return "symlink";
+ case QTFILE: return "file";
+ }
+
+ return "unknown";
+}
+
static void
hexdump_ppline(int x, uint8_t *data, size_t len)
{
blob - 78cf40028b8b5683ce038ac20a23e0920073697f
blob + 55b0bbe929a564d5e891da783db89f48371265d8
--- utils.h
+++ utils.h
void *xmemdup(const void *, size_t);
const char *pp_msg_type(uint8_t);
+const char *pp_qid_type(uint8_t);
void hexdump(const char *, uint8_t *data, size_t len);