1/*
2 * Copyright (c) 2009 Mark Heily <mark@heily.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include <sys/types.h>
18
19#include "config.h"
20#include "common.h"
21
22int kqfd;
23static char *cur_test_id = NULL;
24static int testnum = 1;
25
26/* Checks if any events are pending, which is an error. */
27void
28_test_no_kevents(const char *file, int line)
29{
30    int nfds;
31    struct timespec timeo;
32    struct kevent kev;
33    char *kev_str;
34
35    puts("confirming that there are no events pending");
36    memset(&timeo, 0, sizeof(timeo));
37    nfds = kevent(kqfd, NULL, 0, &kev, 1, &timeo);
38    if (nfds != 0) {
39        printf("\n[%s:%d]: Unexpected event:", file, line);
40        kev_str = kevent_to_str(&kev);
41        puts(kev_str);
42        free(kev_str);
43        errx(1, "%d event(s) pending, but none expected:", nfds);
44    }
45}
46
47/* Checks if any events are pending, which is an error. Do not print
48 * out anything unless events are found.
49*/
50void
51test_no_kevents_quietly(void)
52{
53    int nfds;
54    struct timespec timeo;
55    struct kevent kev;
56    char *kev_str;
57
58    memset(&timeo, 0, sizeof(timeo));
59    nfds = kevent(kqfd, NULL, 0, &kev, 1, &timeo);
60    if (nfds != 0) {
61        puts("\nUnexpected event:");
62        kev_str = kevent_to_str(&kev);
63        puts(kev_str);
64        free(kev_str);
65        errx(1, "%d event(s) pending, but none expected:", nfds);
66    }
67}
68
69/* Retrieve a single kevent */
70struct kevent *
71kevent_get(int fd)
72{
73    int nfds;
74    struct kevent *kev;
75
76    if ((kev = calloc(1, sizeof(*kev))) == NULL)
77        err(1, "out of memory");
78
79    nfds = kevent(fd, NULL, 0, kev, 1, NULL);
80    if (nfds < 1)
81        err(1, "kevent(2)");
82
83    return (kev);
84}
85
86/* Retrieve a single kevent, specifying a maximum time to wait for it. */
87struct kevent *
88kevent_get_timeout(int fd, int seconds)
89{
90    int nfds;
91    struct kevent *kev;
92    struct timespec timeout = {seconds, 0};
93
94    if ((kev = calloc(1, sizeof(*kev))) == NULL)
95        err(1, "out of memory");
96
97    nfds = kevent(fd, NULL, 0, kev, 1, &timeout);
98    if (nfds < 0) {
99        err(1, "kevent(2)");
100    } else if (nfds == 0) {
101        free(kev);
102        kev = NULL;
103    }
104
105    return (kev);
106}
107
108static char *
109kevent_fflags_dump(struct kevent *kev)
110{
111    char *buf;
112
113#define KEVFFL_DUMP(attrib) \
114    if (kev->fflags & attrib) \
115        strncat(buf, #attrib" ", 64);
116
117    if ((buf = calloc(1, 1024)) == NULL)
118        abort();
119
120    /* Not every filter has meaningful fflags */
121    if (kev->filter == EVFILT_PROC) {
122        snprintf(buf, 1024, "fflags = %x (", kev->fflags);
123        KEVFFL_DUMP(NOTE_EXIT);
124        KEVFFL_DUMP(NOTE_FORK);
125        KEVFFL_DUMP(NOTE_EXEC);
126        KEVFFL_DUMP(NOTE_CHILD);
127        KEVFFL_DUMP(NOTE_TRACKERR);
128        KEVFFL_DUMP(NOTE_TRACK);
129        buf[strlen(buf) - 1] = ')';
130    } else if (kev->filter == EVFILT_PROCDESC) {
131        snprintf(buf, 1024, "fflags = %x (", kev->fflags);
132        KEVFFL_DUMP(NOTE_EXIT);
133        KEVFFL_DUMP(NOTE_FORK);
134        KEVFFL_DUMP(NOTE_EXEC);
135        buf[strlen(buf) - 1] = ')';
136    } else if (kev->filter == EVFILT_VNODE) {
137        snprintf(buf, 1024, "fflags = %x (", kev->fflags);
138        KEVFFL_DUMP(NOTE_DELETE);
139        KEVFFL_DUMP(NOTE_WRITE);
140        KEVFFL_DUMP(NOTE_EXTEND);
141#if HAVE_NOTE_TRUNCATE
142        KEVFFL_DUMP(NOTE_TRUNCATE);
143#endif
144        KEVFFL_DUMP(NOTE_ATTRIB);
145        KEVFFL_DUMP(NOTE_LINK);
146        KEVFFL_DUMP(NOTE_RENAME);
147#if HAVE_NOTE_REVOKE
148        KEVFFL_DUMP(NOTE_REVOKE);
149#endif
150        buf[strlen(buf) - 1] = ')';
151    } else {
152        snprintf(buf, 1024, "fflags = %x", kev->fflags);
153    }
154
155    return (buf);
156}
157
158static char *
159kevent_flags_dump(struct kevent *kev)
160{
161    char *buf;
162
163#define KEVFL_DUMP(attrib) \
164    if (kev->flags & attrib) \
165        strncat(buf, #attrib" ", 64);
166
167    if ((buf = calloc(1, 1024)) == NULL)
168        abort();
169
170    snprintf(buf, 1024, "flags = %d (", kev->flags);
171    KEVFL_DUMP(EV_ADD);
172    KEVFL_DUMP(EV_ENABLE);
173    KEVFL_DUMP(EV_DISABLE);
174    KEVFL_DUMP(EV_DELETE);
175    KEVFL_DUMP(EV_ONESHOT);
176    KEVFL_DUMP(EV_CLEAR);
177    KEVFL_DUMP(EV_EOF);
178    KEVFL_DUMP(EV_ERROR);
179#if HAVE_EV_DISPATCH
180    KEVFL_DUMP(EV_DISPATCH);
181#endif
182#if HAVE_EV_RECEIPT
183    KEVFL_DUMP(EV_RECEIPT);
184#endif
185    buf[strlen(buf) - 1] = ')';
186
187    return (buf);
188}
189
190/* Copied from ../kevent.c kevent_dump() and improved */
191char *
192kevent_to_str(struct kevent *kev)
193{
194    char buf[512];
195    char *flags_str = kevent_flags_dump(kev);
196    char *fflags_str = kevent_fflags_dump(kev);
197
198    snprintf(&buf[0], sizeof(buf),
199            "[ident=%ju, filter=%d, %s, %s, data=%jd, udata=%p, "
200            "ext=[%jx %jx %jx %jx]",
201            (uintmax_t) kev->ident,
202            kev->filter,
203            flags_str,
204            fflags_str,
205            (uintmax_t)kev->data,
206            kev->udata,
207            (uintmax_t)kev->ext[0],
208            (uintmax_t)kev->ext[1],
209            (uintmax_t)kev->ext[2],
210            (uintmax_t)kev->ext[3]);
211
212    free(flags_str);
213    free(fflags_str);
214
215    return (strdup(buf));
216}
217
218void
219kevent_add(int fd, struct kevent *kev,
220        uintptr_t ident,
221        short     filter,
222        u_short   flags,
223        u_int     fflags,
224        intptr_t  data,
225        void      *udata)
226{
227    char *kev_str;
228
229    EV_SET(kev, ident, filter, flags, fflags, data, udata);
230    if (kevent(fd, kev, 1, NULL, 0, NULL) < 0) {
231        kev_str = kevent_to_str(kev);
232        printf("Unable to add the following kevent:\n%s\n",
233                kev_str);
234        free(kev_str);
235        err(1, "kevent(): %s", strerror(errno));
236    }
237}
238
239void
240_kevent_cmp(struct kevent *k1, struct kevent *k2, const char *file, int line)
241{
242    char *kev1_str;
243    char *kev2_str;
244
245/* XXX-
246   Workaround for inconsistent implementation of kevent(2)
247 */
248#ifdef __FreeBSD__
249    if (k1->flags & EV_ADD)
250        k2->flags |= EV_ADD;
251#endif
252    if (k1->ident != k2->ident || k1->filter != k2->filter ||
253      k1->flags != k2->flags || k1->fflags != k2->fflags ||
254      k1->data != k2->data || k1->udata != k2->udata ||
255      k1->ext[0] != k2->ext[0] || k1->ext[1] != k2->ext[1] ||
256      k1->ext[0] != k2->ext[2] || k1->ext[0] != k2->ext[3]) {
257        kev1_str = kevent_to_str(k1);
258        kev2_str = kevent_to_str(k2);
259        printf("[%s:%d]: kevent_cmp: mismatch:\n  %s !=\n  %s\n",
260	        file, line, kev1_str, kev2_str);
261        free(kev1_str);
262        free(kev2_str);
263        abort();
264    }
265}
266
267void
268test_begin(const char *func)
269{
270    if (cur_test_id)
271        free(cur_test_id);
272    cur_test_id = strdup(func);
273    if (!cur_test_id)
274        err(1, "strdup failed");
275
276    printf("\n\nTest %d: %s\n", testnum++, func);
277}
278
279void
280success(void)
281{
282    printf("%-70s %s\n", cur_test_id, "passed");
283    free(cur_test_id);
284    cur_test_id = NULL;
285}
286
287static void
288test_kqueue(void)
289{
290    test_begin("kqueue()");
291    if ((kqfd = kqueue()) < 0)
292        err(1, "kqueue()");
293    test_no_kevents();
294    success();
295}
296
297static void
298test_kqueue_close(void)
299{
300    test_begin("close(kq)");
301    if (close(kqfd) < 0)
302        err(1, "close()");
303    success();
304}
305
306int
307main(int argc, char **argv)
308{
309    int test_proc = 1;
310    int test_socket = 1;
311    int test_signal = 1;
312    int test_vnode = 1;
313    int test_timer = 1;
314#ifdef __FreeBSD__
315    int test_user = 1;
316#else
317    /* XXX-FIXME temporary */
318    int test_user = 0;
319#endif
320
321    while (argc) {
322        if (strcmp(argv[0], "--no-proc") == 0)
323            test_proc = 0;
324        if (strcmp(argv[0], "--no-socket") == 0)
325            test_socket = 0;
326        if (strcmp(argv[0], "--no-timer") == 0)
327            test_timer = 0;
328        if (strcmp(argv[0], "--no-signal") == 0)
329            test_signal = 0;
330        if (strcmp(argv[0], "--no-vnode") == 0)
331            test_vnode = 0;
332        if (strcmp(argv[0], "--no-user") == 0)
333            test_user = 0;
334        argv++;
335        argc--;
336    }
337
338    /*
339     * Some tests fork.  If output is fully buffered,
340     * the children inherit some buffered data and flush
341     * it when they exit, causing some data to be printed twice.
342     * Use line buffering to avoid this problem.
343     */
344    setlinebuf(stdout);
345    setlinebuf(stderr);
346
347    test_kqueue();
348    test_kqueue_close();
349
350    if (test_socket)
351        test_evfilt_read();
352    if (test_signal)
353        test_evfilt_signal();
354    if (test_vnode)
355        test_evfilt_vnode();
356#if HAVE_EVFILT_USER
357    if (test_user)
358        test_evfilt_user();
359#endif
360    if (test_timer)
361        test_evfilt_timer();
362    if (test_proc)
363        test_evfilt_proc();
364
365    printf("\n---\n"
366            "+OK All %d tests completed.\n", testnum - 1);
367    return (0);
368}
369