/*      $NetBSD: dump.c,v 1.48 2021/05/01 18:07:52 rillig Exp $ */

/*-
* Copyright (c) 1988, 1993
*      The Regents of the University of California.  All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided 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.
* 3. Neither the name of the University nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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 <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1988, 1993\
The Regents of the University of California.  All rights reserved.");
#endif /* not lint */

#ifndef lint
#if 0
static char sccsid[] = "@(#)kdump.c     8.4 (Berkeley) 4/28/95";
#endif
__RCSID("$NetBSD: dump.c,v 1.48 2021/05/01 18:07:52 rillig Exp $");
#endif /* not lint */

#include <sys/param.h>
#define _KERNEL
#include <sys/errno.h>
#undef _KERNEL
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/ktrace.h>
#include <sys/ptrace.h>
#include <sys/queue.h>

#include <err.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <vis.h>

#include "ktrace.h"
#include "misc.h"
#include "setemul.h"

int timestamp, decimal, fancy = 1, tail, maxdata;

int width;                      /* Keep track of current columns. */

#include <sys/syscall.h>

static const char *const ptrace_ops[] = {
       PT_STRINGS
};

#ifdef PT_MACHDEP_STRINGS
static const char * const ptrace_machdep_ops[] = { PT_MACHDEP_STRINGS };
#endif

struct ktr_entry {
       TAILQ_ENTRY(ktr_entry) kte_list;
       struct ktr_header kte_kth;
};

TAILQ_HEAD(kteq, ktr_entry) ktependq = TAILQ_HEAD_INITIALIZER(ktependq);

void    argprint(const char *, register_t **, int *);
void    dumpheader(struct ktr_header *);
int     dumprecord(int, FILE *);
void    flushpendq(struct ktr_entry *);
int     fread_tail(void *, int, int, FILE *);
void    genioprint(struct ktr_header *);
struct ktr_entry *
       getpendq(struct ktr_header *, int, struct kteq *);
struct ktr_entry *
       getrecord(FILE *);
void    indent(int);
void    ioctldecode(u_long);
void    ktrcsw(struct ktr_entry *);
void    ktremul(struct ktr_entry *);
void    ktrgenio(struct ktr_entry *);
void    ktrnamei(struct ktr_entry *);
void    ktrpsig(struct ktr_entry *);
void    ktrsyscall(struct ktr_entry *);
void    ktrsysret(struct ktr_entry *);
void    nameiargprint(const char *, struct ktr_header *, register_t **, int *);
void    nameiprint(struct ktr_header *);
void    newline(void);
void    putpendq(struct ktr_entry *);
void    syscallnameprint(int);
void    syscallprint(struct ktr_header *);
void    sysretprint(struct ktr_header *);
int     xwprintf(const char *, ...) __printflike(1, 2);
void    *xrealloc(void *, size_t *, size_t);

int
xwprintf(const char *fmt, ...)
{
       va_list ap;
       int w;

       va_start(ap, fmt);
       w = vprintf(fmt, ap);
       if (w == -1)
               warn("vprintf");
       else
               width += w;
       va_end(ap);
       return (w);
}

void
newline(void)
{

       if (width > 0) {
               printf("\n");
               width = 0;
       }
}

void
indent(int col)
{

       while (width < col)
               if (xwprintf(" ") < 0)
                       break;
}

void *
xrealloc(void *p, size_t *siz, size_t req)
{

       if (*siz < req) {
               if (*siz == 0)
                       *siz = 1;
               while (*siz < req)
                       *siz <<= 1;
               p = realloc(p, *siz);
               if (p == NULL)
                       err(EXIT_FAILURE, "realloc: %lu bytes",
                           (u_long)*siz);
       }
       return (p);
}

struct ktr_entry *
getrecord(FILE *fp)
{
       struct ktr_entry *kte;
       struct ktr_header *kth;
       char *cp;
       size_t siz, len;

       siz = 0;
       kte = xrealloc(NULL, &siz, sizeof(struct ktr_entry));
       kth = &kte->kte_kth;
       if (fread_tail(kth, sizeof(struct ktr_header), 1, fp) == 0) {
               free(kte);
               return (NULL);
       }

       if (kth->ktr_len < 0)
               errx(EXIT_FAILURE, "bogus length 0x%x", kth->ktr_len);
       len = kth->ktr_len;
       if (len > 0) {
               /* + 1 to ensure room for NUL terminate */
               kte = xrealloc(kte, &siz, sizeof(struct ktr_entry) + len + 1);
               if (fread_tail(cp = (char *)(&kte->kte_kth + 1),
                   len, 1, fp) == 0)
                       errx(EXIT_FAILURE, "data too short");
               cp[len] = 0;
       }

       return (kte);
}

#define KTE_TYPE(kte)           ((kte)->kte_kth.ktr_type)
#define KTE_PID(kte)            ((kte)->kte_kth.ktr_pid)
#define KTE_LID(kte)            ((kte)->kte_kth.ktr_lid)
#define KTE_MATCH(kte, type, pid, lid)                          \
       (KTE_TYPE(kte) == (type) && KTE_PID(kte) == (pid) &&    \
       KTE_LID(kte) == (lid))

void
putpendq(struct ktr_entry *kte)
{

       TAILQ_INSERT_TAIL(&ktependq, kte, kte_list);
}

void
flushpendq(struct ktr_entry *us)
{
       struct ktr_entry *kte, *kte_next;
       int pid = KTE_PID(us), lid = KTE_LID(us);

       for (kte = TAILQ_FIRST(&ktependq); kte != NULL; kte = kte_next) {
               kte_next = TAILQ_NEXT(kte, kte_list);
               if (KTE_PID(kte) == pid || KTE_LID(kte) == lid) {
                       TAILQ_REMOVE(&ktependq, kte, kte_list);
                       free(kte);
               }
       }
}

struct ktr_entry *
getpendq(struct ktr_header *us, int type, struct kteq *kteq)
{
       struct ktr_entry *kte, *kte_next;
       int pid = us->ktr_pid, lid = us->ktr_lid;

       if (kteq != NULL)
               TAILQ_INIT(kteq);
       for (kte = TAILQ_FIRST(&ktependq); kte != NULL; kte = kte_next) {
               kte_next = TAILQ_NEXT(kte, kte_list);
               if (KTE_MATCH(kte, type, pid, lid)) {
                       TAILQ_REMOVE(&ktependq, kte, kte_list);
                       if (kteq != NULL)
                               TAILQ_INSERT_TAIL(kteq, kte, kte_list);
                       else
                               break;
               }
       }

       return (kteq ? TAILQ_FIRST(kteq) : kte);
}

int
dumprecord(int trpoints, FILE *fp)
{
       struct ktr_entry *kte;
       struct ktr_header *kth;

       kte = getrecord(fp);
       if (kte == NULL)
               return (0);

       kth = &kte->kte_kth;
       if ((trpoints & (1 << kth->ktr_type)) == 0) {
               free(kte);
               goto out;
       }

       /* Update context to match currently processed record. */
       ectx_sanify(kth->ktr_pid);

       switch (kth->ktr_type) {
       case KTR_SYSCALL:
               ktrsyscall(kte);
               break;
       case KTR_SYSRET:
               ktrsysret(kte);
               break;
       case KTR_NAMEI:
               putpendq(kte);
               break;
       case KTR_GENIO:
               putpendq(kte);
               break;
       case KTR_PSIG:
               ktrpsig(kte);
               break;
       case KTR_CSW:
               ktrcsw(kte);
               break;
       case KTR_EMUL:
               putpendq(kte);
               break;
       default:
               /*
                * XXX: Other types added recently.
                */
               free(kte);
               break;
       }
       newline();

out:
       return (1);
}

void
dumpfile(const char *file, int fd, int trpoints)
{
       FILE *fp;

       if (file == NULL || *file == 0) {
               if ((fp = fdopen(fd, "r")) == NULL)
                       err(EXIT_FAILURE, "fdopen(%d)", fd);
       } else if (strcmp(file, "-") == 0)
               fp = stdin;
       else if ((fp = fopen(file, "r")) == NULL)
               err(EXIT_FAILURE, "fopen: %s", file);

       for (width = 0; dumprecord(trpoints, fp) != 0;)
               if (tail)
                       (void)fflush(stdout);

       newline();

       /*
        * XXX: Dump pending KTR_SYSCALL if any?
        */
}

int
fread_tail(void *buf, int size, int num, FILE *fp)
{
       int i;

       while ((i = fread(buf, size, num, fp)) == 0 && tail) {
               (void)sleep(1);
               clearerr(fp);
       }
       return (i);
}

void
dumpheader(struct ktr_header *kth)
{
       union timeholder {
               struct timeval tv;
               struct timespec ts;
       };
       static union timeholder prevtime;
       union timeholder temp;

       temp.tv.tv_sec = temp.tv.tv_usec = 0;
       xwprintf("%6d ", kth->ktr_pid);
       if (kth->ktr_version > KTRFAC_VERSION(KTRFACv0))
               xwprintf("%6d ", kth->ktr_lid);
       xwprintf("%-8.*s ", MAXCOMLEN, kth->ktr_comm);
       if (timestamp) {
               if (timestamp == 2) {
                       switch (kth->ktr_version) {
                       case KTRFAC_VERSION(KTRFACv0):
                               if (prevtime.tv.tv_sec == 0)
                                       temp.tv.tv_sec = temp.tv.tv_usec = 0;
                               else
                                       timersub(&kth->ktr_otv,
                                           &prevtime.tv, &temp.tv);
                               prevtime.tv.tv_sec = kth->ktr_otv.tv_sec;
                               prevtime.tv.tv_usec = kth->ktr_otv.tv_usec;
                               break;

                       case KTRFAC_VERSION(KTRFACv1):
                               if (prevtime.ts.tv_sec == 0)
                                       temp.ts.tv_sec = temp.ts.tv_nsec = 0;
                               else
                                       timespecsub(&kth->ktr_time,
                                           &prevtime.ts, &temp.ts);
                               prevtime.ts.tv_sec = kth->ktr_ots.tv_sec;
                               prevtime.ts.tv_nsec = kth->ktr_ots.tv_nsec;
                               break;

                       case KTRFAC_VERSION(KTRFACv2):
                               if (prevtime.ts.tv_sec == 0)
                                       temp.ts.tv_sec = temp.ts.tv_nsec = 0;
                               else
                                       timespecsub(&kth->ktr_time,
                                           &prevtime.ts, &temp.ts);
                               prevtime.ts.tv_sec = kth->ktr_ts.tv_sec;
                               prevtime.ts.tv_nsec = kth->ktr_ts.tv_nsec;
                               break;
                       }
               } else {
                       switch (kth->ktr_version) {
                       case KTRFAC_VERSION(KTRFACv0):
                               temp.tv.tv_sec = kth->ktr_otv.tv_sec;
                               temp.tv.tv_usec = kth->ktr_otv.tv_usec;
                               break;
                       case KTRFAC_VERSION(KTRFACv1):
                               temp.ts.tv_sec = kth->ktr_ots.tv_sec;
                               temp.ts.tv_nsec = kth->ktr_ots.tv_nsec;
                               break;
                       case KTRFAC_VERSION(KTRFACv2):
                               temp.ts.tv_sec = kth->ktr_ts.tv_sec;
                               temp.ts.tv_nsec = kth->ktr_ts.tv_nsec;
                               break;
                       }
               }
               if (kth->ktr_version == KTRFAC_VERSION(KTRFACv0))
                       xwprintf("%lld.%06ld ",
                           (long long)temp.tv.tv_sec, (long)temp.tv.tv_usec);
               else
                       xwprintf("%lld.%09ld ",
                           (long long)temp.ts.tv_sec, (long)temp.ts.tv_nsec);
       }
}

void
ioctldecode(u_long cmd)
{
       char dirbuf[4], *dir = dirbuf;

       if (cmd & IOC_OUT)
               *dir++ = 'W';
       if (cmd & IOC_IN)
               *dir++ = 'R';
       *dir = '\0';

       xwprintf(decimal ? ", _IO%s('%c',%ld" : ", _IO%s('%c',%#lx",
           dirbuf, (int) ((cmd >> 8) & 0xff), cmd & 0xff);
       if ((cmd & IOC_VOID) == 0)
               xwprintf(decimal ? ",%ld)" : ",%#lx)",
                   (cmd >> 16) & 0xff);
       else
               xwprintf(")");
}

void
nameiargprint(const char *prefix, struct ktr_header *kth,
   register_t **ap, int *argsize)
{
       struct ktr_entry *kte;

       if (*argsize == 0)
               errx(EXIT_FAILURE, "argument expected");
       /*
        * XXX: binary emulation mode.
        */
       kte = getpendq(kth, KTR_NAMEI, NULL);
       if (kte == NULL)
               argprint(prefix, ap, argsize);
       else {
               xwprintf("%s", prefix);
               nameiprint(&kte->kte_kth);
               free(kte);
               (*ap)++;
               *argsize -= sizeof(register_t);
       }
}

void
syscallnameprint(int code)
{

       if (code >= cur_emul->nsysnames || code < 0)
               xwprintf("[%d]", code);
       else
               xwprintf("%s", cur_emul->sysnames[code]);
}

void
argprint(const char *prefix, register_t **ap, int *argsize)
{

       if (decimal)
               xwprintf("%s%ld", prefix, (long)**ap);
       else
               xwprintf("%s%#lx", prefix, (long)**ap);
       (*ap)++;
       *argsize -= sizeof(register_t);
}

void
syscallprint(struct ktr_header *kth)
{
       struct ktr_syscall *ktr = (struct ktr_syscall *)(kth + 1);
       register_t *ap;
       const char *s;
       int argsize;

       syscallnameprint(ktr->ktr_code);

       /*
        * Arguments processing.
        */
       argsize = ktr->ktr_argsize;
       if (argsize == 0) {
               xwprintf("(");
               goto noargument;
       }

       ap = (register_t *)(ktr + 1);
       if (!fancy)
               goto print_first;

       switch (ktr->ktr_code) {
       /*
        * All these have a path as the first param.
        * The order is same as syscalls.master.
        */
       case SYS_open:
       case SYS_link:
       case SYS_unlink:
       case SYS_chdir:
       case SYS___mknod50:
       case SYS_chmod:
       case SYS_chown:
       case SYS_unmount:
       case SYS_access:
       case SYS_chflags:
       case SYS_acct:
       case SYS_revoke:
       case SYS_symlink:
       case SYS_readlink:
       case SYS_execve:
       case SYS_chroot:
       case SYS_rename:
       case SYS_mkfifo:
       case SYS_mkdir:
       case SYS_rmdir:
       case SYS___utimes50:
       case SYS_compat_50_quotactl:
       case SYS___quotactl:
       case SYS___statvfs190:
       case SYS_compat_30_getfh:
       case SYS_pathconf:
       case SYS_truncate:
       case SYS_undelete:
       case SYS___posix_rename:
       case SYS_lchmod:
       case SYS_lchown:
       case SYS___lutimes50:
       case SYS___stat50:
       case SYS___lstat50:
       case SYS___posix_chown:
       case SYS___posix_lchown:
       case SYS_lchflags:
       case SYS___getfh30:
               nameiargprint("(", kth, &ap, &argsize);

               /*
                * 2nd argument is also pathname.
                */
               switch (ktr->ktr_code) {
               case SYS_link:
               case SYS_rename:
               case SYS___posix_rename:
                       nameiargprint(", ", kth, &ap, &argsize);
                       break;
               }
               break;

       case SYS_compat_16___sigaction14 :
               if ((int)*ap < 0 || (int)*ap >= MAXSIGNALS)
                       xwprintf("(%d", (int)*ap);
               else
                       xwprintf("(%s", signals[(int)*ap].name);
               ap++;
               argsize -= sizeof(register_t);
               break;

       case SYS_ioctl :
               argprint("(", &ap, &argsize);
               if ((s = ioctlname(*ap)) != NULL)
                       xwprintf(", %s", s);
               else
                       ioctldecode(*ap);
               ap++;
               argsize -= sizeof(register_t);
               break;

       case SYS_ptrace :
               if ((long)*ap >= 0 &&
                   *ap < (register_t)__arraycount(ptrace_ops))
                       xwprintf("(%s", ptrace_ops[*ap]);
#ifdef PT_MACHDEP_STRINGS
               else if (*ap >= PT_FIRSTMACH &&
                   *ap - PT_FIRSTMACH < (register_t)
                   __arraycount(ptrace_machdep_ops))
                       xwprintf("(%s", ptrace_machdep_ops[*ap - PT_FIRSTMACH]);
#endif
               else
                       xwprintf("(%ld", (long)*ap);
               ap++;
               argsize -= sizeof(register_t);
               break;

       default:
print_first:
               argprint("(", &ap, &argsize);
               break;
       }

       /* Print rest of argument. */
       while (argsize > 0)
               argprint(", ", &ap, &argsize);

noargument:
       xwprintf(")");
}

void
ktrsyscall(struct ktr_entry *kte)
{
       struct ktr_header *kth = &kte->kte_kth;
       struct ktr_syscall *ktr = (struct ktr_syscall *)(kth + 1);

       switch (ktr->ktr_code) {
       case SYS_exit:
               dumpheader(kth);
               syscallprint(kth);
               break;
       default:
               putpendq(kte);
               return;
       }

       free(kte);
}

void
sysretprint(struct ktr_header *kth)
{
       struct ktr_sysret *ktr = (struct ktr_sysret *)(kth + 1);
       register_t ret = ktr->ktr_retval;
       int error = ktr->ktr_error;

       indent(50);
       if (error == EJUSTRETURN)
               xwprintf(" JUSTRETURN");
       else if (error == ERESTART)
               xwprintf(" RESTART");
       else if (error) {
               xwprintf(" Err#%d", error);
               if (error < MAXERRNOS && error >= 0)
                       xwprintf(" %s", errnos[error].name);
       } else
               switch (ktr->ktr_code) {
               case SYS_mremap:
               case SYS_mmap:
                       xwprintf(" = %p", (void *)(intptr_t)ret);
                       break;
               default:
                       xwprintf(" = %ld", (long)ret);
                       if (kth->ktr_len > (int)offsetof(struct ktr_sysret,
                           ktr_retval_1) && ktr->ktr_retval_1 != 0)
                               xwprintf(", %ld", (long)ktr->ktr_retval_1);
                       break;
               }
}

void
ktrsysret(struct ktr_entry *kte)
{
       struct ktr_header *kth = &kte->kte_kth;
       struct ktr_sysret *ktr = (struct ktr_sysret *)(kth + 1);
       struct ktr_entry *emul;
       struct ktr_entry *genio;
       struct ktr_entry *syscall_ent;

       dumpheader(kth);

       /* Print syscall name and arguments. */
       syscall_ent = getpendq(kth, KTR_SYSCALL, NULL);
       if (syscall_ent == NULL) {
               /*
                * Possibly a child of fork/vfork, or tracing of
                * process started during system call.
                */
               syscallnameprint(ktr->ktr_code);
       } else {
               syscallprint(&syscall_ent->kte_kth);
               free(syscall_ent);
       }

       /* Print return value and an error if any. */
       sysretprint(kth);

       genio = getpendq(kth, KTR_GENIO, NULL);
       if (genio != NULL) {
               genioprint(&genio->kte_kth);
               free(genio);
       }

       emul = getpendq(kth, KTR_EMUL, NULL);
       if (emul != NULL) {
               newline();
               ktremul(emul);
       }

       flushpendq(kte);
       free(kte);
}

void
nameiprint(struct ktr_header *kth)
{

       xwprintf("\"%.*s\"", kth->ktr_len, (char *)(kth + 1));
}

#ifdef notused
void
ktrnamei(struct ktr_entry *kte)
{
       struct ktr_header *kth = &kte->kte_kth;

       dumpheader(kth);
       xwprintf("namei(");
       nameiprint(kth);
       xwprintf(")");

       free(kte);
}
#endif

void
ktremul(struct ktr_entry *kte)
{
       struct ktr_header *kth = &kte->kte_kth;
       char *emul = (char *)(kth + 1);

       dumpheader(kth);
       xwprintf("emul(%s)", emul);
       setemul(emul, kth->ktr_pid, 1);

       free(kte);
}

void
genioprint(struct ktr_header *kth)
{
       struct ktr_genio *ktr = (struct ktr_genio *)(kth + 1);
       static int screenwidth = 0;
       int datalen = kth->ktr_len - sizeof(struct ktr_genio);
       /*
        * Need to be unsigned type so that positive value is passed
        * to vis(), which will call isgraph().
        */
       unsigned char *dp = (unsigned char *)(ktr + 1);
       int w;
       char visbuf[5];

       if (screenwidth == 0) {
               struct winsize ws;

               if (fancy && ioctl(fileno(stderr), TIOCGWINSZ, &ws) != -1 &&
                   ws.ws_col > 8)
                       screenwidth = ws.ws_col;
               else
                       screenwidth = 80;
       }

       if (maxdata && datalen > maxdata)
               datalen = maxdata;
       newline();
       xwprintf("       \"");
       for (; datalen > 0; datalen--, dp++) {
               (void) vis(visbuf, *dp, VIS_NL|VIS_TAB|VIS_CSTYLE,
                   /* We put NUL at the end of buffer when reading */
                   *(dp + 1));
               visbuf[4] = '\0';
               w = strlen(visbuf);
               if (width + w + 2 >= screenwidth)
                       break;
               xwprintf("%s", visbuf);
               if (width + 2 >= screenwidth)
                       break;
       }
       xwprintf("\"");
}

#ifdef notused
void
ktrgenio(struct ktr_entry *kte)
{
       struct ktr_header *kth = &kte->kte_kth;
       struct ktr_genio *ktr = (struct ktr_genio *)(kth + 1);

       dumpheader(kth);
       xwprintf("genio fd %d %s",
           ktr->ktr_fd, ktr->ktr_rw ? "write" : "read");
       genioprint(kth);

       free(kte);
}
#endif

void
ktrpsig(struct ktr_entry *kte)
{
       struct ktr_header *kth = &kte->kte_kth;
       struct ktr_psig *psig = (struct ktr_psig *)(kth + 1);

       dumpheader(kth);
       xwprintf("SIG%s ", sys_signame[psig->signo]);
       if (psig->action == SIG_DFL)
               xwprintf("SIG_DFL");
       else {
               xwprintf("caught handler=0x%lx mask=0x%lx code=0x%x",
                   (u_long)psig->action, (unsigned long)psig->mask.__bits[0],
                   psig->code);
       }

       free(kte);
}

void
ktrcsw(struct ktr_entry *kte)
{
       struct ktr_header *kth = &kte->kte_kth;
       struct ktr_csw *cs = (struct ktr_csw *)(kth + 1);

       dumpheader(kth);
       xwprintf("%s %s", cs->out ? "stop" : "resume",
           cs->user ? "user" : "kernel");

       free(kte);
}