[107864] trunk/base/src

cal at macports.org cal at macports.org
Sun Jul 7 15:53:34 PDT 2013


Revision: 107864
          https://trac.macports.org/changeset/107864
Author:   cal at macports.org
Date:     2013-07-07 15:53:34 -0700 (Sun, 07 Jul 2013)
Log Message:
-----------
darwintrace/tracelib: lots of bug fixes

 - tracelib: switched to using the C registry API directly rather than calling
   into Tcl and using registry2.0. This requires linking against registry.dylib
   from pextlib, which caused:
 - build system: assign proper library IDs to registry.dylib and pextlib.dylib
   to enable proper linking (Darwin only). Reorder package build order in src/
   to ensure registry2.0/registry.dylib is available when building pextlib.
 - cregistry: expose reg_entry_owner_id which is used in tracelib
 - tracelib: switch from select(2) to kevent(2), drop previously necessary
   timeout-hack.
 - tracelib/darwintrace: unify communication protocol to always include the
   size of the request in the header (previously only the case from tracelib to
   darwintrace, but not in the opposite direction). The trailing '\0' is not
   implicit and no longer sent over the wire.
 - tracelib: simplify command processing by using blocking I/O
 - tracelib/darwintrace: drop unused execve command
 - tracelib: fix a race condition that could occur when the socket was closed
   before the first kevent(2) (or previously: select(2))
 - tracelib: fix a race condition that could occur between select(2) and
   accept(2), when the new connection attempt was dropped before accept(2)
   (which would then block indefinitely) by switching the listening socket to
   non-blocking I/O
 - tracelib: send a death pill to the kevent(2) main loop when calling tracelib
   closesocket.
 - darwintrace: support multi-threaded writers by using thread-local storage
   and separate connections to tracelib. Make filemap loading thread-safe by
   using non-blocking sync (thanks to wosch for making sure I did learn that)

Modified Paths:
--------------
    trunk/base/src/Makefile.in
    trunk/base/src/cregistry/entry.h
    trunk/base/src/darwintracelib1.0/darwintrace.c
    trunk/base/src/pextlib1.0/Makefile
    trunk/base/src/pextlib1.0/tracelib.c
    trunk/base/src/registry2.0/Makefile

Modified: trunk/base/src/Makefile.in
===================================================================
--- trunk/base/src/Makefile.in	2013-07-07 22:40:07 UTC (rev 107863)
+++ trunk/base/src/Makefile.in	2013-07-07 22:53:34 UTC (rev 107864)
@@ -1,10 +1,10 @@
 TCLPKG=		@OUR_INCLUDED_PACKAGES@ \
 			cregistry \
+			registry2.0 \
 			macports1.0 \
 			port1.0 \
 			package1.0 \
 			pextlib1.0 \
-			registry2.0 \
 			darwintracelib1.0 \
 			machista1.0
 SUBDIR=		${TCLPKG} port programs

Modified: trunk/base/src/cregistry/entry.h
===================================================================
--- trunk/base/src/cregistry/entry.h	2013-07-07 22:40:07 UTC (rev 107863)
+++ trunk/base/src/cregistry/entry.h	2013-07-07 22:53:34 UTC (rev 107864)
@@ -62,6 +62,7 @@
 int reg_entry_installed(reg_registry* reg, char* name, reg_entry*** entries,
         reg_error* errPtr);
 
+sqlite_int64 reg_entry_owner_id(reg_registry* reg, char* path);
 int reg_entry_owner(reg_registry* reg, char* path, reg_entry** entry,
         reg_error* errPtr);
 

Modified: trunk/base/src/darwintracelib1.0/darwintrace.c
===================================================================
--- trunk/base/src/darwintracelib1.0/darwintrace.c	2013-07-07 22:40:07 UTC (rev 107863)
+++ trunk/base/src/darwintracelib1.0/darwintrace.c	2013-07-07 22:53:34 UTC (rev 107864)
@@ -64,6 +64,7 @@
 #include <dlfcn.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <pthread.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -112,6 +113,7 @@
 static inline int __darwintrace_pathbeginswith(const char *str, const char *prefix);
 static inline void __darwintrace_log_op(const char *op, const char *path, int fd);
 static void __darwintrace_copy_env() __attribute__((constructor));
+static void __darwintrace_setup_tls() __attribute__((constructor));
 static inline char *__darwintrace_alloc_env(const char *varName, const char *varValue);
 static inline char *const *__darwintrace_restore_env(char *const envp[]);
 static inline void __darwintrace_setup();
@@ -119,11 +121,6 @@
 static char *__send(const char *buf, size_t len, int answer);
 
 /**
- * Communication socket to the macports process, or NULL.
- */
-static FILE *__darwintrace_sock = NULL;
-
-/**
  * PID of the process darwintrace was last used in. This is used to detect
  * forking and opening a new connection to the control socket in the child
  * process. Not doing so would potentially cause two processes writing to the
@@ -132,6 +129,13 @@
 static pid_t __darwintrace_pid = (pid_t) - 1;
 
 /**
+ * pthread_key_ts for the pthread_t returned by pthread_self() and the
+ * darwintrace socket to ensure the socket is only used from a single thread.
+ */
+static pthread_key_t tid_key;
+static pthread_key_t sock_key;
+
+/**
  * Helper variable containing the number of the darwintrace socket, iff the
  * close(2) syscall should be allowed to close it. Used by \c
  * __darwintrace_close.
@@ -199,6 +203,56 @@
 #endif
 
 /**
+ * Setup method called as constructor to set up thread-local storage for the
+ * thread id and the darwintrace socket.
+ */
+static void __darwintrace_setup_tls() {
+	if (0 != (errno = pthread_key_create(&tid_key, NULL))) {
+		perror("darwintrace: pthread_key_create");
+		abort();
+	}
+	if (0 != (errno = pthread_key_create(&sock_key, NULL))) {
+		perror("darwintrace: pthread_key_create");
+		abort();
+	}
+}
+
+/**
+ * Convenience getter function for the thread-local darwintrace socket
+ */
+static inline FILE *__darwintrace_sock() {
+	return (FILE *) pthread_getspecific(sock_key);
+}
+
+/**
+ * Convenience getter function for the thread ID
+ */
+static inline pthread_t __darwintrace_tid() {
+	return (pthread_t) pthread_getspecific(tid_key);
+}
+
+/**
+ * Convenience setter function for the thread-local darwintrace socket
+ */
+static inline void __darwintrace_sock_set(FILE *stream) {
+	if (0 != (errno = pthread_setspecific(sock_key, stream))) {
+		perror("darwintrace: pthread_setspecific");
+		abort();
+	}
+}
+
+/**
+ * Convenience setter function for the thread-local darwintrace socket
+ */
+static inline void __darwintrace_tid_set() {
+	if (0 != (errno = pthread_setspecific(tid_key, pthread_self()))) {
+		perror("darwintrace: pthread_setspecific");
+		abort();
+	}
+}
+
+
+/**
  * Return 0 if str doesn't begin with prefix, 1 otherwise. Note that this is
  * not a simple string comparison, but works on a path component level.
  * A prefix of /var/tmp will not match a string of /var/tmpfoo.
@@ -438,17 +492,29 @@
  * of the trace setup) and store it.
  */
 static void __darwintrace_get_filemap() {
-#if DARWINTRACE_DEBUG
+	char *newfilemap;
+#if DARWINTRACE_DEBUG && 0
 	filemap_iterator_t it;
 	char *path, *replacement, command;
 #endif
 
-	/* if this is called multiple times, free the old value; on first call,
-	 * filemap == NULL */
-	free(filemap);
-	filemap = __send("filemap\t", sizeof("filemap\t"), 1);
+	/*
+	 * ensure we have a filemap present; this might be called simultanously
+	 * from multiple threads and needs to work without leaking and in a way
+	 * that ensures a filemap has been set before any of the calls return. We
+	 * achieve that by using non-blocking synchronization. Blocking
+	 * synchronization might be a bad idea, because we never know where this
+	 * code is actually called in an application.
+	 */
+	newfilemap = NULL;
+	do {
+		free(newfilemap);
+		if (filemap != NULL)
+			break;
+		newfilemap = __send("filemap\t", strlen("filemap\t"), 1);
+	} while (!__sync_bool_compare_and_swap(&filemap, NULL, newfilemap));
 
-#if DARWINTRACE_DEBUG
+#if DARWINTRACE_DEBUG && 0
 	for (__darwintrace_filemap_iterator_init(&it);
 	        (path = __darwintrace_filemap_iter(&command, &replacement, &it));) {
 		debug_printf("filemap: {cmd=%d, path=%-120s, replacement=%s}\n", command, path, (command == 1) ? replacement : "-");
@@ -457,20 +523,18 @@
 }
 
 /**
- * Close the stream at location \c *stream and set \c stream to \c NULL. Since
- * this uses \c fclose(3), which internally calls \c close(2), which is
- * intercepted by this library and this library prevents closing the socket to
- * MacPorts, we use \c __darwintrace_close_sock to allow closing specific FDs.
- *
- * \param[inout] stream pointer to a stream to be closed. Will be set to \c
- *                      NULL.
+ * Close the darwintrace socket and set it to \c NULL. Since this uses \c
+ * fclose(3), which internally calls \c close(2), which is intercepted by this
+ * library and this library prevents closing the socket to MacPorts, we use \c
+ * __darwintrace_close_sock to allow closing specific FDs.
  */
-static inline void __darwintrace_close(FILE **stream) {
-	if (stream && *stream) {
-		__darwintrace_close_sock = fileno(*stream);
-		fclose(*stream);
-		*stream = NULL;
+static inline void __darwintrace_close() {
+	FILE *dtsock = __darwintrace_sock();
+	if (dtsock) {
+		__darwintrace_close_sock = fileno(dtsock);
+		fclose(dtsock);
 		__darwintrace_close_sock = -1;
+		pthread_setspecific(sock_key, NULL);
 	}
 }
 
@@ -484,18 +548,34 @@
 	/**
 	 * Check whether this is a child process and we've inherited the socket. We
 	 * want to avoid race conditions with our parent process when communicating
-	 * with tracelib and thus re-open all sockets, if that's the case.
+	 * with tracelib and thus re-open all sockets, if that's the case. Note
+	 * this also applies to threads within the same process, since we really
+	 * want to avoid mixing up the results from two calls in different threads
+	 * when reading from the socket.
 	 */
-	if (__darwintrace_pid != (pid_t) - 1 && __darwintrace_pid != getpid()) {
-		__darwintrace_close(&__darwintrace_sock);
-		__darwintrace_pid = (pid_t) - 1;
+
+	/*
+	 * if the PID changed, close the current socket (which will force the
+	 * following code to re-open it).
+	 */
+	if (__darwintrace_pid != (pid_t) -1 && __darwintrace_pid != getpid()) {
+		__darwintrace_close();
+		__darwintrace_pid = (pid_t) -1;
 	}
 
-	if (__darwintrace_pid == (pid_t) - 1) {
+	/*
+	 * We don't need to watch for TID changes, because each thread has thread
+	 * local storage for the socket that will contain NULL when the socket has
+	 * not been initialized.
+	 */
+
+	if (__darwintrace_sock() == NULL) {
 		int sock;
+		FILE *stream;
 		struct sockaddr_un sun;
 
 		__darwintrace_pid = getpid();
+		__darwintrace_tid_set();
 		if (__env_darwintrace_log == NULL) {
 			fprintf(stderr, "darwintrace: trace library loaded, but DARWINTRACE_LOG not set\n");
 			abort();
@@ -519,11 +599,14 @@
 			abort();
 		}
 
-		if (NULL == (__darwintrace_sock = fdopen(sock, "a+"))) {
+		if (NULL == (stream = fdopen(sock, "a+"))) {
 			perror("darwintrace: fdopen");
 			abort();
 		}
 
+		/* store FILE * into thread local storage for the socket */
+		__darwintrace_sock_set(stream);
+
 		/* request sandbox bounds */
 		__darwintrace_get_filemap();
 	}
@@ -570,7 +653,7 @@
 	__darwintrace_cleanup_path(somepath);
 
 	size = snprintf(logbuffer, sizeof(logbuffer), "%s\t%s", op, somepath);
-	__send(logbuffer, size + 1, 0);
+	__send(logbuffer, size, 0);
 }
 
 /**
@@ -647,10 +730,10 @@
 	}
 
 	len = snprintf(buffer, sizeof(buffer), "dep_check\t%s", path);
-	if (len + 1 > sizeof(buffer)) {
+	if (len > sizeof(buffer)) {
 		len = sizeof(buffer) - 1;
 	}
-	p = __send(buffer, len + 1, 1);
+	p = __send(buffer, len, 1);
 	if (!p) {
 		fprintf(stderr, "darwintrace: dependency check failed for %s\n", path);
 		abort();
@@ -667,7 +750,7 @@
 			result = -1;
 			break;
 		default:
-			fprintf(stderr, "darwintrace: unexpected answer from tracelib: %c", *p);
+			fprintf(stderr, "darwintrace: unexpected answer from tracelib: '%c' (0x%x)\n", *p, *p);
 			abort();
 			break;
 	}
@@ -685,8 +768,9 @@
  * \param[in]  size number of bytes to read from the socket
  */
 static void frecv(void *restrict buf, size_t size) {
-	if (1 != fread(buf, size, 1, __darwintrace_sock)) {
-		if (ferror(__darwintrace_sock)) {
+	FILE *stream = __darwintrace_sock();
+	if (1 != fread(buf, size, 1, stream)) {
+		if (ferror(stream)) {
 			perror("darwintrace: fread");
 		} else {
 			fprintf(stderr, "darwintrace: fread: end-of-file\n");
@@ -703,15 +787,16 @@
  * \param[in] size number of bytes in the buffer
  */
 static void fsend(const void *restrict buf, size_t size) {
-	if (1 != fwrite(buf, size, 1, __darwintrace_sock)) {
-		if (ferror(__darwintrace_sock)) {
+	FILE *stream = __darwintrace_sock();
+	if (1 != fwrite(buf, size, 1, stream)) {
+		if (ferror(stream)) {
 			perror("darwintrace: fwrite");
 		} else {
 			fprintf(stderr, "darwintrace: fwrite: end-of-file\n");
 		}
 		abort();
 	}
-	fflush(__darwintrace_sock);
+	fflush(stream);
 }
 
 /**
@@ -726,6 +811,7 @@
  *         answer was not requested, \c NULL.
  */
 static char *__send(const char *buf, size_t len, int answer) {
+	fsend(&len, sizeof(len));
 	fsend(buf, len);
 
 	if (!answer) {
@@ -938,10 +1024,6 @@
 	 */
 	if (lstat(path, &sb) == 0) {
 		int fd;
-		if (S_ISLNK(sb.st_mode)) {
-			/* for symlinks, print both */
-			__darwintrace_log_op("execve", path, 0);
-		}
 
 		fd = open(path, O_RDONLY, 0);
 		if (fd > 0) {
@@ -954,9 +1036,6 @@
 				return -1;
 			}
 
-			/* once we have an open fd, if a full path was requested, do it */
-			__darwintrace_log_op("execve", path, fd);
-
 			/* read the file for the interpreter */
 			bytes_read = read(fd, buffer, MAXPATHLEN);
 			buffer[bytes_read] = 0;
@@ -978,17 +1057,15 @@
 						break;
 					}
 				}
-				/* we have liftoff */
-				if (interp && interp[0] != '\0') {
-					__darwintrace_log_op("execve", interp, 0);
-				}
 			}
+
+			/* TODO check the iterpreter against the sandbox */
 			close(fd);
 		}
 	}
 
 	/* our variables won't survive exec, clean up */
-	__darwintrace_close(&__darwintrace_sock);
+	__darwintrace_close();
 	__darwintrace_pid = (pid_t) - 1;
 
 	/* call the original execve function, but fix the environment if required. */
@@ -1003,8 +1080,9 @@
  * descriptor */
 int close(int fd) {
 #define close(x) syscall(SYS_close, (x))
-	if (__darwintrace_sock) {
-		int dtsock = fileno(__darwintrace_sock);
+	FILE *stream = __darwintrace_sock();
+	if (stream) {
+		int dtsock = fileno(stream);
 		if (fd == dtsock && dtsock != __darwintrace_close_sock) {
 			errno = EBADF;
 			return -1;
@@ -1018,25 +1096,28 @@
 /* if darwintrace has been initialized, trap attempts to dup2 over our file descriptor */
 int dup2(int filedes, int filedes2) {
 #define dup2(x, y) syscall(SYS_dup2, (x), (y))
+	FILE *stream = __darwintrace_sock();
 
 	debug_printf("dup2(%d, %d)\n", filedes, filedes2);
-	if (__darwintrace_sock && filedes2 == fileno(__darwintrace_sock)) {
+	if (stream && filedes2 == fileno(stream)) {
 		/* if somebody tries to close our file descriptor, just move it out of
 		 * the way. Make sure it doesn't end up as stdin/stdout/stderr, though!
 		 * */
 		int new_darwintrace_fd;
+		FILE *new_stream;
 
-		if (-1 == (new_darwintrace_fd = fcntl(fileno(__darwintrace_sock), F_DUPFD, STDOUT_FILENO + 1))) {
+		if (-1 == (new_darwintrace_fd = fcntl(fileno(stream), F_DUPFD, STDOUT_FILENO + 1))) {
 			/* if duplicating fails, do not allow overwriting either! */
 			return -1;
 		}
 
-		debug_printf("moving __darwintrace FD from %d to %d\n", fileno(__darwintrace_sock), new_darwintrace_fd);
-		__darwintrace_close(&__darwintrace_sock);
-		if (NULL == (__darwintrace_sock = fdopen(new_darwintrace_fd, "a+"))) {
+		debug_printf("moving __darwintrace FD from %d to %d\n", fileno(stream), new_darwintrace_fd);
+		__darwintrace_close();
+		if (NULL == (new_stream = fdopen(new_darwintrace_fd, "a+"))) {
 			perror("darwintrace: fdopen");
 			abort();
 		}
+		__darwintrace_sock_set(new_stream);
 	}
 
 	return dup2(filedes, filedes2);

Modified: trunk/base/src/pextlib1.0/Makefile
===================================================================
--- trunk/base/src/pextlib1.0/Makefile	2013-07-07 22:40:07 UTC (rev 107863)
+++ trunk/base/src/pextlib1.0/Makefile	2013-07-07 22:53:34 UTC (rev 107864)
@@ -3,7 +3,7 @@
 	fs-traverse.o strcasecmp.o vercomp.o filemap.o base32cmd.o \
 	sha1cmd.o curl.o rmd160cmd.o sha256cmd.o readline.o uid.o \
 	tracelib.o tty.o readdir.o pipe.o flock.o \
-	system.o mktemp.o realpath.o
+	system.o mktemp.o realpath.o ../registry2.0/registry${SHLIB_SUFFIX}
 SHLIB_NAME= Pextlib${SHLIB_SUFFIX}
 INSTALLDIR= ${DESTDIR}${datadir}/macports/Tcl/pextlib1.0
 
@@ -12,6 +12,9 @@
 
 CFLAGS+= ${CURL_CFLAGS} ${MD5_CFLAGS} ${READLINE_CFLAGS}
 LIBS+= ${CURL_LIBS} ${MD5_LIBS} ${READLINE_LIBS}
+ifeq ($(shell uname), Darwin)
+	SHLIB_LDFLAGS+= -install_name ${datadir}/macports/Tcl/pextlib1.0/${SHLIB_NAME}
+endif
 
 .PHONY: test
 

Modified: trunk/base/src/pextlib1.0/tracelib.c
===================================================================
--- trunk/base/src/pextlib1.0/tracelib.c	2013-07-07 22:40:07 UTC (rev 107863)
+++ trunk/base/src/pextlib1.0/tracelib.c	2013-07-07 22:53:34 UTC (rev 107864)
@@ -37,20 +37,25 @@
 #include <config.h>
 #endif
 
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-#include <sys/time.h>
+#include <sys/event.h>
 #include <sys/resource.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <stdlib.h>
 #include <sys/socket.h>
+#include <sys/time.h>
 #include <sys/types.h>
-#include <sys/select.h>
 #include <sys/un.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <pthread.h>
-#include <limits.h>
+#include <unistd.h>
+
+#include <cregistry/entry.h>
+#include <registry2.0/registry.h>
+
 #include "tracelib.h"
 
 #ifndef HAVE_STRLCPY
@@ -75,6 +80,7 @@
 static char *filemap, *filemap_end;
 static char *depends;
 static int sock = -1;
+static int kq = -1;
 static int enable_fence = 0;
 static Tcl_Interp *interp;
 static pthread_mutex_t sock_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -82,14 +88,26 @@
 static char *sdk = NULL;
 
 static void send_file_map(int sock);
-static void dep_check(int sock, const char *path);
+static void dep_check(int sock, char *path);
 static void sandbox_violation(int sock, const char *path);
 static void ui_warn(const char *format, ...);
+#if 0
 static void ui_info(const char *format, ...);
+#endif
 static void ui_error(const char *format, ...);
 
-#define MAX_SOCKETS ((FD_SETSIZE)-1)
+#define MAX_SOCKETS (1024)
+#define BUFSIZE     (1024)
 
+static void answer_s(int sock, const char *buf, size_t size) {
+    send(sock, &size, sizeof(size), 0);
+    send(sock, buf, size, 0);
+}
+
+static void answer(int sock, const char *buf) {
+    answer_s(sock, buf, strlen(buf));
+}
+
 static int TracelibSetNameCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
     if (objc != 3) {
         Tcl_WrongNumArgs(interp, 2, objv, "number of arguments should be exactly 3");
@@ -105,13 +123,14 @@
     return TCL_OK;
 }
 
-/*
- * Save sandbox path into memory and prepare it for checks.
- * For now it just change : to \0, and add last \0
+/**
+ * Save sandbox boundaries to memory and format them for darwintrace. This
+ * means changing : to \0 (with \ being an escape char).
+ *
  * Input:
- *  /dev/null:/dev/tty:/tmp
+ *  /dev/null:/dev/tty:/tmp\:
  * In variable;
- *  /dev/null\0/dev/tty\0/tmp\0\0
+ *  /dev/null\0/dev/tty\0/tmp:\0\0
  */
 static int TracelibSetSandboxCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
     int len;
@@ -172,81 +191,63 @@
     return TCL_OK;
 }
 
-/*
- * Is there more data? (return 1 if more data in socket, 0 otherwise)
- */
-static char data_available(int sock) {
-    struct timeval tv;
-    fd_set fdr;
-    tv.tv_sec  = 0;
-    tv.tv_usec = 0;
-
-    FD_ZERO(&fdr);
-    FD_SET(sock, &fdr);
-    return select(sock + 1, &fdr, 0, 0, &tv) == 1;
-}
-
-/*
+/**
  * receive line from socket, parse it and send answer
  */
-static char process_line(int sock) {
-    char *t, buf[1024] = {0}, *f, *next_t;
-    int len;
+static int process_line(int sock) {
+    char *f;
+    char buf[BUFSIZE];
+    size_t len;
+    ssize_t ret;
 
-    if ((len = recv(sock, buf, sizeof(buf) - 1, 0)) == -1) {
+    if ((ret = recv(sock, &len, sizeof(len), MSG_WAITALL)) != sizeof(len)) {
+        if (ret < 0) {
+            perror("tracelib: recv");
+        } else if (ret == 0) {
+            /* this usually means the socket was closed by the remote side */
+        } else {
+            fprintf(stderr, "tracelib: partial data received: expected %zd, but got %zd on socket %d\n", sizeof(len), ret, sock);
+        }
         return 0;
     }
-    if (!len) {
+
+    if (len > BUFSIZE - 1) {
+        fprintf(stderr, "tracelib: transfer too large: %zd bytes sent, but buffer holds %zd on socket %d\n", len, BUFSIZE - 1, sock);
         return 0;
     }
 
+    if ((ret = recv(sock, buf, len, MSG_WAITALL)) != (ssize_t) len) {
+        if (ret < 0) {
+            perror("tracelib: recv");
+        } else {
+            fprintf(stderr, "tracelib: partial data received: expected %zd, but got %zd on socket %d\n", len, ret, sock);
+        }
+        return 0;
+    }
     buf[len] = '\0';
 
-    for (t = buf; *t && t - buf < (int)sizeof(buf); t = next_t) {
-        next_t = t + strlen(t) + 1;
-        if (next_t == buf + sizeof(buf) && len == sizeof(buf) - 1) {
-            memmove(buf, t, next_t - t);
-            t = buf;
-            {
-                char *end_of_t = t + strlen(t);
-                *end_of_t = ' ';
-                for (; data_available(sock);) {
-                    if (recv(sock, end_of_t, 1, 0) != 1) {
-                        ui_warn("recv failed");
-                        return 0;
-                    }
-                    if (*end_of_t++ == '\0') {
-                        break;
-                    }
-                }
-            }
-        }
+    f = strchr(buf, '\t');
+    if (!f) {
+        fprintf(stderr, "tracelib: malformed command '%s' from socket %d\n", buf, sock);
+        return 0;
+    }
 
-        f = strchr(t, '\t');
-        if (!f) {
-            ui_warn("malformed command %s", t);
-            break;
-        }
+    /* Replace \t with \0 */
+    *f = '\0';
+    /* Advance pointer to arguments */
+    f++;
 
-        /* Replace \t with \0 */
-        *f = '\0';
-        /* Advance pointer to arguments */
-        f++;
+    if (strcmp(buf, "filemap") == 0) {
+        send_file_map(sock);
+    } else if (strcmp(buf, "sandbox_violation") == 0) {
+        sandbox_violation(sock, f);
+    } else if (strcmp(buf, "dep_check") == 0) {
+        dep_check(sock, f);
+    } else {
+        fprintf(stderr, "tracelib: unexpected command %s (%s)\n", buf, f);
+        return 0;
+    }
 
-        if (!strcmp(t, "filemap")) {
-            send_file_map(sock);
-        } else if (!strcmp(t, "sandbox_violation")) {
-            sandbox_violation(sock, f);
-        } else if (!strcmp(t, "dep_check")) {
-            dep_check(sock, f);
-        } else if (!strcmp(t, "execve")) {
-            /* ====================== */
-            /* = TODO: do something = */
-            /* ====================== */
-        } else {
-            ui_warn("unknown command %s (%s)", t, f);
-        }
-    }
     return 1;
 }
 
@@ -323,54 +324,46 @@
     Tcl_UnsetVar(interp, "path", 0);
 }
 
-static void dep_check(int sock, const char *path) {
+static void dep_check(int sock, char *path) {
     char *port = 0;
-    size_t len = 1;
-    char resolution = '!';
-    int tcl_retval;
+    reg_registry *reg;
+    reg_entry entry;
+    reg_error error;
 
-    Tcl_SetVar(interp, "path", path, 0);
-    /* FIXME: Use C registry API */
-    tcl_retval = Tcl_Eval(interp, "registry::file_registered $path");
-    port = strdup(Tcl_GetStringResult(interp));
-    if (!port) {
-        ui_warn("dep_check: memory allocation failed");
+    if (NULL == (reg = registry_for(interp, reg_attached))) {
+        ui_error(Tcl_GetStringResult(interp));
+        /* send unexpected output to make the build fail */
+        answer(sock, "#");
+    }
+
+    /* find the port id */
+    entry.reg = reg;
+    entry.proc = NULL;
+    entry.id = reg_entry_owner_id(reg, path);
+    if (entry.id == 0) {
+        /* file isn't known to MacPorts */
+        answer(sock, "?");
         return;
     }
-    if (tcl_retval != TCL_OK) {
-        ui_error("failed to run registry::file_registered \"%s\": %s", path, port);
-    }
-    Tcl_UnsetVar(interp, "path", 0);
 
-    if (tcl_retval == TCL_OK && (*port != '0' || port[1])) {
-        char *t;
-
-        t = depends;
-        for (; *t; t += strlen(t) + 1) {
-            /* fprintf(stderr, "trace: %s =?= %s\n", t, port); */
-            if (!strcmp(t, port)) {
-                resolution = '+';
-                break;
-            }
-        }
+    /* find the port's name to compare with out list */
+    if (!reg_entry_propget(&entry, "name", &port, &error)) {
+        /* send unexpected output to make the build fail */
+        ui_error(error.description);
+        answer(sock, "#");
     }
 
-    if (resolution != '+') {
-        if (*port == '0' && !port[1]) {
-            ui_info("trace: access denied to %s (*unknown*)", path);
-        } else {
-            ui_info("trace: access denied to %s (%s)", path, port);
+    /* check our list of dependencies */
+    for (char *t = depends; *t; t += strlen(t) + 1) {
+        if (strcmp(t, port) == 0) {
+            free(port);
+            answer(sock, "+");
+            return;
         }
     }
 
     free(port);
-
-    if (send(sock, &len, sizeof(len), 0) == -1) {
-        ui_warn("tracelib send failed");
-    }
-    if (send(sock, &resolution, 1, 0) == -1) {
-        ui_warn("tracelib send failed");
-    }
+    answer(sock, "!");
 }
 
 static void ui_msg(const char *severity, const char *format, va_list va) {
@@ -396,6 +389,7 @@
     va_end(va);
 }
 
+#if 0
 static void ui_info(const char *format, ...) {
     va_list va;
 
@@ -403,6 +397,7 @@
     ui_msg("info", format, va);
     va_end(va);
 }
+#endif
 
 static void ui_error(const char *format, ...) {
     va_list va;
@@ -415,13 +410,10 @@
     struct sockaddr_un sun;
     struct rlimit rl;
 
+    cleanuping = 0;
+
     pthread_mutex_lock(&sock_mutex);
-    if (cleanuping) {
-        pthread_mutex_unlock(&sock_mutex);
-        return 0;
-    }
-    sock = socket(AF_UNIX, SOCK_STREAM, 0);
-    if (sock == -1) {
+    if (-1 == (sock = socket(PF_LOCAL, SOCK_STREAM, 0))) {
         Tcl_SetErrno(errno);
         Tcl_ResetResult(interp);
         Tcl_AppendResult(interp, "socket: ", (char *) Tcl_PosixError(interp), NULL);
@@ -448,100 +440,200 @@
         }
     }
 
-
     sun.sun_family = AF_UNIX;
     strlcpy(sun.sun_path, name, sizeof(sun.sun_path));
-    if (bind(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+    sun.sun_len = SUN_LEN(&sun);
+
+    if (-1 == (bind(sock, (struct sockaddr *) &sun, sun.sun_len))) {
         Tcl_SetErrno(errno);
         Tcl_ResetResult(interp);
         Tcl_AppendResult(interp, "bind: ", (char *) Tcl_PosixError(interp), NULL);
+        close(sock);
+        sock = -1;
         return TCL_ERROR;
     }
 
-    if (listen(sock, 5) == -1) {
+    if (-1 == listen(sock, 32)) {
         Tcl_SetErrno(errno);
         Tcl_ResetResult(interp);
         Tcl_AppendResult(interp, "listen: ", (char *) Tcl_PosixError(interp), NULL);
+        close(sock);
+        sock = -1;
         return TCL_ERROR;
     }
 
     return TCL_OK;
 }
 
+/* create this on heap rather than stack, due to its rather large size */
+static struct kevent res_kevents[MAX_SOCKETS];
+static int TracelibRunCmd(Tcl_Interp *in) {
+    struct kevent kev;
+    int flags;
+    int oldsock;
 
-static int TracelibRunCmd(Tcl_Interp *in UNUSED) {
-    int max_fd, max_used, socks[MAX_SOCKETS];
-    fd_set fdr;
-    int i;
+    if (-1 == (kq = kqueue())) {
+        Tcl_SetErrno(errno);
+        Tcl_ResetResult(in);
+        Tcl_AppendResult(in, "kqueue: ", (char *) Tcl_PosixError(in), NULL);
+        return TCL_ERROR;
+    }
 
-    max_used = 0;
-    max_fd = sock;
+    pthread_mutex_lock(&sock_mutex);
+    if (sock != -1) {
+        oldsock = sock;
 
-    for (; sock != -1 && !cleanuping;) {
-        FD_ZERO(&fdr);
-        FD_SET(sock, &fdr);
-        for (i = 0; i < max_used; ++i) {
-            FD_SET(socks[i], &fdr);
+        /* mark listen socket non-blocking in order to prevent a race condition
+         * that would occur between kevent(2) and accept(2), if a incoming
+         * connection is aborted before it is accepted. Using a non-blocking
+         * accept(2) prevents the problem.*/
+        flags = fcntl(oldsock, F_GETFL, 0);
+        if (-1 == fcntl(oldsock, F_SETFL, flags | O_NONBLOCK)) {
+            Tcl_SetErrno(errno);
+            Tcl_ResetResult(in);
+            Tcl_AppendResult(in, "fcntl(F_SETFL, += O_NONBLOCK): ", (char *) Tcl_PosixError(in), NULL);
+            return TCL_ERROR;
         }
 
-        if (select(max_fd + 1, &fdr, 0, 0, 0) < 1) {
-            continue;
+        /* register the listen socket in the kqueue */
+        EV_SET(&kev, oldsock, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
+        if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
+            Tcl_SetErrno(errno);
+            Tcl_ResetResult(in);
+            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
+            close(kq);
+            return TCL_ERROR;
         }
-        if (sock == -1) {
-            break;
+        /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
+         * always be returned. When a filter is successfully added, the data field
+         * will be zero. */
+        if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
+            Tcl_SetErrno(kev.data);
+            Tcl_ResetResult(in);
+            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
+            close(kq);
+            return TCL_ERROR;
         }
-        if (FD_ISSET(sock, &fdr)) {
-            int s;
-            s = accept(sock, 0, 0);
 
-            if (s == -1) {
-                if (cleanuping) {
-                    break;
-                } else {
-                    ui_warn("tracelib: accept return -1 (errno: %d)", errno);
-                }
-                /* failed sometimes and i dunno why*/
-                continue;
+        /* wait for the user event on the listen socket, as sent by CloseCmd as
+         * deathpill */
+        EV_SET(&kev, oldsock, EVFILT_USER, EV_ADD | EV_RECEIPT, 0, 0, NULL);
+        if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
+            Tcl_SetErrno(errno);
+            Tcl_ResetResult(in);
+            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
+            close(kq);
+            return TCL_ERROR;
+        }
+        /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
+         * always be returned. When a filter is successfully added, the data field
+         * will be zero. */
+        if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
+            Tcl_SetErrno(kev.data);
+            Tcl_ResetResult(in);
+            Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
+            close(kq);
+            return TCL_ERROR;
+        }
+    }
+    pthread_mutex_unlock(&sock_mutex);
+
+    while (sock != -1 && !cleanuping) {
+        int keventstatus;
+
+        /* run kevent(2) until new activity is available */
+        do {
+            if (-1 == (keventstatus = kevent(kq, NULL, 0, res_kevents, MAX_SOCKETS, NULL))) {
+                Tcl_SetErrno(errno);
+                Tcl_ResetResult(in);
+                Tcl_AppendResult(in, "kevent: ", (char *) Tcl_PosixError(in), NULL);
+                close(kq);
+                return TCL_ERROR;
             }
-            /* Temporary solution, it's better to regenerate this variable in each iteration, because when closing socket we'll get it too high */
-            if (s > max_fd) {
-                max_fd = s;
-            }
-            for (i = 0; i < max_used; ++i)
-                if (!socks[i]) {
-                    socks[i] = s;
-                    break;
+        } while (keventstatus == 0);
+
+        for (int i = 0; i < keventstatus; ++i) {
+            /* the control socket has activity – we might have a new
+             * connection. We use a copy of sock here, because sock might have
+             * been set to -1 by the close command */
+            if ((int) res_kevents[i].ident == oldsock) {
+                int s;
+
+                /* handle error conditions */
+                if ((res_kevents[i].flags & (EV_ERROR | EV_EOF)) > 0) {
+                    if (cleanuping) {
+                        break;
+                    }
+                    Tcl_ResetResult(in);
+                    Tcl_SetResult(in, "control socket closed", NULL);
+                    close(kq);
+                    return TCL_ERROR;
                 }
-            if (i == max_used) {
-                if (max_used == MAX_SOCKETS - 1) {
-                    ui_warn("There is no place to store socket");
-                    close(s);
-                } else {
-                    socks[max_used++] = s;
+
+                /* else: new connection attempt(s) */
+                for (;;) {
+                    if (-1 == (s = accept(sock, NULL, NULL))) {
+                        if (cleanuping) {
+                            break;
+                        }
+                        if (errno == EWOULDBLOCK) {
+                            break;
+                        }
+                        Tcl_SetErrno(errno);
+                        Tcl_ResetResult(in);
+                        Tcl_AppendResult(in, "accept: ", (char *) Tcl_PosixError(in), NULL);
+                        close(kq);
+                        return TCL_ERROR;
+                    }
+
+                    flags = fcntl(s, F_GETFL, 0);
+                    if (-1 == fcntl(s, F_SETFL, flags & ~O_NONBLOCK)) {
+                        ui_warn("tracelib: couldn't mark socket as blocking");
+                        close(s);
+                        continue;
+                    }
+
+                    /* register the new socket in the kqueue */
+                    EV_SET(&kev, s, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, NULL);
+                    if (1 != kevent(kq, &kev, 1, &kev, 1, NULL)) {
+                        ui_warn("tracelib: error adding socket to kqueue");
+                        close(s);
+                        continue;
+                    }
+                    /* kevent(2) on EV_RECEIPT: When passed as input, it forces EV_ERROR to
+                     * always be returned. When a filter is successfully added, the data field
+                     * will be zero. */
+                    if ((kev.flags & EV_ERROR) == 0 || ((kev.flags & EV_ERROR) > 0 && kev.data != 0)) {
+                        ui_warn("tracelib: error adding socket to kqueue");
+                        close(s);
+                        continue;
+                    }
                 }
-            }
-        }
 
-        for (i = 0; i < max_used; ++i) {
-            if (!socks[i]) {
-                continue;
-            }
-            if (FD_ISSET(socks[i], &fdr)) {
-                if (!process_line(socks[i])) {
-                    close(socks[i]);
-                    socks[i] = 0;
-                    continue;
+                if (cleanuping) {
+                    break;
                 }
+            } else {
+                /* if the socket is to be closed, or */
+                if ((res_kevents[i].flags & (EV_EOF | EV_ERROR)) > 0
+                    /* new data is available, and its processing tells us to
+                     * close the socket */
+                    || (!process_line(res_kevents[i].ident))) {
+                    /* an error occured or process_line suggested closing this
+                     * socket */
+                    close(res_kevents[i].ident);
+                    /* closing the socket will automatically remove it from the
+                     * kqueue :) */
+                }
             }
         }
     }
 
-    for (i = 0; i < max_used; ++i) {
-        if (socks[i]) {
-            close(socks[i]);
-            socks[i] = 0;
-        }
-    }
+    /* NOTE: We aren't necessarily closing all client sockets here! */
+    pthread_mutex_lock(&sock_mutex);
+    close(kq);
+    kq = -1;
+    pthread_mutex_unlock(&sock_mutex);
 
     return TCL_OK;
 }
@@ -568,17 +660,30 @@
     }
     enable_fence = 0;
 #undef safe_free
-    cleanuping = 0;
     return TCL_OK;
 }
 
-static int TracelibCloseSocketCmd(Tcl_Interp *interp UNUSED) {
+static int TracelibCloseSocketCmd(Tcl_Interp *interp) {
     cleanuping = 1;
     pthread_mutex_lock(&sock_mutex);
     if (sock != -1) {
+        int oldsock = sock;
         /*shutdown(sock, SHUT_RDWR);*/
         close(sock);
         sock = -1;
+
+        if (kq != -1) {
+            int ret;
+            struct kevent kev;
+            EV_SET(&kev, oldsock, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+            if (-1 == (ret = kevent(kq, &kev, 1, NULL, 0, NULL))) {
+                Tcl_SetErrno(errno);
+                Tcl_ResetResult(interp);
+                Tcl_AppendResult(interp, "kevent: ", (char *) Tcl_PosixError(interp), NULL);
+                pthread_mutex_unlock(&sock_mutex);
+                return TCL_ERROR;
+            }
+        }
     }
     pthread_mutex_unlock(&sock_mutex);
     return TCL_OK;

Modified: trunk/base/src/registry2.0/Makefile
===================================================================
--- trunk/base/src/registry2.0/Makefile	2013-07-07 22:40:07 UTC (rev 107863)
+++ trunk/base/src/registry2.0/Makefile	2013-07-07 22:53:34 UTC (rev 107864)
@@ -17,6 +17,9 @@
 
 CFLAGS+=	${SQLITE3_CFLAGS}
 LIBS+=	${SQLITE3_LIBS}
+ifeq ($(shell uname), Darwin)
+	SHLIB_LDFLAGS+= -install_name ${datadir}/macports/Tcl/registry2.0/${SHLIB_NAME}
+endif
 
 .PHONY: test
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macports-changes/attachments/20130707/93092f67/attachment-0001.html>


More information about the macports-changes mailing list