/* -*- coding: utf-8 -*-
 
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 
// See https://llvm.org/LICENSE.txt for license information.
 
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
*/
 
 
 
/**
 
 * This file implements a shared library. This library can be pre-loaded by
 
 * the dynamic linker of the Operating System (OS). It implements a few function
 
 * related to process creation. By pre-load this library the executed process
 
 * uses these functions instead of those from the standard library.
 
 *
 
 * The idea here is to inject a logic before call the real methods. The logic is
 
 * to dump the call into a file. To call the real method this library is doing
 
 * the job of the dynamic linker.
 
 *
 
 * The only input for the log writing is about the destination directory.
 
 * This is passed as environment variable.
 
 */
 
 
 
// NOLINTNEXTLINE
 
#include "config.h"
 
 
 
#include <dlfcn.h>
 
#include <pthread.h>
 
#include <stdarg.h>
 
#include <stddef.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 
#include <unistd.h>
 
 
 
#if defined HAVE_POSIX_SPAWN || defined HAVE_POSIX_SPAWNP
 
#include <spawn.h>
 
#endif
 
 
 
#if defined HAVE_NSGETENVIRON
 
#include <crt_externs.h>
 
#else
 
extern char **environ;
 
#endif
 
 
 
#define ENV_OUTPUT "INTERCEPT_BUILD_TARGET_DIR"
 
#ifdef APPLE
 
#define ENV_FLAT "DYLD_FORCE_FLAT_NAMESPACE"
 
#define ENV_PRELOAD "DYLD_INSERT_LIBRARIES"
 
#define ENV_SIZE 3
 
#else
 
#define ENV_PRELOAD "LD_PRELOAD"
 
#define ENV_SIZE 2
 
#endif
 
 
 
#define DLSYM(TYPE_, VAR_, SYMBOL_)                                            \
 
  union {                                                                      \
 
    void *from;                                                                \
 
    TYPE_ to;                                                                  \
 
  } cast;                                                                      \
 
  if (0 == (cast.from = dlsym(RTLD_NEXT, SYMBOL_))) {                          \
 
    perror("bear: dlsym");                                                     \
 
    exit(EXIT_FAILURE);                                                        \
 
  }                                                                            \
 
  TYPE_ const VAR_ = cast.to;
 
 
 
typedef char const *bear_env_t[ENV_SIZE];
 
 
 
static int bear_capture_env_t(bear_env_t *env);
 
static int bear_reset_env_t(bear_env_t *env);
 
static void bear_release_env_t(bear_env_t *env);
 
static char const **bear_update_environment(char *const envp[],
 
                                            bear_env_t *env);
 
static char const **bear_update_environ(char const **in, char const *key,
 
                                        char const *value);
 
static char **bear_get_environment();
 
static void bear_report_call(char const *fun, char const *const argv[]);
 
static char const **bear_strings_build(char const *arg, va_list *ap);
 
static char const **bear_strings_copy(char const **const in);
 
static char const **bear_strings_append(char const **in, char const *e);
 
static size_t bear_strings_length(char const *const *in);
 
static void bear_strings_release(char const **);
 
 
 
static bear_env_t env_names = {ENV_OUTPUT, ENV_PRELOAD
 
#ifdef ENV_FLAT
 
                               ,
 
                               ENV_FLAT
 
#endif
 
};
 
 
 
static bear_env_t initial_env = {0, 0
 
#ifdef ENV_FLAT
 
                                 ,
 
                                 0
 
#endif
 
};
 
 
 
static int initialized = 0;
 
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 
 
 
static void on_load(void) __attribute__((constructor));
 
static void on_unload(void) __attribute__((destructor));
 
 
 
#ifdef HAVE_EXECVE
 
static int call_execve(const char *path, char *const argv[],
 
                       char *const envp[]);
 
#endif
 
#ifdef HAVE_EXECVP
 
static int call_execvp(const char *file, char *const argv[]);
 
#endif
 
#ifdef HAVE_EXECVPE
 
static int call_execvpe(const char *file, char *const argv[],
 
                        char *const envp[]);
 
#endif
 
#ifdef HAVE_EXECVP2
 
static int call_execvP(const char *file, const char *search_path,
 
                       char *const argv[]);
 
#endif
 
#ifdef HAVE_EXECT
 
static int call_exect(const char *path, char *const argv[], char *const envp[]);
 
#endif
 
#ifdef HAVE_POSIX_SPAWN
 
static int call_posix_spawn(pid_t *restrict 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]);
 
#endif
 
#ifdef HAVE_POSIX_SPAWNP
 
static int call_posix_spawnp(pid_t *restrict pid, const char *restrict file,
 
                             const posix_spawn_file_actions_t *file_actions,
 
                             const posix_spawnattr_t *restrict attrp,
 
                             char *const argv[restrict],
 
                             char *const envp[restrict]);
 
#endif
 
 
 
/* Initialization method to Captures the relevant environment variables.
 
 */
 
 
 
static void on_load(void) {
 
  pthread_mutex_lock(&mutex);
 
  if (!initialized)
 
    initialized = bear_capture_env_t(&initial_env);
 
  pthread_mutex_unlock(&mutex);
 
}
 
 
 
static void on_unload(void) {
 
  pthread_mutex_lock(&mutex);
 
  bear_release_env_t(&initial_env);
 
  initialized = 0;
 
  pthread_mutex_unlock(&mutex);
 
}
 
 
 
/* These are the methods we are try to hijack.
 
 */
 
 
 
#ifdef HAVE_EXECVE
 
int execve(const char *path, char *const argv[], char *const envp[]) {
 
  bear_report_call(__func__, (char const *const *)argv);
 
  return call_execve(path, argv, envp);
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECV
 
#ifndef HAVE_EXECVE
 
#error can not implement execv without execve
 
#endif
 
int execv(const char *path, char *const argv[]) {
 
  bear_report_call(__func__, (char const *const *)argv);
 
  char *const *envp = bear_get_environment();
 
  return call_execve(path, argv, envp);
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECVPE
 
int execvpe(const char *file, char *const argv[], char *const envp[]) {
 
  bear_report_call(__func__, (char const *const *)argv);
 
  return call_execvpe(file, argv, envp);
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECVP
 
int execvp(const char *file, char *const argv[]) {
 
  bear_report_call(__func__, (char const *const *)argv);
 
  return call_execvp(file, argv);
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECVP2
 
int execvP(const char *file, const char *search_path, char *const argv[]) {
 
  bear_report_call(__func__, (char const *const *)argv);
 
  return call_execvP(file, search_path, argv);
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECT
 
int exect(const char *path, char *const argv[], char *const envp[]) {
 
  bear_report_call(__func__, (char const *const *)argv);
 
  return call_exect(path, argv, envp);
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECL
 
#ifndef HAVE_EXECVE
 
#error can not implement execl without execve
 
#endif
 
int execl(const char *path, const char *arg, ...) {
 
  va_list args;
 
  char const **argv = bear_strings_build(arg, &args);
 
 
 
  bear_report_call(__func__, (char const *const *)argv);
 
  char *const *envp = bear_get_environment();
 
  int const result = call_execve(path, (char *const *)argv, envp);
 
 
 
  bear_strings_release(argv);
 
  return result;
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECLP
 
#ifndef HAVE_EXECVP
 
#error can not implement execlp without execvp
 
#endif
 
int execlp(const char *file, const char *arg, ...) {
 
  va_list args;
 
  char const **argv = bear_strings_build(arg, &args);
 
 
 
  bear_report_call(__func__, (char const *const *)argv);
 
  int const result = call_execvp(file, (char *const *)argv);
 
 
 
  bear_strings_release(argv);
 
  return result;
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECLE
 
#ifndef HAVE_EXECVE
 
#error can not implement execle without execve
 
#endif
 
// int execle(const char *path, const char *arg, ..., char * const envp[]);
 
int execle(const char *path, const char *arg, ...) {
 
  va_list args;
 
  char const **argv = bear_strings_build(arg, &args);
 
  char const **envp 
= va_arg(args
, char const **);  
 
 
  bear_report_call(__func__, (char const *const *)argv);
 
  int const result =
 
      call_execve(path, (char *const *)argv, (char *const *)envp);
 
 
 
  bear_strings_release(argv);
 
  return result;
 
}
 
#endif
 
 
 
#ifdef HAVE_POSIX_SPAWN
 
int posix_spawn(pid_t *restrict 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]) {
 
  bear_report_call(__func__, (char const *const *)argv);
 
  return call_posix_spawn(pid, path, file_actions, attrp, argv, envp);
 
}
 
#endif
 
 
 
#ifdef HAVE_POSIX_SPAWNP
 
int posix_spawnp(pid_t *restrict pid, const char *restrict file,
 
                 const posix_spawn_file_actions_t *file_actions,
 
                 const posix_spawnattr_t *restrict attrp,
 
                 char *const argv[restrict], char *const envp[restrict]) {
 
  bear_report_call(__func__, (char const *const *)argv);
 
  return call_posix_spawnp(pid, file, file_actions, attrp, argv, envp);
 
}
 
#endif
 
 
 
/* These are the methods which forward the call to the standard implementation.
 
 */
 
 
 
#ifdef HAVE_EXECVE
 
static int call_execve(const char *path, char *const argv[],
 
                       char *const envp[]) {
 
  typedef int (*func)(const char *, char *const *, char *const *);
 
 
 
  DLSYM(func, fp, "execve");
 
 
 
  char const **const menvp = bear_update_environment(envp, &initial_env);
 
  int const result = (*fp)(path, argv, (char *const *)menvp);
 
  bear_strings_release(menvp);
 
  return result;
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECVPE
 
static int call_execvpe(const char *file, char *const argv[],
 
                        char *const envp[]) {
 
  typedef int (*func)(const char *, char *const *, char *const *);
 
 
 
  DLSYM(func, fp, "execvpe");
 
 
 
  char const **const menvp = bear_update_environment(envp, &initial_env);
 
  int const result = (*fp)(file, argv, (char *const *)menvp);
 
  bear_strings_release(menvp);
 
  return result;
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECVP
 
static int call_execvp(const char *file, char *const argv[]) {
 
  typedef int (*func)(const char *file, char *const argv[]);
 
 
 
  DLSYM(func, fp, "execvp");
 
 
 
  bear_env_t current_env;
 
  bear_capture_env_t(¤t_env);
 
  bear_reset_env_t(&initial_env);
 
  int const result = (*fp)(file, argv);
 
  bear_reset_env_t(¤t_env);
 
  bear_release_env_t(¤t_env);
 
 
 
  return result;
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECVP2
 
static int call_execvP(const char *file, const char *search_path,
 
                       char *const argv[]) {
 
  typedef int (*func)(const char *, const char *, char *const *);
 
 
 
  DLSYM(func, fp, "execvP");
 
 
 
  bear_env_t current_env;
 
  bear_capture_env_t(¤t_env);
 
  bear_reset_env_t(&initial_env);
 
  int const result = (*fp)(file, search_path, argv);
 
  bear_reset_env_t(¤t_env);
 
  bear_release_env_t(¤t_env);
 
 
 
  return result;
 
}
 
#endif
 
 
 
#ifdef HAVE_EXECT
 
static int call_exect(const char *path, char *const argv[],
 
                      char *const envp[]) {
 
  typedef int (*func)(const char *, char *const *, char *const *);
 
 
 
  DLSYM(func, fp, "exect");
 
 
 
  char const **const menvp = bear_update_environment(envp, &initial_env);
 
  int const result = (*fp)(path, argv, (char *const *)menvp);
 
  bear_strings_release(menvp);
 
  return result;
 
}
 
#endif
 
 
 
#ifdef HAVE_POSIX_SPAWN
 
static int call_posix_spawn(pid_t *restrict 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]) {
 
  typedef int (*func)(pid_t *restrict, const char *restrict,
 
                      const posix_spawn_file_actions_t *,
 
                      const posix_spawnattr_t *restrict, char *const *restrict,
 
                      char *const *restrict);
 
 
 
  DLSYM(func, fp, "posix_spawn");
 
 
 
  char const **const menvp = bear_update_environment(envp, &initial_env);
 
  int const result =
 
      (*fp)(pid, path, file_actions, attrp, argv, (char *const *restrict)menvp);
 
  bear_strings_release(menvp);
 
  return result;
 
}
 
#endif
 
 
 
#ifdef HAVE_POSIX_SPAWNP
 
static int call_posix_spawnp(pid_t *restrict pid, const char *restrict file,
 
                             const posix_spawn_file_actions_t *file_actions,
 
                             const posix_spawnattr_t *restrict attrp,
 
                             char *const argv[restrict],
 
                             char *const envp[restrict]) {
 
  typedef int (*func)(pid_t *restrict, const char *restrict,
 
                      const posix_spawn_file_actions_t *,
 
                      const posix_spawnattr_t *restrict, char *const *restrict,
 
                      char *const *restrict);
 
 
 
  DLSYM(func, fp, "posix_spawnp");
 
 
 
  char const **const menvp = bear_update_environment(envp, &initial_env);
 
  int const result =
 
      (*fp)(pid, file, file_actions, attrp, argv, (char *const *restrict)menvp);
 
  bear_strings_release(menvp);
 
  return result;
 
}
 
#endif
 
 
 
/* this method is to write log about the process creation. */
 
 
 
static void bear_report_call(char const *fun, char const *const argv[]) {
 
  static int const GS = 0x1d;
 
  static int const RS = 0x1e;
 
  static int const US = 0x1f;
 
 
 
  if (!initialized)
 
    return;
 
 
 
  pthread_mutex_lock(&mutex);
 
  const char *cwd = getcwd(NULL, 0);
 
  if (0 == cwd) {
 
    pthread_mutex_unlock(&mutex);
 
  }
 
  char const *const out_dir = initial_env[0];
 
  size_t const path_max_length 
= strlen(out_dir
) + 32;  
  char filename[path_max_length];
 
  if (-1 ==
 
      snprintf(filename
, path_max_length
, "%s/%d.cmd", out_dir
, getpid
())) {  
    pthread_mutex_unlock(&mutex);
 
  }
 
  FILE 
*fd 
= fopen(filename
, "a+"); 
  if (0 == fd) {
 
    pthread_mutex_unlock(&mutex);
 
  }
 
  fprintf(fd
, "%d%c", getppid
(), RS
);  
  size_t const argc = bear_strings_length(argv);
 
  for (size_t it = 0; it < argc; ++it) {
 
  }
 
    pthread_mutex_unlock(&mutex);
 
  }
 
  pthread_mutex_unlock(&mutex);
 
}
 
 
 
/* update environment assure that children processes will copy the desired
 
 * behaviour */
 
 
 
static int bear_capture_env_t(bear_env_t *env) {
 
  int status = 1;
 
  for (size_t it = 0; it < ENV_SIZE; ++it) {
 
    char const *const env_value 
= getenv(env_names
[it
]);  
    char const *const env_copy = (env_value) ? strdup(env_value) : env_value;
 
    (*env)[it] = env_copy;
 
    status &= (env_copy) ? 1 : 0;
 
  }
 
  return status;
 
}
 
 
 
static int bear_reset_env_t(bear_env_t *env) {
 
  int status = 1;
 
  for (size_t it = 0; it < ENV_SIZE; ++it) {
 
    if ((*env)[it]) {
 
      setenv(env_names[it], (*env)[it], 1);
 
    } else {
 
      unsetenv(env_names[it]);
 
    }
 
  }
 
  return status;
 
}
 
 
 
static void bear_release_env_t(bear_env_t *env) {
 
  for (size_t it = 0; it < ENV_SIZE; ++it) {
 
    free((void *)(*env
)[it
]);  
    (*env)[it] = 0;
 
  }
 
}
 
 
 
static char const **bear_update_environment(char *const envp[],
 
                                            bear_env_t *env) {
 
  char const **result = bear_strings_copy((char const **)envp);
 
  for (size_t it = 0; it < ENV_SIZE && (*env)[it]; ++it)
 
    result = bear_update_environ(result, env_names[it], (*env)[it]);
 
  return result;
 
}
 
 
 
static char const **bear_update_environ(char const *envs[], char const *key,
 
                                        char const *const value) {
 
  // find the key if it's there
 
  size_t const key_length 
= strlen(key
);  
  char const **it = envs;
 
  for (; (it) && (*it); ++it) {
 
    if ((0 == strncmp(*it
, key
, key_length
)) && (strlen(*it
) > key_length
) &&  
        ('=' == (*it)[key_length]))
 
      break;
 
  }
 
  // allocate a environment entry
 
  size_t const value_length 
= strlen(value
);  
  size_t const env_length = key_length + value_length + 2;
 
  char *env 
= malloc(env_length
);  
  if (0 == env) {
 
    perror("bear: malloc [in env_update]");  
  }
 
  if (-1 == snprintf(env
, env_length
, "%s=%s", key
, value
)) {  
  }
 
  // replace or append the environment entry
 
  if (it && *it) {
 
    *it = env;
 
    return envs;
 
  }
 
  return bear_strings_append(envs, env);
 
}
 
 
 
static char **bear_get_environment() {
 
#if defined HAVE_NSGETENVIRON
 
  return *_NSGetEnviron();
 
#else
 
  return environ;
 
#endif
 
}
 
 
 
/* util methods to deal with string arrays. environment and process arguments
 
 * are both represented as string arrays. */
 
 
 
static char const **bear_strings_build(char const *const arg, va_list *args) {
 
  char const **result = 0;
 
  size_t size = 0;
 
  for (char const *it 
= arg
; it
; it 
= va_arg(*args
, char const *)) {  
    result 
= realloc(result
, (size 
+ 1) * sizeof(char const *)); 
    if (0 == result) {
 
    }
 
    char const *copy = strdup(it);
 
    if (0 == copy) {
 
    }
 
    result[size++] = copy;
 
  }
 
  result 
= realloc(result
, (size 
+ 1) * sizeof(char const *)); 
  if (0 == result) {
 
  }
 
  result[size++] = 0;
 
 
 
  return result;
 
}
 
 
 
static char const **bear_strings_copy(char const **const in) {
 
  size_t const size = bear_strings_length(in);
 
 
 
  char const **const result 
= malloc((size 
+ 1) * sizeof(char const *));  
  if (0 == result) {
 
  }
 
 
 
  char const **out_it = result;
 
  for (char const *const *in_it = in; (in_it) && (*in_it); ++in_it, ++out_it) {
 
    *out_it = strdup(*in_it);
 
    if (0 == *out_it) {
 
    }
 
  }
 
  *out_it = 0;
 
  return result;
 
}
 
 
 
static char const **bear_strings_append(char const **const in,
 
                                        char const *const e) {
 
  size_t size = bear_strings_length(in);
 
  char const **result 
= realloc(in
, (size 
+ 2) * sizeof(char const *));  
  if (0 == result) {
 
  }
 
  result[size++] = e;
 
  result[size++] = 0;
 
  return result;
 
}
 
 
 
static size_t bear_strings_length(char const *const *const in) {
 
  size_t result = 0;
 
  for (char const *const *it = in; (it) && (*it); ++it)
 
    ++result;
 
  return result;
 
}
 
 
 
static void bear_strings_release(char const **in) {
 
  for (char const *const *it = in; (it) && (*it); ++it) {
 
  }
 
}