text: /* -*- coding: utf-8 -*-
   * ----------------------------------------------------------------------
   * Copyright © 2013, RedJack, LLC.
   * All rights reserved.
   *
   * Please see the COPYING file in this distribution for license
   * details.
   * ----------------------------------------------------------------------
   */
  
  #include <errno.h>
  #include <unistd.h>
  
  #include "libcork/core.h"
  #include "libcork/ds.h"
  #include "libcork/os/subprocess.h"
  #include "libcork/helpers/errors.h"
  
  #define ri_check_posix(call) \
      do { \
          while (true) { \
              if ((call) == -1) { \
                  if (errno == EINTR) { \
                      continue; \
                  } else { \
                      cork_system_error_set(); \
                      CORK_PRINT_ERROR(); \
                      return -1; \
                  } \
              } else { \
                  break; \
              } \
          } \
      } while (0)
  
  
  struct cork_exec {
      const char  *program;
      struct cork_string_array  params;
      struct cork_env  *env;
      const char  *cwd;
      struct cork_buffer  description;
  };
  
  struct cork_exec *
  cork_exec_new(const char *program)
  {
      struct cork_exec  *exec = cork_new(struct cork_exec);
      exec->program = cork_strdup(program);
      cork_string_array_init(&exec->params);
      exec->env = NULL;
      exec->cwd = NULL;
      cork_buffer_init(&exec->description);
      cork_buffer_set_string(&exec->description, program);
      return exec;
  }
  
  struct cork_exec *
  cork_exec_new_with_params(const char *program, ...)
  {
      struct cork_exec  *exec;
      va_list  args;
      const char  *param;
  
      exec = cork_exec_new(program);
      cork_exec_add_param(exec, program);
      va_start(args, program);
      while ((param = va_arg(args, const char *)) != NULL) {
          cork_exec_add_param(exec, param);
      }
      return exec;
  }
  
  struct cork_exec *
  cork_exec_new_with_param_array(const char *program, char * const *params)
  {
      char * const  *curr;
      struct cork_exec  *exec = cork_exec_new(program);
      for (curr = params; *curr != NULL; curr++) {
          cork_exec_add_param(exec, *curr);
      }
      return exec;
  }
  
  void
  cork_exec_free(struct cork_exec *exec)
  {
      cork_strfree(exec->program);
      cork_array_done(&exec->params);
      if (exec->env != NULL) {
          cork_env_free(exec->env);
      }
      if (exec->cwd != NULL) {
          cork_strfree(exec->cwd);
      }
      cork_buffer_done(&exec->description);
      free(exec);
  }
  
  const char *
  cork_exec_description(struct cork_exec *exec)
  {
      return exec->description.buf;
  }
  
  const char *
  cork_exec_program(struct cork_exec *exec)
  {
      return exec->program;
  }
  
  size_t
  cork_exec_param_count(struct cork_exec *exec)
  {
      return cork_array_size(&exec->params);
  }
  
  const char *
  cork_exec_param(struct cork_exec *exec, size_t index)
  {
      return cork_array_at(&exec->params, index);
  }
  
  void
  cork_exec_add_param(struct cork_exec *exec, const char *param)
  {
      /* Don't add the first parameter to the description; that's a copy of the
       * program name, which we've already added. */
      if (!cork_array_is_empty(&exec->params)) {
          cork_buffer_append(&exec->description, " ", 1);
          cork_buffer_append_string(&exec->description, param);
      }
      cork_array_append(&exec->params, cork_strdup(param));
  }
  
  struct cork_env *
  cork_exec_env(struct cork_exec *exec)
  {
      return exec->env;
  }
  
  void
  cork_exec_set_env(struct cork_exec *exec, struct cork_env *env)
  {
      if (exec->env != NULL) {
          cork_env_free(exec->env);
      }
      exec->env = env;
  }
  
  const char *
  cork_exec_cwd(struct cork_exec *exec)
  {
      return exec->cwd;
  }
  
  void
  cork_exec_set_cwd(struct cork_exec *exec, const char *directory)
  {
      if (exec->cwd != NULL) {
          cork_strfree(exec->cwd);
      }
      exec->cwd = cork_strdup(directory);
  }
  
  int
  cork_exec_run(struct cork_exec *exec)
  {
      const char  **params;
  
      /* Make sure the parameter array is NULL-terminated. */
      cork_array_append(&exec->params, NULL);
      params = cork_array_elements(&exec->params);
  
      /* Fill in the requested environment */
      if (exec->env != NULL) {
          cork_env_replace_current(exec->env);
      }
  
      /* Change the working directory, if requested */
      if (exec->cwd != NULL) {
          ri_check_posix(chdir(exec->cwd));
      }
  
      /* Execute the new program */
      ri_check_posix(execvp(exec->program, (char * const *) params));
  
      /* This is unreachable */
      return 0;
  }
  