source: trunk/base/src/pextlib1.0/tracelib.c @ 108070

Last change on this file since 108070 was 108070, checked in by jmr@…, 7 years ago

wrap kqueue use in appropriate ifdefs

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 30.4 KB
Line 
1/* # -*- coding: utf-8; mode: c; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=c:et:sw=4:ts=4:sts=4
2 */
3/*
4 * tracelib.c
5 * $Id: tracelib.c 108070 2013-07-12 05:55:23Z jmr@macports.org $
6 *
7 * Copyright (c) 2007-2008 Eugene Pimenov (GSoC)
8 * Copyright (c) 2008-2010, 2012-2013 The MacPorts Project
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the MacPorts Team nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 */
35
36#if HAVE_CONFIG_H
37#include <config.h>
38#endif
39
40#include <errno.h>
41#include <fcntl.h>
42#include <limits.h>
43#include <pthread.h>
44#include <stdarg.h>
45#include <stdint.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#if HAVE_SYS_EVENT_H
50#include <sys/event.h>
51#endif
52#include <sys/resource.h>
53#include <sys/socket.h>
54#include <sys/time.h>
55#include <sys/types.h>
56#include <sys/un.h>
57#include <unistd.h>
58
59#include <cregistry/entry.h>
60#include <registry2.0/registry.h>
61
62#include "tracelib.h"
63
64#include "strlcat.h"
65
66#ifndef HAVE_STRLCPY
67/* Define strlcpy if it's not available. */
68size_t strlcpy(char *dst, const char *src, size_t size);
69size_t strlcpy(char *dst, const char *src, size_t size) {
70    size_t result = strlen(src);
71    if (size > 0) {
72        size_t copylen = size - 1;
73        if (copylen > result) {
74            copylen = result;
75        }
76        memcpy(dst, src, copylen);
77        dst[copylen] = 0;
78    }
79    return result;
80}
81#endif
82
83static char *name;
84static char *sandbox;
85static char *filemap, *filemap_end;
86static char *depends;
87static int sock = -1;
88static int kq = -1;
89#ifndef EVFILT_USER
90/* if EVFILT_USER isn't available (< 10.6), use the self-pipe trick to return
91 * from the blocking kqueue(2) call by writing a byte to the pipe */
92static int selfpipe[2];
93#endif
94static int enable_fence = 0;
95static Tcl_Interp *interp;
96static pthread_mutex_t sock_mutex = PTHREAD_MUTEX_INITIALIZER;
97static int cleanuping = 0;
98static char *sdk = NULL;
99
100static void send_file_map(int sock);
101static void dep_check(int sock, char *path);
102static void sandbox_violation(int sock, const char *path);
103static void ui_warn(const char *format, ...);
104#if 0
105static void ui_info(const char *format, ...);
106#endif
107static void ui_error(const char *format, ...);
108
109#define MAX_SOCKETS (1024)
110#define BUFSIZE     (1024)
111
112/**
113 * send a buffer \c buf with the given length \c size to the socket \c sock, by
114 * using the communication protocol between darwintrace and tracelib (i.e., by
115 * prefixing the code with a uint32_t containing the length of the message)
116 *
117 * \param[in] sock the socket to send to
118 * \param[in] buf the buffer to send, should contain at least \c size bytes
119 * \param[in] size the number of bytes in \c buf
120 */
121static void answer_s(int sock, const char *buf, uint32_t size) {
122    send(sock, &size, sizeof(size), 0);
123    send(sock, buf, size, 0);
124}
125
126/**
127 * send a '\0'-terminated string given in \c buf to the socket \c by using the
128 * communication protocol between darwintrace and tracelib. See \c answer_s for
129 * details.
130 *
131 * \param[in] sock the socket to send to
132 * \param[in] buf the string to send; must be \0-terminated
133 */
134static void answer(int sock, const char *buf) {
135    answer_s(sock, buf, (uint32_t) strlen(buf));
136}
137
138/**
139 * Sets the path of the tracelib unix socket where darwintrace should attempt
140 * to connect to. This path should be specific to the port being installed.
141 * Different sockets should be used for different ports (and maybe even
142 * phases).
143 *
144 * \param[inout] interp the Tcl interpreter
145 * \param[in] objc the number of parameters
146 * \param[in] the parameters
147 * \return a Tcl return code
148 */
149static int TracelibSetNameCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
150    if (objc != 3) {
151        Tcl_WrongNumArgs(interp, 2, objv, "number of arguments should be exactly 3");
152        return TCL_ERROR;
153    }
154
155    name = strdup(Tcl_GetString(objv[2]));
156    if (!name) {
157        Tcl_SetResult(interp, "memory allocation failed", TCL_STATIC);
158        return TCL_ERROR;
159    }
160
161    return TCL_OK;
162}
163
164/**
165 * Save sandbox boundaries to memory and format them for darwintrace. This
166 * means changing : to \0 (with \ being an escape char).
167 *
168 * Input:
169 *  /dev/null:/dev/tty:/tmp\:
170 * In variable;
171 *  /dev/null\0/dev/tty\0/tmp:\0\0
172 *
173 * \param[inout] interp the Tcl interpreter
174 * \param[in] objc the number of parameters
175 * \param[in] the parameters
176 * \return a Tcl return code
177 */
178static int TracelibSetSandboxCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
179    int len;
180    char *src, *dst;
181    enum { NORMAL, ESCAPE } state = NORMAL;
182
183    if (objc != 3) {
184        Tcl_WrongNumArgs(interp, 2, objv, "number of arguments should be exactly 3");
185        return TCL_ERROR;
186    }
187
188    src = Tcl_GetString(objv[2]);
189    len = strlen(src) + 2;
190    sandbox = malloc(len);
191    if (!sandbox) {
192        Tcl_SetResult(interp, "memory allocation failed", TCL_STATIC);
193        return TCL_ERROR;
194    }
195    for (dst = sandbox; *src != '\0'; src++) {
196        switch (*src) {
197            case '\\':
198                if (state == ESCAPE) {
199                    /* double backslash, turn into single backslash (note
200                     * C strings use \ as escape char, too! */
201                    *dst++ = '\\';
202                    state = NORMAL;
203                } else {
204                    /* hit a backslash, assume this is an escape sequence */
205                    state = ESCAPE;
206                }
207                break;
208            case ':':
209                if (state == ESCAPE) {
210                    /* : was escaped, keep literally */
211                    *dst++ = ':';
212                    state = NORMAL;
213                } else {
214                    /* : -> \0, unless it has been escaped */
215                    *dst++ = '\0';
216                }
217                break;
218            default:
219                if (state == ESCAPE) {
220                    /* unknown escape sequence, free buffer and raise an error */
221                    free(sandbox);
222                    Tcl_SetResult(interp, "unknown escape sequence", TCL_STATIC);
223                    return TCL_ERROR;
224                }
225                /* otherwise: copy the char */
226                *dst++ = *src;
227                break;
228        }
229    }
230    /* add two \0 to mark the end */
231    *dst++ = '\0';
232    *dst = '\0';
233
234    return TCL_OK;
235}
236
237/**
238 * Receive line from socket, parse it and send an answer, if necessary. The
239 * caller should ensure that data is available for reading from the given
240 * socket. This method will block until a complete message has been read.
241 *
242 * \param[in] sock the socket to communicate with
243 * \return 1, if the communication was successful, 0 in case of errors and/or
244 *         when the socket should be closed
245 */
246static int process_line(int sock) {
247    char *f;
248    char buf[BUFSIZE];
249    uint32_t len;
250    ssize_t ret;
251
252    if ((ret = recv(sock, &len, sizeof(len), MSG_WAITALL)) != sizeof(len)) {
253        if (ret < 0) {
254            perror("tracelib: recv");
255        } else if (ret == 0) {
256            /* this usually means the socket was closed by the remote side */
257        } else {
258            fprintf(stderr, "tracelib: partial data received: expected %ld, but got %ld on socket %d\n", (unsigned long) sizeof(len), (unsigned long) ret, sock);
259        }
260        return 0;
261    }
262
263    if (len > BUFSIZE - 1) {
264        fprintf(stderr, "tracelib: transfer too large: %ld bytes sent, but buffer holds %d on socket %d\n", (unsigned long) len, (int) (BUFSIZE - 1), sock);
265        return 0;
266    }
267
268    if ((ret = recv(sock, buf, len, MSG_WAITALL)) != (ssize_t) len) {
269        if (ret < 0) {
270            perror("tracelib: recv");
271        } else {
272            fprintf(stderr, "tracelib: partial data received: expected %ld, but got %ld on socket %d\n", (unsigned long) len, (unsigned long) ret, sock);
273        }
274        return 0;
275    }
276    buf[len] = '\0';
277
278    f = strchr(buf, '\t');
279    if (!f) {
280        fprintf(stderr, "tracelib: malformed command '%s' from socket %d\n", buf, sock);
281        return 0;
282    }
283
284    /* Replace \t with \0 */
285    *f = '\0';
286    /* Advance pointer to arguments */
287    f++;
288
289    if (strcmp(buf, "filemap") == 0) {
290        send_file_map(sock);
291    } else if (strcmp(buf, "sandbox_violation") == 0) {
292        sandbox_violation(sock, f);
293    } else if (strcmp(buf, "dep_check") == 0) {
294        dep_check(sock, f);
295    } else {
296        fprintf(stderr, "tracelib: unexpected command %s (%s)\n", buf, f);
297        return 0;
298    }
299
300    return 1;
301}
302
303/**
304 * Construct an in-memory representation of the sandbox file map and send it to
305 * the socket indicated by \c sock.
306 *
307 * \param[in] sock the socket to send the sandbox bounds to
308 */
309static void send_file_map(int sock) {
310    if (!filemap) {
311        char *t, * _;
312
313        size_t remaining = 1024;
314        filemap = (char *)malloc(remaining);
315        if (!filemap) {
316            ui_warn("send_file_map: memory allocation failed");
317            return;
318        }
319        t = filemap;
320
321#       define append_allow(path, resolution) do { strlcpy(t, path, remaining); \
322            if (remaining < (strlen(t)+3)) \
323                remaining=0; \
324            else \
325                remaining-=strlen(t)+3; \
326            t+=strlen(t)+1; \
327            *t++=resolution; \
328            *t++=0; \
329        } while(0);
330
331        if (enable_fence) {
332            for (_ = sandbox; *_; _ += strlen(_) + 1) {
333                append_allow(_, 0);
334            }
335
336            append_allow("/bin", 0);
337            append_allow("/sbin", 0);
338            append_allow("/dev", 0);
339            append_allow(Tcl_GetVar(interp, "prefix", TCL_GLOBAL_ONLY), 2);
340            /* If there is no SDK we will allow everything in /usr /System/Library etc, else add binaries to allow, and redirect root to SDK. */
341            if (sdk && *sdk) {
342                char buf[260];
343                buf[0] = '\0';
344                strlcat(buf, Tcl_GetVar(interp, "developer_dir", TCL_GLOBAL_ONLY), 260);
345                strlcat(buf, "/SDKs/", 260);
346                strlcat(buf, sdk, 260);
347
348                append_allow("/usr/bin", 0);
349                append_allow("/usr/sbin", 0);
350                append_allow("/usr/libexec/gcc", 0);
351                append_allow("/System/Library/Perl", 0);
352                append_allow("/", 1);
353                strlcpy(t - 1, buf, remaining);
354                t += strlen(t) + 1;
355            } else {
356                append_allow("/usr", 0);
357                append_allow("/System/Library", 0);
358                append_allow("/Library", 0);
359                append_allow(Tcl_GetVar(interp, "developer_dir", TCL_GLOBAL_ONLY), 0);
360            }
361        } else {
362            append_allow("/", 0);
363        }
364        append_allow("", 0);
365        filemap_end = t;
366#       undef append_allow
367    }
368
369    answer_s(sock, filemap, filemap_end - filemap);
370}
371
372/**
373 * Process a sandbox violation reported by darwintrace. Calls back up to Tcl to
374 * run a callback with the reported violation path.
375 *
376 * \param[in] sock socket reporting the violation; unused.
377 * \param[in] path the offending path to be passed to the callback
378 */
379static void sandbox_violation(int sock UNUSED, const char *path) {
380    Tcl_SetVar(interp, "path", path, 0);
381    Tcl_Eval(interp, "slave_add_sandbox_violation $path");
382    Tcl_UnsetVar(interp, "path", 0);
383}
384
385/**
386 * Check whether a path is in the transitive hull of dependencies of the port
387 * currently being installed and send the result of the query back to the
388 * socket.
389 *
390 * Sends one of the following characters as return code to the socket:
391 *  - #: in case of errors. Not handled by the darwintrace code, which will
392 *       lead to an error and the termination of the processing that sent the
393 *       request causing this error.
394 *  - ?: if the file isn't known to MacPorts (i.e., not registered to any port)
395 *  - +: if the file was installed by a dependency and access should be granted
396 *  - !: if the file was installed by a MacPorts port which is not in the
397 *       transitive hull of dependencies and access should be denied.
398 *
399 * \param[in] sock the socket to answer to
400 * \param[in] path the path to return the dependency information for
401 */
402static void dep_check(int sock, char *path) {
403    char *port = 0;
404    char *t;
405    reg_registry *reg;
406    reg_entry entry;
407    reg_error error;
408
409    if (NULL == (reg = registry_for(interp, reg_attached))) {
410        ui_error(Tcl_GetStringResult(interp));
411        /* send unexpected output to make the build fail */
412        answer(sock, "#");
413    }
414
415    /* find the port id */
416    entry.reg = reg;
417    entry.proc = NULL;
418    entry.id = reg_entry_owner_id(reg, path);
419    if (entry.id == 0) {
420        /* file isn't known to MacPorts */
421        answer(sock, "?");
422        return;
423    }
424
425    /* find the port's name to compare with out list */
426    if (!reg_entry_propget(&entry, "name", &port, &error)) {
427        /* send unexpected output to make the build fail */
428        ui_error(error.description);
429        answer(sock, "#");
430    }
431
432    /* check our list of dependencies */
433    for (t = depends; *t; t += strlen(t) + 1) {
434        if (strcmp(t, port) == 0) {
435            free(port);
436            answer(sock, "+");
437            return;
438        }
439    }
440
441    free(port);
442    answer(sock, "!");
443}
444
445static void ui_msg(const char *severity, const char *format, va_list va) {
446    char buf[1024], tclcmd[32];
447
448    vsnprintf(buf, sizeof(buf), format, va);
449
450    snprintf(tclcmd, sizeof(tclcmd), "ui_%s $warn", severity);
451
452    Tcl_SetVar(interp, "warn", buf, 0);
453    if (TCL_OK != Tcl_Eval(interp, tclcmd)) {
454        fprintf(stderr, "Error evaluating tcl statement `%s': %s\n", tclcmd, Tcl_GetStringResult(interp));
455    }
456    Tcl_UnsetVar(interp, "warn", 0);
457
458}
459
460static void ui_warn(const char *format, ...) {
461    va_list va;
462
463    va_start(va, format);
464    ui_msg("warn", format, va);
465    va_end(va);
466}
467
468#if 0
469static void ui_info(const char *format, ...) {
470    va_list va;
471
472    va_start(va, format);
473    ui_msg("info", format, va);
474    va_end(va);
475}
476#endif
477
478static void ui_error(const char *format, ...) {
479    va_list va;
480    va_start(va, format);
481    ui_msg("error", format, va);
482    va_end(va);
483}
484
485static int TracelibOpenSocketCmd(Tcl_Interp *in) {
486    struct sockaddr_un sun;
487    struct rlimit rl;
488
489    cleanuping = 0;
490
491    pthread_mutex_lock(&sock_mutex);
492    if (-1 == (sock = socket(PF_LOCAL, SOCK_STREAM, 0))) {
493        Tcl_SetErrno(errno);
494        Tcl_ResetResult(interp);
495        Tcl_AppendResult(interp, "socket: ", (char *) Tcl_PosixError(interp), NULL);
496        pthread_mutex_unlock(&sock_mutex);
497        return TCL_ERROR;
498    }
499    pthread_mutex_unlock(&sock_mutex);
500
501    interp = in;
502
503    /* raise the limit of open files to the maximum from the default soft limit
504     * of 256 */
505    if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
506        ui_warn("getrlimit failed (%d), skipping setrlimit", errno);
507    } else {
508#if defined(__APPLE__) && defined(OPEN_MAX)
509        if (rl.rlim_max > OPEN_MAX) {
510            rl.rlim_max = OPEN_MAX;
511        }
512#endif
513        rl.rlim_cur = rl.rlim_max;
514        if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
515            ui_warn("setrlimit failed (%d)", errno);
516        }
517    }
518
519    sun.sun_family = AF_UNIX;
520    strlcpy(sun.sun_path, name, sizeof(sun.sun_path));
521
522    if (-1 == (bind(sock, (struct sockaddr *) &sun, sizeof(sun)))) {
523        Tcl_SetErrno(errno);
524        Tcl_ResetResult(interp);
525        Tcl_AppendResult(interp, "bind: ", (char *) Tcl_PosixError(interp), NULL);
526        close(sock);
527        sock = -1;
528        return TCL_ERROR;
529    }
530
531    if (-1 == listen(sock, 32)) {
532        Tcl_SetErrno(errno);
533        Tcl_ResetResult(interp);
534        Tcl_AppendResult(interp, "listen: ", (char *) Tcl_PosixError(interp), NULL);
535        close(sock);
536        sock = -1;
537        return TCL_ERROR;
538    }
539
540    return TCL_OK;
541}
542
543#if HAVE_KQUEUE
544/* create this on heap rather than stack, due to its rather large size */
545static struct kevent res_kevents[MAX_SOCKETS];
546#endif
547
548static int TracelibRunCmd(Tcl_Interp *in) {
549#if HAVE_KQUEUE
550    struct kevent kev;
551    int flags;
552    int oldsock;
553    int opensockcount = 0;
554
555    if (-1 == (kq = kqueue())) {
556        Tcl_SetErrno(errno);
557        Tcl_ResetResult(in);
558        Tcl_AppendResult(in, "kqueue: ", (char *) Tcl_PosixError(in), NULL);
559        return TCL_ERROR;
560    }
561
562    pthread_mutex_lock(&sock_mutex);
563    if (sock != -1) {
564        oldsock = sock;
565
566        /* mark listen socket non-blocking in order to prevent a race condition
567         * that would occur between kevent(2) and accept(2), if a incoming
568         * connection is aborted before it is accepted. Using a non-blocking
569         * accept(2) prevents the problem.*/
570        flags = fcntl(oldsock, F_GETFL, 0);
571        if (-1 == fcntl(oldsock, F_SETFL, flags | O_NONBLOCK)) {
572            Tcl_SetErrno(errno);
573            Tcl_ResetResult(in);
574            Tcl_AppendResult(in, "fcntl(F_SETFL, += O_NONBLOCK): ", (char *) Tcl_PosixError(in), NULL);
575            return TCL_ERROR;
576        }
577
578        /* register the listen socket in the kqueue */
579        EV_SET(&kev, oldsock, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
580        if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
581            Tcl_SetErrno(errno);
582            Tcl_ResetResult(in);
583            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
584            close(kq);
585            return TCL_ERROR;
586        }
587        /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
588         * always be returned. When a filter is successfully added, the data field
589         * will be zero. */
590        if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
591            Tcl_SetErrno(kev.data);
592            Tcl_ResetResult(in);
593            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
594            close(kq);
595            return TCL_ERROR;
596        }
597
598
599#       ifndef EVFILT_USER
600        /* on systems that don't have EVFILT_USER, use the self-pipe trick to
601         * trigger returning from kevent(2) when tracelib closesocket is
602         * called. */
603        if (-1 == pipe(selfpipe)) {
604            Tcl_SetErrno(errno);
605            Tcl_ResetResult(in);
606            Tcl_AppendResult(in, "pipe: ", (char *) Tcl_PosixError(in), NULL);
607            return TCL_ERROR;
608        }
609
610        /* mark the write side of the pipe non-blocking */
611        flags = fcntl(selfpipe[1], F_GETFL, 0);
612        if (-1 == fcntl(selfpipe[1], F_SETFL, flags | O_NONBLOCK)) {
613            Tcl_SetErrno(errno);
614            Tcl_ResetResult(in);
615            Tcl_AppendResult(in, "fcntl(F_SETFL, += O_NONBLOCK): ", (char *) Tcl_PosixError(in), NULL);
616            return TCL_ERROR;
617        }
618
619        /* wait for the user event on the listen socket, as sent by CloseCmd as
620         * deathpill */
621        EV_SET(&kev, selfpipe[0], EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
622        if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
623            Tcl_SetErrno(errno);
624            Tcl_ResetResult(in);
625            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
626            close(kq);
627            return TCL_ERROR;
628        }
629        /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
630         * always be returned. When a filter is successfully added, the data field
631         * will be zero. */
632        if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
633            Tcl_SetErrno(kev.data);
634            Tcl_ResetResult(in);
635            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
636            close(kq);
637            return TCL_ERROR;
638        }
639#       else /* ifndef EVFILT_USER */
640        /* wait for the user event on the listen socket, as sent by CloseCmd as
641         * deathpill */
642        EV_SET(&kev, oldsock, EVFILT_USER, EV_ADD | EV_RECEIPT, 0, 0, NULL);
643        if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
644            Tcl_SetErrno(errno);
645            Tcl_ResetResult(in);
646            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
647            close(kq);
648            return TCL_ERROR;
649        }
650        /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
651         * always be returned. When a filter is successfully added, the data field
652         * will be zero. */
653        if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
654            Tcl_SetErrno(kev.data);
655            Tcl_ResetResult(in);
656            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
657            close(kq);
658            return TCL_ERROR;
659        }
660#       endif /* ifndef EVFILT_USER */
661    }
662    pthread_mutex_unlock(&sock_mutex);
663
664    while (sock != -1 && !cleanuping) {
665        int keventstatus;
666        int i;
667
668        /* run kevent(2) until new activity is available */
669        do {
670            if (-1 == (keventstatus = kevent(kq, NULL, 0, res_kevents, MAX_SOCKETS, NULL))) {
671                Tcl_SetErrno(errno);
672                Tcl_ResetResult(in);
673                Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
674                close(kq);
675                return TCL_ERROR;
676            }
677        } while (keventstatus == 0);
678
679        for (i = 0; i < keventstatus; ++i) {
680#           ifndef EVFILT_USER
681            /* handle traffic on the selfpipe */
682            if ((int) res_kevents[i].ident == selfpipe[0]) {
683                pthread_mutex_lock(&sock_mutex);
684                close(selfpipe[0]);
685                close(selfpipe[1]);
686                selfpipe[0] = -1;
687                selfpipe[1] = -1;
688                pthread_mutex_unlock(&sock_mutex);
689                break;
690            }
691#           endif
692            /* the control socket has activity – we might have a new
693             * connection. We use a copy of sock here, because sock might have
694             * been set to -1 by the close command */
695            if ((int) res_kevents[i].ident == oldsock) {
696                int s;
697
698                /* handle error conditions */
699                if ((res_kevents[i].flags & (EV_ERROR | EV_EOF)) > 0) {
700                    if (cleanuping) {
701                        break;
702                    }
703                    Tcl_ResetResult(in);
704                    Tcl_SetResult(in, "control socket closed", NULL);
705                    close(kq);
706                    return TCL_ERROR;
707                }
708
709                /* else: new connection attempt(s) */
710                for (;;) {
711                    if (-1 == (s = accept(sock, NULL, NULL))) {
712                        if (cleanuping) {
713                            break;
714                        }
715                        if (errno == EWOULDBLOCK) {
716                            break;
717                        }
718                        Tcl_SetErrno(errno);
719                        Tcl_ResetResult(in);
720                        Tcl_AppendResult(in, "accept: ", (char *) Tcl_PosixError(in), NULL);
721                        close(kq);
722                        return TCL_ERROR;
723                    }
724
725                    flags = fcntl(s, F_GETFL, 0);
726                    if (-1 == fcntl(s, F_SETFL, flags & ~O_NONBLOCK)) {
727                        ui_warn("tracelib: couldn't mark socket as blocking");
728                        close(s);
729                        continue;
730                    }
731
732                    /* register the new socket in the kqueue */
733                    EV_SET(&kev, s, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
734                    if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
735                        ui_warn("tracelib: error adding socket to kqueue");
736                        close(s);
737                        continue;
738                    }
739                    /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
740                     * always be returned. When a filter is successfully added, the data field
741                     * will be zero. */
742                    if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
743                        ui_warn("tracelib: error adding socket to kqueue");
744                        close(s);
745                        continue;
746                    }
747
748                    opensockcount++;
749                }
750
751                if (cleanuping) {
752                    break;
753                }
754            } else {
755                /* if the socket is to be closed, or */
756                if ((res_kevents[i].flags & (EV_EOF | EV_ERROR)) > 0
757                    /* new data is available, and its processing tells us to
758                     * close the socket */
759                    || (!process_line(res_kevents[i].ident))) {
760                    /* an error occured or process_line suggested closing this
761                     * socket */
762                    close(res_kevents[i].ident);
763                    /* closing the socket will automatically remove it from the
764                     * kqueue :) */
765                    opensockcount--;
766                }
767            }
768        }
769    }
770
771    /* NOTE: We aren't necessarily closing all client sockets here! */
772    if (opensockcount > 0) {
773        fprintf(stderr, "tracelib: %d open sockets will leak at end of runcmd\n", opensockcount);
774    }
775    pthread_mutex_lock(&sock_mutex);
776    close(kq);
777    kq = -1;
778    pthread_mutex_unlock(&sock_mutex);
779
780    return TCL_OK;
781#else
782    Tcl_SetResult(in, "tracelib not supported on this platform", TCL_STATIC);
783    return TCL_ERROR;
784#endif
785}
786
787static int TracelibCleanCmd(Tcl_Interp *interp UNUSED) {
788#define safe_free(x) do{free(x); x=0;}while(0);
789    cleanuping = 1;
790    pthread_mutex_lock(&sock_mutex);
791    if (sock != -1) {
792        /* shutdown(sock, SHUT_RDWR);*/
793        close(sock);
794        sock = -1;
795    }
796    pthread_mutex_unlock(&sock_mutex);
797    if (name) {
798        unlink(name);
799        safe_free(name);
800    }
801    if (filemap) {
802        safe_free(filemap);
803    }
804    if (depends) {
805        safe_free(depends);
806    }
807    enable_fence = 0;
808#undef safe_free
809    return TCL_OK;
810}
811
812static int TracelibCloseSocketCmd(Tcl_Interp *interp UNUSED) {
813    /* interp might be UNUSED on systems without EVFILT_USER */
814    cleanuping = 1;
815    pthread_mutex_lock(&sock_mutex);
816    if (sock != -1) {
817        int oldsock = sock;
818        /*shutdown(sock, SHUT_RDWR);*/
819        close(oldsock);
820        sock = -1;
821
822        if (kq != -1) {
823#           ifdef EVFILT_USER
824            int ret;
825            struct kevent kev;
826            EV_SET(&kev, oldsock, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
827            if (-1 == (ret = kevent(kq, &kev, 1, NULL, 0, NULL))) {
828                Tcl_SetErrno(errno);
829                Tcl_ResetResult(interp);
830                Tcl_AppendResult(interp, "kevent: ", (char *) Tcl_PosixError(interp), NULL);
831                pthread_mutex_unlock(&sock_mutex);
832                return TCL_ERROR;
833            }
834#           else /* ifdef EVFILT_USER */
835            /* We know the pipes have been created because kq != -1 and we have
836             * the lock. We don't have to check for errors, because none should
837             * occur but when the pipe is full, which we wouldn't care about.
838             * */
839            write(selfpipe[1], "!", 1);
840#           endif /* ifdef EVFILT_USER */
841        }
842    }
843    pthread_mutex_unlock(&sock_mutex);
844    return TCL_OK;
845}
846
847static int TracelibSetDeps(Tcl_Interp *interp UNUSED, int objc, Tcl_Obj *CONST objv[]) {
848    char *t, * d;
849    size_t l;
850    if (objc != 3) {
851        Tcl_WrongNumArgs(interp, 2, objv, "number of arguments should be exactly 3");
852        return TCL_ERROR;
853    }
854
855    d = Tcl_GetString(objv[2]);
856    l = strlen(d);
857    depends = malloc(l + 2);
858    if (!depends) {
859        Tcl_SetResult(interp, "memory allocation failed", TCL_STATIC);
860        return TCL_ERROR;
861    }
862    depends[l + 1] = 0;
863    strlcpy(depends, d, l + 2);
864    for (t = depends; *t; ++t)
865        if (*t == ' ') {
866            *t++ = 0;
867        }
868
869    return TCL_OK;
870}
871
872static int TracelibEnableFence(Tcl_Interp *interp UNUSED) {
873    enable_fence = 1;
874    if (filemap) {
875        free(filemap);
876    }
877    filemap = 0;
878    return TCL_OK;
879}
880
881int TracelibCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
882    int result = TCL_OK;
883    static const char *options[] = {"setname", "opensocket", "run", "clean", "setsandbox", "closesocket", "setdeps", "enablefence", 0};
884    typedef enum {
885        kSetName,
886        kOpenSocket,
887        kRun,
888        kClean,
889        kSetSandbox,
890        kCloseSocket,
891        kSetDeps,
892        kEnableFence
893    } EOptions;
894    EOptions current_option;
895
896    /* There is no args for commands now. */
897    if (objc < 2) {
898        Tcl_WrongNumArgs(interp, 1, objv, "option");
899        return TCL_ERROR;
900    }
901
902    result = Tcl_GetIndexFromObj(interp, objv[1], options, "option", 0, (int *)&current_option);
903    if (result == TCL_OK) {
904        switch (current_option) {
905            case kSetName:
906                result = TracelibSetNameCmd(interp, objc, objv);
907                break;
908            case kOpenSocket:
909                result = TracelibOpenSocketCmd(interp);
910                break;
911            case kRun:
912                result = TracelibRunCmd(interp);
913                break;
914            case kClean:
915                result = TracelibCleanCmd(interp);
916                break;
917            case kCloseSocket:
918                result = TracelibCloseSocketCmd(interp);
919                break;
920            case kSetSandbox:
921                result = TracelibSetSandboxCmd(interp, objc, objv);
922                break;
923            case kSetDeps:
924                result = TracelibSetDeps(interp, objc, objv);
925                break;
926            case kEnableFence:
927                result = TracelibEnableFence(interp);
928                break;
929        }
930    }
931
932    return result;
933}
Note: See TracBrowser for help on using the repository browser.