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 +27/−0
- README.md +4/−0
- Setup.hs +159/−0
- changelog +3/−0
- libvoyeur/LICENSE +27/−0
- libvoyeur/Makefile +152/−0
- libvoyeur/README.md +38/−0
- libvoyeur/include/voyeur.h +205/−0
- libvoyeur/src/dyld.h +30/−0
- libvoyeur/src/env.c +110/−0
- libvoyeur/src/env.h +20/−0
- libvoyeur/src/event.c +307/−0
- libvoyeur/src/event.h +58/−0
- libvoyeur/src/net.c +253/−0
- libvoyeur/src/net.h +101/−0
- libvoyeur/src/util.c +15/−0
- libvoyeur/src/util.h +80/−0
- libvoyeur/src/voyeur-close.c +80/−0
- libvoyeur/src/voyeur-exec.c +456/−0
- libvoyeur/src/voyeur-exit.c +113/−0
- libvoyeur/src/voyeur-open.c +103/−0
- libvoyeur/src/voyeur.c +270/−0
- src/System/Process/Voyeur.hs +141/−0
- src/System/Process/Voyeur/FFI.hs +308/−0
- voyeur.cabal +54/−0
+ 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