/* * 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__); } }