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

Last change on this file was 153567, checked in by raimue@…, 4 years ago

pextlib: avoid duplicate newlines in log messages

The ui_* functions will already add a newline to the string on output.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 35.2 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 153567 2016-10-04 15:46:20Z raimue@macports.org $
6 *
7 * Copyright (c) 2007-2008 Eugene Pimenov (GSoC)
8 * Copyright (c) 2008-2010, 2012-2013, 2014-2015 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 <inttypes.h>
43#include <limits.h>
44#include <pthread.h>
45#include <signal.h>
46#include <stdarg.h>
47#include <stdbool.h>
48#include <stdint.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#if HAVE_SYS_EVENT_H
53#include <sys/event.h>
54#endif
55#include <sys/resource.h>
56#include <sys/socket.h>
57#include <sys/time.h>
58#include <sys/types.h>
59#include <sys/un.h>
60#include <unistd.h>
61
62#include <cregistry/portgroup.h>
63#include <cregistry/entry.h>
64#include <registry2.0/registry.h>
65#include <darwintracelib1.0/sandbox_actions.h>
66
67#if defined(LOCAL_PEERPID) && defined(HAVE_LIBPROC_H)
68#include <libproc.h>
69#define HAVE_PEERPID_LIST
70#endif /* defined(LOCAL_PEERPID) && defined(HAVE_LIBPROC_H) */
71
72#include "tracelib.h"
73
74#include "Pextlib.h"
75
76#include "strlcat.h"
77
78#ifdef HAVE_TRACEMODE_SUPPORT
79#ifndef HAVE_STRLCPY
80/* Define strlcpy if it's not available. */
81size_t strlcpy(char *dst, const char *src, size_t size);
82size_t strlcpy(char *dst, const char *src, size_t size) {
83    size_t result = strlen(src);
84    if (size > 0) {
85        size_t copylen = size - 1;
86        if (copylen > result) {
87            copylen = result;
88        }
89        memcpy(dst, src, copylen);
90        dst[copylen] = 0;
91    }
92    return result;
93}
94#endif
95
96#ifdef HAVE_PEERPID_LIST
97static bool peerpid_list_enqueue(int sock, pid_t pid);
98static pid_t peerpid_list_dequeue(int sock);
99static pid_t peerpid_list_get(int sock, const char **progname);
100static void peerpid_list_walk(bool (*callback)(int sock, pid_t pid, const char *progname));
101#endif /* defined(HAVE_PEERPID_LIST) */
102
103
104static char *name;
105static char *sandbox;
106static size_t sandboxLength;
107static char *depends;
108static int sock = -1;
109static int kq = -1;
110/* EVFILT_USER isn't available (< 10.6), use the self-pipe trick to return from
111 * the blocking kqueue(2) call by writing a byte to the pipe */
112static int selfpipe[2];
113static int enable_fence = 0;
114static Tcl_Interp *interp;
115
116/**
117 * Mutex that shall be acquired to exclusively lock checking and acting upon
118 * the value of kq, indicating whether the event loop has started. If it has
119 * started, shutdown of the event loop shall occur by writing to the write end
120 * of the selfpipe (which is non-blocking), which will in turn trigger the
121 * event loop termination and a signal on the evloop_signal condition variable
122 * when the loop has been terminated and it is safe to free the resources that
123 * were used by the loop.
124 *
125 * If kq is -1, the event loop has not been started and resources can
126 * immediately be free(3)d (under the lock to avoid concurrent set up of the
127 * event loop in a different thread).
128 */
129static pthread_mutex_t evloop_mutex = PTHREAD_MUTEX_INITIALIZER;
130
131/**
132 * Condition variable that shall be used to signal the end of the event loop
133 * after a termination signal has been sent to it via the write end of
134 * selfpipe. The associated mutex is evloop_mtx.
135 */
136static pthread_cond_t evloop_signal = PTHREAD_COND_INITIALIZER;
137
138static void send_file_map(int sock);
139static void dep_check(int sock, char *path);
140
141typedef enum {
142    SANDBOX_UNKNOWN,
143    SANDBOX_VIOLATION
144} sandbox_violation_t;
145static void sandbox_violation(int sock, const char *path, sandbox_violation_t type);
146
147#ifdef HAVE_PEERPID_LIST
148typedef struct _peerpid {
149    struct _peerpid *ppid_next;
150    char            *ppid_prog;
151    int              ppid_sock;
152    pid_t            ppid_pid;
153} peerpid_entry_t;
154
155static peerpid_entry_t *peer_list = NULL;
156
157/**
158 * Add a new entry to the list of PIDs of peers. Call this once for each
159 * accepted socket with the socket and the peer's PID.
160 *
161 * @param sock The new socket that was opened by the process with the given PID
162 *             and should be added to the list of peers.
163 * @param pid The PID of the new peer.
164 * @return boolean indicating success.
165 */
166static bool peerpid_list_enqueue(int sock, pid_t pid) {
167    char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
168    const char *progname = "<unknown>";
169
170    peerpid_entry_t *ppid = malloc(sizeof(peerpid_entry_t));
171    if (!ppid) {
172        return false;
173    }
174
175    if (proc_pidpath(pid, pathbuf, sizeof(pathbuf))) {
176        progname = pathbuf;
177    }
178
179    ppid->ppid_prog = strdup(progname);
180    if (!ppid->ppid_prog) {
181        free(ppid);
182        return false;
183    }
184    ppid->ppid_sock = sock;
185    ppid->ppid_pid = pid;
186    ppid->ppid_next = peer_list;
187    peer_list = ppid;
188    return true;
189}
190
191/**
192 * Given a socket, dequeue a peer from the current list of peers. Use this when
193 * a socket is closed.
194 *
195 * @param sock The socket that is being closed and should be dequeued.
196 * @return The PID of the socket that has been dequeued, or (pid_t) -1
197 */
198static pid_t peerpid_list_dequeue(int sock) {
199    peerpid_entry_t **ref = &peer_list;
200    while (*ref) {
201        peerpid_entry_t *curr = *ref;
202        if (curr->ppid_sock == sock) {
203            // dequeue the element
204            *ref = curr->ppid_next;
205            pid_t pid = curr->ppid_pid;
206            free(curr->ppid_prog);
207            free(curr);
208            return pid;
209        }
210
211        ref = &curr->ppid_next;
212    }
213
214    return (pid_t) -1;
215}
216
217/**
218 * Return the peer PID given a socket.
219 *
220 * @param sock The socket for which the peer PID is needed.
221 * @param progname A pointer that will point to the string that holds the
222 *                 command line corresponding to the PID at the time of
223 *                 enqueuing. Set to NULL if not needed.
224 * @return The peer's PID or (pid_t) -1, if the socket could not be found in the list.
225 */
226static pid_t peerpid_list_get(int sock, const char **progname) {
227    peerpid_entry_t *curr = peer_list;
228    while (curr) {
229        if (curr->ppid_sock == sock) {
230            if (progname) {
231                *progname = curr->ppid_prog;
232            }
233            return curr->ppid_pid;
234        }
235
236        curr = curr->ppid_next;
237    }
238
239    return (pid_t) -1;
240}
241
242/**
243 * Walk the current list of (socket, peer PID) pairs and call a callback
244 * function for each pair.
245 *
246 * @param func Callback function to call for each tuple of socket, peer PID and
247 *             peer command line. The function should take an integer (the
248 *             socket), a pid_t (the peer's PID) and a const char * (the peer's
249 *             command line) and return a boolean (true, if the element should
250 *             be removed from the list, false otherwise). The callback must
251 *             not modify the list using peerpid_list_enqueue() or
252 *             peerpid_list_dequeue().
253 */
254static void peerpid_list_walk(bool (*callback)(int sock, pid_t pid, const char *progname)) {
255    peerpid_entry_t **ref = &peer_list;
256    while (*ref) {
257        peerpid_entry_t *curr = *ref;
258        if (callback(curr->ppid_sock, curr->ppid_pid, curr->ppid_prog)) {
259            // dequeue the element
260            *ref = curr->ppid_next;
261            free(curr->ppid_prog);
262            free(curr);
263            continue;
264        }
265
266        ref = &curr->ppid_next;
267    }
268}
269#endif /* defined(HAVE_PEERPID_LIST) */
270
271#define MAX_SOCKETS (64)
272#define BUFSIZE     (4096)
273
274/**
275 * send a buffer \c buf with the given length \c size to the socket \c sock, by
276 * using the communication protocol between darwintrace and tracelib (i.e., by
277 * prefixing the code with a uint32_t containing the length of the message)
278 *
279 * \param[in] sock the socket to send to
280 * \param[in] buf the buffer to send, should contain at least \c size bytes
281 * \param[in] size the number of bytes in \c buf
282 */
283static void answer_s(int sock, const char *buf, uint32_t size) {
284    send(sock, &size, sizeof(size), 0);
285    send(sock, buf, size, 0);
286}
287
288/**
289 * send a '\0'-terminated string given in \c buf to the socket \c by using the
290 * communication protocol between darwintrace and tracelib. See \c answer_s for
291 * details.
292 *
293 * \param[in] sock the socket to send to
294 * \param[in] buf the string to send; must be \0-terminated
295 */
296static void answer(int sock, const char *buf) {
297    answer_s(sock, buf, (uint32_t) strlen(buf));
298}
299
300/**
301 * Closes the two sockets given in \a p and sets their values to -1.
302 */
303static void pipe_cleanup(int p[2]) {
304    for (size_t i = 0; i < 2; ++i) {
305        if (p[i] != -1) {
306            close(p[i]);
307            p[i] = -1;
308        }
309    }
310}
311
312/**
313 * Helper function to simplify error handling. Converts the error indicated by
314 * \a msg, appended with a string representation of the UNIX error \a errno
315 * into a Tcl error by setting up the result of the Tcl interpreter \a interp
316 * accordingly.
317 *
318 * Returns TCL_ERROR to be used as the return value of the caller.
319 */
320static int error2tcl(const char *msg, int err, Tcl_Interp *interp) {
321    Tcl_SetErrno(err);
322    Tcl_ResetResult(interp);
323    if (err != 0) {
324        Tcl_AppendResult(interp, msg, (char *) Tcl_PosixError(interp), NULL);
325    } else {
326        Tcl_AppendResult(interp, msg, NULL);
327    }
328
329    return TCL_ERROR;
330}
331
332/**
333 * Sets the path of the tracelib unix socket where darwintrace should attempt
334 * to connect to. This path should be specific to the port being installed.
335 * Different sockets should be used for different ports (and maybe even
336 * phases).
337 *
338 * \param[in,out] interp the Tcl interpreter
339 * \param[in] objc the number of parameters
340 * \param[in] objv the parameters
341 * \return a Tcl return code
342 */
343static int TracelibSetNameCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
344    if (objc != 3) {
345        Tcl_WrongNumArgs(interp, 2, objv, "number of arguments should be exactly 3");
346        return TCL_ERROR;
347    }
348
349    name = strdup(Tcl_GetString(objv[2]));
350    if (!name) {
351        Tcl_SetResult(interp, "memory allocation failed", TCL_STATIC);
352        return TCL_ERROR;
353    }
354
355    // initialize the depends field, in case we don't actually have any dependencies
356    depends = NULL;
357
358    return TCL_OK;
359}
360
361/**
362 * Save sandbox boundaries to memory and format them for darwintrace. This
363 * means changing : to \0 (with \ being an escape char).
364 *
365 * Input:
366 *  /dev/null:/dev/tty:/tmp\:
367 * In variable;
368 *  /dev/null\0/dev/tty\0/tmp:\0\0
369 *
370 * \param[in,out] interp the Tcl interpreter
371 * \param[in] objc the number of parameters
372 * \param[in] objv the parameters
373 * \return a Tcl return code
374 */
375static int TracelibSetSandboxCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
376    char *src, *dst;
377    enum { NORMAL, ACTION, ESCAPE } state = NORMAL;
378
379    if (objc != 3) {
380        Tcl_WrongNumArgs(interp, 2, objv, "number of arguments should be exactly 3");
381        return TCL_ERROR;
382    }
383
384    src = Tcl_GetString(objv[2]);
385    sandboxLength = strlen(src) + 2;
386    sandbox = malloc(sandboxLength);
387    if (!sandbox) {
388        Tcl_SetResult(interp, "memory allocation failed", TCL_STATIC);
389        return TCL_ERROR;
390    }
391    for (dst = sandbox; *src != '\0'; src++) {
392        switch (*src) {
393            case '\\':
394                if (state == ESCAPE) {
395                    /* double backslash, turn into single backslash (note
396                     * C strings use \ as escape char, too! */
397                    *dst++ = '\\';
398                    state = NORMAL;
399                } else {
400                    /* hit a backslash, assume this is an escape sequence */
401                    state = ESCAPE;
402                }
403                break;
404            case ':':
405                if (state == ESCAPE) {
406                    /* : was escaped, keep literally */
407                    *dst++ = ':';
408                    state = NORMAL;
409                } else if (state == ACTION) {
410                    /* : -> \0, we're done with this entry */
411                    *dst++ = '\0';
412                    state = NORMAL;
413                } else {
414                    /* unescaped : should never occur in normal state */
415                    free(sandbox);
416                    Tcl_SetResult(interp, "Unexpected colon before action specification.", TCL_STATIC);
417                    return TCL_ERROR;
418                }
419                break;
420            case '=':
421                if (state == ESCAPE) {
422                    /* = was escaped, keep literally */
423                    *dst++ = '=';
424                    state = NORMAL;
425                } else {
426                    /* hit =, this is the end of the path, the action follows */
427                    *dst++ = '\0';
428                    state = ACTION;
429                }
430                break;
431            case '+':
432            case '-':
433            case '?':
434                if (state == ACTION) {
435                    /* control character after equals, convert to binary */
436                    switch (*src) {
437                        case '+':
438                            *dst++ = FILEMAP_ALLOW;
439                            break;
440                        case '-':
441                            *dst++ = FILEMAP_DENY;
442                            break;
443                        case '?':
444                            *dst++ = FILEMAP_ASK;
445                            break;
446                    }
447                } else {
448                    /* before equals sign, copy literally */
449                    *dst++ = *src;
450                }
451                break;
452            default:
453                if (state == ESCAPE) {
454                    /* unknown escape sequence, free buffer and raise an error */
455                    free(sandbox);
456                    Tcl_SetResult(interp, "Unknown escape sequence.", TCL_STATIC);
457                    return TCL_ERROR;
458                }
459                if (state == ACTION) {
460                    /* unknown control character, free buffer and raise an error */
461                    free(sandbox);
462                    Tcl_SetResult(interp, "Unknown control character. Possible values are +, -, and ?.", TCL_STATIC);
463                    return TCL_ERROR;
464                }
465                /* otherwise: copy the char */
466                *dst++ = *src;
467                break;
468        }
469    }
470    /* add two \0 to mark the end */
471    *dst++ = '\0';
472    *dst = '\0';
473
474    return TCL_OK;
475}
476
477/**
478 * Receive line from socket, parse it and send an answer, if necessary. The
479 * caller should ensure that data is available for reading from the given
480 * socket. This method will block until a complete message has been read.
481 *
482 * \param[in] sock the socket to communicate with
483 * \return 1, if the communication was successful, 0 in case of errors and/or
484 *         when the socket should be closed
485 */
486static int process_line(int sock) {
487    char *f;
488    char buf[BUFSIZE];
489    uint32_t len = 0;
490    ssize_t ret;
491
492    if ((ret = recv(sock, &len, sizeof(len), MSG_WAITALL)) != sizeof(len)) {
493        if (ret < 0) {
494            perror("tracelib: recv");
495        } else if (ret == 0) {
496            /* this usually means the socket was closed by the remote side */
497        } else {
498            fprintf(stderr, "tracelib: partial data received: expected %zu, but got %zd on socket %d\n", sizeof(len), ret, sock);
499        }
500        return 0;
501    }
502
503    if (len > BUFSIZE - 1) {
504        pid_t pid = (pid_t) -1;
505#ifdef HAVE_PEERPID_LIST
506        pid = peerpid_list_get(sock, NULL);
507#endif
508        fprintf(stderr, "tracelib: transfer too large: %" PRIu32 " bytes sent, but buffer holds %d on socket %d from pid %ld\n", len, BUFSIZE - 1, sock, (unsigned long) pid);
509        return 0;
510    }
511
512    if ((ret = recv(sock, buf, len, MSG_WAITALL)) != (ssize_t) len) {
513        if (ret < 0) {
514            perror("tracelib: recv");
515        } else {
516            fprintf(stderr, "tracelib: partial data received: expected %" PRIu32 ", but got %zd on socket %d\n", len, ret, sock);
517        }
518        return 0;
519    }
520    buf[len] = '\0';
521
522    f = strchr(buf, '\t');
523    if (!f) {
524        fprintf(stderr, "tracelib: malformed command '%s' from socket %d\n", buf, sock);
525        return 0;
526    }
527
528    /* Replace \t with \0 */
529    *f = '\0';
530    /* Advance pointer to arguments */
531    f++;
532
533    if (strcmp(buf, "filemap") == 0) {
534        send_file_map(sock);
535    } else if (strcmp(buf, "sandbox_unknown") == 0) {
536        sandbox_violation(sock, f, SANDBOX_UNKNOWN);
537    } else if (strcmp(buf, "sandbox_violation") == 0) {
538        sandbox_violation(sock, f, SANDBOX_VIOLATION);
539    } else if (strcmp(buf, "dep_check") == 0) {
540        dep_check(sock, f);
541    } else {
542        fprintf(stderr, "tracelib: unexpected command %s (%s)\n", buf, f);
543        return 0;
544    }
545
546    return 1;
547}
548
549/**
550 * Construct an in-memory representation of the sandbox file map and send it to
551 * the socket indicated by \c sock.
552 *
553 * \param[in] sock the socket to send the sandbox bounds to
554 */
555static void send_file_map(int sock) {
556    if (enable_fence) {
557        answer_s(sock, sandbox, sandboxLength);
558    } else {
559        char allowAllSandbox[5] = {'/', '\0', FILEMAP_ALLOW, '\0', '\0'};
560        answer_s(sock, allowAllSandbox, sizeof(allowAllSandbox));
561    }
562}
563
564/**
565 * Process a sandbox violation reported by darwintrace. Calls back up to Tcl to
566 * run a callback with the reported violation path.
567 *
568 * \param[in] sock socket reporting the violation; unused.
569 * \param[in] path the offending path to be passed to the callback
570 */
571static void sandbox_violation(int sock UNUSED, const char *path, sandbox_violation_t type) {
572    Tcl_SetVar(interp, "_sandbox_viol_path", path, 0);
573    int retVal = TCL_OK;
574    switch (type) {
575        case SANDBOX_VIOLATION:
576            retVal = Tcl_Eval(interp, "slave_add_sandbox_violation ${_sandbox_viol_path}");
577            break;
578        case SANDBOX_UNKNOWN:
579            retVal = Tcl_Eval(interp, "slave_add_sandbox_unknown ${_sandbox_viol_path}");
580            break;
581    }
582
583    if (retVal != TCL_OK) {
584        fprintf(stderr, "Error evaluating Tcl statement to add sandbox violation: %s\n", Tcl_GetStringResult(interp));
585    }
586
587    Tcl_UnsetVar(interp, "_sandbox_viol_path", 0);
588}
589
590/**
591 * Check whether a path is in the transitive hull of dependencies of the port
592 * currently being installed and send the result of the query back to the
593 * socket.
594 *
595 * Sends one of the following characters as return code to the socket:
596 *  - #: in case of errors. Not handled by the darwintrace code, which will
597 *       lead to an error and the termination of the processing that sent the
598 *       request causing this error.
599 *  - ?: if the file isn't known to MacPorts (i.e., not registered to any port)
600 *  - +: if the file was installed by a dependency and access should be granted
601 *  - !: if the file was installed by a MacPorts port which is not in the
602 *       transitive hull of dependencies and access should be denied.
603 *
604 * \param[in] sock the socket to answer to
605 * \param[in] path the path to return the dependency information for
606 */
607static void dep_check(int sock, char *path) {
608    char *port = 0;
609    char *t;
610    reg_registry *reg;
611    reg_entry entry;
612    reg_error error;
613
614    if (NULL == (reg = registry_for(interp, reg_attached))) {
615        ui_error(interp, "%s", Tcl_GetStringResult(interp));
616        /* send unexpected output to make the build fail */
617        answer(sock, "#");
618    }
619
620    /* find the port id */
621    entry.reg = reg;
622    entry.proc = NULL;
623    entry.id = reg_entry_owner_id(reg, path);
624    if (entry.id == 0) {
625        /* file isn't known to MacPorts */
626        answer(sock, "?");
627        return;
628    }
629
630    /* find the port's name to compare with out list */
631    if (!reg_entry_propget(&entry, "name", &port, &error)) {
632        /* send unexpected output to make the build fail */
633        ui_error(interp, "%s", error.description);
634        answer(sock, "#");
635    }
636
637    /* check our list of dependencies */
638    for (t = depends; t && *t; t += strlen(t) + 1) {
639        if (strcmp(t, port) == 0) {
640            free(port);
641            answer(sock, "+");
642            return;
643        }
644    }
645
646    free(port);
647    answer(sock, "!");
648}
649
650static int TracelibOpenSocketCmd(Tcl_Interp *in) {
651    struct sockaddr_un sun;
652    struct rlimit rl;
653
654    if (-1 == (sock = socket(PF_LOCAL, SOCK_STREAM, 0))) {
655        return error2tcl("socket: ", errno, in);
656    }
657
658    /* raise the limit of open files to the maximum from the default soft limit
659     * of 256 */
660    if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
661        ui_warn(interp, "getrlimit failed (%d), skipping setrlimit", errno);
662    } else {
663#ifdef OPEN_MAX
664        if (rl.rlim_max > OPEN_MAX) {
665            rl.rlim_max = OPEN_MAX;
666        }
667#endif
668        rl.rlim_cur = rl.rlim_max;
669        if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
670            ui_warn(interp, "setrlimit failed (%d)", errno);
671        }
672    }
673
674    sun.sun_family = AF_UNIX;
675    strlcpy(sun.sun_path, name, sizeof(sun.sun_path));
676
677    if (-1 == (bind(sock, (struct sockaddr *) &sun, sizeof(sun)))) {
678        int err = errno;
679        close(sock);
680        sock = -1;
681        return error2tcl("bind: ", err, in);
682    }
683
684    if (-1 == listen(sock, SOMAXCONN)) {
685        int err = errno;
686        close(sock);
687        sock = -1;
688        return error2tcl("bind: ", err, in);
689    }
690
691    // keep a reference to the interpreter that opened the socket
692    interp = in;
693
694    return TCL_OK;
695}
696
697#ifdef HAVE_PEERPID_LIST
698/**
699 * Callback to be passed to peerpid_list_walk(). Closes the open sockets and
700 * sends SIGTERM to the associated processes. Leaves the list unmodified.
701 */
702static bool close_and_send_sigterm(int sock UNUSED, pid_t pid, const char *progname) {
703    ui_warn(interp, "Sending SIGTERM to process %ld: %s", (unsigned long) pid, progname);
704    kill(pid, SIGTERM);
705
706    // keep the elements in the list
707    return false;
708}
709
710/**
711 * Callback to be passed to peerpid_list_walk(). Sends SIGKILL to the processes
712 * and deletes the elements from the list.
713 */
714static bool send_sigkill_and_free(int sock, pid_t pid, const char *progname UNUSED) {
715    close(sock);
716    kill(pid, SIGKILL);
717
718    // remove the elements from the list
719    return true;
720}
721#endif
722
723/* create this on heap rather than stack, due to its rather large size */
724static struct kevent res_kevents[MAX_SOCKETS];
725
726static int TracelibRunCmd(Tcl_Interp *in) {
727    struct kevent kev;
728    int retval = TCL_ERROR;
729    int flags;
730    int opensockcount = 0;
731    bool break_eventloop = false;
732
733    pthread_mutex_lock(&evloop_mutex);
734    /* bring all variables into a defined state so the cleanup code can be
735     * called from anywhere */
736    selfpipe[0] = -1;
737    selfpipe[1] = -1;
738    kq = -1;
739
740    if (-1 == (kq = kqueue())) {
741        error2tcl("kqueue: ", errno, in);
742        goto error_locked;
743    }
744
745    if (sock != -1) {
746        /* mark listen socket non-blocking in order to prevent a race condition
747         * that would occur between kevent(2) and accept(2), if a incoming
748         * connection is aborted before it is accepted. Using a non-blocking
749         * accept(2) prevents the problem.*/
750        flags = fcntl(sock, F_GETFL, 0);
751        if (-1 == fcntl(sock, F_SETFL, flags | O_NONBLOCK)) {
752            error2tcl("fcntl(F_SETFL, += O_NONBLOCK): ", errno, in);
753            goto error_locked;
754        }
755
756        /* register the listen socket in the kqueue */
757        EV_SET(&kev, sock, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
758        if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
759            error2tcl("kevent (listen socket): ", errno, in);
760            goto error_locked;
761        }
762        /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
763         * always be returned. When a filter is successfully added, the data field
764         * will be zero. */
765        if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
766            error2tcl("kevent (listen socket receipt): ", kev.data, in);
767            goto error_locked;
768        }
769
770
771        /* use the self-pipe trick to trigger returning from kevent(2) when
772         * tracelib closesocket is called. */
773        if (-1 == pipe(selfpipe)) {
774            error2tcl("pipe: ", errno, in);
775            goto error_locked;
776        }
777
778        /* mark the write side of the pipe non-blocking */
779        flags = fcntl(selfpipe[1], F_GETFL, 0);
780        if (-1 == fcntl(selfpipe[1], F_SETFL, flags | O_NONBLOCK)) {
781            error2tcl("fcntl(F_SETFL, += O_NONBLOCK): ", errno, in);
782            goto error_locked;
783        }
784
785        /* wait for the user event on the listen socket, as sent by CloseCmd as
786         * deathpill */
787        EV_SET(&kev, selfpipe[0], EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
788        if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
789            error2tcl("kevent (selfpipe): ", errno, in);
790            goto error_locked;
791        }
792        /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
793         * always be returned. When a filter is successfully added, the data field
794         * will be zero. */
795        if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
796            error2tcl("kevent (selfpipe receipt): ", kev.data, in);
797            goto error_locked;
798        }
799    }
800    pthread_mutex_unlock(&evloop_mutex);
801
802    while (sock != -1 && !break_eventloop) {
803        int keventstatus;
804        bool incoming = false;
805
806        /* run kevent(2) until new activity is available */
807        do {
808            if (-1 == (keventstatus = kevent(kq, NULL, 0, res_kevents, MAX_SOCKETS, NULL))) {
809                error2tcl("kevent (main loop): ", errno, in);
810                goto error_unlocked;
811            }
812        } while (keventstatus == 0);
813
814        for (int i = 0; i < keventstatus; ++i) {
815            /* handle traffic on the selfpipe */
816            if ((int) res_kevents[i].ident == selfpipe[0]) {
817                /* traffic on the selfpipe means we should clean up */
818                break_eventloop = true;
819                /* finish processing this batch */
820                continue;
821            } else if ((int) res_kevents[i].ident != sock) {
822                /* if the socket is to be closed, or */
823                if ((res_kevents[i].flags & (EV_EOF | EV_ERROR)) > 0
824                    /* new data is available, and its processing tells us to
825                     * close the socket */
826                    || (!process_line(res_kevents[i].ident))) {
827                        /* an error occured or process_line suggested closing
828                         * this socket */
829                        close(res_kevents[i].ident);
830                        /* closing the socket will automatically remove it from the
831                         * kqueue :) */
832                        opensockcount--;
833
834#ifdef HAVE_PEERPID_LIST
835                        if (peerpid_list_dequeue(res_kevents[i].ident) == (pid_t) -1) {
836                            fprintf(stderr, "tracelib: didn't find PID for closed socket %d\n", (int) res_kevents[i].ident);
837                        }
838#endif
839                }
840            } else {
841                /* the control socket has activity – we might have a new
842                 * connection. */
843
844                /* handle error conditions */
845                if ((res_kevents[i].flags & (EV_ERROR | EV_EOF)) > 0) {
846                    error2tcl("control socket closed", 0, in);
847                    goto error_unlocked;
848                }
849
850                /* delay processing, process data on existing sockets first */
851                incoming = true;
852            }
853        }
854
855        if (incoming) {
856            /* new connection attempt(s) */
857            for (;;) {
858                int s;
859
860                if (-1 == (s = accept(sock, NULL, NULL))) {
861                    if (errno == EWOULDBLOCK) {
862                        break;
863                    }
864
865                    error2tcl("accept: ", errno, in);
866                    goto error_unlocked;
867                }
868
869                flags = fcntl(s, F_GETFL, 0);
870                if (-1 == fcntl(s, F_SETFL, flags & ~O_NONBLOCK)) {
871                    ui_warn(interp, "tracelib: couldn't mark socket as blocking");
872                    close(s);
873                    continue;
874                }
875
876                /* register the new socket in the kqueue */
877                EV_SET(&kev, s, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
878                if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
879                    ui_warn(interp, "tracelib: error adding socket to kqueue");
880                    close(s);
881                    continue;
882                }
883                /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
884                 * always be returned. When a filter is successfully added, the data field
885                 * will be zero. */
886                if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
887                    ui_warn(interp, "tracelib: error adding socket to kqueue (receipt)");
888                    close(s);
889                    continue;
890                }
891
892#ifdef HAVE_PEERPID_LIST
893                pid_t peer_pid = (pid_t) -1;
894                socklen_t peer_pid_len = sizeof(peer_pid);
895                if (getsockopt(s, SOL_LOCAL, LOCAL_PEERPID, &peer_pid, &peer_pid_len) == 0) {
896                    // We found a PID for the remote side
897                    peerpid_list_enqueue(s, peer_pid);
898                } else {
899                    // Error occured, process has probably already terminated
900                    close(s);
901                    continue;
902                }
903#endif
904                opensockcount++;
905            }
906        }
907    }
908
909    retval = TCL_OK;
910
911error_unlocked:
912    pthread_mutex_lock(&evloop_mutex);
913error_locked:
914    // Close remainig sockets to avoid dangling processes
915    if (opensockcount > 0) {
916#ifdef HAVE_PEERPID_LIST
917        ui_warn(interp, "tracelib: %d open sockets leaking at end of runcmd, closing, sending SIGTERM and SIGKILL", opensockcount);
918        peerpid_list_walk(close_and_send_sigterm);
919        peerpid_list_walk(send_sigkill_and_free);
920#else
921        ui_warn(interp, "tracelib: %d open sockets leaking at end of runcmd", opensockcount);
922#endif
923    }
924
925    // cleanup selfpipe and set it to -1
926    pipe_cleanup(selfpipe);
927
928    // close kqueue(2) socket
929    if (kq != -1) {
930        close(kq);
931        kq = -1;
932    }
933
934    pthread_mutex_unlock(&evloop_mutex);
935    // wake up any waiting threads in TracelibCloseSocketCmd
936    pthread_cond_broadcast(&evloop_signal);
937
938    return retval;
939}
940
941static int TracelibCleanCmd(Tcl_Interp *interp UNUSED) {
942#define safe_free(x) do{ \
943        free(x); \
944        x = NULL; \
945    } while(0);
946
947    if (sock != -1) {
948        close(sock);
949        sock = -1;
950    }
951
952    if (name) {
953        unlink(name);
954        safe_free(name);
955    }
956
957    safe_free(depends);
958
959    enable_fence = 0;
960    return TCL_OK;
961
962#undef safe_free
963}
964
965static int TracelibCloseSocketCmd(Tcl_Interp *interp UNUSED) {
966    pthread_mutex_lock(&evloop_mutex);
967    if (kq != -1 && selfpipe[1] != -1) {
968        /* We know the pipes have been created because kq != -1 and we have the
969         * lock. We don't have to check for errors, because none should occur
970         * but when the pipe is full, which we wouldn't care about. */
971        write(selfpipe[1], "!", 1);
972
973        /* Wait for the kqueue event loop to terminate. We must not return
974         * earlier than that because the next call will be to tracelib clean,
975         * and that frees up memory that would be used by the event loop
976         * otherwise. */
977        pthread_cond_wait(&evloop_signal, &evloop_mutex);
978    } else {
979        /* The kqueue(2) loop isn't running yet, so we can just close the
980         * socket and make sure it stays closed. In this situation, the kqueue
981         * will not be created. */
982        if (sock != -1) {
983            close(sock);
984            sock = -1;
985        }
986    }
987    pthread_mutex_unlock(&evloop_mutex);
988
989    return TCL_OK;
990}
991
992static int TracelibSetDeps(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
993    char *t, * d;
994    size_t l;
995    if (objc != 3) {
996        Tcl_WrongNumArgs(interp, 2, objv, "number of arguments should be exactly 3");
997        return TCL_ERROR;
998    }
999
1000    d = Tcl_GetString(objv[2]);
1001    l = strlen(d);
1002    depends = malloc(l + 2);
1003    if (!depends) {
1004        Tcl_SetResult(interp, "memory allocation failed", TCL_STATIC);
1005        return TCL_ERROR;
1006    }
1007    depends[l + 1] = 0;
1008    strlcpy(depends, d, l + 2);
1009    for (t = depends; *t; ++t)
1010        if (*t == ' ') {
1011            *t++ = 0;
1012        }
1013
1014    return TCL_OK;
1015}
1016
1017static int TracelibEnableFence(Tcl_Interp *interp UNUSED) {
1018    enable_fence = 1;
1019    return TCL_OK;
1020}
1021#endif /* defined(HAVE_TRACEMODE_SUPPORT) */
1022
1023int TracelibCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
1024    int result = TCL_OK;
1025
1026    /* There is no args for commands now. */
1027    if (objc < 2) {
1028        Tcl_WrongNumArgs(interp, 1, objv, "option");
1029        return TCL_ERROR;
1030    }
1031
1032#ifdef HAVE_TRACEMODE_SUPPORT
1033    static const char *options[] = {"setname", "opensocket", "run", "clean", "setsandbox", "closesocket", "setdeps", "enablefence", 0};
1034    typedef enum {
1035        kSetName,
1036        kOpenSocket,
1037        kRun,
1038        kClean,
1039        kSetSandbox,
1040        kCloseSocket,
1041        kSetDeps,
1042        kEnableFence
1043    } EOptions;
1044    EOptions current_option;
1045
1046    result = Tcl_GetIndexFromObj(interp, objv[1], options, "option", 0, (int *)&current_option);
1047    if (result == TCL_OK) {
1048        switch (current_option) {
1049            case kSetName:
1050                result = TracelibSetNameCmd(interp, objc, objv);
1051                break;
1052            case kOpenSocket:
1053                result = TracelibOpenSocketCmd(interp);
1054                break;
1055            case kRun:
1056                result = TracelibRunCmd(interp);
1057                break;
1058            case kClean:
1059                result = TracelibCleanCmd(interp);
1060                break;
1061            case kCloseSocket:
1062                result = TracelibCloseSocketCmd(interp);
1063                break;
1064            case kSetSandbox:
1065                result = TracelibSetSandboxCmd(interp, objc, objv);
1066                break;
1067            case kSetDeps:
1068                result = TracelibSetDeps(interp, objc, objv);
1069                break;
1070            case kEnableFence:
1071                result = TracelibEnableFence(interp);
1072                break;
1073        }
1074    }
1075#else /* defined(HAVE_TRACEMODE_SUPPORT) */
1076    Tcl_SetResult(interp, "tracelib not supported on this platform", TCL_STATIC);
1077    result = TCL_ERROR;
1078#endif /* defined(HAVE_TRACEMODE_SUPPORT) */
1079
1080    return result;
1081}
Note: See TracBrowser for help on using the repository browser.