summaryrefslogtreecommitdiff
path: root/devel/bmake/files/filemon/filemon_ktrace.c
blob: dd11e02f2554c523ab2e114e5adf5519e2d87df5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
/*	$NetBSD: filemon_ktrace.c,v 1.1.1.1 2020/05/24 05:35:53 nia Exp $	*/

/*-
 * Copyright (c) 2019 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Taylor R. Campbell.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

#define	_KERNTYPES		/* register_t */

#include "filemon.h"

#include <sys/param.h>
#include <sys/types.h>
#include <sys/rbtree.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/wait.h>

#include <sys/ktrace.h>

#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef AT_CWD
#define AT_CWD -1
#endif

struct filemon;
struct filemon_key;
struct filemon_state;

typedef struct filemon_state *filemon_syscall_t(struct filemon *,
    const struct filemon_key *, const struct ktr_syscall *);

static filemon_syscall_t filemon_sys_chdir;
static filemon_syscall_t filemon_sys_execve;
static filemon_syscall_t filemon_sys_exit;
static filemon_syscall_t filemon_sys_fork;
static filemon_syscall_t filemon_sys_link;
static filemon_syscall_t filemon_sys_open;
static filemon_syscall_t filemon_sys_openat;
static filemon_syscall_t filemon_sys_symlink;
static filemon_syscall_t filemon_sys_unlink;
static filemon_syscall_t filemon_sys_rename;

static filemon_syscall_t *const filemon_syscalls[] = {
	[SYS_chdir] = &filemon_sys_chdir,
	[SYS_execve] = &filemon_sys_execve,
	[SYS_exit] = &filemon_sys_exit,
	[SYS_fork] = &filemon_sys_fork,
	[SYS_link] = &filemon_sys_link,
	[SYS_open] = &filemon_sys_open,
	[SYS_openat] = &filemon_sys_openat,
	[SYS_symlink] = &filemon_sys_symlink,
	[SYS_unlink] = &filemon_sys_unlink,
	[SYS_rename] = &filemon_sys_rename,
};

struct filemon {
	int			ktrfd; /* kernel writes ktrace events here */
	FILE			*in;   /* we read ktrace events from here */
	FILE			*out;  /* we write filemon events to here */
	rb_tree_t		active;
	pid_t			child;

	/* I/O state machine.  */
	enum {
		FILEMON_START = 0,
		FILEMON_HEADER,
		FILEMON_PAYLOAD,
		FILEMON_ERROR,
	}			state;
	unsigned char		*p;
	size_t			resid;

	/* I/O buffer.  */
	struct ktr_header	hdr;
	union {
		struct ktr_syscall	syscall;
		struct ktr_sysret	sysret;
		char			namei[PATH_MAX];
		unsigned char		buf[4096];
	}			payload;
};

struct filemon_state {
	struct filemon_key {
		pid_t		pid;
		lwpid_t		lid;
	}		key;
	struct rb_node	node;
	int		syscode;
	void		(*show)(struct filemon *, const struct filemon_state *,
			    const struct ktr_sysret *);
	unsigned	i;
	unsigned	npath;
	char		*path[/*npath*/];
};

static int
compare_filemon_states(void *cookie, const void *na, const void *nb)
{
	const struct filemon_state *Sa = na;
	const struct filemon_state *Sb = nb;

	if (Sa->key.pid < Sb->key.pid)
		return -1;
	if (Sa->key.pid > Sb->key.pid)
		return +1;
	if (Sa->key.lid < Sb->key.lid)
		return -1;
	if (Sa->key.lid > Sb->key.lid)
		return +1;
	return 0;
}

static int
compare_filemon_key(void *cookie, const void *n, const void *k)
{
	const struct filemon_state *S = n;
	const struct filemon_key *key = k;

	if (S->key.pid < key->pid)
		return -1;
	if (S->key.pid > key->pid)
		return +1;
	if (S->key.lid < key->lid)
		return -1;
	if (S->key.lid > key->lid)
		return +1;
	return 0;
}

static const rb_tree_ops_t filemon_rb_ops = {
	.rbto_compare_nodes = &compare_filemon_states,
	.rbto_compare_key = &compare_filemon_key,
	.rbto_node_offset = offsetof(struct filemon_state, node),
	.rbto_context = NULL,
};

/*
 * filemon_path()
 *
 *	Return a pointer to a constant string denoting the `path' of
 *	the filemon.
 */
const char *
filemon_path(void)
{

	return "ktrace";
}

/*
 * filemon_open()
 *
 *	Allocate a filemon descriptor.  Returns NULL and sets errno on
 *	failure.
 */
struct filemon *
filemon_open(void)
{
	struct filemon *F;
	int ktrpipe[2];
	int error;

	/* Allocate and zero a struct filemon object.  */
	F = calloc(1, sizeof(*F));
	if (F == NULL)
		return NULL;

	/* Create a pipe for ktrace events.  */
	if (pipe2(ktrpipe, O_CLOEXEC|O_NONBLOCK) == -1) {
		error = errno;
		goto fail0;
	}

	/* Create a file stream for reading the ktrace events.  */
	if ((F->in = fdopen(ktrpipe[0], "r")) == NULL) {
		error = errno;
		goto fail1;
	}
	ktrpipe[0] = -1;	/* claimed by fdopen */

	/*
	 * Set the fd for writing ktrace events and initialize the
	 * rbtree.  The rest can be safely initialized to zero.
	 */
	F->ktrfd = ktrpipe[1];
	rb_tree_init(&F->active, &filemon_rb_ops);

	/* Success!  */
	return F;

fail2: __unused
	(void)fclose(F->in);
fail1:	(void)close(ktrpipe[0]);
	(void)close(ktrpipe[1]);
fail0:	free(F);
	errno = error;
	return NULL;
}

/*
 * filemon_closefd(F)
 *
 *	Internal subroutine to try to flush and close the output file.
 *	If F is not open for output, do nothing.  Never leaves F open
 *	for output even on failure.  Returns 0 on success; sets errno
 *	and return -1 on failure.
 */
static int
filemon_closefd(struct filemon *F)
{
	int error = 0;

	/* If we're not open, nothing to do.  */
	if (F->out == NULL)
		return 0;

	/*
	 * Flush it, close it, and null it unconditionally, but be
	 * careful to return the earliest error in errno.
	 */
	if (fflush(F->out) == EOF && error == 0)
		error = errno;
	if (fclose(F->out) == EOF && error == 0)
		error = errno;
	F->out = NULL;

	/* Set errno and return -1 if anything went wrong.  */
	if (error) {
		errno = error;
		return -1;
	}

	/* Success!  */
	return 0;
}

/*
 * filemon_setfd(F, fd)
 *
 *	Cause filemon activity on F to be sent to fd.  Claims ownership
 *	of fd; caller should not use fd afterward, and any duplicates
 *	of fd may see their file positions changed.
 */
int
filemon_setfd(struct filemon *F, int fd)
{

	/*
	 * Close an existing output file if done.  Fail now if there's
	 * an error closing.
	 */
	if ((filemon_closefd(F)) == -1)
		return -1;
	assert(F->out == NULL);

	/* Open a file stream and claim ownership of the fd.  */
	if ((F->out = fdopen(fd, "a")) == NULL)
		return -1;

	/*
	 * Print the opening output.  Any failure will be deferred
	 * until closing.  For hysterical raisins, we show the parent
	 * pid, not the child pid.
	 */
	fprintf(F->out, "# filemon version 4\n");
	fprintf(F->out, "# Target pid %jd\n", (intmax_t)getpid());
	fprintf(F->out, "V 4\n");

	/* Success!  */
	return 0;
}

/*
 * filemon_setpid_parent(F, pid)
 *
 *	Set the traced pid, from the parent.  Never fails.
 */
void
filemon_setpid_parent(struct filemon *F, pid_t pid)
{

	F->child = pid;
}

/*
 * filemon_setpid_child(F, pid)
 *
 *	Set the traced pid, from the child.  Returns 0 on success; sets
 *	errno and returns -1 on failure.
 */
int
filemon_setpid_child(const struct filemon *F, pid_t pid)
{
	int ops, trpoints;

	ops = KTROP_SET|KTRFLAG_DESCEND;
	trpoints = KTRFACv2;
	trpoints |= KTRFAC_SYSCALL|KTRFAC_NAMEI|KTRFAC_SYSRET;
	trpoints |= KTRFAC_INHERIT;
	if (fktrace(F->ktrfd, ops, trpoints, pid) == -1)
		return -1;

	return 0;
}

/*
 * filemon_close(F)
 *
 *	Close F for output if necessary, and free a filemon descriptor.
 *	Returns 0 on success; sets errno and returns -1 on failure, but
 *	frees the filemon descriptor either way;
 */
int
filemon_close(struct filemon *F)
{
	struct filemon_state *S;
	int error = 0;

	/* Close for output.  */
	if (filemon_closefd(F) == -1 && error == 0)
		error = errno;

	/* Close the ktrace pipe.  */
	if (fclose(F->in) == EOF && error == 0)
		error = errno;
	if (close(F->ktrfd) == -1 && error == 0)
		error = errno;

	/* Free any active records.  */
	while ((S = RB_TREE_MIN(&F->active)) != NULL) {
		rb_tree_remove_node(&F->active, S);
		free(S);
	}

	/* Free the filemon descriptor.  */
	free(F);

	/* Set errno and return -1 if anything went wrong.  */
	if (error) {
		errno = error;
		return -1;
	}

	/* Success!  */
	return 0;
}

/*
 * filemon_readfd(F)
 *
 *	Returns a file descriptor which will select/poll ready for read
 *	when there are filemon events to be processed by
 *	filemon_process, or -1 if anything has gone wrong.
 */
int
filemon_readfd(const struct filemon *F)
{

	if (F->state == FILEMON_ERROR)
		return -1;
	return fileno(F->in);
}

/*
 * filemon_dispatch(F)
 *
 *	Internal subroutine to dispatch a filemon ktrace event.
 *	Silently ignore events that we don't recognize.
 */
static void
filemon_dispatch(struct filemon *F)
{
	const struct filemon_key key = {
		.pid = F->hdr.ktr_pid,
		.lid = F->hdr.ktr_lid,
	};
	struct filemon_state *S;

	switch (F->hdr.ktr_type) {
	case KTR_SYSCALL: {
		struct ktr_syscall *call = &F->payload.syscall;
		struct filemon_state *S1;

		/* Validate the syscall code.  */
		if (call->ktr_code < 0 ||
		    (size_t)call->ktr_code >= __arraycount(filemon_syscalls) ||
		    filemon_syscalls[call->ktr_code] == NULL)
			break;

		/*
		 * Invoke the syscall-specific logic to create a new
		 * active state.
		 */
		S = (*filemon_syscalls[call->ktr_code])(F, &key, call);
		if (S == NULL)
			break;

		/*
		 * Insert the active state, or ignore it if there
		 * already is one.
		 *
		 * Collisions shouldn't happen because the states are
		 * keyed by <pid,lid>, in which syscalls should happen
		 * sequentially in CALL/RET pairs, but let's be
		 * defensive.
		 */
		S1 = rb_tree_insert_node(&F->active, S);
		if (S1 != S) {
			/* XXX Which one to drop?  */
			free(S);
			break;
		}
		break;
	}
	case KTR_NAMEI:
		/* Find an active syscall state, or drop it.  */
		S = rb_tree_find_node(&F->active, &key);
		if (S == NULL)
			break;
		/* Find the position of the next path, or drop it.  */
		if (S->i >= S->npath)
			break;
		/* Record the path.  */
		S->path[S->i++] = strndup(F->payload.namei,
		    sizeof F->payload.namei);
		break;
	case KTR_SYSRET: {
		struct ktr_sysret *ret = &F->payload.sysret;
		unsigned i;

		/* Find and remove an active syscall state, or drop it.  */
		S = rb_tree_find_node(&F->active, &key);
		if (S == NULL)
			break;
		rb_tree_remove_node(&F->active, S);

		/*
		 * If the active syscall state matches this return,
		 * invoke the syscall-specific logic to show a filemon
		 * event.
		 */
		/* XXX What to do if syscall code doesn't match?  */
		if (S->i == S->npath && S->syscode == ret->ktr_code)
			(*S->show)(F, S, ret);

		/* Free the state now that it is no longer active.  */
		for (i = 0; i < S->i; i++)
			free(S->path[i]);
		free(S);
		break;
	}
	default:
		/* Ignore all other ktrace events.  */
		break;
	}
}

/*
 * filemon_process(F)
 *
 *	Process all pending events after filemon_readfd(F) has
 *	selected/polled ready for read.
 *
 *	Returns -1 on failure, 0 on end of events, and anything else if
 *	there may be more events.
 *
 *	XXX What about fairness to other activities in the event loop?
 *	If we stop while there's events buffered in F->in, then select
 *	or poll may not return ready even though there's work queued up
 *	in the buffer of F->in, but if we don't stop then ktrace events
 *	may overwhelm all other activity in the event loop.
 */
int
filemon_process(struct filemon *F)
{
	size_t nread;

top:	/* If the child has exited, nothing to do.  */
	/* XXX What if one thread calls exit while another is running?  */
	if (F->child == 0)
		return 0;

	/* If we're waiting for input, read some.  */
	if (F->resid) {
		nread = fread(F->p, 1, F->resid, F->in);
		if (nread == 0) {
			if (feof(F->in))
				return 0;
			assert(ferror(F->in));
			/*
			 * If interrupted or would block, there may be
			 * more events.  Otherwise fail.
			 */
			if (errno == EAGAIN || errno == EINTR)
				return 1;
			F->state = FILEMON_ERROR;
			F->p = NULL;
			F->resid = 0;
			return -1;
		}
		assert(nread <= F->resid);
		F->p += nread;
		F->resid -= nread;
		if (F->resid)	/* may be more events */
			return 1;
	}

	/* Process a state transition now that we've read a buffer.  */
	switch (F->state) {
	case FILEMON_START:	/* just started filemon; read header next */
		F->state = FILEMON_HEADER;
		F->p = (void *)&F->hdr;
		F->resid = sizeof F->hdr;
		goto top;
	case FILEMON_HEADER:	/* read header */
		/* Sanity-check ktrace header; then read payload.  */
		if (F->hdr.ktr_len < 0 ||
		    (size_t)F->hdr.ktr_len > sizeof F->payload) {
			F->state = FILEMON_ERROR;
			F->p = NULL;
			F->resid = 0;
			errno = EIO;
			return -1;
		}
		F->state = FILEMON_PAYLOAD;
		F->p = (void *)&F->payload;
		F->resid = (size_t)F->hdr.ktr_len;
		goto top;
	case FILEMON_PAYLOAD:	/* read header and payload */
		/* Dispatch ktrace event; then read next header.  */
		filemon_dispatch(F);
		F->state = FILEMON_HEADER;
		F->p = (void *)&F->hdr;
		F->resid = sizeof F->hdr;
		goto top;
	default:		/* paranoia */
		F->state = FILEMON_ERROR;
		/*FALLTHROUGH*/
	case FILEMON_ERROR:	/* persistent error indicator */
		F->p = NULL;
		F->resid = 0;
		errno = EIO;
		return -1;
	}
}

static struct filemon_state *
syscall_enter(struct filemon *F,
    const struct filemon_key *key, const struct ktr_syscall *call,
    unsigned npath,
    void (*show)(struct filemon *, const struct filemon_state *,
	const struct ktr_sysret *))
{
	struct filemon_state *S;
	unsigned i;

	S = calloc(1, offsetof(struct filemon_state, path[npath]));
	if (S == NULL)
		return NULL;
	S->key = *key;
	S->show = show;
	S->syscode = call->ktr_code;
	S->i = 0;
	S->npath = npath;
	for (i = 0; i < npath; i++)
		 S->path[i] = NULL; /* paranoia */

	return S;
}

static void
show_paths(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret, const char *prefix)
{
	unsigned i;

	/* Caller must ensure all paths have been specified.  */
	assert(S->i == S->npath);

	/*
	 * Ignore it if it failed or yielded EJUSTRETURN (-2), or if
	 * we're not producing output.
	 */
	if (ret->ktr_error && ret->ktr_error != -2)
		return;
	if (F->out == NULL)
		return;

	/*
	 * Print the prefix, pid, and paths -- with the paths quoted if
	 * there's more than one.
	 */
	fprintf(F->out, "%s %jd", prefix, (intmax_t)S->key.pid);
	for (i = 0; i < S->npath; i++) {
		const char *q = S->npath > 1 ? "'" : "";
		fprintf(F->out, " %s%s%s", q, S->path[i], q);
	}
	fprintf(F->out, "\n");
}

static void
show_retval(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret, const char *prefix)
{

	/*
	 * Ignore it if it failed or yielded EJUSTRETURN (-2), or if
	 * we're not producing output.
	 */
	if (ret->ktr_error && ret->ktr_error != -2)
		return;
	if (F->out == NULL)
		return;

	fprintf(F->out, "%s %jd %jd\n", prefix, (intmax_t)S->key.pid,
	    (intmax_t)ret->ktr_retval);
}

static void
show_chdir(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	show_paths(F, S, ret, "C");
}

static void
show_execve(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	return show_paths(F, S, ret, "E");
}

static void
show_fork(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	show_retval(F, S, ret, "F");
}

static void
show_link(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	show_paths(F, S, ret, "L"); /* XXX same as symlink */
}

static void
show_open_read(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	show_paths(F, S, ret, "R");
}

static void
show_open_write(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	show_paths(F, S, ret, "W");
}

static void
show_open_readwrite(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	show_paths(F, S, ret, "R");
	show_paths(F, S, ret, "W");
}

static void
show_openat_read(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	if (S->path[0][0] != '/')
		show_paths(F, S, ret, "A");
	show_paths(F, S, ret, "R");
}

static void
show_openat_write(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	if (S->path[0][0] != '/')
		show_paths(F, S, ret, "A");
	show_paths(F, S, ret, "W");
}

static void
show_openat_readwrite(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	if (S->path[0][0] != '/')
		show_paths(F, S, ret, "A");
	show_paths(F, S, ret, "R");
	show_paths(F, S, ret, "W");
}

static void
show_symlink(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	show_paths(F, S, ret, "L"); /* XXX same as link */
}

static void
show_unlink(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	show_paths(F, S, ret, "D");
}

static void
show_rename(struct filemon *F, const struct filemon_state *S,
    const struct ktr_sysret *ret)
{
	show_paths(F, S, ret, "M");
}

static struct filemon_state *
filemon_sys_chdir(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	return syscall_enter(F, key, call, 1, &show_chdir);
}

static struct filemon_state *
filemon_sys_execve(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	return syscall_enter(F, key, call, 1, &show_execve);
}

static struct filemon_state *
filemon_sys_exit(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	const register_t *args = (const void *)&call[1];
	int status = args[0];

	if (F->out) {
		fprintf(F->out, "X %jd %d\n", (intmax_t)key->pid, status);
		if (key->pid == F->child) {
			fprintf(F->out, "# Bye bye\n");
			F->child = 0;
		}
	}
	return NULL;
}

static struct filemon_state *
filemon_sys_fork(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	return syscall_enter(F, key, call, 0, &show_fork);
}

static struct filemon_state *
filemon_sys_link(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	return syscall_enter(F, key, call, 2, &show_link);
}

static struct filemon_state *
filemon_sys_open(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	const register_t *args = (const void *)&call[1];
	int flags;

	if (call->ktr_argsize < 2)
		return NULL;
	flags = args[1];

	if ((flags & O_RDWR) == O_RDWR)
		return syscall_enter(F, key, call, 1, &show_open_readwrite);
	else if ((flags & O_WRONLY) == O_WRONLY)
		return syscall_enter(F, key, call, 1, &show_open_write);
	else if ((flags & O_RDONLY) == O_RDONLY)
		return syscall_enter(F, key, call, 1, &show_open_read);
	else
		return NULL;	/* XXX Do we care if no read or write?  */
}

static struct filemon_state *
filemon_sys_openat(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	const register_t *args = (const void *)&call[1];
	int flags, fd;

	if (call->ktr_argsize < 3)
		return NULL;
	fd = args[0];
	flags = args[2];

	if (fd == AT_CWD) {
		if ((flags & O_RDWR) == O_RDWR)
			return syscall_enter(F, key, call, 1,
			    &show_open_readwrite);
		else if ((flags & O_WRONLY) == O_WRONLY)
			return syscall_enter(F, key, call, 1,
			    &show_open_write);
		else if ((flags & O_RDONLY) == O_RDONLY)
			return syscall_enter(F, key, call, 1, &show_open_read);
		else
			return NULL;
	} else {
		if ((flags & O_RDWR) == O_RDWR)
			return syscall_enter(F, key, call, 1,
			    &show_openat_readwrite);
		else if ((flags & O_WRONLY) == O_WRONLY)
			return syscall_enter(F, key, call, 1,
			    &show_openat_write);
		else if ((flags & O_RDONLY) == O_RDONLY)
			return syscall_enter(F, key, call, 1,
			    &show_openat_read);
		else
			return NULL;
	}
}

static struct filemon_state *
filemon_sys_symlink(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	return syscall_enter(F, key, call, 2, &show_symlink);
}

static struct filemon_state *
filemon_sys_unlink(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	return syscall_enter(F, key, call, 1, &show_unlink);
}

static struct filemon_state *
filemon_sys_rename(struct filemon *F, const struct filemon_key *key,
    const struct ktr_syscall *call)
{
	return syscall_enter(F, key, call, 2, &show_rename);
}