packages feed

voyeur (empty) → 0.1.0.0

raw patch · 25 files changed

+3114/−0 lines, 25 filesdep +basedep +bytestringdep +processbuild-type:Customsetup-changed

Dependencies added: base, bytestring, process, utf8-string

Files

+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2014, Seth Fowler+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice, this+  list of conditions and the following disclaimer in the documentation and/or+  other materials provided with the distribution.++* Neither the name of the {organization} nor the names of its+  contributors may be used to endorse or promote products derived from+  this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,4 @@+hslibvoyeur+===========++Haskell bindings for libvoyeur
+ Setup.hs view
@@ -0,0 +1,159 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}++import Control.Applicative+import Control.Exception+import Control.Monad+import Data.List+import Data.Traversable (for)+import Distribution.PackageDescription+import Distribution.Simple+import Distribution.Simple.BuildPaths+import Distribution.Simple.LocalBuildInfo+import Distribution.Simple.Setup+import Distribution.Simple.Utils+import Distribution.Simple.Program+import qualified Distribution.Verbosity as Verbosity+import System.Directory+import System.FilePath+import System.Info++main :: IO ()+main = defaultMainWithHooks simpleUserHooks+    { confHook  = customConfHook+    , buildHook = customBuildHook+    , copyHook  = customCopyHook+    , cleanHook = customCleanHook++    , hookedPrograms = hookedPrograms simpleUserHooks+                    ++ [ makeProgram ]+    }++customConfHook :: (GenericPackageDescription, HookedBuildInfo) -> ConfigFlags+               -> IO LocalBuildInfo+customConfHook (pkg, pbi) flags = do+  (_, includeDir, _) <- libvoyeurPaths+  let addIncludeDirs = (onLocalLibBuildInfo . onIncludeDirs) (++ [".", includeDir])+      addLibs = if os == "darwin"+                  then id+                  else (onLocalLibBuildInfo . onLdOptions) (++ ["-lbsd"])++  lbi <- confHook simpleUserHooks (pkg, pbi) flags+  return $ (addLibs . addIncludeDirs) lbi++customBuildHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> BuildFlags -> IO ()+customBuildHook pkg lbi usrHooks flags = do+  putStrLn "Building libvoyeur..."+  +  (libvoyeurDir, _, libDir) <- libvoyeurPaths+  let verbosity = fromFlag (buildVerbosity flags)+      runMake = runDbProgram verbosity makeProgram (withPrograms lbi)+  +  inDir libvoyeurDir $+    runMake []+  +  buildHook simpleUserHooks pkg lbi usrHooks flags+  +  notice verbosity "Relinking libvoyeur.."+  +  let libObjs = map (libObjPath libDir) [ "voyeur"+                                        , "net"+                                        , "env"+                                        , "event"+                                        , "util"+                                        ]+  +      componentLibs = concatMap componentLibNames $ componentsConfigs lbi+      addStaticObjectFile libName objName = runAr ["r", libName, objName]+      runAr = runDbProgram verbosity arProgram (withPrograms lbi)+  +  forM_ componentLibs $ \componentLib -> do+    when (withVanillaLib lbi) $+         let libName = buildDir lbi </> mkLibName componentLib+         in mapM_ (addStaticObjectFile libName) libObjs+  +    when (withProfLib lbi) $+         let libName = buildDir lbi </> mkProfLibName componentLib+         in mapM_ (addStaticObjectFile libName) libObjs+  +    when (withSharedLib lbi) $+         let libName = buildDir lbi </> mkSharedLibName buildCompilerId componentLib+         in mapM_ (addStaticObjectFile libName) libObjs++customCopyHook :: PackageDescription -> LocalBuildInfo -> UserHooks -> CopyFlags -> IO ()+customCopyHook pkg lbi hooks flags = do+  let verb = fromFlagOrDefault Verbosity.normal $ copyVerbosity flags++  copyHook simpleUserHooks pkg lbi hooks flags++  putStrLn "Installing libvoyeur helper libraries..."++  let helperLibs = [ "exec", "exit", "open", "close" ]+      helperLibFiles = map (("libvoyeur-" ++) . (<.> dllExtension)) helperLibs+      helperLibDir = datadir (absoluteInstallDirs pkg lbi NoCopyDest)++  (_, _, libDir) <- libvoyeurPaths+  copyFiles verb helperLibDir $ map (libDir,) helperLibFiles+      +customCleanHook :: PackageDescription -> () -> UserHooks -> CleanFlags -> IO ()+customCleanHook pkg v hooks flags = do+  putStrLn "Cleaning libvoyeur..."+  +  let verb = fromFlagOrDefault Verbosity.normal $ cleanVerbosity flags+  pgmConf <- configureProgram verb (simpleProgram "make") defaultProgramConfiguration+  +  (libvoyeurDir, _, _) <- libvoyeurPaths+  inDir libvoyeurDir $+    runDbProgram verb makeProgram pgmConf ["clean"]+  +  cleanHook simpleUserHooks pkg v hooks flags++libvoyeurPaths :: IO (FilePath, FilePath, FilePath)+libvoyeurPaths = do+  curDir <- getCurrentDirectory+  return (curDir </> "libvoyeur",+          curDir </> "libvoyeur" </> "include",+          curDir </> "libvoyeur" </> "build")+  +componentLibNames :: (ComponentName, ComponentLocalBuildInfo, [ComponentName]) -> [LibraryName]+componentLibNames (_, LibComponentLocalBuildInfo {..}, _) = componentLibraries+componentLibNames _                                       = []++makeProgram :: Program+makeProgram = simpleProgram "make"++libObjPath :: FilePath -> FilePath -> FilePath+libObjPath dir name = dir </> name <.> objExtension++inDir :: FilePath -> IO a -> IO a+inDir dir act = do+  curDir <- getCurrentDirectory+  bracket_ (setCurrentDirectory dir)+           (setCurrentDirectory curDir)+           act++type Lifter a b = (a -> a) -> b -> b++onLocalPkgDescr :: Lifter PackageDescription LocalBuildInfo+onLocalPkgDescr f lbi = lbi { localPkgDescr = f (localPkgDescr lbi) }++onLibrary :: Lifter Library PackageDescription+onLibrary f lpd = lpd { library = f <$> library lpd }++onLibBuildInfo :: Lifter BuildInfo Library+onLibBuildInfo f lib = lib { libBuildInfo = f (libBuildInfo lib) }++onLocalLibBuildInfo :: Lifter BuildInfo LocalBuildInfo+onLocalLibBuildInfo = onLocalPkgDescr . onLibrary . onLibBuildInfo++onIncludeDirs :: Lifter [FilePath] BuildInfo+onIncludeDirs f libbi = libbi { includeDirs = f (includeDirs libbi) }++onLdOptions :: Lifter [FilePath] BuildInfo+onLdOptions f libbi = libbi { ldOptions = f (ldOptions libbi) }++onPkgDescr :: Lifter PackageDescription GenericPackageDescription+onPkgDescr f gpd = gpd { packageDescription = f (packageDescription gpd) }++onExtraSrcFiles :: Lifter [FilePath] PackageDescription+onExtraSrcFiles f pd = pd { extraSrcFiles = f (extraSrcFiles pd) }
+ changelog view
@@ -0,0 +1,3 @@+0.1.0.0++	* Initial release.
+ libvoyeur/LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2014, Seth Fowler+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice, this+  list of conditions and the following disclaimer in the documentation and/or+  other materials provided with the distribution.++* Neither the name of the {organization} nor the names of its+  contributors may be used to endorse or promote products derived from+  this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ libvoyeur/Makefile view
@@ -0,0 +1,152 @@+###############################################################################+# Customizable values+###############################################################################++CC?=clang+CFLAGS?=+LDFLAGS?=+PREFIX?=/usr/local/bin++CFLAGS+=-I./include -std=c99++ifdef DEBUG+CFLAGS+=-g -DDEBUG+endif+++###############################################################################+# Outputs+###############################################################################++MAINLIBNAME=libvoyeur+LIBNAMES=libvoyeur-exec libvoyeur-exit libvoyeur-open libvoyeur-close+TESTNAMES=test-exec test-exec-recursive test-open test-exec-and-open test-open-and-close test-exec-variants+TESTHARNESSNAME=voyeur-test+LIBNULLNAME=libnull+EXAMPLENAMES=voyeur-watch-exec voyeur-watch-open+++###############################################################################+# Computed values+###############################################################################++UNAME := $(shell uname -s)+ifeq ($(UNAME), Darwin)+  LIBSUFFIX=dylib+else+  LIBSUFFIX=so+  CFLAGS+=-fPIC -pthread+  LDFLAGS+=-lbsd -ldl+endif++HEADERS=$(wildcard src/*.h) $(wildcard include/*.h)+LIBSOURCES=$(wildcard src/*.c)+LIBOBJECTS=$(patsubst src/%.c, build/%.o, $(LIBSOURCES))+TESTSOURCES=$(wildcard test/*.c)+TESTOBJECTS=$(patsubst test/%.c, build/%.o, $(TESTSOURCES))+OBJECTS=$(LIBOBJECTS)+LIBS=$(addprefix build/, $(addsuffix .$(LIBSUFFIX), $(LIBNAMES)))+MAINLIB=$(addprefix build/, $(addsuffix .$(LIBSUFFIX), $(MAINLIBNAME)))+MAINSTATICLIB=$(addprefix build/, $(addsuffix .a, $(MAINLIBNAME)))+TESTS=$(addprefix build/, $(TESTNAMES))+TESTHARNESS=$(addprefix build/, $(TESTHARNESSNAME))+LIBNULL=$(addprefix build/, $(addsuffix .$(LIBSUFFIX), $(LIBNULLNAME)))+EXAMPLES=$(addprefix build/, $(EXAMPLENAMES))+BUILDDIR=$(realpath build/)++ifeq ($(UNAME), Darwin)+  define make-dynamic-lib+  $(CC) $(CFLAGS) $^ -dynamiclib -install_name lib$*.$(LIBSUFFIX) -o $@ $(LDFLAGS)+  endef++  define make-exec+  $(CC) $(CFLAGS) $< -o $@ -Lbuild -lvoyeur $(LDFLAGS)+  endef++  define make-test+  $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)+  endef+else+  define make-dynamic-lib+  $(CC) $(CFLAGS) $^ -shared -Wl,-soname,lib$*.$(LIBSUFFIX) -o $@ $(LDFLAGS)+  endef++  define make-exec+  $(CC) $(CFLAGS) -Wl,-rpath '-Wl,$$ORIGIN' $< -o $@ -Lbuild -lvoyeur $(LDFLAGS)+  endef++  define make-test+  $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)+  endef+endif++.PHONY: default check examples install clean+++###############################################################################+# Library targets+###############################################################################++default: build $(LIBS) $(MAINLIB) $(MAINSTATICLIB)++build:+	mkdir -p $@++$(OBJECTS): build/%.o : src/%.c $(HEADERS)+	$(CC) $(CFLAGS) -c $< -o $@++$(LIBS): build/lib%.$(LIBSUFFIX) : build/%.o build/net.o build/env.o build/event.o build/util.o+	$(make-dynamic-lib)++$(MAINLIB): build/lib%.$(LIBSUFFIX) : build/%.o build/net.o build/env.o build/event.o build/util.o+	$(make-dynamic-lib)++$(MAINSTATICLIB): build/lib%.a : build/%.o build/net.o build/env.o build/event.o build/util.o+	$(AR) rcs $@ $^+++###############################################################################+# Test targets+###############################################################################++check: default $(TESTHARNESS) $(TESTS) $(LIBNULL)+	cd build && ./$(TESTHARNESSNAME)++$(TESTHARNESS): build/% : test/%.c $(LIBS)+	$(make-exec)++$(TESTS): build/% : test/%.c $(LIBS)+	$(make-test)++$(LIBNULL): build/lib%.$(LIBSUFFIX) : test/%.c+	$(make-dynamic-lib)+++###############################################################################+# Example targets+###############################################################################++examples: default $(EXAMPLES)++$(EXAMPLES): build/% : examples/%.c $(HEADERS)+	$(make-exec)+++###############################################################################+# Install targets+###############################################################################++install: default+	install -c $(MAINLIB) $(MAINSTATICLIB) $(LIBS) $(PREFIX)++uninstall:+	rm $(addprefix $(PREFIX)/, $(notdir $(MAINLIB)))+	rm $(addprefix $(PREFIX)/, $(notdir $(MAINSTATICLIB)))+	rm $(addprefix $(PREFIX)/, $(notdir $(LIBS)))++###############################################################################+# Utility targets+###############################################################################++clean:+	rm -rf build
+ libvoyeur/README.md view
@@ -0,0 +1,38 @@+libvoyeur+=========++A BSD-licensed library for observing the private behavior of a child process.++Mac OS X and Linux are supported.++The following calls are currently observable:++- Process creation with `exec*`+- File access with `open` and `close`++More are planned. The library has been designed so that adding support for new+calls is easy.++There is a significant performance cost to the observed process, because the+details of the calls are sent to the observing process over IPC, and locking is+required. However, libvoyeur tries to be as efficient as possible: you only pay+a performance penalty for the calls that you actually want to observe.++Compilation+===========++Just run `make`. You can check that everything built correctly with `make check`.++Usage+=====++The program doing the observing must link against `libvoyeur`.++If you link statically, or if the various helper libraries like `libvoyeur-exec`+are not in the same directory as `libvoyeur`, you'll need to tell the library+where to find them using `voyeur_set_resource_path`.++The public API is documented in [voyeur.h](include/voyeur.h).++The [examples directory](examples/) contains some sample programs built using+`libvoyeur`. You can build the examples with `make examples`.
+ libvoyeur/include/voyeur.h view
@@ -0,0 +1,205 @@+#ifndef LIBVOYEUR_VOYEUR_H+#define LIBVOYEUR_VOYEUR_H++#include <stdint.h>+#include <stdlib.h>+#include <sys/types.h>++//////////////////////////////////////////////////+// Overview of libvoyeur.+//////////////////////////////////////////////////++// Libvoyeur is a library for observing the private activity of a+// child process. It does this by using the dynamic linker to override+// the implementations of standard library functions. When those+// functions are called, libvoyeur generates events, and by+// registering callbacks you can observe those events and take+// whatever action you want.++// The flow of using libvoyeur is simple. Start by creating a context:+//+// > voyeur_context_t ctx = voyeur_context_create();+//+// Then register to observe some events:+//+// > voyeur_observe_exec(ctx,+// >                     OBSERVE_EXEC_CMD | OBSERVE_EXEC_ENV,+// >                     my_exec_callback,+// >                     NULL);+//+// Use voyeur_exec() to start and observe the child process:+//+// > voyeur_exec(ctx, "/usr/bin/program", argv, envp);+//+// Finally, destroy the context:+//+// > voyeur_context_destroy(ctx);++// Users with more complex needs or who intend to provide bindings to+// languages other than C will probably want to use voyeur_prepare()+// and voyeur_start() rather than voyeur_exec(). Their documentation+// is below.+++//////////////////////////////////////////////////+// Creating and destroying libvoyeur contexts.+//////////////////////////////////////////////////++typedef void* voyeur_context_t;++// Creates a context used to specify which events you want to observe+// and as an owner for resources allocated by voyeur_prepare().+voyeur_context_t voyeur_context_create();++// Destroys a context. This must always be called after voyeur_start()+// or voyeur_exec() is finished to release the resources libvoyeur has allocated.+// After destroying a context, it is invalid and should not be used again.+void voyeur_context_destroy(voyeur_context_t ctx);+++//////////////////////////////////////////////////+// Registering callbacks for particular events.+//////////////////////////////////////////////////++// Observing exec*() calls.+typedef void (*voyeur_exec_callback)(const char* file,+                                     char* const argv[],+                                     char* const envp[],+                                     const char* path,+                                     const char* cwd,+                                     pid_t pid,+                                     pid_t ppid,+                                     void* userdata);+typedef enum {+  OBSERVE_EXEC_DEFAULT  = 0,+  OBSERVE_EXEC_CWD      = 1 << 0,  // Include 'cwd' (working directory).+  OBSERVE_EXEC_ENV      = 1 << 1,  // Include 'envp' (environment).+  OBSERVE_EXEC_PATH     = 1 << 2,  // Include the value of 'PATH'.+  OBSERVE_EXEC_NOACCESS = 1 << 3,  // Include exec calls for paths+                                   // that don't exist or aren't executable.+} voyeur_exec_options;+void voyeur_observe_exec(voyeur_context_t ctx,+                         uint8_t opts,+                         voyeur_exec_callback callback,+                         void* userdata);++// Observing when processes exit.+typedef void (*voyeur_exit_callback)(int status,+                                     pid_t pid,+                                     pid_t ppid,+                                     void* userdata);+typedef enum {+  OBSERVE_EXIT_DEFAULT  = 0,+} voyeur_exit_options;+void voyeur_observe_exit(voyeur_context_t ctx,+                         uint8_t opts,+                         voyeur_exit_callback callback,+                         void* userdata);++// Observing open() calls.+typedef void (*voyeur_open_callback)(const char* path,+                                     int oflag,+                                     mode_t mode,+                                     const char* cwd,+                                     int retval,+                                     pid_t pid,+                                     void* userdata);+typedef enum {+  OBSERVE_OPEN_DEFAULT = 0,+  OBSERVE_OPEN_CWD     = 1 << 0,  // Include 'cwd' (working directory).+} voyeur_open_options;+void voyeur_observe_open(voyeur_context_t ctx,+                         uint8_t opts,+                         voyeur_open_callback callback,+                         void* userdata);++// Observing close() calls.+typedef void (*voyeur_close_callback)(int fd,+                                      int retval,+                                      pid_t pid,+                                      void* userdata);+typedef enum {+  OBSERVE_CLOSE_DEFAULT = 0,+} voyeur_close_options;+void voyeur_observe_close(voyeur_context_t ctx,+                          uint8_t opts,+                          voyeur_close_callback callback,+                          void* userdata);+++//////////////////////////////////////////////////+// Other context configuration options.+//////////////////////////////////////////////////++// Set the path where libvoyeur should look for its resources.+//+// To inject code into child processes, libvoyeur uses a set+// of helper dynamic libraries. By default, libvoyeur assumes+// that those libraries are located in the same directory as+// libvoyeur itself, which is to say in the same directory as+// libvoyeur.so/.dylib if using dynamic linking, or in the same+// directory as the main program if using static linking. If+// this assumption doesn't hold, this function can be used to+// provide the path where libvoyeur should look for these files.+//+// The provided path must have a trailing path separator -+// in other words, it must end with a '/'.+void voyeur_set_resource_path(voyeur_context_t ctx,+                              const char* path);+++//////////////////////////////////////////////////+// Observing processes.+//////////////////////////////////////////////////++// Prepare to observe a child process.+//+// This will acquire resources that libvoyeur needs to do its work and+// create a modified environment, based upon the provided template,+// that should be passed to the child process when it gets exec'd.+// It's the caller's responsibility to free this environment. (Note+// that unlike other resources acquired by libvoyeur, the environment+// is not freed when voyeur_context_destroy() is called.)+//+// This function should be called before forking. After calling+// voyeur_prepare(), it isn't safe to call voyeur_observe_* functions+// on the same context anymore.+//+// Returns NULL on failure.+char** voyeur_prepare(voyeur_context_t ctx, char* const envp[]);++// Start observing a child process.+//+// This will block until the child process completes, at which time+// it will return its exit status. While the child process is running,+// the callbacks you've registered with the voyeur_observe_* functions+// will be called.+//+// Call this after forking, in the parent process. After+// voyeur_start() returns, you should use voyeur_context_destroy()+// to release the resources libvoyeur has acquired.+int voyeur_start(voyeur_context_t ctx, pid_t child_pid);++// Create and observe a new child process.+//+// voyeur_exec() is a convenience function that behaves just as if+// you had made this sequence of calls:+//+// > voyeur_envp = voyeur_prepare(ctx, envp);+// > posix_spawn(&pid, path, NULL, NULL, argv, voyeur_envp);+// > voyeur_start(ctx, pid);+// > free(voyeur_envp);+//+// Like voyeur_start(), it returns the exit status of the child. After+// voyeur_exec() returns, you should use voyeur_context_destroy() to+// release the resources libvoyeur has acquired.+//+// voyeur_exec() may fail, in which case it will return -1. If you need+// to distinguish between a child process exit status of -1 and a failure+// inside libvoyeur, use voyeur_prepare() and voyeur_start().+int voyeur_exec(voyeur_context_t ctx,+                const char* path,+                char* const argv[],+                char* const envp[]);++#endif
+ libvoyeur/src/dyld.h view
@@ -0,0 +1,30 @@+#ifndef VOYEUR_DYLD_H+#define VOYEUR_DYLD_H++#include <dlfcn.h>++#ifdef __APPLE__++#define VOYEUR_FUNC(_foo) voyeur_##_foo+#define VOYEUR_DECLARE_NEXT(_foo_t, _foo)+#define VOYEUR_STATIC_DECLARE_NEXT(_foo_t, _foo)+#define VOYEUR_LOOKUP_NEXT(_foo_t, _foo)+#define VOYEUR_CALL_NEXT(_foo, ...) _foo(__VA_ARGS__)++// Based on DYLD_INTERPOSE in dyld-interposing.h:+#define VOYEUR_INTERPOSE(_replacee) \+  __attribute__((used)) static struct{ const void* replacment; const void* replacee; } _interpose_##_replacee \+  __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&VOYEUR_FUNC(_replacee), (const void*)(unsigned long)&_replacee }; ++#else++#define VOYEUR_FUNC(_foo) _foo+#define VOYEUR_DECLARE_NEXT(_foo_t, _foo) _foo_t voyeur_##_foo##_next = NULL;+#define VOYEUR_STATIC_DECLARE_NEXT(_foo_t, _foo) static _foo_t voyeur_##_foo##_next = NULL;+#define VOYEUR_LOOKUP_NEXT(_foo_t, _foo) voyeur_##_foo##_next = (_foo_t) dlsym(RTLD_NEXT, #_foo)+#define VOYEUR_CALL_NEXT(_foo, ...) voyeur_##_foo##_next(__VA_ARGS__)+#define VOYEUR_INTERPOSE(_replacee)++#endif++#endif
+ libvoyeur/src/env.c view
@@ -0,0 +1,110 @@+#ifdef __linux__+#define _GNU_SOURCE+#endif++#include <stdlib.h>+#include <stdio.h>+#include <string.h>++#ifdef __linux__+#include <bsd/bsd.h>+#endif++#include "env.h"++#ifdef __APPLE__+#define INSERT_LIBS "DYLD_INSERT_LIBRARIES="+#else+#define INSERT_LIBS "LD_PRELOAD="+#endif++typedef char env_buf[2048];++char** voyeur_augment_environment(char* const* envp,+                                  const char* voyeur_libs,+                                  const char* voyeur_opts,+                                  const char* sockpath,+                                  void** buf_out)+{+  // Determine the size of the original environment.+  unsigned envlen = 0;+  unsigned existing_insert_idx = 0;+  char* existing_insert = NULL;+  for ( ; envp[envlen] != NULL ; ++envlen) {+    if (strncmp(envp[envlen], INSERT_LIBS, sizeof(INSERT_LIBS) - 1) == 0) {+      existing_insert = envp[envlen] ++                             sizeof(INSERT_LIBS) - 1;+      existing_insert_idx = envlen;+    }+  }++  char must_add_voyeur_libs = 1;+  if (existing_insert && strnstr(existing_insert, voyeur_libs, sizeof(env_buf) * 2)) {+    must_add_voyeur_libs = 0;+  }+  if (must_add_voyeur_libs &&+      (strlen(voyeur_libs) + (existing_insert ? strlen(existing_insert) : 0)) > sizeof(env_buf)) {+    fprintf(stderr, "Not enough space to insert libvoyeur into " INSERT_LIBS);+    must_add_voyeur_libs = 0;+  }++  // Allocate the new environment variables.+  env_buf* buf = malloc(sizeof(env_buf) * 4);+  *buf_out = (void*) buf;++  strlcpy(buf[0], "LIBVOYEUR_LIBS=", sizeof(env_buf));+  strlcat(buf[0], voyeur_libs, sizeof(env_buf));++  strlcpy(buf[1], "LIBVOYEUR_OPTS=", sizeof(env_buf));+  strlcat(buf[1], voyeur_opts, sizeof(env_buf));++  strlcpy(buf[2], "LIBVOYEUR_SOCKET=", sizeof(env_buf));+  strlcat(buf[2], sockpath, sizeof(env_buf));++  strlcpy(buf[3], INSERT_LIBS, sizeof(env_buf));+  if (must_add_voyeur_libs) {+    strlcat(buf[3], voyeur_libs, sizeof(env_buf));+    if (existing_insert) {+      strlcat(buf[3], ":", sizeof(env_buf));+      strlcat(buf[3], existing_insert, sizeof(env_buf));+    }+  } else if (existing_insert) {+    strlcat(buf[3], existing_insert, sizeof(env_buf));+  }++  // Allocate a new environment, including additional space for the 4+  // extra environment variables we'll add and a terminating NULL.+  char** newenvp = malloc(sizeof(char*) * (envlen + 5));+  memcpy(newenvp, envp, sizeof(char*) * envlen);+  newenvp[envlen + 0] = buf[0];+  newenvp[envlen + 1] = buf[1];+  newenvp[envlen + 2] = buf[2];++  if (existing_insert) {+    // Make sure we don't have two INSERT_LIBS environment variables.+    newenvp[existing_insert_idx] = buf[3];+    newenvp[envlen + 3] = NULL;+  } else {+    newenvp[envlen + 3] = buf[3];+    newenvp[envlen + 4] = NULL;+  }++  return newenvp;+}++char voyeur_encode_options(uint8_t opts)+{+  // Stripping all but the last 5 bits and bitwise-or'ing with '@' will always+  // result in a printable character. The downside is that we can only+  // support 5 flags this way.+  return '@' | ((char) opts & 0x1F);+}++uint8_t voyeur_decode_options(const char* opts, uint8_t offset)+{+  if (!opts || offset > strnlen(opts, 8)) {+    return 0;+  }++  return (uint8_t) (opts[offset] & 0x1F);+}
+ libvoyeur/src/env.h view
@@ -0,0 +1,20 @@+#ifndef VOYEUR_ENV_H+#define VOYEUR_ENV_H++#include <voyeur.h>++// Augments the provided list of environment variables with the+// variables required for libvoyeur to observe a process. If the+// caller doesn't fork, then after calling exec() they should free+// both the returned environment and the buffer returned in buf_out.+char** voyeur_augment_environment(char* const* envp,+                                  const char* voyeur_libs,+                                  const char* voyeur_opts,+                                  const char* sockpath,+                                  void** buf_out);++// Encoding and decoding options.+char voyeur_encode_options(uint8_t opts);+uint8_t voyeur_decode_options(const char* opts, uint8_t offset);++#endif
+ libvoyeur/src/event.c view
@@ -0,0 +1,307 @@+#ifdef __linux__+#define _GNU_SOURCE+#endif++#include <dlfcn.h>+#include <stdbool.h>+#include <stdio.h>+#include <stdlib.h>+#include <string.h>+#include <unistd.h>++#ifdef __linux__+#include <bsd/bsd.h>+#endif++#include "env.h"+#include "event.h"+#include "net.h"+#include "util.h"+#include <voyeur.h>++// Note that in event handlers, we always have to read the data,+// even if the callback isn't present, so we can move on to the+// next event in the stream. In practice the callback should always be+// present, so it's not worth worrying about.++static void handle_exec(voyeur_context* context, int fd)+{+  // Read the path.+  char* file;+  RETURN_ON_FAIL(voyeur_read_string, fd, &file, 0);++  // Read the arguments.+  int argc;+  RETURN_ON_FAIL(voyeur_read_int, fd, &argc);++  char** argv = malloc(sizeof(char*) * (argc + 1));+  for (int i = 0 ; i < argc ; ++i) {+    char* arg;+    RETURN_ON_FAIL(voyeur_read_string, fd, &arg, 0);+    argv[i] = arg;+  }+  argv[argc] = NULL;++  // Read the environment.+  int envc;+  char** envp = NULL;+  if (context->exec_opts & OBSERVE_EXEC_ENV) {+    RETURN_ON_FAIL(voyeur_read_int, fd, &envc);++    envp = malloc(sizeof(char*) * (envc + 1));+    for (int i = 0 ; i < envc ; ++i) {+      char* envvar;+      RETURN_ON_FAIL(voyeur_read_string, fd, &envvar, 0);+      envp[i] = envvar;+    }+    envp[envc] = NULL;+  }++  // Read the value of PATH.+  char* path = NULL;+  if (context->exec_opts & OBSERVE_EXEC_PATH) {+    RETURN_ON_FAIL(voyeur_read_string, fd, &path, 0);+  }++  // Read the current working directory.+  char* cwd = NULL;+  if (context->exec_opts & OBSERVE_EXEC_CWD) {+    RETURN_ON_FAIL(voyeur_read_string, fd, &cwd, 0);+  }++  // Read the pid and ppid.+  pid_t pid, ppid;+  RETURN_ON_FAIL(voyeur_read_pid, fd, &pid);+  RETURN_ON_FAIL(voyeur_read_pid, fd, &ppid);++  if (context->exec_cb) {+    ((voyeur_exec_callback)context->exec_cb)(file, argv, envp,+                                             path, cwd,+                                             pid, ppid,+                                             context->exec_userdata);+  }++  // Free everything.+  free(file);++  for (int i = 0 ; i < argc ; ++i) {+    free(argv[i]);+  }+  free(argv);++  if (context->exec_opts & OBSERVE_EXEC_ENV) {+    for (int i = 0 ; i < envc ; ++i) {+      free(envp[i]);+    }+    free(envp);+  }++  if (context->exec_opts & OBSERVE_EXEC_PATH) {+    free(path);+  }++  if (context->exec_opts & OBSERVE_EXEC_CWD) {+    free(cwd);+  }+}++static void handle_exit(voyeur_context* context, int fd)+{+  int status;+  pid_t pid, ppid;++  RETURN_ON_FAIL(voyeur_read_int, fd, &status);+  RETURN_ON_FAIL(voyeur_read_pid, fd, &pid);+  RETURN_ON_FAIL(voyeur_read_pid, fd, &ppid);++  if (context->exit_cb) {+    ((voyeur_exit_callback)context->exit_cb)(status, pid, ppid,+                                             context->exit_userdata);+  }+}++static void handle_open(voyeur_context* context, int fd)+{+  char* path;+  int oflag, mode, retval;+  char* cwd = NULL;+  pid_t pid;++  RETURN_ON_FAIL(voyeur_read_string, fd, &path, 0);+  RETURN_ON_FAIL(voyeur_read_int, fd, &oflag);+  RETURN_ON_FAIL(voyeur_read_int, fd, &mode);+  RETURN_ON_FAIL(voyeur_read_int, fd, &retval);++  if (context->open_opts & OBSERVE_OPEN_CWD) {+    RETURN_ON_FAIL(voyeur_read_string, fd, &cwd, 0);+  }++  RETURN_ON_FAIL(voyeur_read_pid, fd, &pid);++  if (context->open_cb) {+    ((voyeur_open_callback)context->open_cb)(path, oflag,+                                             (mode_t) mode,+                                             cwd, retval, pid,+                                             context->open_userdata);+  }++  free(path);+}++static void handle_close(voyeur_context* context, int fd)+{+  int fildes, retval;+  pid_t pid;++  RETURN_ON_FAIL(voyeur_read_int, fd, &fildes);+  RETURN_ON_FAIL(voyeur_read_int, fd, &retval);+  RETURN_ON_FAIL(voyeur_read_pid, fd, &pid);++  if (context->close_cb) {+    ((voyeur_close_callback)context->close_cb)(fildes, retval, pid,+                                               context->close_userdata);+  }+}++#define ON_EVENT(E, e)                          \+  case VOYEUR_EVENT_##E:                        \+    handle_##e(context, fd);                    \+    break;++void voyeur_handle_event(voyeur_context* context, voyeur_event_type type, int fd)+{+  switch (type) {+    MAP_EVENTS+    default:+      SHOULD_NOT_REACH("libvoyeur: got unknown event type %u\n",+                       (unsigned) type);+      return;+  }+}++#undef ON_EVENT++#ifdef __APPLE__+#define LIB_SUFFIX ".dylib"+#else+#define LIB_SUFFIX ".so"+#endif++#define ON_EVENT(_, e)           \+  "libvoyeur-" #e LIB_SUFFIX ":" \++static size_t compute_libs_size(size_t libdir_size)+{+  static char all_libs[] =+    MAP_EVENTS+    ;++  return sizeof(all_libs) + VOYEUR_EVENT_MAX * libdir_size;+}++#undef ON_EVENT++static char* get_default_resource_path(voyeur_context* context, bool* did_allocate)+{+  // We want absolute paths to the libraries, relative to the location of+  // libvoyeur. We use dladdr() to determine that location.+  *did_allocate = false;+  char* libdir = "./";+  Dl_info dlinfo;++  if (dladdr(voyeur_requested_libs, &dlinfo) && dlinfo.dli_fname) {+    // Strip the filename. It'd be great if 'dirname' could be used+    // for this, but it's not thread safe. Note that if no slash is+    // found, we just stick with "./".+    char* last_slash = strrchr(dlinfo.dli_fname, '/');+    if (last_slash) {+      *did_allocate = true;+      int len_with_slash_and_null = (last_slash - dlinfo.dli_fname) + 2;+      libdir = calloc(1, len_with_slash_and_null);+      strlcpy(libdir, dlinfo.dli_fname, len_with_slash_and_null);+    }+  }++  return libdir;+}++#define ON_EVENT(_, e)                                    \+  if (context->e##_cb) {                                  \+    if (prev) strlcat(libs, ":", libs_size);              \+    strlcat(libs, libdir, libs_size);                     \+    strlcat(libs, "libvoyeur-" #e LIB_SUFFIX, libs_size); \+    prev = 1;                                             \+  }                                                       \++char* voyeur_requested_libs(voyeur_context* context)+{+  bool did_allocate = false;+  char* libdir = context->resource_path+               ? context->resource_path+               : get_default_resource_path(context, &did_allocate);++  size_t libs_size = compute_libs_size(strnlen(libdir, 4096));+  char* libs = calloc(1, libs_size);+  char prev = 0;+  +  // If the user isn't observing exec events, we still want to apply libvoyeur+  // recursively, so we link in voyeur-exec in any case. (We set+  // OBSERVE_EXEC_SILENT so they won't get any events because of this.)+  char did_force_exec = 0;+  if (!context->exec_cb) {+    did_force_exec = 1;+    context->exec_cb = &did_force_exec;  // Just a dummy value.+  }++  MAP_EVENTS++  if (did_force_exec) {+    context->exec_cb = NULL;+  }++  if (did_allocate) {+    free(libdir);+  }++  return libs;+}++#undef ON_EVENT++#define ON_EVENT(E, e) opts[VOYEUR_EVENT_##E] = voyeur_encode_options(context->e##_opts);++char* voyeur_requested_opts(voyeur_context* context)+{+  // VOYEUR_EVENT_MAX is one greater than the highest event number and+  // thus leaves room for a terminating null.+  char* opts = calloc(1, VOYEUR_EVENT_MAX);++  // If the user isn't observing exec events, we still want to apply libvoyeur+  // recursively, so we link in voyeur-exec in any case. (We set+  // OBSERVE_EXEC_SILENT so they won't get any events because of this.)+  if (!context->exec_cb) {+    context->exec_opts = OBSERVE_EXEC_SILENT;+  }++  MAP_EVENTS++  return opts;+}++#undef ON_EVENT++#define ON_EVENT(_, e)                                  \+void voyeur_observe_##e(voyeur_context_t ctx,           \+                        uint8_t opts,                   \+                        voyeur_##e##_callback callback, \+                        void* userdata)                 \+{                                                       \+  voyeur_context* context = (voyeur_context*) ctx;      \+  context->e##_opts = opts;                             \+  context->e##_cb = (void*) callback;                   \+  context->e##_userdata = userdata;                     \+}++MAP_EVENTS++#undef ON_EVENT
+ libvoyeur/src/event.h view
@@ -0,0 +1,58 @@+#ifndef VOYEUR_EVENTS_H+#define VOYEUR_EVENTS_H++#include <stdint.h>++// How to define a new event:+// 1. Add the new event name to MAP_EVENTS.+// 2. Define the public API in voyeur.h. (To keep things readable, avoid using+//    MAP_EVENTS there.)+// 3. Implement voyeur-xxx.c based on one of the existing events, and+//    add it to LIBNAMES in the Makefile.+// 4. Implement a handle_xxx function in event.c.+// 5. Add a test!++#define MAP_EVENTS                              \+  ON_EVENT(EXEC, exec)                          \+  ON_EVENT(EXIT, exit)                          \+  ON_EVENT(OPEN, open)                          \+  ON_EVENT(CLOSE, close)                        \++// Define an enumeration for event types.+#define ON_EVENT(E, _) VOYEUR_EVENT_##E,+typedef enum {+  MAP_EVENTS+  VOYEUR_EVENT_MAX+} voyeur_event_type;+#undef ON_EVENT++// Define the voyeur_context type, which is primarily a container for+// event options and callbacks.+#define ON_EVENT(_, e)                          \+  uint8_t e##_opts;                             \+  void* e##_cb;                                 \+  void* e##_userdata;++typedef struct {+  MAP_EVENTS++  char* resource_path;+  void* server_state;+} voyeur_context;++#undef ON_EVENT++// Dispatch to the correct handler for the given event type.+void voyeur_handle_event(voyeur_context* context, voyeur_event_type type, int fd);++// Create the VOYEUR_LIBS and VOYEUR_OPTS strings based on the+// context. The caller is responsible for freeing them.+char* voyeur_requested_libs(voyeur_context* context);+char* voyeur_requested_opts(voyeur_context* context);++// A special hidden option for the exec() handler.+typedef enum {+  OBSERVE_EXEC_SILENT = 1 << 4+} voyeur_extra_exec_options;++#endif
+ libvoyeur/src/net.c view
@@ -0,0 +1,253 @@+#ifdef __linux__+#define _GNU_SOURCE+#endif++#include <errno.h>+#include <fcntl.h>+#include <stdio.h>+#include <stdlib.h>+#include <string.h>+#include <sys/socket.h>+#include <sys/stat.h>+#include <sys/un.h>+#include <unistd.h>++#ifdef __linux__+#include <bsd/bsd.h>+#endif++#include "net.h"+#include "util.h"++int voyeur_create_server_socket(struct sockaddr_un* sockinfo)+{+  // Configure a unix domain socket at a temporary path.+  char sockdir[] = "/tmp/libvoyeur-XXXXXXXXX";+  mkdtemp(sockdir);+  +  memset(sockinfo, 0, sizeof(struct sockaddr_un));+  sockinfo->sun_family = AF_UNIX;+  strlcpy(sockinfo->sun_path, sockdir, sizeof(sockinfo->sun_path));+  strlcat(sockinfo->sun_path, "/socket", sizeof(sockinfo->sun_path));+  unlink(sockinfo->sun_path);++  // Start the server.+  int server_sock = socket(AF_UNIX, SOCK_STREAM, 0);+  RETURN_ERROR_ON_FAIL(bind, server_sock, (struct sockaddr*) sockinfo,+                                          sizeof(struct sockaddr_un));+  RETURN_ERROR_ON_FAIL(listen, server_sock, 200);++  //chmod(sockinfo->sun_path, 0777);+  //fchmod(server_sock, 0777);++  fcntl(server_sock, F_SETFD, FD_CLOEXEC);+  return server_sock;+}++#define CONNECT_RETRIES 20+#define CONNECT_WAIT_MS 50+#define CONNECT_DEVIATION_MS 20++int voyeur_create_client_socket(const char* sockpath)+{+  struct sockaddr_un sockinfo;++  memset(&sockinfo, 0, sizeof(struct sockaddr_un));+  sockinfo.sun_family = AF_UNIX;+  strlcpy(sockinfo.sun_path, sockpath, sizeof(sockinfo.sun_path));++  // Connect to the server.+  int client_sock = socket(AF_UNIX, SOCK_STREAM, 0);+  int set = 1;++#ifdef __APPLE__+  setsockopt(client_sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));+#endif++  unsigned try = 0;+  int connect_status = connect(client_sock,+                               (struct sockaddr*) &sockinfo,+                               sizeof(struct sockaddr_un));+  +  while (connect_status < 0 && ++try < CONNECT_RETRIES) {+    usleep(((CONNECT_WAIT_MS - CONNECT_DEVIATION_MS / 2) ++            arc4random_uniform(CONNECT_DEVIATION_MS)) * 1000);+    connect_status = connect(client_sock,+                             (struct sockaddr*) &sockinfo,+                             sizeof(struct sockaddr_un));+  }++  fcntl(client_sock, F_SETFD, FD_CLOEXEC);+  return client_sock;+}++void voyeur_close_socket(int fd)+{+  while (close(fd) < 0) {+    if (errno != EINTR) {+      // We really only want to keep spinning for EINTR.+      break;+    }+  }+}++#ifdef __linux__+#define SEND_OPTS MSG_NOSIGNAL+#else+#define SEND_OPTS 0+#endif++static int do_write(int fd, void* buf, size_t buf_size)+{+  ssize_t total_out = 0;+  while (total_out < (ssize_t) buf_size) {+    ssize_t out = send(fd,+                       buf + total_out,+                       buf_size - total_out,+                       SEND_OPTS);++    // Handle errors.+    if (out < 0) {+      if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {+        continue;+      } else {+        return -1;+      }+    }++    total_out += out;+  }++  return 0;+}++static int do_read(int fd, void* buf, size_t buf_size)+{+  ssize_t total_in = 0;+  while (total_in < (ssize_t) buf_size) {+    ssize_t in = read(fd,+                      buf + total_in,+                      buf_size - total_in);++    // Handle errors.+    if (in < 0) {+      if (errno == EAGAIN || errno == EINTR) {+        continue;+      } else {+        return -1;+      }+    }++    // Handle EOF.+    if (in == 0) {+      return -1;+    }++    total_in += in;+  }++  return 0;+}+  +int voyeur_write_msg_type(int fd, voyeur_msg_type val)+{+  return do_write(fd, (void*) &val, sizeof(voyeur_msg_type));+}++int voyeur_read_msg_type(int fd, voyeur_msg_type* val)+{+  return do_read(fd, (void*) val, sizeof(voyeur_msg_type));+}++int voyeur_write_event_type(int fd, voyeur_event_type val)+{+  return do_write(fd, (void*) &val, sizeof(voyeur_event_type));+}++int voyeur_read_event_type(int fd, voyeur_event_type* val)+{+  return do_read(fd, (void*) val, sizeof(voyeur_event_type));+}++int voyeur_write_byte(int fd, char val)+{+  return do_write(fd, (void*) &val, sizeof(char));+}++int voyeur_read_byte(int fd, char* val)+{+  return do_read(fd, (void*) val, sizeof(char));+}++int voyeur_write_int(int fd, int val)+{+  return do_write(fd, (void*) &val, sizeof(int));+}++int voyeur_read_int(int fd, int* val)+{+  return do_read(fd, (void*) val, sizeof(int));+}++int voyeur_write_size(int fd, size_t val)+{+  return do_write(fd, (void*) &val, sizeof(size_t));+}++int voyeur_read_size(int fd, size_t* val)+{+  return do_read(fd, (void*) val, sizeof(size_t));+}++int voyeur_write_pid(int fd, pid_t val)+{+  return do_write(fd, (void*) &val, sizeof(pid_t));+}++int voyeur_read_pid(int fd, pid_t* val)+{+  return do_read(fd, (void*) val, sizeof(pid_t));+}++int voyeur_write_string(int fd, const char* val, size_t len)+{+  if (val == NULL) {+    len = 0;+  } else if (len == 0) {+    len = strnlen(val, VOYEUR_MAX_STRLEN);+  }++  if (voyeur_write_size(fd, len) < 0) {+    return -1;+  }+  +  return do_write(fd, (void*) val, len);+}++int voyeur_read_string(int fd, char** val, size_t maxlen)+{+  size_t len;+  if (voyeur_read_size(fd, &len) < 0) {+    return -1;+  }++  if (maxlen == 0) {+    *val = malloc(len + 1);+  } else if (maxlen <= len) {+    SHOULD_NOT_REACH("libvoyeur: string buffer of size %zu "+                     "is too small for string of length %zu\n",+                     maxlen,+                     len);+    return -1;+  }+  +  if (do_read(fd, (void*) *val, len) < 0) {+    if (maxlen == 0) {+      free(*val);+    }+    return -1;+  }++  (*val)[len] = '\0';+  return 0;+}
+ libvoyeur/src/net.h view
@@ -0,0 +1,101 @@+#ifndef VOYEUR_NET_H+#define VOYEUR_NET_H++#include <stddef.h>+#include <sys/un.h>+#include <unistd.h>++#include "event.h"++//////////////////////////////////////////////////+// Sock creation and connection.+//////////////////////////////////////////////////++// Creates a socket and starts listening on it. The caller must+// provided an uninitialized sockaddr_un struct. After+// create_server_socket returns, the socket path will be available+// in sockinfo->sun_path.+int voyeur_create_server_socket(struct sockaddr_un* sockinfo);++// Creates a socket and connects to the provided socket path on it.+int voyeur_create_client_socket(const char* sockpath);++// Closes the provided socket (or pipe) safely.+void voyeur_close_socket(int fd);+++//////////////////////////////////////////////////+// Message serialization.+//////////////////////////////////////////////////++// Every libvoyeur network message starts with a message type.++typedef enum {+  VOYEUR_MSG_EVENT,+  VOYEUR_MSG_DONE+} voyeur_msg_type;++// Reader and writer for message types.+int voyeur_write_msg_type(int fd, voyeur_msg_type val);+int voyeur_read_msg_type(int fd, voyeur_msg_type* val);++//////////////////////////////////////////////////+// Event serialization.+//////////////////////////////////////////////////++// All libvoyeur events consist of an event type followed by a+// sequence of bytes, integers, and strings particular to the event.+//+// A typical sequence of calls for a writer:+//   voyeur_write_msg_type(fd, VOYEUR_MSG_EVENT);+//   voyeur_write_event_type(fd, VOYEUR_EVENT_XXX);+//   voyeur_write_string(fd, file, strlen(file));+//   voyeur_write_int(fd, flags);+//+// A matching sequence of calls for a reader:+//   voyeur_read_msg_type(fd, &msgtype);+//   /* dispatch to handler for VOYEUR_MSG_EVENT */+//   voyeur_read_event_type(fd, &type);+//   /* dispatch to handler for VOYEUR_EVENT_XXX */+//   voyeur_read_string(fd, &file, &len);+//   voyeur_read_int(fd, &flags);+//+// Every read/write function returns 0 on success and -1 on error.++// Reader and writer for event types.+int voyeur_write_event_type(int fd, voyeur_event_type val);+int voyeur_read_event_type(int fd, voyeur_event_type* val);++// Reader and writer for bytes.+int voyeur_write_byte(int fd, char val);+int voyeur_read_byte(int fd, char* val);++// Reader and writer for integers.+int voyeur_write_int(int fd, int val);+int voyeur_read_int(int fd, int* val);++// Reader and writer for size_t.+int voyeur_write_size(int fd, size_t val);+int voyeur_read_size(int fd, size_t* val);++// Reader and writer for pid_t.+int voyeur_write_pid(int fd, pid_t val);+int voyeur_read_pid(int fd, pid_t* val);++#define VOYEUR_MAX_STRLEN 4096++// Write a string. If 'len' is 0, the length is determined by calling+// strnlen(val, VOYEUR_MAX_STRLEN).+int voyeur_write_string(int fd, const char* val, size_t len);++// Read a string. If 'maxlen' is 0, voyeur_read_string will allocate a+// buffer large enough to hold the string, which the caller is+// responsible for freeing. Otherwise, voyeur_read_string will use the+// buffer provided by the caller.+//+// Note that if you use a maximum length when reading, you must make+// sure to use the same limit when writing. If there isn't enough+// space in the buffer, voyeur_read_string will report an error.+int voyeur_read_string(int fd, char** val, size_t maxlen);++#endif
+ libvoyeur/src/util.c view
@@ -0,0 +1,15 @@+#include <unistd.h>++#include "util.h"++extern void voyeur_log(const char* str);++void voyeur_request_debug(const char* reason)+{+  perror(reason);+  char keep_looping = 1;+  while (keep_looping) {+    printf("ATTACH DEBUGGER TO %u\n", (unsigned) getpid());+    sleep(10);+  }+}
+ libvoyeur/src/util.h view
@@ -0,0 +1,80 @@+#ifndef LIBVOYEUR_UTIL_H+#define LIBVOYEUR_UTIL_H++#include <stdio.h>++#define RETURN_ON_FAIL(_f, ...)                 \+  do {                                          \+    if (_f(__VA_ARGS__) < 0) {                  \+      perror(#_f);                              \+      return;                                   \+    }                                           \+  } while (0)++#define RETURN_ERROR_ON_FAIL(_f, ...)           \+  do {                                          \+    if (_f(__VA_ARGS__) < 0) {                  \+      perror(#_f);                              \+      return -1;                                \+    }                                           \+  } while (0)++#define ABORT_ON_FAIL(_f, ...)                  \+  do {                                          \+    if (_f(__VA_ARGS__) < 0) {                  \+      perror(#_f);                              \+      exit(EXIT_FAILURE);                       \+    }                                           \+  } while (0)++#ifdef DEBUG++#  define WARN_ON_FAIL(_f, ...)                 \+  do {                                          \+    if (_f(__VA_ARGS__) < 0) {                  \+      perror(#_f);                              \+    }                                           \+  } while (0)++#  define WARN_ON_FAIL_VALUE(_var, _err)        \+  do {                                          \+    if (_var < 0) {                             \+      perror(_err);                             \+      exit(EXIT_FAILURE);                       \+    }                                           \+  } while (0)++#  define ASSERT(_check, ...)                   \+  do {                                          \+    if (!(_check)) {                             \+      fprintf(stderr, __VA_ARGS__);             \+      exit(EXIT_FAILURE);                       \+    }                                           \+  } while (0)++#else++#  define WARN_ON_FAIL(...)+#  define WARN_ON_FAIL_VALUE(...)+#  define ASSERT(...)++#endif++#define SHOULD_NOT_REACH(...) ASSERT(-1, __VA_ARGS__)++inline void voyeur_log(const char* str)+{+#ifdef DEBUG+  fprintf(stderr, "%s", str);+  fflush(stderr);+#else+  // Do nothing.+#endif+}++// Enters an infinite loop and requests the user to attach a+// debugger. For debugging only.+void voyeur_request_debug(const char* reason);+++#endif
+ libvoyeur/src/voyeur-close.c view
@@ -0,0 +1,80 @@+#ifndef __APPLE__+#define _GNU_SOURCE+#endif++#include <fcntl.h>+#include <pthread.h>+#include <stdarg.h>+#include <stdint.h>+#include <stdio.h>+#include <stdlib.h>+#include <unistd.h>++#include "dyld.h"+#include "env.h"+#include "net.h"++typedef int (*close_fptr_t)(int);+VOYEUR_STATIC_DECLARE_NEXT(close_fptr_t, close)++static pthread_mutex_t voyeur_close_mutex = PTHREAD_MUTEX_INITIALIZER;+static char voyeur_close_initialized = 0;+static char voyeur_close_finalized = 0;+static uint8_t voyeur_close_opts = 0;+static int voyeur_close_sock = 0;++__attribute__((destructor)) void voyeur_cleanup_close()+{+  pthread_mutex_lock(&voyeur_close_mutex);++  voyeur_close_finalized = 1;++  if (voyeur_close_initialized) {+    if (voyeur_close_sock >= 0) {+      voyeur_write_msg_type(voyeur_close_sock, VOYEUR_MSG_DONE);+      voyeur_close_socket(voyeur_close_sock);+      voyeur_close_sock = -1;+    }+    +    voyeur_close_initialized = 0;+  }++  pthread_mutex_unlock(&voyeur_close_mutex);+}++int VOYEUR_FUNC(close)(int fildes)+{+  if (voyeur_close_finalized) {+    // We're in the process of shutting down, so just forward to the real close.+    return VOYEUR_CALL_NEXT(close, fildes);+  }++  pthread_mutex_lock(&voyeur_close_mutex);++  if (!voyeur_close_initialized) {+    const char* voyeur_close_sockpath = getenv("LIBVOYEUR_SOCKET");+    voyeur_close_opts = voyeur_decode_options(getenv("LIBVOYEUR_OPTS"),+                                              VOYEUR_EVENT_CLOSE);+    voyeur_close_sock = voyeur_create_client_socket(voyeur_close_sockpath);+    VOYEUR_LOOKUP_NEXT(close_fptr_t, close);+    voyeur_close_initialized = 1;+  }++  // Pass through the call to the real close.+  int retval = VOYEUR_CALL_NEXT(close, fildes);++  // Write the event to the socket.+  if (voyeur_close_sock >= 0) {+    voyeur_write_msg_type(voyeur_close_sock, VOYEUR_MSG_EVENT);+    voyeur_write_event_type(voyeur_close_sock, VOYEUR_EVENT_CLOSE);+    voyeur_write_int(voyeur_close_sock, fildes);+    voyeur_write_int(voyeur_close_sock, retval);+    voyeur_write_pid(voyeur_close_sock, getpid());+  }++  pthread_mutex_unlock(&voyeur_close_mutex);++  return retval;+}++VOYEUR_INTERPOSE(close)
+ libvoyeur/src/voyeur-exec.c view
@@ -0,0 +1,456 @@+#ifndef __APPLE__+#define _GNU_SOURCE+#endif++#include <pthread.h>+#include <spawn.h>+#include <stdarg.h>+#include <stdlib.h>+#include <stdio.h>+#include <unistd.h>++#include "dyld.h"+#include "env.h"+#include "net.h"+#include "util.h"+++//////////////////////////////////////////////////+// Shared code for all exec*() functions.+//////////////////////////////////////////////////++static void write_exec_event(int sock, uint8_t options, const char* path,+                             char* const argv[], char* const envp[],+                             pid_t pid, pid_t ppid)+{+  if (options & OBSERVE_EXEC_SILENT) {+    // We're just here to propagate libvoyeur instrumentation.+    return;+  }++  if (!(options & OBSERVE_EXEC_NOACCESS)) {+    // Make sure this exec() call could succeed before reporting the event.+    if (access(path, X_OK) < 0) {+      return;+    }+  }++  voyeur_write_msg_type(sock, VOYEUR_MSG_EVENT);+  voyeur_write_event_type(sock, VOYEUR_EVENT_EXEC);+  voyeur_write_string(sock, path, 0);++  int argc = 0;+  while (argv[argc]) {+    ++argc;+  }+  voyeur_write_int(sock, argc);+  for (int i = 0 ; i < argc ; ++i) {+    voyeur_write_string(sock, argv[i], 0);+  }++  if (options & OBSERVE_EXEC_ENV) {+    int envc = 0;+    while (envp[envc]) {+      ++envc;+    }+    voyeur_write_int(sock, envc);+    for (int i = 0 ; i < envc ; ++i) {+      voyeur_write_string(sock, envp[i], 0);+    }+  }++  if (options & OBSERVE_EXEC_PATH) {+    voyeur_write_string(sock, getenv("PATH"), 0);+  }++  if (options & OBSERVE_EXEC_CWD) {+    voyeur_write_string(sock, getcwd(NULL, 0), 0);+  }++  voyeur_write_pid(sock, pid);+  voyeur_write_pid(sock, ppid);+}+++//////////////////////////////////////////////////+// Replace vfork() with fork()+//////////////////////////////////////////////////++typedef pid_t (*vfork_fptr_t)();++pid_t VOYEUR_FUNC(vfork)(void)+{+    return fork();+}++VOYEUR_INTERPOSE(vfork)+++//////////////////////////////////////////////////+// execve+//////////////////////////////////////////////////++typedef int (*execve_fptr_t)(const char*, char* const[], char* const []);++int VOYEUR_FUNC(execve)(const char* path, char* const argv[], char* const envp[])+{+  // In the case of exec we don't bother caching anything, since exec+  // will wipe out this whole process image anyway.+  const char* libs = getenv("LIBVOYEUR_LIBS");+  const char* opts = getenv("LIBVOYEUR_OPTS");+  uint8_t options = voyeur_decode_options(opts, VOYEUR_EVENT_EXEC);+  const char* sockpath = getenv("LIBVOYEUR_SOCKET");++  // Write the event to the socket.+  int sock = voyeur_create_client_socket(sockpath);+  if (sock >= 0) {+    write_exec_event(sock, options, path, argv, envp, getpid(), getppid());++    // We might as well close the socket since there's no chance we'll+    // ever be called a second time by the same process. (Even if the+    // exec fails, generally the fork'd process will just bail.)+    voyeur_write_msg_type(sock, VOYEUR_MSG_DONE);+    voyeur_close_socket(sock);+  }++  // Add libvoyeur-specific environment variables. (We don't bother+  // freeing 'buf' since we need it until the execve call and we have+  // no way of freeing it after that.)+  void* buf;+  char** voyeur_envp =+    voyeur_augment_environment(envp, libs, opts, sockpath, &buf);++  // Pass through the call to the real execve.+  VOYEUR_DECLARE_NEXT(execve_fptr_t, execve);+  VOYEUR_LOOKUP_NEXT(execve_fptr_t, execve);+  return VOYEUR_CALL_NEXT(execve, path, argv, voyeur_envp);+}++VOYEUR_INTERPOSE(execve)+++//////////////////////////////////////////////////+// posix_spawn+//////////////////////////////////////////////////++typedef int (*posix_spawn_fptr_t)(pid_t* restrict,+                                  const char* restrict,+                                  const posix_spawn_file_actions_t*,+                                  const posix_spawnattr_t* restrict,+                                  char* const[restrict],+                                  char* const[restrict]);++static pthread_mutex_t voyeur_posix_spawn_mutex = PTHREAD_MUTEX_INITIALIZER;+static char voyeur_posix_spawn_initialized = 0;+static char* voyeur_posix_spawn_libs = NULL;+static char* voyeur_posix_spawn_opts = NULL;+static uint8_t voyeur_posix_spawn_options = 0;+static char* voyeur_posix_spawn_sockpath = NULL;+static int voyeur_posix_spawn_sock = 0;+VOYEUR_STATIC_DECLARE_NEXT(posix_spawn_fptr_t, posix_spawn);+VOYEUR_STATIC_DECLARE_NEXT(posix_spawn_fptr_t, posix_spawnp);++__attribute__((destructor)) void voyeur_cleanup_posix_spawn()+{+  pthread_mutex_lock(&voyeur_posix_spawn_mutex);++  if (voyeur_posix_spawn_initialized) {+    if (voyeur_posix_spawn_sock >= 0) {+      voyeur_write_msg_type(voyeur_posix_spawn_sock, VOYEUR_MSG_DONE);+      voyeur_close_socket(voyeur_posix_spawn_sock);+      voyeur_posix_spawn_sock = -1;+    }++    voyeur_posix_spawn_initialized = 0;+  }++  pthread_mutex_unlock(&voyeur_posix_spawn_mutex);+}++static void voyeur_init_posix_spawn()+{+  if (!voyeur_posix_spawn_initialized) {+    voyeur_posix_spawn_libs = getenv("LIBVOYEUR_LIBS");+    voyeur_posix_spawn_opts = getenv("LIBVOYEUR_OPTS");+    voyeur_posix_spawn_options =+      voyeur_decode_options(voyeur_posix_spawn_opts, VOYEUR_EVENT_EXEC);+    voyeur_posix_spawn_sockpath = getenv("LIBVOYEUR_SOCKET");+    voyeur_posix_spawn_sock =+      voyeur_create_client_socket(voyeur_posix_spawn_sockpath);+    VOYEUR_LOOKUP_NEXT(posix_spawn_fptr_t, posix_spawn);+    VOYEUR_LOOKUP_NEXT(posix_spawn_fptr_t, posix_spawnp);+    voyeur_posix_spawn_initialized = 1;+  }+}++int VOYEUR_FUNC(posix_spawn)(pid_t* pid,+                             const char* restrict path,+                             const posix_spawn_file_actions_t* file_actions,+                             const posix_spawnattr_t* restrict attrp,+                             char* const argv[restrict],+                             char* const envp[restrict])+{+  pthread_mutex_lock(&voyeur_posix_spawn_mutex);++  voyeur_init_posix_spawn();++  // Add libvoyeur-specific environment variables.+  void* buf;+  char** voyeur_envp =+    voyeur_augment_environment(envp,+                               voyeur_posix_spawn_libs,+                               voyeur_posix_spawn_opts,+                               voyeur_posix_spawn_sockpath,+                               &buf);++  // Pass through the call to the real posix_spawn.+  pid_t child_pid;+  int retval = VOYEUR_CALL_NEXT(posix_spawn, &child_pid, path,+                                file_actions, attrp,+                                argv, voyeur_envp);++  // Write the event to the socket.+  if (voyeur_posix_spawn_sock >= 0) {+    write_exec_event(voyeur_posix_spawn_sock,+                     voyeur_posix_spawn_options,+                     path, argv, envp,+                     child_pid, getpid());+  }++  pthread_mutex_unlock(&voyeur_posix_spawn_mutex);++  // Free the resources we allocated.+  free(voyeur_envp);+  free(buf);++  // It's legal to pass NULL for the pid argument, so double-check we+  // have somewhere to write the pid to before doing it.+  if (pid) {+    *pid = child_pid;+  }++  return retval;+}++VOYEUR_INTERPOSE(posix_spawn)++#ifdef __linux__++// On Linux we need to interpose on every exec variant separately because they+// don't just forward the work to execve and posix_spawn like OS X's versions do.++//////////////////////////////////////////////////+// execl, execle, execv+//////////////////////////////////////////////////++#define VARARGS_TO_ARGV(_start, _path, _argv, _envp)     \+  do {                                                   \+    va_list args;                                        \+                                                         \+    /* Get total number of entries. */                   \+    unsigned length = 0;                                 \+    va_start(args, (_start));                            \+    while (va_arg(args, char*)) {                        \+      ++length;                                          \+    }                                                    \+    va_end(args);                                        \+                                                         \+    /* Increase size to fit argv[0] and final NULL. */   \+    length += 2;                                         \+                                                         \+    /* Create an appropriately sized _argv. */           \+    _argv = malloc(sizeof(const char*) * length);        \+    _argv[0] = (char *) _path;                           \+                                                         \+    /* Copy. */                                          \+    unsigned index = 1;                                  \+    va_start(args, (_start));                            \+    while (index < length) {                             \+      _argv[index] = va_arg(args, char*);                \+      ++index;                                           \+    }                                                    \+                                                         \+    /* Pull out envp from one past the end of argv. */   \+    _envp = va_arg(args, char**);                        \+                                                         \+    va_end(args);                                        \+  } while(0)+++int VOYEUR_FUNC(execl)(const char* path, const char* start, ...)+{+  char** argv;+  char** dummy_envp;+  VARARGS_TO_ARGV(start, path, argv, dummy_envp);++  return execve(path, argv, environ);+}++VOYEUR_INTERPOSE(execl)+++int VOYEUR_FUNC(execle)(const char* path, const char* start, ...)+{+  char** argv;+  char** envp;+  VARARGS_TO_ARGV(start, path, argv, envp);++  return execve(path, argv, envp);+}++VOYEUR_INTERPOSE(execle)+++int VOYEUR_FUNC(execv)(const char* path, char* const argv[])+{+  return execve(path, argv, environ);+}++VOYEUR_INTERPOSE(execv)+++//////////////////////////////////////////////////+// execlp, execvp, execvpe+//////////////////////////////////////////////////++typedef int (*execvpe_fptr_t)(const char*, char* const [], char* const []);++int VOYEUR_FUNC(execlp)(const char* path, const char* start, ...)+{+  const char* libs = getenv("LIBVOYEUR_LIBS");+  const char* opts = getenv("LIBVOYEUR_OPTS");+  uint8_t options = voyeur_decode_options(opts, VOYEUR_EVENT_EXEC);+  const char* sockpath = getenv("LIBVOYEUR_SOCKET");++  char** argv;+  char** dummy_envp;+  VARARGS_TO_ARGV(start, path, argv, dummy_envp);+  char** envp = environ;++  int sock = voyeur_create_client_socket(sockpath);+  if (sock >= 0) {+    write_exec_event(sock, options, path, argv, envp, getpid(), getppid());+    voyeur_write_msg_type(sock, VOYEUR_MSG_DONE);+    voyeur_close_socket(sock);+  }++  void* buf;+  char** voyeur_envp =+    voyeur_augment_environment(envp, libs, opts, sockpath, &buf);++  // Need to pass through to execvpe since we need to provide an environment.+  VOYEUR_DECLARE_NEXT(execvpe_fptr_t, execvpe);+  VOYEUR_LOOKUP_NEXT(execvpe_fptr_t, execvpe);+  return VOYEUR_CALL_NEXT(execvpe, path, argv, voyeur_envp);+}++VOYEUR_INTERPOSE(execlp)+++int VOYEUR_FUNC(execvp)(const char* path, char* const argv[])+{+  const char* libs = getenv("LIBVOYEUR_LIBS");+  const char* opts = getenv("LIBVOYEUR_OPTS");+  uint8_t options = voyeur_decode_options(opts, VOYEUR_EVENT_EXEC);+  const char* sockpath = getenv("LIBVOYEUR_SOCKET");++  char** envp = environ;++  int sock = voyeur_create_client_socket(sockpath);+  if (sock >= 0) {+    write_exec_event(sock, options, path, argv, envp, getpid(), getppid());+    voyeur_write_msg_type(sock, VOYEUR_MSG_DONE);+    voyeur_close_socket(sock);+  }++  void* buf;+  char** voyeur_envp =+    voyeur_augment_environment(envp, libs, opts, sockpath, &buf);++  // Need to pass through to execvpe since we need to provide an environment.+  VOYEUR_DECLARE_NEXT(execvpe_fptr_t, execvpe);+  VOYEUR_LOOKUP_NEXT(execvpe_fptr_t, execvpe);+  return VOYEUR_CALL_NEXT(execvpe, path, argv, voyeur_envp);+}++VOYEUR_INTERPOSE(execvp)+++int VOYEUR_FUNC(execvpe)(const char* path, char* const argv[], char* const envp[])+{+  const char* libs = getenv("LIBVOYEUR_LIBS");+  const char* opts = getenv("LIBVOYEUR_OPTS");+  uint8_t options = voyeur_decode_options(opts, VOYEUR_EVENT_EXEC);+  const char* sockpath = getenv("LIBVOYEUR_SOCKET");++  int sock = voyeur_create_client_socket(sockpath);+  if (sock >= 0) {+    write_exec_event(sock, options, path, argv, envp, getpid(), getppid());+    voyeur_write_msg_type(sock, VOYEUR_MSG_DONE);+    voyeur_close_socket(sock);+  }++  void* buf;+  char** voyeur_envp =+    voyeur_augment_environment(envp, libs, opts, sockpath, &buf);++  // Pass through the call to the real execvpe.+  VOYEUR_DECLARE_NEXT(execvpe_fptr_t, execvpe);+  VOYEUR_LOOKUP_NEXT(execvpe_fptr_t, execvpe);+  return VOYEUR_CALL_NEXT(execvpe, path, argv, voyeur_envp);+}++VOYEUR_INTERPOSE(execvpe)+++//////////////////////////////////////////////////+// posix_spawnp+//////////////////////////////////////////////////++int VOYEUR_FUNC(posix_spawnp)(pid_t* pid,+                              const char* restrict path,+                              const posix_spawn_file_actions_t* file_actions,+                              const posix_spawnattr_t* restrict attrp,+                              char* const argv[restrict],+                              char* const envp[restrict])+{+  pthread_mutex_lock(&voyeur_posix_spawn_mutex);++  voyeur_init_posix_spawn();++  void* buf;+  char** voyeur_envp =+    voyeur_augment_environment(envp,+                               voyeur_posix_spawn_libs,+                               voyeur_posix_spawn_opts,+                               voyeur_posix_spawn_sockpath,+                               &buf);++  // Pass through the call to the real posix_spawnp.+  pid_t child_pid;+  int retval = VOYEUR_CALL_NEXT(posix_spawnp, &child_pid, path,+                                file_actions, attrp,+                                argv, voyeur_envp);++  if (voyeur_posix_spawn_sock >= 0) {+    write_exec_event(voyeur_posix_spawn_sock,+                     voyeur_posix_spawn_options,+                     path, argv, envp,+                     child_pid, getpid());+  }++  pthread_mutex_unlock(&voyeur_posix_spawn_mutex);++  free(voyeur_envp);+  free(buf);++  if (pid) {+    *pid = child_pid;+  }++  return retval;+}++VOYEUR_INTERPOSE(posix_spawnp)++#endif
+ libvoyeur/src/voyeur-exit.c view
@@ -0,0 +1,113 @@+#ifndef __APPLE__+#define _GNU_SOURCE+#endif++#include <stdio.h>+#include <unistd.h>++#include "dyld.h"+#include "env.h"+#include "net.h"++static char did_exit_already = 0;++//////////////////////////////////////////////////+// Shared code for all exit*() functions.+//////////////////////////////////////////////////++static void write_exit_event(int status)+{+  if (!did_exit_already) {+    did_exit_already = 1;++    // In the case of exit we don't bother caching anything; we are about to exit,+    // after all!+    const char* sockpath = getenv("LIBVOYEUR_SOCKET");+    int sock = voyeur_create_client_socket(sockpath);+    if (sock >= 0) {+      voyeur_write_msg_type(sock, VOYEUR_MSG_EVENT);+      voyeur_write_event_type(sock, VOYEUR_EVENT_EXIT);+      voyeur_write_int(sock, status);+      voyeur_write_pid(sock, getpid());+      voyeur_write_pid(sock, getppid());++      // We might as well close the socket since there's no chance we'll+      // ever be called a second time by the same process.+      voyeur_write_msg_type(sock, VOYEUR_MSG_DONE);+      voyeur_close_socket(sock);+    }+  }+}+++//////////////////////////////////////////////////+// exit*() variants.+//////////////////////////////////////////////////++// There are a lot of variants here because none of them seem to be perfectly+// reliable, especially on Linux. We are still likely to miss the case where a+// process gets signaled, unfortunately.++typedef void (*exit_fptr_t)(int);++void VOYEUR_FUNC(exit)(int status)+{+  write_exit_event(status);++  // Pass through the call to the real exit.+  VOYEUR_DECLARE_NEXT(exit_fptr_t, exit);+  VOYEUR_LOOKUP_NEXT(exit_fptr_t, exit);+  return VOYEUR_CALL_NEXT(exit, status);+}++VOYEUR_INTERPOSE(exit)++void VOYEUR_FUNC(_exit)(int status)+{+  write_exit_event(status);++  // Pass through the call to the real _exit.+  VOYEUR_DECLARE_NEXT(exit_fptr_t, _exit);+  VOYEUR_LOOKUP_NEXT(exit_fptr_t, _exit);+  return VOYEUR_CALL_NEXT(_exit, status);+}++VOYEUR_INTERPOSE(_exit)++void VOYEUR_FUNC(_Exit)(int status)+{+  write_exit_event(status);++  // Pass through the call to the real _Exit.+  VOYEUR_DECLARE_NEXT(exit_fptr_t, _Exit);+  VOYEUR_LOOKUP_NEXT(exit_fptr_t, _Exit);+  return VOYEUR_CALL_NEXT(_Exit, status);+}++VOYEUR_INTERPOSE(_Exit)++#ifdef __linux__++void VOYEUR_FUNC(exit_group)(int status)+{+  write_exit_event(status);++  // Pass through the call to the real exit_group.+  VOYEUR_DECLARE_NEXT(exit_fptr_t, exit_group);+  VOYEUR_LOOKUP_NEXT(exit_fptr_t, exit_group);+  return VOYEUR_CALL_NEXT(exit_group, status);+}++VOYEUR_INTERPOSE(exit_group)++static void voyeur_exit_handler(int status, void* unused)+{+  write_exit_event(status);+}++__attribute__((constructor)) void voyeur_init_exit_handler()+{+  on_exit(voyeur_exit_handler, 0);+}++#endif
+ libvoyeur/src/voyeur-open.c view
@@ -0,0 +1,103 @@+#ifndef __APPLE__+#define _GNU_SOURCE+#endif++#include <fcntl.h>+#include <pthread.h>+#include <stdarg.h>+#include <stdint.h>+#include <stdlib.h>+#include <unistd.h>++#include "dyld.h"+#include "env.h"+#include "net.h"++typedef int (*open_fptr_t)(const char*, int, ...);+VOYEUR_STATIC_DECLARE_NEXT(open_fptr_t, open)++static pthread_mutex_t voyeur_open_mutex = PTHREAD_MUTEX_INITIALIZER;+static char voyeur_open_initialized = 0;+static uint8_t voyeur_open_opts = 0;+static int voyeur_open_sock = 0;++__attribute__((destructor)) void voyeur_cleanup_open()+{+  pthread_mutex_lock(&voyeur_open_mutex);++  if (voyeur_open_initialized) {+    if (voyeur_open_sock >= 0) {+      voyeur_write_msg_type(voyeur_open_sock, VOYEUR_MSG_DONE);+      voyeur_close_socket(voyeur_open_sock);+      voyeur_open_sock = -1;+    }++    voyeur_open_initialized = 0;+  }++  pthread_mutex_unlock(&voyeur_open_mutex);+}++int VOYEUR_FUNC(open)(const char* path, int oflag, ...)+{+  pthread_mutex_lock(&voyeur_open_mutex);+  +  if (!voyeur_open_initialized) {+    const char* voyeur_open_sockpath = getenv("LIBVOYEUR_SOCKET");+    voyeur_open_opts = voyeur_decode_options(getenv("LIBVOYEUR_OPTS"),+                                             VOYEUR_EVENT_OPEN);+    voyeur_open_sock = voyeur_create_client_socket(voyeur_open_sockpath);+    VOYEUR_LOOKUP_NEXT(open_fptr_t, open);+    voyeur_open_initialized = 1;+  }++  // Extract the mode argument if necessary.+  mode_t mode;+  if (oflag & O_CREAT) {+    va_list args;+    va_start(args, oflag);+    // Note that 'int' is used instead of 'mode_t' due to warnings+    // that mode_t, being in reality a short on OS X, is of promotable+    // type, which is verboten for va_arg.+    mode = va_arg(args, int);+    va_end(args);+  }+  +  // Pass through the call to the real open.+  int retval;+  if (oflag & O_CREAT) {+    retval = VOYEUR_CALL_NEXT(open, path, oflag, mode);+  } else {+    retval = VOYEUR_CALL_NEXT(open, path, oflag);+  }++  // Write the event to the socket.+  if (voyeur_open_sock >= 0) {+    voyeur_write_msg_type(voyeur_open_sock, VOYEUR_MSG_EVENT);+    voyeur_write_event_type(voyeur_open_sock, VOYEUR_EVENT_OPEN);+    voyeur_write_string(voyeur_open_sock, path, 0);+    voyeur_write_int(voyeur_open_sock, oflag);++    if (oflag & O_CREAT) {+      voyeur_write_int(voyeur_open_sock, (int) mode);+    } else {+      voyeur_write_int(voyeur_open_sock, 0);+    }++    voyeur_write_int(voyeur_open_sock, retval);++    if (voyeur_open_opts & OBSERVE_OPEN_CWD) {+      char* cwd = getcwd(NULL, 0);+      voyeur_write_string(voyeur_open_sock, cwd, 0);+      free(cwd);+    }++    voyeur_write_pid(voyeur_open_sock, getpid());+  }++  pthread_mutex_unlock(&voyeur_open_mutex);++  return retval;+}++VOYEUR_INTERPOSE(open)
+ libvoyeur/src/voyeur.c view
@@ -0,0 +1,270 @@+#ifdef __linux__+#define _GNU_SOURCE+#endif++#include <errno.h>+#include <pthread.h>+#include <spawn.h>+#include <stdlib.h>+#include <stdio.h>+#include <string.h>+#include <sys/socket.h>+#include <sys/wait.h>+#include <sys/un.h>+#include <unistd.h>++#ifdef __linux__+#include <bsd/bsd.h>+#endif++#include <voyeur.h>+#include "env.h"+#include "event.h"+#include "net.h"+#include "util.h"++typedef struct {+  struct sockaddr_un sockinfo;+  int server_sock;+  void* env_buf;+} server_state;++voyeur_context_t voyeur_context_create()+{+  voyeur_context* ctx = calloc(1, sizeof(voyeur_context));+  return (voyeur_context_t) ctx;+}++void voyeur_context_destroy(voyeur_context_t ctx)+{+  voyeur_context* context = (voyeur_context*) ctx;++  if (context->resource_path) {+    free(context->resource_path);+  }++  if (context->server_state) {+    server_state* state = (server_state*) context->server_state;+    free(state->env_buf);+    free(state);+  }+  +  free(context);+}++void voyeur_set_resource_path(voyeur_context_t ctx,+                              const char* path)+{+  voyeur_context* context = (voyeur_context*) ctx;+  context->resource_path = calloc(1, strnlen(path, 4096));+  strlcat(context->resource_path, path, 4096);+}++typedef struct {+  pid_t child_pid;+  int child_pipe_input;+} waitpid_thread_arg;++static void* waitpid_thread(void* arg_ptr)+{+  waitpid_thread_arg* arg = (waitpid_thread_arg*) arg_ptr;+  +  int status;+  waitpid(arg->child_pid, &status, 0);++  voyeur_write_int(arg->child_pipe_input, status);++  voyeur_close_socket(arg->child_pipe_input);+  free(arg);++  return NULL;+}++static int start_waitpid_thread(pid_t child_pid)+{+  // Create a pipe that will be used to announce the termination of+  // the child process.+  int waitpid_pipe[2];+  ABORT_ON_FAIL(pipe, waitpid_pipe);++  waitpid_thread_arg* arg = malloc(sizeof(waitpid_thread_arg));+  arg->child_pid = child_pid;+  arg->child_pipe_input = waitpid_pipe[1];+  +  pthread_t thread;+  pthread_create(&thread, NULL, waitpid_thread, (void*) arg);+  pthread_detach(thread);++  return waitpid_pipe[0];+}++static int accept_connection(int server_sock)+{+  struct sockaddr_un client_info;+  socklen_t client_info_len = sizeof(struct sockaddr_un);++  int client_sock =+    accept(server_sock, (struct sockaddr *) &client_info, &client_info_len);+  WARN_ON_FAIL_VALUE(client_sock, "accept");+  +  return client_sock;+}++static int handle_message(voyeur_context* context, int sock)+{+  voyeur_msg_type msgtype;+  if (voyeur_read_msg_type(sock, &msgtype) < 0) {+    return -1;+  }++  if (msgtype == VOYEUR_MSG_DONE) {+    // The client is done sending messages on this socket.+    return -1;+  } else if (msgtype == VOYEUR_MSG_EVENT) {+    voyeur_event_type type;+    if (voyeur_read_event_type(sock, &type) < 0) {+      return -1;+    }++    // Got a voyeur event; dispatch to the appropriate handler.+    voyeur_handle_event(context, type, sock);+    return 0;+  } else {+    // Got an unknown message type.+    voyeur_log("Unknown message type\n");+    return -1;+  }+}++static int run_server(voyeur_context* context,+                      int server_sock,+                      int child_pipe_output)+{+  fd_set active_fd_set, read_fd_set, error_fd_set;+  FD_ZERO(&active_fd_set);+  FD_SET(server_sock, &active_fd_set);+  FD_SET(child_pipe_output, &active_fd_set);+  +  int child_exited = 0;+  int child_status = 0;+  +  while (!child_exited) {+    // Block until input arrives.+    read_fd_set = active_fd_set;+    error_fd_set = active_fd_set;+    if (select(FD_SETSIZE, &read_fd_set, NULL, &error_fd_set, NULL) < 0) {+      if (errno == EAGAIN || errno == EINTR) {+        continue;  // This is a temporary error.+      } else {+        perror("select");+        break;     // This is unrecoverable.+      }+    }++    for (int fd = 0 ; fd < FD_SETSIZE ; ++fd) {+      if (FD_ISSET(fd, &error_fd_set)) {+        voyeur_log("Closed file descriptor due to error\n");+        voyeur_close_socket(fd);+        FD_CLR(fd, &active_fd_set);+      } else if (FD_ISSET(fd, &read_fd_set)) {+        if (fd == server_sock) {+          int client_sock = accept_connection(server_sock);+          if (client_sock >= 0) {+            FD_SET(client_sock, &active_fd_set);+          }+        } else if (fd == child_pipe_output) {+          child_exited = 1;+          voyeur_read_int(fd, &child_status);+          voyeur_close_socket(fd);+          FD_CLR(fd, &active_fd_set);+        } else if (handle_message(context, fd) < 0) {+          voyeur_close_socket(fd);+          FD_CLR(fd, &active_fd_set);+        }+      }+    }+  }++  voyeur_close_socket(server_sock);++  // Clean up any stragglers.+  for (int fd = 0 ; fd < FD_SETSIZE ; ++fd) {+    if (FD_ISSET(fd, &active_fd_set)) {+      voyeur_close_socket(fd);+    }+  }+  +  if (WIFEXITED(child_status)) {+    return WEXITSTATUS(child_status);+  } else {+    fprintf(stderr, "libvoyeur: child process did not terminate normally\n");+    return -1;+  }+}++char** voyeur_prepare(voyeur_context_t ctx, char* const envp[])+{+  voyeur_context* context = (voyeur_context*) ctx;+  server_state* state = calloc(1, sizeof(server_state));+  context->server_state = (void*) state;++  // Prepare the server. We need to do this in advance both to avoid+  // racing and so that we can include the socket path in the+  // environment variables.+  state->server_sock = voyeur_create_server_socket(&state->sockinfo);+  if (state->server_sock < 0) {+    return NULL;+  }+  +  // Add libvoyeur-specific environment variables.+  char* libs = voyeur_requested_libs(context);+  char* opts = voyeur_requested_opts(context);+  char** voyeur_envp = voyeur_augment_environment(envp, libs, opts,+                                                  state->sockinfo.sun_path,+                                                  &state->env_buf);++  return voyeur_envp;+}++int voyeur_start(voyeur_context_t ctx, pid_t child_pid)+{+  voyeur_context* context = (voyeur_context*) ctx;+  server_state* state = (server_state*) context->server_state;++  // Run the server.+  int child_pipe_output = start_waitpid_thread(child_pid);+  int res = run_server(context,+                       state->server_sock,+                       child_pipe_output);++  // Clean up the socket file.+  WARN_ON_FAIL(unlink, state->sockinfo.sun_path);+  char* last_slash = strrchr(state->sockinfo.sun_path, '/');+  if (last_slash) {+    *last_slash = '\0';+  }+  WARN_ON_FAIL(rmdir, state->sockinfo.sun_path);++  return res;+}++int voyeur_exec(voyeur_context_t ctx,+                const char* path,+                char* const argv[],+                char* const envp[])+{+  char** voyeur_envp = voyeur_prepare(ctx, envp);+  if (!voyeur_envp) {+    return -1;+  }+  +  pid_t child_pid;+  if (posix_spawnp(&child_pid, path, NULL, NULL, argv, voyeur_envp) != 0) {+    free(voyeur_envp);+    return -1;+  }+  +  int retval = voyeur_start(ctx, child_pid);+  free(voyeur_envp);+  return retval;+}
+ src/System/Process/Voyeur.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE TypeSynonymInstances #-}++-- | This package provides bindings to libvoyeur, a library for+-- observing the private activity of processes. Libvoyeur observes+-- a child process and all of its descendants, so it works even+-- when the child process calls out to other processes to do the+-- actual work.+--+-- To observe a process, use 'withVoyeur' to create an+-- 'FFI.VoyeurContext', then register handlers for the events you want+-- to observe using functions like 'FFI.observeExec'. When you've+-- set up all your handlers, use 'FFI.prepareEnvironment' to create a+-- special environment that will inject libvoyeur code into the+-- child process, and pass that environment to a function like+-- 'System.Process.runProcess'. Finally, pass the resulting 'ProcessHandle'+-- or 'ProcessID' to 'startObserving', and your handlers will be+-- called as events happen.+--+-- A simple function that prints a message every time a child+-- process opened a file might look like this:+--+-- > withVoyeur $ \ctx -> do+-- >   -- Set up a handler.+-- >   observeOpen ctx defaultOpenFlags $+-- >     \path _ _ _ _ pid -> putStrLn $ show pid ++ " opened " ++ show path+-- >+-- >   -- Set up the environment.+-- >   curEnv <- getEnvironment+-- >   newEnv <- prepareEnvironment ctx+-- >   +-- >   when (isJust newEnv) $ do+-- >     -- Start the child process.+-- >     handle <- runProcess program args Nothing newEnv Nothing Nothing Nothing+-- >+-- >     -- Observe it! startObserving only returns when the child process+-- >     -- exits, so we don't need to wait.+-- >     void $ startObserving ctx handle+--+-- A larger example program is included with the source code to this package.+module System.Process.Voyeur+(+-- * Observing a process+  withVoyeur+, FFI.prepareEnvironment+, startObserving++-- * Observing 'exec*' calls+, FFI.ObserveExecFlags(..)+, FFI.defaultExecFlags+, FFI.ObserveExecHandler+, FFI.observeExec++-- * Observing \'exit\' calls+, FFI.ObserveExitHandler+, FFI.observeExit++-- * Observing \'open\' calls+, FFI.ObserveOpenFlags(..)+, FFI.defaultOpenFlags+, FFI.ObserveOpenHandler+, FFI.observeOpen++-- * Observing \'close\' calls+, FFI.ObserveCloseHandler+, FFI.observeClose++-- * Types+, FFI.VoyeurContext+, HasPid+) where++import Control.Concurrent.MVar (readMVar)+import Control.Exception (bracket)+import System.Exit (ExitCode(..))+import System.Posix.Types (ProcessID)+import System.Process.Internals (ProcessHandle(..), ProcessHandle__(..))++import Paths_voyeur (getDataFileName)+import qualified System.Process.Voyeur.FFI as FFI++-- | Creates a 'FFI.VoyeurContext' and runs an IO action that observes+-- a process using it.+withVoyeur :: (FFI.VoyeurContext -> IO a) -> IO a+withVoyeur = bracket initContext FFI.destroyContext+ where+   initContext = do+     c <- FFI.createContext+     rPath <- getDataFileName $ "/"+     FFI.setResourcePath c rPath+     return c++-- | The class of values that contain a 'ProcessID'. This is used to+-- abstract over the different representations of a process used by+-- the various process libraries.+class HasPid a where+  toPid :: a -> IO (Maybe ProcessID)++instance HasPid ProcessID where+  toPid = return . Just++-- Unfortunately, we have to reach into the internals of+-- System.Process for this one.++#if MIN_VERSION_process(1,2,0)++instance HasPid ProcessHandle where+  toPid (ProcessHandle m _) = do+    p <- readMVar m+    case p of+      (OpenHandle pid) -> return $ Just pid+      _                -> return Nothing+    +#else++instance HasPid ProcessHandle where+  toPid (ProcessHandle m) = do+    p <- readMVar m+    case p of+      (OpenHandle pid) -> return $ Just pid+      _                -> return Nothing++#endif++-- | Start observing a child process. Your handlers will be called+-- while the process runs. Note that no handlers will be called if+-- you didn't start the process with an environment produced by+-- 'FFI.prepareEnvironment'.+--+-- When the child process exits, 'startObserving' will terminate the+-- server component of libvoyeur and return. This means that+-- 'startObserving' implicitly waits for the child process, so you+-- don't need to do this on your own.+startObserving :: HasPid a+               => FFI.VoyeurContext  -- ^ The context.+               -> a                  -- ^ The child process to observe.+               -> IO ExitCode        -- ^ The exit status of the child process.+startObserving c p = do+  mayPid <- toPid p+  case mayPid of+    Just pid -> FFI.startObserving c pid+    Nothing  -> return $ ExitFailure (-1)
+ src/System/Process/Voyeur/FFI.hs view
@@ -0,0 +1,308 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE RecordWildCards #-}++module System.Process.Voyeur.FFI+( VoyeurContext+, createContext+, destroyContext+, ObserveExecFlags(..)+, defaultExecFlags+, ObserveExecHandler+, observeExec+, ObserveExitHandler+, observeExit+, ObserveOpenFlags(..)+, defaultOpenFlags+, ObserveOpenHandler+, observeOpen+, ObserveCloseHandler+, observeClose+, setResourcePath+, prepareEnvironment+, startObserving+) where++import Control.Applicative+import Control.Exception+import Data.Bits+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BC+import Data.Word+import Foreign+import Foreign.C.String+import Foreign.C.Types+import System.Exit+import System.Posix.Types++--------------------------------------------------+-- Creating and destroying libvoyeur contexts.+--------------------------------------------------++-- | The context libvoyeur uses to store its state.+newtype VoyeurContext = VoyeurContext { unVoyeurContext :: Ptr () }++foreign import ccall unsafe "voyeur.h voyeur_context_create"+  voyeur_context_create :: IO (Ptr ())++createContext :: IO VoyeurContext+createContext = VoyeurContext <$> voyeur_context_create++foreign import ccall unsafe "voyeur.h voyeur_context_destroy"+  voyeur_context_destroy :: Ptr () -> IO ()++destroyContext :: VoyeurContext -> IO ()+destroyContext = voyeur_context_destroy . unVoyeurContext+++--------------------------------------------------+-- Registering callbacks for particular events.+--------------------------------------------------++-- | Flags for observing 'exec*' calls.+data ObserveExecFlags = ObserveExecFlags+  { observeExecCWD      :: !Bool  -- ^ True if you want the current working directory.+  , observeExecEnv      :: !Bool  -- ^ True if you want the environment. (Potentially slow.)+  , observeExecPath     :: !Bool  -- ^ True if you want the value of 'PATH'.+  , observeExecNoAccess :: !Bool  -- ^ True if you want to see calls that+                                  --   fail due to access restrictions.+  } deriving (Eq, Show)++-- | Default flags which observe the minimum amount of information.+defaultExecFlags :: ObserveExecFlags+defaultExecFlags = ObserveExecFlags False False False False++type ExecCallback = CString -> Ptr CString -> Ptr CString -> CString+                 -> CString -> CPid -> CPid -> Ptr () -> IO ()++foreign import ccall "wrapper"+  wrapExecCallback :: ExecCallback -> IO (FunPtr ExecCallback)++foreign import ccall unsafe "voyeur.h voyeur_observe_exec"+  voyeur_observe_exec :: Ptr () -> Word8 -> FunPtr ExecCallback -> Ptr () -> IO ()+                         +-- | A handler for 'exec*' calls.+type ObserveExecHandler = BS.ByteString                     -- ^ The file being executed.+                       -> [BS.ByteString]                   -- ^ The arguments.+                       -> [(BS.ByteString, BS.ByteString)]  -- ^ The environment (if requested).+                       -> BS.ByteString                     -- ^ The value of 'PATH' (if requested).+                       -> BS.ByteString                     -- ^ The working directory+                                                            --   (if requested).+                       -> ProcessID                         -- ^ The new process ID.+                       -> ProcessID                         -- ^ The parent process ID.+                       -> IO ()++-- | Observe calls to the 'exec*' and 'posix_spawn*' families of functions.+observeExec :: VoyeurContext        -- ^ The context.+            -> ObserveExecFlags     -- ^ Flags controlling what will be observed.+            -> ObserveExecHandler   -- ^ A handler for observed 'exec*' events.+            -> IO ()+observeExec c (ObserveExecFlags {..}) h = do+  let flags =  asBit 0 observeExecCWD+           .|. asBit 1 observeExecEnv+           .|. asBit 2 observeExecPath+           .|. asBit 3 observeExecNoAccess++  h' <- wrapExecCallback $ \file argv envp path cwd pid ppid _ -> do+    file' <- safePackCString file+    argv' <- packCStringArray argv+    envp' <- packCStringArray envp+    path' <- safePackCString path+    cwd'  <- safePackCString cwd+    h file' argv' (map bsEnvSplit envp') path' cwd' pid ppid++  voyeur_observe_exec (unVoyeurContext c) flags h' nullPtr+    +-- Observing exit() calls.+type ExitCallback = CInt -> CPid -> CPid -> Ptr () -> IO ()++foreign import ccall "wrapper"+  wrapExitCallback :: ExitCallback -> IO (FunPtr ExitCallback)++foreign import ccall unsafe "voyeur.h voyeur_observe_exit"+  voyeur_observe_exit :: Ptr () -> Word8 -> FunPtr ExitCallback -> Ptr () -> IO ()+                         +-- | A handler for \'exit\' calls.+type ObserveExitHandler = ExitCode   -- ^ The exit status.+                       -> ProcessID  -- ^ The process ID of the exiting process.+                       -> ProcessID  -- ^ The parent process ID of the exiting process.+                       -> IO ()++-- | Observe calls to \'exit\'.+observeExit :: VoyeurContext       -- ^ The context.+            -> ObserveExitHandler  -- ^ A handler for observed \'exit\' events.+            -> IO ()+observeExit c h = do+  h' <- wrapExitCallback $ \status pid ppid _ ->+    h (asExitCode status) pid ppid++  voyeur_observe_exit (unVoyeurContext c) 0 h' nullPtr++-- | Flags for observing \'open\' calls.+data ObserveOpenFlags = ObserveOpenFlags+  { observeOpenCWD :: !Bool  -- ^ True if you want the current working directory.+  } deriving (Eq, Show)++-- | Default flags which observe the minimum amount of information.+defaultOpenFlags :: ObserveOpenFlags+defaultOpenFlags = ObserveOpenFlags False++type OpenCallback = CString -> CInt -> CMode -> CString -> CInt -> CPid -> Ptr () -> IO ()++foreign import ccall "wrapper"+  wrapOpenCallback :: OpenCallback -> IO (FunPtr OpenCallback)++foreign import ccall unsafe "voyeur.h voyeur_observe_open"+  voyeur_observe_open :: Ptr () -> Word8 -> FunPtr OpenCallback -> Ptr () -> IO ()+                         +-- | A handler for \'open\' calls.+type ObserveOpenHandler = BS.ByteString  -- ^ The file being opened.+                       -> Int            -- ^ The flags used to open the file.+                       -> FileMode       -- ^ The mode. Only meaningful if O_CREAT+                                         --   was specified.+                       -> BS.ByteString  -- ^ The working directory (if requested).+                       -> Int            -- ^ The return value of \'open\'. May be a+                                         --   file descriptor or an error value.+                       -> ProcessID      -- ^ The process ID of the observed process.+                       -> IO ()++-- | Observe calls to \'open\'.+observeOpen :: VoyeurContext       -- ^ The context.+            -> ObserveOpenFlags    -- ^ Flags controlling what will be observed.+            -> ObserveOpenHandler  -- ^ A handler for observed \'open\' events.+            -> IO ()+observeOpen c (ObserveOpenFlags {..}) h = do+  let flags = (asBit 0 observeOpenCWD)++  h' <- wrapOpenCallback $ \path oflag mode cwd retval pid _ -> do+    path' <- safePackCString path+    cwd' <- safePackCString cwd+    h path' (fromIntegral oflag) mode cwd' (fromIntegral retval) pid++  voyeur_observe_open (unVoyeurContext c) flags h' nullPtr++-- Observing close() calls.+type CloseCallback = CInt -> CInt -> CPid -> Ptr () -> IO ()++foreign import ccall "wrapper"+  wrapCloseCallback :: CloseCallback -> IO (FunPtr CloseCallback)++foreign import ccall unsafe "voyeur.h voyeur_observe_close"+  voyeur_observe_close :: Ptr () -> Word8 -> FunPtr CloseCallback -> Ptr () -> IO ()+                         +-- | A handler for \'close\' calls.+type ObserveCloseHandler = Int        -- ^ The file descriptor being closed.+                        -> Int        -- ^ The return value of \'close\'.+                        -> ProcessID  -- ^ The process ID of the observed process.+                        -> IO ()++-- | Observe calls to \'close\'.+observeClose :: VoyeurContext        -- ^ The context.+             -> ObserveCloseHandler  -- ^ A handler for observed \'close\' events.+             -> IO ()+observeClose c h = do+  h' <- wrapCloseCallback $ \fd retval pid _ ->+    h (fromIntegral fd) (fromIntegral retval) pid++  voyeur_observe_close (unVoyeurContext c) 0 h' nullPtr+++--------------------------------------------------+-- Other context configuration options.+--------------------------------------------------++foreign import ccall unsafe "voyeur.h voyeur_set_resource_path"+  voyeur_set_resource_path :: Ptr () -> CString -> IO ()++setResourcePath :: VoyeurContext -> FilePath -> IO ()+setResourcePath c path = withCString path $ \cPath ->+                           voyeur_set_resource_path (unVoyeurContext c) cPath++--------------------------------------------------+-- Observing processes.+--------------------------------------------------++foreign import ccall unsafe "voyeur.h voyeur_prepare"+  voyeur_prepare :: Ptr () -> Ptr CString -> IO (Ptr CString)++-- | Prepares an environment for a child process you want to observe.+-- 'prepareEnvironment' starts the server component of libvoyeur and+-- adds or modifies environment variables as necessary to inject code+-- into the child process you're about to create and make sure it can+-- connect to the server.+--+-- Generally after calling 'prepareEnvironment', you'll want to start+-- the child process using the returned environment, and then call+-- 'System.Process.Voyeur.startObserving' to begin receiving events.+--+-- If something goes wrong, 'prepareEnvironment' will return 'Nothing'.+prepareEnvironment :: VoyeurContext                  -- ^ The context.+                   -> [(String, String)]             -- ^ The environment you want to use.+                   -> IO (Maybe [(String, String)])  -- ^ A modified version of that+                                                     --   environment, or 'Nothing' if something+                                                     --   went wrong.+prepareEnvironment c envp = bracket newCEnvp freeCEnvp $ \envp' -> do+    cEnvp <- withArray0 nullPtr envp' (voyeur_prepare (unVoyeurContext c))+    if cEnvp == nullPtr+       then return Nothing+       else do envp'' <- peekCStringArray cEnvp+               free cEnvp+               return . Just $ map envSplit envp''+  where+    newCEnvp = mapM (newCString . envJoin) envp+    freeCEnvp = mapM free++foreign import ccall safe "voyeur.h voyeur_start"+  voyeur_start :: Ptr () -> CPid -> IO CInt++startObserving :: VoyeurContext -> ProcessID -> IO ExitCode+startObserving c pid = asExitCode <$> voyeur_start (unVoyeurContext c) pid+++--------------------------------------------------+-- Utility functions.+--------------------------------------------------++asBit :: Int -> Bool -> Word8+asBit n True  = shiftL 1 n+asBit _ False = 0++asExitCode :: CInt -> ExitCode+asExitCode 0 = ExitSuccess+asExitCode s = ExitFailure (fromIntegral s)++safePackCString :: CString -> IO BS.ByteString+safePackCString s | s == nullPtr = return BS.empty+                  | otherwise    = BS.packCString s++safePeekCString :: CString -> IO String+safePeekCString s | s == nullPtr = return ""+                  | otherwise    = peekCString s++packCStringArray :: Ptr CString -> IO [BS.ByteString]+packCStringArray array+  | array == nullPtr = return []+  | otherwise        = do array' <- peekArray0 nullPtr array+                          mapM safePackCString array'++peekCStringArray :: Ptr CString -> IO [String]+peekCStringArray array+  | array == nullPtr = return []+  | otherwise        = do array' <- peekArray0 nullPtr array+                          mapM safePeekCString array'++envJoin :: (String, String) -> String+envJoin (k, v) = concat [k, "=", v]++envSplit :: String -> (String, String)+envSplit = safeTailSnd . break (== '=')++bsEnvSplit :: BS.ByteString -> (BS.ByteString, BS.ByteString)+bsEnvSplit = bsSafeTailSnd . BC.break (== '=')+  +safeTailSnd :: (String, String) -> (String, String)+safeTailSnd (a, b) | null b    = (a, b)+                   | otherwise = (a, tail b)++bsSafeTailSnd :: (BS.ByteString, BS.ByteString) -> (BS.ByteString, BS.ByteString)+bsSafeTailSnd (a, b) | BS.null b = (a, b)+                     | otherwise = (a, BS.tail b)
+ voyeur.cabal view
@@ -0,0 +1,54 @@+name:                voyeur+version:             0.1.0.0+synopsis:            Haskell bindings for libvoyeur+description:         Haskell bindings for libvoyeur, a library for observing the private+                     activities of child processes.+homepage:            https://github.com/sethfowler/hslibvoyeur+license:             BSD3+license-file:        LICENSE+author:              Seth Fowler <mark.seth.fowler@gmail.com>+maintainer:          Seth Fowler <mark.seth.fowler@gmail.com>+copyright:           Copyright (C) 2014 Seth Fowler+category:            System+build-type:          Custom+cabal-version:       >=1.18+extra-source-files: README.md,+                    changelog,++                    -- Bake in libvoyeur in its entirety.+                    libvoyeur/include/voyeur.h,+                    libvoyeur/LICENSE,+                    libvoyeur/Makefile,+                    libvoyeur/README.md,+                    libvoyeur/src/dyld.h,+                    libvoyeur/src/env.c,+                    libvoyeur/src/env.h,+                    libvoyeur/src/event.c,+                    libvoyeur/src/event.h,+                    libvoyeur/src/net.c,+                    libvoyeur/src/net.h,+                    libvoyeur/src/util.c,+                    libvoyeur/src/util.h,+                    libvoyeur/src/voyeur-close.c,+                    libvoyeur/src/voyeur-exec.c,+                    libvoyeur/src/voyeur-exit.c,+                    libvoyeur/src/voyeur-open.c,+                    libvoyeur/src/voyeur.c++source-repository head+  type: git+  location: https://github.com/sethfowler/hslibvoyeur.git+  tag: 0.1++library+  ghc-options:         -Wall+  exposed-modules:     System.Process.Voyeur+  other-modules:       System.Process.Voyeur.FFI,+                       Paths_voyeur+  build-depends:       base >=4.6 && <4.8,+                       bytestring >=0.10 && <0.11,+                       process >=1.1 && <1.3,+                       utf8-string >=0.3 && <0.4+  hs-source-dirs:      src+  default-language:    Haskell2010+  default-extensions:  CPP