1269 lines
35 KiB
C
1269 lines
35 KiB
C
|
/*
|
||
|
* Copyright 2016 Jakub Klama <jceel@FreeBSD.org>
|
||
|
* All rights reserved
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted providing that the following conditions
|
||
|
* are met:
|
||
|
* 1. Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <errno.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <unistd.h>
|
||
|
#include <assert.h>
|
||
|
#include <inttypes.h>
|
||
|
#include <sys/param.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/uio.h>
|
||
|
#if defined(__FreeBSD__)
|
||
|
#include <sys/sbuf.h>
|
||
|
#else
|
||
|
#include "sbuf/sbuf.h"
|
||
|
#endif
|
||
|
#include "lib9p.h"
|
||
|
#include "fcall.h"
|
||
|
#include "linux_errno.h"
|
||
|
|
||
|
#ifdef __APPLE__
|
||
|
#define GETGROUPS_GROUP_TYPE_IS_INT
|
||
|
#endif
|
||
|
|
||
|
#define N(ary) (sizeof(ary) / sizeof(*ary))
|
||
|
|
||
|
/* See l9p_describe_bits() below. */
|
||
|
struct descbits {
|
||
|
uint64_t db_mask; /* mask value */
|
||
|
uint64_t db_match; /* match value */
|
||
|
const char *db_name; /* name for matched value */
|
||
|
};
|
||
|
|
||
|
|
||
|
static bool l9p_describe_bits(const char *, uint64_t, const char *,
|
||
|
const struct descbits *, struct sbuf *);
|
||
|
static void l9p_describe_fid(const char *, uint32_t, struct sbuf *);
|
||
|
static void l9p_describe_mode(const char *, uint32_t, struct sbuf *);
|
||
|
static void l9p_describe_name(const char *, char *, struct sbuf *);
|
||
|
static void l9p_describe_perm(const char *, uint32_t, struct sbuf *);
|
||
|
static void l9p_describe_lperm(const char *, uint32_t, struct sbuf *);
|
||
|
static void l9p_describe_qid(const char *, struct l9p_qid *, struct sbuf *);
|
||
|
static void l9p_describe_l9stat(const char *, struct l9p_stat *,
|
||
|
enum l9p_version, struct sbuf *);
|
||
|
static void l9p_describe_statfs(const char *, struct l9p_statfs *,
|
||
|
struct sbuf *);
|
||
|
static void l9p_describe_time(struct sbuf *, const char *, uint64_t, uint64_t);
|
||
|
static void l9p_describe_readdir(struct sbuf *, struct l9p_f_io *);
|
||
|
static void l9p_describe_size(const char *, uint64_t, struct sbuf *);
|
||
|
static void l9p_describe_ugid(const char *, uint32_t, struct sbuf *);
|
||
|
static void l9p_describe_getattr_mask(uint64_t, struct sbuf *);
|
||
|
static void l9p_describe_unlinkat_flags(const char *, uint32_t, struct sbuf *);
|
||
|
static const char *lookup_linux_errno(uint32_t);
|
||
|
|
||
|
/*
|
||
|
* Using indexed initializers, we can have these occur in any order.
|
||
|
* Using adjacent-string concatenation ("T" #name, "R" #name), we
|
||
|
* get both Tfoo and Rfoo strings with one copy of the name.
|
||
|
* Alas, there is no stupid cpp trick to lowercase-ify, so we
|
||
|
* have to write each name twice. In which case we might as well
|
||
|
* make the second one a string in the first place and not bother
|
||
|
* with the stringizing.
|
||
|
*
|
||
|
* This table should have entries for each enum value in fcall.h.
|
||
|
*/
|
||
|
#define X(NAME, name) [L9P_T##NAME - L9P__FIRST] = "T" name, \
|
||
|
[L9P_R##NAME - L9P__FIRST] = "R" name
|
||
|
static const char *ftype_names[] = {
|
||
|
X(VERSION, "version"),
|
||
|
X(AUTH, "auth"),
|
||
|
X(ATTACH, "attach"),
|
||
|
X(ERROR, "error"),
|
||
|
X(LERROR, "lerror"),
|
||
|
X(FLUSH, "flush"),
|
||
|
X(WALK, "walk"),
|
||
|
X(OPEN, "open"),
|
||
|
X(CREATE, "create"),
|
||
|
X(READ, "read"),
|
||
|
X(WRITE, "write"),
|
||
|
X(CLUNK, "clunk"),
|
||
|
X(REMOVE, "remove"),
|
||
|
X(STAT, "stat"),
|
||
|
X(WSTAT, "wstat"),
|
||
|
X(STATFS, "statfs"),
|
||
|
X(LOPEN, "lopen"),
|
||
|
X(LCREATE, "lcreate"),
|
||
|
X(SYMLINK, "symlink"),
|
||
|
X(MKNOD, "mknod"),
|
||
|
X(RENAME, "rename"),
|
||
|
X(READLINK, "readlink"),
|
||
|
X(GETATTR, "getattr"),
|
||
|
X(SETATTR, "setattr"),
|
||
|
X(XATTRWALK, "xattrwalk"),
|
||
|
X(XATTRCREATE, "xattrcreate"),
|
||
|
X(READDIR, "readdir"),
|
||
|
X(FSYNC, "fsync"),
|
||
|
X(LOCK, "lock"),
|
||
|
X(GETLOCK, "getlock"),
|
||
|
X(LINK, "link"),
|
||
|
X(MKDIR, "mkdir"),
|
||
|
X(RENAMEAT, "renameat"),
|
||
|
X(UNLINKAT, "unlinkat"),
|
||
|
};
|
||
|
#undef X
|
||
|
|
||
|
void
|
||
|
l9p_seek_iov(struct iovec *iov1, size_t niov1, struct iovec *iov2,
|
||
|
size_t *niov2, size_t seek)
|
||
|
{
|
||
|
size_t remainder = 0;
|
||
|
size_t left = seek;
|
||
|
size_t i, j;
|
||
|
|
||
|
for (i = 0; i < niov1; i++) {
|
||
|
size_t toseek = MIN(left, iov1[i].iov_len);
|
||
|
left -= toseek;
|
||
|
|
||
|
if (toseek == iov1[i].iov_len)
|
||
|
continue;
|
||
|
|
||
|
if (left == 0) {
|
||
|
remainder = toseek;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (j = i; j < niov1; j++) {
|
||
|
iov2[j - i].iov_base = (char *)iov1[j].iov_base + remainder;
|
||
|
iov2[j - i].iov_len = iov1[j].iov_len - remainder;
|
||
|
remainder = 0;
|
||
|
}
|
||
|
|
||
|
*niov2 = j - i;
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
l9p_truncate_iov(struct iovec *iov, size_t niov, size_t length)
|
||
|
{
|
||
|
size_t i, done = 0;
|
||
|
|
||
|
for (i = 0; i < niov; i++) {
|
||
|
size_t toseek = MIN(length - done, iov[i].iov_len);
|
||
|
done += toseek;
|
||
|
|
||
|
if (toseek < iov[i].iov_len) {
|
||
|
iov[i].iov_len = toseek;
|
||
|
return (i + 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (niov);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This wrapper for getgrouplist() that malloc'ed memory, and
|
||
|
* papers over FreeBSD vs Mac differences in the getgrouplist()
|
||
|
* argument types.
|
||
|
*
|
||
|
* Note that this function guarantees that *either*:
|
||
|
* return value != NULL and *angroups has been set
|
||
|
* or: return value == NULL and *angroups is 0
|
||
|
*/
|
||
|
gid_t *
|
||
|
l9p_getgrlist(const char *name, gid_t basegid, int *angroups)
|
||
|
{
|
||
|
#ifdef GETGROUPS_GROUP_TYPE_IS_INT
|
||
|
int i, *int_groups;
|
||
|
#endif
|
||
|
gid_t *groups;
|
||
|
int ngroups;
|
||
|
|
||
|
/*
|
||
|
* Todo, perhaps: while getgrouplist() returns -1, expand.
|
||
|
* For now just use NGROUPS_MAX.
|
||
|
*/
|
||
|
ngroups = NGROUPS_MAX;
|
||
|
groups = malloc((size_t)ngroups * sizeof(*groups));
|
||
|
#ifdef GETGROUPS_GROUP_TYPE_IS_INT
|
||
|
int_groups = groups ? malloc((size_t)ngroups * sizeof(*int_groups)) :
|
||
|
NULL;
|
||
|
if (int_groups == NULL) {
|
||
|
free(groups);
|
||
|
groups = NULL;
|
||
|
}
|
||
|
#endif
|
||
|
if (groups == NULL) {
|
||
|
*angroups = 0;
|
||
|
return (NULL);
|
||
|
}
|
||
|
#ifdef GETGROUPS_GROUP_TYPE_IS_INT
|
||
|
(void) getgrouplist(name, (int)basegid, int_groups, &ngroups);
|
||
|
for (i = 0; i < ngroups; i++)
|
||
|
groups[i] = (gid_t)int_groups[i];
|
||
|
#else
|
||
|
(void) getgrouplist(name, basegid, groups, &ngroups);
|
||
|
#endif
|
||
|
*angroups = ngroups;
|
||
|
return (groups);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* For the various debug describe ops: decode bits in a bit-field-y
|
||
|
* value. For example, we might produce:
|
||
|
* value=0x3c[FOO,BAR,QUUX,?0x20]
|
||
|
* when FOO is bit 0x10, BAR is 0x08, and QUUX is 0x04 (as defined
|
||
|
* by the table). This leaves 0x20 (bit 5) as a mystery, while bits
|
||
|
* 4, 3, and 2 were decoded. (Bits 0 and 1 were 0 on input hence
|
||
|
* were not attempted here.)
|
||
|
*
|
||
|
* For general use we take a uint64_t <value>. The bit description
|
||
|
* table <db> is an array of {mask, match, str} values ending with
|
||
|
* {0, 0, NULL}.
|
||
|
*
|
||
|
* If <str> is non-NULL we'll print it and the mask as well (if
|
||
|
* str is NULL we'll print neither). The mask is always printed in
|
||
|
* hex at the moment. See undec description too.
|
||
|
*
|
||
|
* For convenience, you can use a mask-and-match value, e.g., to
|
||
|
* decode a 2-bit field in bits 0 and 1 you can mask against 3 and
|
||
|
* match the values 0, 1, 2, and 3. To handle this, make sure that
|
||
|
* all masks-with-same-match are sequential.
|
||
|
*
|
||
|
* If there are any nonzero undecoded bits, print them after
|
||
|
* all the decode-able bits have been handled.
|
||
|
*
|
||
|
* The <oc> argument defines the open and close bracket characters,
|
||
|
* typically "[]", that surround the entire string. If NULL, no
|
||
|
* brackets are added, else oc[0] goes in the front and oc[1] at
|
||
|
* the end, after printing any <str><value> part.
|
||
|
*
|
||
|
* Returns true if it printed anything (other than the implied
|
||
|
* str-and-value, that is).
|
||
|
*/
|
||
|
static bool
|
||
|
l9p_describe_bits(const char *str, uint64_t value, const char *oc,
|
||
|
const struct descbits *db, struct sbuf *sb)
|
||
|
{
|
||
|
const char *sep;
|
||
|
char bracketbuf[2] = "";
|
||
|
bool printed = false;
|
||
|
|
||
|
if (str != NULL)
|
||
|
sbuf_printf(sb, "%s0x%" PRIx64, str, value);
|
||
|
|
||
|
if (oc != NULL)
|
||
|
bracketbuf[0] = oc[0];
|
||
|
sep = bracketbuf;
|
||
|
for (; db->db_name != NULL; db++) {
|
||
|
if ((value & db->db_mask) == db->db_match) {
|
||
|
sbuf_printf(sb, "%s%s", sep, db->db_name);
|
||
|
sep = ",";
|
||
|
printed = true;
|
||
|
|
||
|
/*
|
||
|
* Clear the field, and make sure we
|
||
|
* won't match a zero-valued field with
|
||
|
* this same mask.
|
||
|
*/
|
||
|
value &= ~db->db_mask;
|
||
|
while (db[1].db_mask == db->db_mask &&
|
||
|
db[1].db_name != NULL)
|
||
|
db++;
|
||
|
}
|
||
|
}
|
||
|
if (value != 0) {
|
||
|
sbuf_printf(sb, "%s?0x%" PRIx64, sep, value);
|
||
|
printed = true;
|
||
|
}
|
||
|
if (printed && oc != NULL) {
|
||
|
bracketbuf[0] = oc[1];
|
||
|
sbuf_cat(sb, bracketbuf);
|
||
|
}
|
||
|
return (printed);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show file ID.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_fid(const char *str, uint32_t fid, struct sbuf *sb)
|
||
|
{
|
||
|
|
||
|
sbuf_printf(sb, "%s%" PRIu32, str, fid);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show user or group ID.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_ugid(const char *str, uint32_t ugid, struct sbuf *sb)
|
||
|
{
|
||
|
|
||
|
sbuf_printf(sb, "%s%" PRIu32, str, ugid);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show file mode (O_RDWR, O_RDONLY, etc). The argument is
|
||
|
* an l9p_omode, not a Linux flags mode. Linux flags are
|
||
|
* decoded with l9p_describe_lflags.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_mode(const char *str, uint32_t mode, struct sbuf *sb)
|
||
|
{
|
||
|
static const struct descbits bits[] = {
|
||
|
{ L9P_OACCMODE, L9P_OREAD, "OREAD" },
|
||
|
{ L9P_OACCMODE, L9P_OWRITE, "OWRITE" },
|
||
|
{ L9P_OACCMODE, L9P_ORDWR, "ORDWR" },
|
||
|
{ L9P_OACCMODE, L9P_OEXEC, "OEXEC" },
|
||
|
|
||
|
{ L9P_OCEXEC, L9P_OCEXEC, "OCEXEC" },
|
||
|
{ L9P_ODIRECT, L9P_ODIRECT, "ODIRECT" },
|
||
|
{ L9P_ORCLOSE, L9P_ORCLOSE, "ORCLOSE" },
|
||
|
{ L9P_OTRUNC, L9P_OTRUNC, "OTRUNC" },
|
||
|
{ 0, 0, NULL }
|
||
|
};
|
||
|
|
||
|
(void) l9p_describe_bits(str, mode, "[]", bits, sb);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show Linux mode/flags.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_lflags(const char *str, uint32_t flags, struct sbuf *sb)
|
||
|
{
|
||
|
static const struct descbits bits[] = {
|
||
|
{ L9P_OACCMODE, L9P_OREAD, "O_READ" },
|
||
|
{ L9P_OACCMODE, L9P_OWRITE, "O_WRITE" },
|
||
|
{ L9P_OACCMODE, L9P_ORDWR, "O_RDWR" },
|
||
|
{ L9P_OACCMODE, L9P_OEXEC, "O_EXEC" },
|
||
|
|
||
|
{ L9P_L_O_APPEND, L9P_L_O_APPEND, "O_APPEND" },
|
||
|
{ L9P_L_O_CLOEXEC, L9P_L_O_CLOEXEC, "O_CLOEXEC" },
|
||
|
{ L9P_L_O_CREAT, L9P_L_O_CREAT, "O_CREAT" },
|
||
|
{ L9P_L_O_DIRECT, L9P_L_O_DIRECT, "O_DIRECT" },
|
||
|
{ L9P_L_O_DIRECTORY, L9P_L_O_DIRECTORY, "O_DIRECTORY" },
|
||
|
{ L9P_L_O_DSYNC, L9P_L_O_DSYNC, "O_DSYNC" },
|
||
|
{ L9P_L_O_EXCL, L9P_L_O_EXCL, "O_EXCL" },
|
||
|
{ L9P_L_O_FASYNC, L9P_L_O_FASYNC, "O_FASYNC" },
|
||
|
{ L9P_L_O_LARGEFILE, L9P_L_O_LARGEFILE, "O_LARGEFILE" },
|
||
|
{ L9P_L_O_NOATIME, L9P_L_O_NOATIME, "O_NOATIME" },
|
||
|
{ L9P_L_O_NOCTTY, L9P_L_O_NOCTTY, "O_NOCTTY" },
|
||
|
{ L9P_L_O_NOFOLLOW, L9P_L_O_NOFOLLOW, "O_NOFOLLOW" },
|
||
|
{ L9P_L_O_NONBLOCK, L9P_L_O_NONBLOCK, "O_NONBLOCK" },
|
||
|
{ L9P_L_O_PATH, L9P_L_O_PATH, "O_PATH" },
|
||
|
{ L9P_L_O_SYNC, L9P_L_O_SYNC, "O_SYNC" },
|
||
|
{ L9P_L_O_TMPFILE, L9P_L_O_TMPFILE, "O_TMPFILE" },
|
||
|
{ L9P_L_O_TMPFILE, L9P_L_O_TMPFILE, "O_TMPFILE" },
|
||
|
{ L9P_L_O_TRUNC, L9P_L_O_TRUNC, "O_TRUNC" },
|
||
|
{ 0, 0, NULL }
|
||
|
};
|
||
|
|
||
|
(void) l9p_describe_bits(str, flags, "[]", bits, sb);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show file name or other similar, potentially-very-long string.
|
||
|
* Actual strings get quotes, a NULL name (if it occurs) gets
|
||
|
* <null> (no quotes), so you can tell the difference.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_name(const char *str, char *name, struct sbuf *sb)
|
||
|
{
|
||
|
size_t len;
|
||
|
|
||
|
if (name == NULL) {
|
||
|
sbuf_printf(sb, "%s<null>", str);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
len = strlen(name);
|
||
|
|
||
|
if (len > 32)
|
||
|
sbuf_printf(sb, "%s\"%.*s...\"", str, 32 - 3, name);
|
||
|
else
|
||
|
sbuf_printf(sb, "%s\"%.*s\"", str, (int)len, name);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show permissions (rwx etc). Prints the value in hex only if
|
||
|
* the rwx bits do not cover the entire value.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_perm(const char *str, uint32_t mode, struct sbuf *sb)
|
||
|
{
|
||
|
char pbuf[12];
|
||
|
|
||
|
strmode(mode & 0777, pbuf);
|
||
|
if ((mode & ~(uint32_t)0777) != 0)
|
||
|
sbuf_printf(sb, "%s0x%" PRIx32 "<%.9s>", str, mode, pbuf + 1);
|
||
|
else
|
||
|
sbuf_printf(sb, "%s<%.9s>", str, pbuf + 1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show "extended" permissions: regular permissions, but also the
|
||
|
* various DM* extension bits from 9P2000.u.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_ext_perm(const char *str, uint32_t mode, struct sbuf *sb)
|
||
|
{
|
||
|
static const struct descbits bits[] = {
|
||
|
{ L9P_DMDIR, L9P_DMDIR, "DMDIR" },
|
||
|
{ L9P_DMAPPEND, L9P_DMAPPEND, "DMAPPEND" },
|
||
|
{ L9P_DMEXCL, L9P_DMEXCL, "DMEXCL" },
|
||
|
{ L9P_DMMOUNT, L9P_DMMOUNT, "DMMOUNT" },
|
||
|
{ L9P_DMAUTH, L9P_DMAUTH, "DMAUTH" },
|
||
|
{ L9P_DMTMP, L9P_DMTMP, "DMTMP" },
|
||
|
{ L9P_DMSYMLINK, L9P_DMSYMLINK, "DMSYMLINK" },
|
||
|
{ L9P_DMDEVICE, L9P_DMDEVICE, "DMDEVICE" },
|
||
|
{ L9P_DMNAMEDPIPE, L9P_DMNAMEDPIPE, "DMNAMEDPIPE" },
|
||
|
{ L9P_DMSOCKET, L9P_DMSOCKET, "DMSOCKET" },
|
||
|
{ L9P_DMSETUID, L9P_DMSETUID, "DMSETUID" },
|
||
|
{ L9P_DMSETGID, L9P_DMSETGID, "DMSETGID" },
|
||
|
{ 0, 0, NULL }
|
||
|
};
|
||
|
bool need_sep;
|
||
|
|
||
|
sbuf_printf(sb, "%s[", str);
|
||
|
need_sep = l9p_describe_bits(NULL, mode & ~(uint32_t)0777, NULL,
|
||
|
bits, sb);
|
||
|
l9p_describe_perm(need_sep ? "," : "", mode & 0777, sb);
|
||
|
sbuf_cat(sb, "]");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show Linux-specific permissions: regular permissions, but also
|
||
|
* the S_IFMT field.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_lperm(const char *str, uint32_t mode, struct sbuf *sb)
|
||
|
{
|
||
|
static const struct descbits bits[] = {
|
||
|
{ S_IFMT, S_IFIFO, "S_IFIFO" },
|
||
|
{ S_IFMT, S_IFCHR, "S_IFCHR" },
|
||
|
{ S_IFMT, S_IFDIR, "S_IFDIR" },
|
||
|
{ S_IFMT, S_IFBLK, "S_IFBLK" },
|
||
|
{ S_IFMT, S_IFREG, "S_IFREG" },
|
||
|
{ S_IFMT, S_IFLNK, "S_IFLNK" },
|
||
|
{ S_IFMT, S_IFSOCK, "S_IFSOCK" },
|
||
|
{ 0, 0, NULL }
|
||
|
};
|
||
|
bool need_sep;
|
||
|
|
||
|
sbuf_printf(sb, "%s[", str);
|
||
|
need_sep = l9p_describe_bits(NULL, mode & ~(uint32_t)0777, NULL,
|
||
|
bits, sb);
|
||
|
l9p_describe_perm(need_sep ? "," : "", mode & 0777, sb);
|
||
|
sbuf_cat(sb, "]");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show qid (<type, version, path> tuple).
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_qid(const char *str, struct l9p_qid *qid, struct sbuf *sb)
|
||
|
{
|
||
|
static const struct descbits bits[] = {
|
||
|
/*
|
||
|
* NB: L9P_QTFILE is 0, i.e., is implied by no
|
||
|
* other bits being set. We get this produced
|
||
|
* when we mask against 0xff and compare for
|
||
|
* L9P_QTFILE, but we must do it first so that
|
||
|
* we mask against the original (not-adjusted)
|
||
|
* value.
|
||
|
*/
|
||
|
{ 0xff, L9P_QTFILE, "FILE" },
|
||
|
{ L9P_QTDIR, L9P_QTDIR, "DIR" },
|
||
|
{ L9P_QTAPPEND, L9P_QTAPPEND, "APPEND" },
|
||
|
{ L9P_QTEXCL, L9P_QTEXCL, "EXCL" },
|
||
|
{ L9P_QTMOUNT, L9P_QTMOUNT, "MOUNT" },
|
||
|
{ L9P_QTAUTH, L9P_QTAUTH, "AUTH" },
|
||
|
{ L9P_QTTMP, L9P_QTTMP, "TMP" },
|
||
|
{ L9P_QTSYMLINK, L9P_QTSYMLINK, "SYMLINK" },
|
||
|
{ 0, 0, NULL }
|
||
|
};
|
||
|
|
||
|
assert(qid != NULL);
|
||
|
|
||
|
sbuf_cat(sb, str);
|
||
|
(void) l9p_describe_bits("<", qid->type, "[]", bits, sb);
|
||
|
sbuf_printf(sb, ",%" PRIu32 ",0x%016" PRIx64 ">",
|
||
|
qid->version, qid->path);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show size.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_size(const char *str, uint64_t size, struct sbuf *sb)
|
||
|
{
|
||
|
|
||
|
sbuf_printf(sb, "%s%" PRIu64, str, size);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show l9stat (including 9P2000.u extensions if appropriate).
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_l9stat(const char *str, struct l9p_stat *st,
|
||
|
enum l9p_version version, struct sbuf *sb)
|
||
|
{
|
||
|
bool dotu = version >= L9P_2000U;
|
||
|
|
||
|
assert(st != NULL);
|
||
|
|
||
|
sbuf_printf(sb, "%stype=0x%04" PRIx32 " dev=0x%08" PRIx32, str,
|
||
|
st->type, st->dev);
|
||
|
l9p_describe_qid(" qid=", &st->qid, sb);
|
||
|
l9p_describe_ext_perm(" mode=", st->mode, sb);
|
||
|
if (st->atime != (uint32_t)-1)
|
||
|
sbuf_printf(sb, " atime=%" PRIu32, st->atime);
|
||
|
if (st->mtime != (uint32_t)-1)
|
||
|
sbuf_printf(sb, " mtime=%" PRIu32, st->mtime);
|
||
|
if (st->length != (uint64_t)-1)
|
||
|
sbuf_printf(sb, " length=%" PRIu64, st->length);
|
||
|
l9p_describe_name(" name=", st->name, sb);
|
||
|
/*
|
||
|
* It's pretty common to have NULL name+gid+muid. They're
|
||
|
* just noise if NULL *and* dot-u; decode only if non-null
|
||
|
* or not-dot-u.
|
||
|
*/
|
||
|
if (st->uid != NULL || !dotu)
|
||
|
l9p_describe_name(" uid=", st->uid, sb);
|
||
|
if (st->gid != NULL || !dotu)
|
||
|
l9p_describe_name(" gid=", st->gid, sb);
|
||
|
if (st->muid != NULL || !dotu)
|
||
|
l9p_describe_name(" muid=", st->muid, sb);
|
||
|
if (dotu) {
|
||
|
if (st->extension != NULL)
|
||
|
l9p_describe_name(" extension=", st->extension, sb);
|
||
|
sbuf_printf(sb,
|
||
|
" n_uid=%" PRIu32 " n_gid=%" PRIu32 " n_muid=%" PRIu32,
|
||
|
st->n_uid, st->n_gid, st->n_muid);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
l9p_describe_statfs(const char *str, struct l9p_statfs *st, struct sbuf *sb)
|
||
|
{
|
||
|
|
||
|
assert(st != NULL);
|
||
|
|
||
|
sbuf_printf(sb, "%stype=0x%04lx bsize=%lu blocks=%" PRIu64
|
||
|
" bfree=%" PRIu64 " bavail=%" PRIu64 " files=%" PRIu64
|
||
|
" ffree=%" PRIu64 " fsid=0x%" PRIx64 " namelen=%" PRIu32 ">",
|
||
|
str, (u_long)st->type, (u_long)st->bsize, st->blocks,
|
||
|
st->bfree, st->bavail, st->files,
|
||
|
st->ffree, st->fsid, st->namelen);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Decode a <seconds,nsec> timestamp.
|
||
|
*
|
||
|
* Perhaps should use asctime_r. For now, raw values.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_time(struct sbuf *sb, const char *s, uint64_t sec, uint64_t nsec)
|
||
|
{
|
||
|
|
||
|
sbuf_cat(sb, s);
|
||
|
if (nsec > 999999999)
|
||
|
sbuf_printf(sb, "%" PRIu64 ".<invalid nsec %" PRIu64 ">)",
|
||
|
sec, nsec);
|
||
|
else
|
||
|
sbuf_printf(sb, "%" PRIu64 ".%09" PRIu64, sec, nsec);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Decode readdir data (.L format, variable length names).
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_readdir(struct sbuf *sb, struct l9p_f_io *io)
|
||
|
{
|
||
|
uint32_t count;
|
||
|
#ifdef notyet
|
||
|
int i;
|
||
|
struct l9p_message msg;
|
||
|
struct l9p_dirent de;
|
||
|
#endif
|
||
|
|
||
|
if ((count = io->count) == 0) {
|
||
|
sbuf_printf(sb, " EOF (count=0)");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Can't do this yet because we do not have the original
|
||
|
* req.
|
||
|
*/
|
||
|
#ifdef notyet
|
||
|
sbuf_printf(sb, " count=%" PRIu32 " [", count);
|
||
|
|
||
|
l9p_init_msg(&msg, req, L9P_UNPACK);
|
||
|
for (i = 0; msg.lm_size < count; i++) {
|
||
|
if (l9p_pudirent(&msg, &de) < 0) {
|
||
|
sbuf_printf(sb, " bad count");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
sbuf_printf(sb, i ? ", " : " ");
|
||
|
l9p_describe_qid(" qid=", &de.qid, sb);
|
||
|
sbuf_printf(sb, " offset=%" PRIu64 " type=%d",
|
||
|
de.offset, de.type);
|
||
|
l9p_describe_name(" name=", de.name);
|
||
|
free(de.name);
|
||
|
}
|
||
|
sbuf_printf(sb, "]=%d dir entries", i);
|
||
|
#else /* notyet */
|
||
|
sbuf_printf(sb, " count=%" PRIu32, count);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Decode Tgetattr request_mask field.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_getattr_mask(uint64_t request_mask, struct sbuf *sb)
|
||
|
{
|
||
|
static const struct descbits bits[] = {
|
||
|
/*
|
||
|
* Note: ALL and BASIC must occur first and second.
|
||
|
* This is a little dirty: it depends on the way the
|
||
|
* describe_bits code clears the values. If we
|
||
|
* match ALL, we clear all those bits and do not
|
||
|
* match BASIC; if we match BASIC, we clear all
|
||
|
* those bits and do not match individual bits. Thus
|
||
|
* if we have BASIC but not all the additional bits,
|
||
|
* we'll see, e.g., [BASIC,BTIME,GEN]; if we have
|
||
|
* all the additional bits too, we'll see [ALL].
|
||
|
*
|
||
|
* Since <undec> is true below, we'll also spot any
|
||
|
* bits added to the protocol since we made this table.
|
||
|
*/
|
||
|
{ L9PL_GETATTR_ALL, L9PL_GETATTR_ALL, "ALL" },
|
||
|
{ L9PL_GETATTR_BASIC, L9PL_GETATTR_BASIC, "BASIC" },
|
||
|
|
||
|
/* individual bits in BASIC */
|
||
|
{ L9PL_GETATTR_MODE, L9PL_GETATTR_MODE, "MODE" },
|
||
|
{ L9PL_GETATTR_NLINK, L9PL_GETATTR_NLINK, "NLINK" },
|
||
|
{ L9PL_GETATTR_UID, L9PL_GETATTR_UID, "UID" },
|
||
|
{ L9PL_GETATTR_GID, L9PL_GETATTR_GID, "GID" },
|
||
|
{ L9PL_GETATTR_RDEV, L9PL_GETATTR_RDEV, "RDEV" },
|
||
|
{ L9PL_GETATTR_ATIME, L9PL_GETATTR_ATIME, "ATIME" },
|
||
|
{ L9PL_GETATTR_MTIME, L9PL_GETATTR_MTIME, "MTIME" },
|
||
|
{ L9PL_GETATTR_CTIME, L9PL_GETATTR_CTIME, "CTIME" },
|
||
|
{ L9PL_GETATTR_INO, L9PL_GETATTR_INO, "INO" },
|
||
|
{ L9PL_GETATTR_SIZE, L9PL_GETATTR_SIZE, "SIZE" },
|
||
|
{ L9PL_GETATTR_BLOCKS, L9PL_GETATTR_BLOCKS, "BLOCKS" },
|
||
|
|
||
|
/* additional bits in ALL */
|
||
|
{ L9PL_GETATTR_BTIME, L9PL_GETATTR_BTIME, "BTIME" },
|
||
|
{ L9PL_GETATTR_GEN, L9PL_GETATTR_GEN, "GEN" },
|
||
|
{ L9PL_GETATTR_DATA_VERSION, L9PL_GETATTR_DATA_VERSION,
|
||
|
"DATA_VERSION" },
|
||
|
{ 0, 0, NULL }
|
||
|
};
|
||
|
|
||
|
(void) l9p_describe_bits(" request_mask=", request_mask, "[]", bits,
|
||
|
sb);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Decode Tunlinkat flags.
|
||
|
*/
|
||
|
static void
|
||
|
l9p_describe_unlinkat_flags(const char *str, uint32_t flags, struct sbuf *sb)
|
||
|
{
|
||
|
static const struct descbits bits[] = {
|
||
|
{ L9PL_AT_REMOVEDIR, L9PL_AT_REMOVEDIR, "AT_REMOVEDIR" },
|
||
|
{ 0, 0, NULL }
|
||
|
};
|
||
|
|
||
|
(void) l9p_describe_bits(str, flags, "[]", bits, sb);
|
||
|
}
|
||
|
|
||
|
static const char *
|
||
|
lookup_linux_errno(uint32_t linux_errno)
|
||
|
{
|
||
|
static char unknown[50];
|
||
|
|
||
|
/*
|
||
|
* Error numbers in the "base" range (1..ERANGE) are common
|
||
|
* across BSD, MacOS, Linux, and Plan 9.
|
||
|
*
|
||
|
* Error numbers outside that range require translation.
|
||
|
*/
|
||
|
const char *const table[] = {
|
||
|
#define X0(name) [name] = name ## _STR
|
||
|
#define X(name) [name] = name ## _STR
|
||
|
X(LINUX_EAGAIN),
|
||
|
X(LINUX_EDEADLK),
|
||
|
X(LINUX_ENAMETOOLONG),
|
||
|
X(LINUX_ENOLCK),
|
||
|
X(LINUX_ENOSYS),
|
||
|
X(LINUX_ENOTEMPTY),
|
||
|
X(LINUX_ELOOP),
|
||
|
X(LINUX_ENOMSG),
|
||
|
X(LINUX_EIDRM),
|
||
|
X(LINUX_ECHRNG),
|
||
|
X(LINUX_EL2NSYNC),
|
||
|
X(LINUX_EL3HLT),
|
||
|
X(LINUX_EL3RST),
|
||
|
X(LINUX_ELNRNG),
|
||
|
X(LINUX_EUNATCH),
|
||
|
X(LINUX_ENOCSI),
|
||
|
X(LINUX_EL2HLT),
|
||
|
X(LINUX_EBADE),
|
||
|
X(LINUX_EBADR),
|
||
|
X(LINUX_EXFULL),
|
||
|
X(LINUX_ENOANO),
|
||
|
X(LINUX_EBADRQC),
|
||
|
X(LINUX_EBADSLT),
|
||
|
X(LINUX_EBFONT),
|
||
|
X(LINUX_ENOSTR),
|
||
|
X(LINUX_ENODATA),
|
||
|
X(LINUX_ETIME),
|
||
|
X(LINUX_ENOSR),
|
||
|
X(LINUX_ENONET),
|
||
|
X(LINUX_ENOPKG),
|
||
|
X(LINUX_EREMOTE),
|
||
|
X(LINUX_ENOLINK),
|
||
|
X(LINUX_EADV),
|
||
|
X(LINUX_ESRMNT),
|
||
|
X(LINUX_ECOMM),
|
||
|
X(LINUX_EPROTO),
|
||
|
X(LINUX_EMULTIHOP),
|
||
|
X(LINUX_EDOTDOT),
|
||
|
X(LINUX_EBADMSG),
|
||
|
X(LINUX_EOVERFLOW),
|
||
|
X(LINUX_ENOTUNIQ),
|
||
|
X(LINUX_EBADFD),
|
||
|
X(LINUX_EREMCHG),
|
||
|
X(LINUX_ELIBACC),
|
||
|
X(LINUX_ELIBBAD),
|
||
|
X(LINUX_ELIBSCN),
|
||
|
X(LINUX_ELIBMAX),
|
||
|
X(LINUX_ELIBEXEC),
|
||
|
X(LINUX_EILSEQ),
|
||
|
X(LINUX_ERESTART),
|
||
|
X(LINUX_ESTRPIPE),
|
||
|
X(LINUX_EUSERS),
|
||
|
X(LINUX_ENOTSOCK),
|
||
|
X(LINUX_EDESTADDRREQ),
|
||
|
X(LINUX_EMSGSIZE),
|
||
|
X(LINUX_EPROTOTYPE),
|
||
|
X(LINUX_ENOPROTOOPT),
|
||
|
X(LINUX_EPROTONOSUPPORT),
|
||
|
X(LINUX_ESOCKTNOSUPPORT),
|
||
|
X(LINUX_EOPNOTSUPP),
|
||
|
X(LINUX_EPFNOSUPPORT),
|
||
|
X(LINUX_EAFNOSUPPORT),
|
||
|
X(LINUX_EADDRINUSE),
|
||
|
X(LINUX_EADDRNOTAVAIL),
|
||
|
X(LINUX_ENETDOWN),
|
||
|
X(LINUX_ENETUNREACH),
|
||
|
X(LINUX_ENETRESET),
|
||
|
X(LINUX_ECONNABORTED),
|
||
|
X(LINUX_ECONNRESET),
|
||
|
X(LINUX_ENOBUFS),
|
||
|
X(LINUX_EISCONN),
|
||
|
X(LINUX_ENOTCONN),
|
||
|
X(LINUX_ESHUTDOWN),
|
||
|
X(LINUX_ETOOMANYREFS),
|
||
|
X(LINUX_ETIMEDOUT),
|
||
|
X(LINUX_ECONNREFUSED),
|
||
|
X(LINUX_EHOSTDOWN),
|
||
|
X(LINUX_EHOSTUNREACH),
|
||
|
X(LINUX_EALREADY),
|
||
|
X(LINUX_EINPROGRESS),
|
||
|
X(LINUX_ESTALE),
|
||
|
X(LINUX_EUCLEAN),
|
||
|
X(LINUX_ENOTNAM),
|
||
|
X(LINUX_ENAVAIL),
|
||
|
X(LINUX_EISNAM),
|
||
|
X(LINUX_EREMOTEIO),
|
||
|
X(LINUX_EDQUOT),
|
||
|
X(LINUX_ENOMEDIUM),
|
||
|
X(LINUX_EMEDIUMTYPE),
|
||
|
X(LINUX_ECANCELED),
|
||
|
X(LINUX_ENOKEY),
|
||
|
X(LINUX_EKEYEXPIRED),
|
||
|
X(LINUX_EKEYREVOKED),
|
||
|
X(LINUX_EKEYREJECTED),
|
||
|
X(LINUX_EOWNERDEAD),
|
||
|
X(LINUX_ENOTRECOVERABLE),
|
||
|
X(LINUX_ERFKILL),
|
||
|
X(LINUX_EHWPOISON),
|
||
|
#undef X0
|
||
|
#undef X
|
||
|
};
|
||
|
if ((size_t)linux_errno < N(table) && table[linux_errno] != NULL)
|
||
|
return (table[linux_errno]);
|
||
|
if (linux_errno <= ERANGE)
|
||
|
return (strerror((int)linux_errno));
|
||
|
(void) snprintf(unknown, sizeof(unknown),
|
||
|
"Unknown error %d", linux_errno);
|
||
|
return (unknown);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
l9p_describe_fcall(union l9p_fcall *fcall, enum l9p_version version,
|
||
|
struct sbuf *sb)
|
||
|
{
|
||
|
uint64_t mask;
|
||
|
uint8_t type;
|
||
|
int i;
|
||
|
|
||
|
assert(fcall != NULL);
|
||
|
assert(sb != NULL);
|
||
|
assert(version <= L9P_2000L && version >= L9P_INVALID_VERSION);
|
||
|
|
||
|
type = fcall->hdr.type;
|
||
|
|
||
|
if (type < L9P__FIRST || type >= L9P__LAST_PLUS_1 ||
|
||
|
ftype_names[type - L9P__FIRST] == NULL) {
|
||
|
const char *rr;
|
||
|
|
||
|
/*
|
||
|
* Can't say for sure that this distinction --
|
||
|
* an even number is a request, an odd one is
|
||
|
* a response -- will be maintained forever,
|
||
|
* but it's good enough for now.
|
||
|
*/
|
||
|
rr = (type & 1) != 0 ? "response" : "request";
|
||
|
sbuf_printf(sb, "<unknown %s %d> tag=%d", rr, type,
|
||
|
fcall->hdr.tag);
|
||
|
} else {
|
||
|
sbuf_printf(sb, "%s tag=%d", ftype_names[type - L9P__FIRST],
|
||
|
fcall->hdr.tag);
|
||
|
}
|
||
|
|
||
|
switch (type) {
|
||
|
case L9P_TVERSION:
|
||
|
case L9P_RVERSION:
|
||
|
sbuf_printf(sb, " version=\"%s\" msize=%d", fcall->version.version,
|
||
|
fcall->version.msize);
|
||
|
return;
|
||
|
|
||
|
case L9P_TAUTH:
|
||
|
l9p_describe_fid(" afid=", fcall->hdr.fid, sb);
|
||
|
sbuf_printf(sb, " uname=\"%s\" aname=\"%s\"",
|
||
|
fcall->tauth.uname, fcall->tauth.aname);
|
||
|
return;
|
||
|
|
||
|
case L9P_TATTACH:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_fid(" afid=", fcall->tattach.afid, sb);
|
||
|
sbuf_printf(sb, " uname=\"%s\" aname=\"%s\"",
|
||
|
fcall->tattach.uname, fcall->tattach.aname);
|
||
|
if (version >= L9P_2000U)
|
||
|
sbuf_printf(sb, " n_uname=%d", fcall->tattach.n_uname);
|
||
|
return;
|
||
|
|
||
|
case L9P_RATTACH:
|
||
|
l9p_describe_qid(" ", &fcall->rattach.qid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RERROR:
|
||
|
sbuf_printf(sb, " ename=\"%s\" errnum=%d", fcall->error.ename,
|
||
|
fcall->error.errnum);
|
||
|
return;
|
||
|
|
||
|
case L9P_RLERROR:
|
||
|
sbuf_printf(sb, " errnum=%d (%s)", fcall->error.errnum,
|
||
|
lookup_linux_errno(fcall->error.errnum));
|
||
|
return;
|
||
|
|
||
|
case L9P_TFLUSH:
|
||
|
sbuf_printf(sb, " oldtag=%d", fcall->tflush.oldtag);
|
||
|
return;
|
||
|
|
||
|
case L9P_RFLUSH:
|
||
|
return;
|
||
|
|
||
|
case L9P_TWALK:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_fid(" newfid=", fcall->twalk.newfid, sb);
|
||
|
if (fcall->twalk.nwname) {
|
||
|
sbuf_cat(sb, " wname=\"");
|
||
|
for (i = 0; i < fcall->twalk.nwname; i++)
|
||
|
sbuf_printf(sb, "%s%s", i == 0 ? "" : "/",
|
||
|
fcall->twalk.wname[i]);
|
||
|
sbuf_cat(sb, "\"");
|
||
|
}
|
||
|
return;
|
||
|
|
||
|
case L9P_RWALK:
|
||
|
sbuf_printf(sb, " wqid=[");
|
||
|
for (i = 0; i < fcall->rwalk.nwqid; i++)
|
||
|
l9p_describe_qid(i == 0 ? "" : ",",
|
||
|
&fcall->rwalk.wqid[i], sb);
|
||
|
sbuf_cat(sb, "]");
|
||
|
return;
|
||
|
|
||
|
case L9P_TOPEN:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_mode(" mode=", fcall->tcreate.mode, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_ROPEN:
|
||
|
l9p_describe_qid(" qid=", &fcall->ropen.qid, sb);
|
||
|
sbuf_printf(sb, " iounit=%d", fcall->ropen.iounit);
|
||
|
return;
|
||
|
|
||
|
case L9P_TCREATE:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_name(" name=", fcall->tcreate.name, sb);
|
||
|
l9p_describe_ext_perm(" perm=", fcall->tcreate.perm, sb);
|
||
|
l9p_describe_mode(" mode=", fcall->tcreate.mode, sb);
|
||
|
if (version >= L9P_2000U && fcall->tcreate.extension != NULL)
|
||
|
l9p_describe_name(" extension=",
|
||
|
fcall->tcreate.extension, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RCREATE:
|
||
|
l9p_describe_qid(" qid=", &fcall->rcreate.qid, sb);
|
||
|
sbuf_printf(sb, " iounit=%d", fcall->rcreate.iounit);
|
||
|
return;
|
||
|
|
||
|
case L9P_TREAD:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
sbuf_printf(sb, " offset=%" PRIu64 " count=%" PRIu32,
|
||
|
fcall->io.offset, fcall->io.count);
|
||
|
return;
|
||
|
|
||
|
case L9P_RREAD:
|
||
|
case L9P_RWRITE:
|
||
|
sbuf_printf(sb, " count=%" PRIu32, fcall->io.count);
|
||
|
return;
|
||
|
|
||
|
case L9P_TWRITE:
|
||
|
case L9P_TREADDIR:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
sbuf_printf(sb, " offset=%" PRIu64 " count=%" PRIu32,
|
||
|
fcall->io.offset, fcall->io.count);
|
||
|
return;
|
||
|
|
||
|
case L9P_TCLUNK:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RCLUNK:
|
||
|
return;
|
||
|
|
||
|
case L9P_TREMOVE:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RREMOVE:
|
||
|
return;
|
||
|
|
||
|
case L9P_TSTAT:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RSTAT:
|
||
|
l9p_describe_l9stat(" ", &fcall->rstat.stat, version, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_TWSTAT:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_l9stat(" ", &fcall->twstat.stat, version, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RWSTAT:
|
||
|
return;
|
||
|
|
||
|
case L9P_TSTATFS:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RSTATFS:
|
||
|
l9p_describe_statfs(" ", &fcall->rstatfs.statfs, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_TLOPEN:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_lflags(" flags=", fcall->tlcreate.flags, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RLOPEN:
|
||
|
l9p_describe_qid(" qid=", &fcall->rlopen.qid, sb);
|
||
|
sbuf_printf(sb, " iounit=%d", fcall->rlopen.iounit);
|
||
|
return;
|
||
|
|
||
|
case L9P_TLCREATE:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_name(" name=", fcall->tlcreate.name, sb);
|
||
|
/* confusing: "flags" is open-mode, "mode" is permissions */
|
||
|
l9p_describe_lflags(" flags=", fcall->tlcreate.flags, sb);
|
||
|
/* TLCREATE mode/permissions have S_IFREG (0x8000) set */
|
||
|
l9p_describe_lperm(" mode=", fcall->tlcreate.mode, sb);
|
||
|
l9p_describe_ugid(" gid=", fcall->tlcreate.gid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RLCREATE:
|
||
|
l9p_describe_qid(" qid=", &fcall->rlcreate.qid, sb);
|
||
|
sbuf_printf(sb, " iounit=%d", fcall->rlcreate.iounit);
|
||
|
return;
|
||
|
|
||
|
case L9P_TSYMLINK:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_name(" name=", fcall->tsymlink.name, sb);
|
||
|
l9p_describe_name(" symtgt=", fcall->tsymlink.symtgt, sb);
|
||
|
l9p_describe_ugid(" gid=", fcall->tsymlink.gid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RSYMLINK:
|
||
|
l9p_describe_qid(" qid=", &fcall->ropen.qid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_TMKNOD:
|
||
|
l9p_describe_fid(" dfid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_name(" name=", fcall->tmknod.name, sb);
|
||
|
/*
|
||
|
* TMKNOD mode/permissions have S_IFBLK/S_IFCHR/S_IFIFO
|
||
|
* bits. The major and minor values are only meaningful
|
||
|
* for S_IFBLK and S_IFCHR, but just decode always here.
|
||
|
*/
|
||
|
l9p_describe_lperm(" mode=", fcall->tmknod.mode, sb);
|
||
|
sbuf_printf(sb, " major=%u minor=%u",
|
||
|
fcall->tmknod.major, fcall->tmknod.minor);
|
||
|
l9p_describe_ugid(" gid=", fcall->tmknod.gid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RMKNOD:
|
||
|
l9p_describe_qid(" qid=", &fcall->rmknod.qid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_TRENAME:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_fid(" dfid=", fcall->trename.dfid, sb);
|
||
|
l9p_describe_name(" name=", fcall->trename.name, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RRENAME:
|
||
|
return;
|
||
|
|
||
|
case L9P_TREADLINK:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RREADLINK:
|
||
|
l9p_describe_name(" target=", fcall->rreadlink.target, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_TGETATTR:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_getattr_mask(fcall->tgetattr.request_mask, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RGETATTR:
|
||
|
/* Don't need to decode bits: they're implied by the output */
|
||
|
mask = fcall->rgetattr.valid;
|
||
|
sbuf_printf(sb, " valid=0x%016" PRIx64, mask);
|
||
|
l9p_describe_qid(" qid=", &fcall->rgetattr.qid, sb);
|
||
|
if (mask & L9PL_GETATTR_MODE)
|
||
|
l9p_describe_lperm(" mode=", fcall->rgetattr.mode, sb);
|
||
|
if (mask & L9PL_GETATTR_UID)
|
||
|
l9p_describe_ugid(" uid=", fcall->rgetattr.uid, sb);
|
||
|
if (mask & L9PL_GETATTR_GID)
|
||
|
l9p_describe_ugid(" gid=", fcall->rgetattr.gid, sb);
|
||
|
if (mask & L9PL_GETATTR_NLINK)
|
||
|
sbuf_printf(sb, " nlink=%" PRIu64,
|
||
|
fcall->rgetattr.nlink);
|
||
|
if (mask & L9PL_GETATTR_RDEV)
|
||
|
sbuf_printf(sb, " rdev=0x%" PRIx64,
|
||
|
fcall->rgetattr.rdev);
|
||
|
if (mask & L9PL_GETATTR_SIZE)
|
||
|
l9p_describe_size(" size=", fcall->rgetattr.size, sb);
|
||
|
if (mask & L9PL_GETATTR_BLOCKS)
|
||
|
sbuf_printf(sb, " blksize=%" PRIu64 " blocks=%" PRIu64,
|
||
|
fcall->rgetattr.blksize, fcall->rgetattr.blocks);
|
||
|
if (mask & L9PL_GETATTR_ATIME)
|
||
|
l9p_describe_time(sb, " atime=",
|
||
|
fcall->rgetattr.atime_sec,
|
||
|
fcall->rgetattr.atime_nsec);
|
||
|
if (mask & L9PL_GETATTR_MTIME)
|
||
|
l9p_describe_time(sb, " mtime=",
|
||
|
fcall->rgetattr.mtime_sec,
|
||
|
fcall->rgetattr.mtime_nsec);
|
||
|
if (mask & L9PL_GETATTR_CTIME)
|
||
|
l9p_describe_time(sb, " ctime=",
|
||
|
fcall->rgetattr.ctime_sec,
|
||
|
fcall->rgetattr.ctime_nsec);
|
||
|
if (mask & L9PL_GETATTR_BTIME)
|
||
|
l9p_describe_time(sb, " btime=",
|
||
|
fcall->rgetattr.btime_sec,
|
||
|
fcall->rgetattr.btime_nsec);
|
||
|
if (mask & L9PL_GETATTR_GEN)
|
||
|
sbuf_printf(sb, " gen=0x%" PRIx64, fcall->rgetattr.gen);
|
||
|
if (mask & L9PL_GETATTR_DATA_VERSION)
|
||
|
sbuf_printf(sb, " data_version=0x%" PRIx64,
|
||
|
fcall->rgetattr.data_version);
|
||
|
return;
|
||
|
|
||
|
case L9P_TSETATTR:
|
||
|
/* As with RGETATTR, we'll imply decode via output. */
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
mask = fcall->tsetattr.valid;
|
||
|
/* NB: tsetattr valid mask is only 32 bits, hence %08x */
|
||
|
sbuf_printf(sb, " valid=0x%08" PRIx64, mask);
|
||
|
if (mask & L9PL_SETATTR_MODE)
|
||
|
l9p_describe_lperm(" mode=", fcall->tsetattr.mode, sb);
|
||
|
if (mask & L9PL_SETATTR_UID)
|
||
|
l9p_describe_ugid(" uid=", fcall->tsetattr.uid, sb);
|
||
|
if (mask & L9PL_SETATTR_GID)
|
||
|
l9p_describe_ugid(" uid=", fcall->tsetattr.gid, sb);
|
||
|
if (mask & L9PL_SETATTR_SIZE)
|
||
|
l9p_describe_size(" size=", fcall->tsetattr.size, sb);
|
||
|
if (mask & L9PL_SETATTR_ATIME) {
|
||
|
if (mask & L9PL_SETATTR_ATIME_SET)
|
||
|
l9p_describe_time(sb, " atime=",
|
||
|
fcall->tsetattr.atime_sec,
|
||
|
fcall->tsetattr.atime_nsec);
|
||
|
else
|
||
|
sbuf_cat(sb, " atime=now");
|
||
|
}
|
||
|
if (mask & L9PL_SETATTR_MTIME) {
|
||
|
if (mask & L9PL_SETATTR_MTIME_SET)
|
||
|
l9p_describe_time(sb, " mtime=",
|
||
|
fcall->tsetattr.mtime_sec,
|
||
|
fcall->tsetattr.mtime_nsec);
|
||
|
else
|
||
|
sbuf_cat(sb, " mtime=now");
|
||
|
}
|
||
|
if (mask & L9PL_SETATTR_CTIME)
|
||
|
sbuf_cat(sb, " ctime=now");
|
||
|
return;
|
||
|
|
||
|
case L9P_RSETATTR:
|
||
|
return;
|
||
|
|
||
|
case L9P_TXATTRWALK:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_fid(" newfid=", fcall->txattrwalk.newfid, sb);
|
||
|
l9p_describe_name(" name=", fcall->txattrwalk.name, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RXATTRWALK:
|
||
|
l9p_describe_size(" size=", fcall->rxattrwalk.size, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_TXATTRCREATE:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_name(" name=", fcall->txattrcreate.name, sb);
|
||
|
l9p_describe_size(" size=", fcall->txattrcreate.attr_size, sb);
|
||
|
sbuf_printf(sb, " flags=%" PRIu32, fcall->txattrcreate.flags);
|
||
|
return;
|
||
|
|
||
|
case L9P_RXATTRCREATE:
|
||
|
return;
|
||
|
|
||
|
case L9P_RREADDIR:
|
||
|
l9p_describe_readdir(sb, &fcall->io);
|
||
|
return;
|
||
|
|
||
|
case L9P_TFSYNC:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RFSYNC:
|
||
|
return;
|
||
|
|
||
|
case L9P_TLOCK:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
/* decode better later */
|
||
|
sbuf_printf(sb, " type=%d flags=0x%" PRIx32
|
||
|
" start=%" PRIu64 " length=%" PRIu64
|
||
|
" proc_id=0x%" PRIx32 " client_id=\"%s\"",
|
||
|
fcall->tlock.type, fcall->tlock.flags,
|
||
|
fcall->tlock.start, fcall->tlock.length,
|
||
|
fcall->tlock.proc_id, fcall->tlock.client_id);
|
||
|
return;
|
||
|
|
||
|
case L9P_RLOCK:
|
||
|
sbuf_printf(sb, " status=%d", fcall->rlock.status);
|
||
|
return;
|
||
|
|
||
|
case L9P_TGETLOCK:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
/* FALLTHROUGH */
|
||
|
|
||
|
case L9P_RGETLOCK:
|
||
|
/* decode better later */
|
||
|
sbuf_printf(sb, " type=%d "
|
||
|
" start=%" PRIu64 " length=%" PRIu64
|
||
|
" proc_id=0x%" PRIx32 " client_id=\"%s\"",
|
||
|
fcall->getlock.type,
|
||
|
fcall->getlock.start, fcall->getlock.length,
|
||
|
fcall->getlock.proc_id, fcall->getlock.client_id);
|
||
|
return;
|
||
|
|
||
|
case L9P_TLINK:
|
||
|
l9p_describe_fid(" dfid=", fcall->tlink.dfid, sb);
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_name(" name=", fcall->tlink.name, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RLINK:
|
||
|
return;
|
||
|
|
||
|
case L9P_TMKDIR:
|
||
|
l9p_describe_fid(" fid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_name(" name=", fcall->tmkdir.name, sb);
|
||
|
/* TMKDIR mode/permissions have S_IFDIR set */
|
||
|
l9p_describe_lperm(" mode=", fcall->tmkdir.mode, sb);
|
||
|
l9p_describe_ugid(" gid=", fcall->tmkdir.gid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RMKDIR:
|
||
|
l9p_describe_qid(" qid=", &fcall->rmkdir.qid, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_TRENAMEAT:
|
||
|
l9p_describe_fid(" olddirfid=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_name(" oldname=", fcall->trenameat.oldname,
|
||
|
sb);
|
||
|
l9p_describe_fid(" newdirfid=", fcall->trenameat.newdirfid, sb);
|
||
|
l9p_describe_name(" newname=", fcall->trenameat.newname,
|
||
|
sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RRENAMEAT:
|
||
|
return;
|
||
|
|
||
|
case L9P_TUNLINKAT:
|
||
|
l9p_describe_fid(" dirfd=", fcall->hdr.fid, sb);
|
||
|
l9p_describe_name(" name=", fcall->tunlinkat.name, sb);
|
||
|
l9p_describe_unlinkat_flags(" flags=",
|
||
|
fcall->tunlinkat.flags, sb);
|
||
|
return;
|
||
|
|
||
|
case L9P_RUNLINKAT:
|
||
|
return;
|
||
|
|
||
|
default:
|
||
|
sbuf_printf(sb, " <missing case in %s()>", __func__);
|
||
|
}
|
||
|
}
|