如何在c和fork之后捕获环境?

时间:2015-07-10 13:04:52

标签: c environment-variables exec fork

我有一个C函数来执行一个将被调用两次的fork和exec。

第一个调用执行一个shell脚本(称之为setenv.sh),它可以是任何一种设置环境变量的shell(bash / korn / c / perl等)。对于此调用,envp数组将为NULL,但意图是在setenv.sh运行后,它将从子进程返回基于environ的填充数组。

第二个调用将是需要某个特定环境运行的C或java程序,因此对于此调用,envp数组将是从第一次调用返回的填充数组。

int execute(char **args, int argc, char **envp)
{
  char *function = "execute";
  int status, i;
  pid_t p, pid;
  extern int errno;
  sigset_t mask, savemask;
  struct sigaction ignore, saveint, savequit;
  int fd[2];

  pipe(fd);

  sigemptyset(&ignore.sa_mask);
  ignore.sa_handler = SIG_IGN;
  ignore.sa_flags=0;

  sigaction(SIGINT, &ignore, &saveint);
  sigaction(SIGQUIT, &ignore, &savequit);

  sigemptyset(&mask);
  sigaddset(&mask, SIGCHLD);
  sigprocmask(SIG_BLOCK, &mask, &savemask);

  if ((pid=fork()) < 0) status = -1;

  if (pid ==0) {
    /* Child */
    close(fd[0]);
    sigaction(SIGINT, &saveint, (struct sigaction *) 0);
    sigaction(SIGQUIT, &savequit, (struct sigaction *) 0);
    sigprocmask(SIG_SETMASK, &savemask, (sigset_t *) 0);

    printf("Command Line Parameters\n");
    printf("-----------------------\n");
    for (i = 0; i < argc; i++) {
        printf("[%d]: %s\n", (i+1), args[i]);
    }

    if (execve(*args, args, envp) < 0)
    {
      sprintf(err_data,"Failed to execute %s", args[0]);
      perror(err_data);
      return(FAILED);
    }
    write(fd[1], &environ, sizeof(environ));
    close(fd[1]);
  }

  while (waitpid(pid, &status, 0) < 0) {
  if (errno != EINTR) {
       status = -1;
       break;
    }
  }  
  if (status==0) {
    read(fd[0], &envp, sizeof(envp));
  }
  close(fd[0]);

  sigaction(SIGINT, &saveint, (struct sigaction *) 0);
  sigaction(SIGQUIT, &savequit, (struct sigaction *) 0);
  sigprocmask(SIG_SETMASK, &savemask, (sigset_t *) 0);

  return(status);
}

这个函数工作正常,没有管道代码来执行传入的实际程序,我也可以在envp数组中传递一组环境变量,它在那个环境中运行很好。

但是,在使用包含管道的测试中,我发现在setenv.sh的exec之后,子进程从不执行向管道写入environ,而父进程只是阻塞了对管道的读取。

我理解为什么它不起作用 - 因为shell脚本的exec会覆盖子代中的原始C代码。问题是,有没有办法实现使用exec运行shell脚本并将结果环境捕获回父级(与捕获stdin / stdout / stderr不同)的目的。假设您无法更改setenv.sh的内容,因为它可能由第三方提供。

无需挑错错误处理等。这是一项正在进行中的工作,所以只是在如何实现目标的一些输入之后。

我考虑的一个替代方法是解析父级中的setenv.sh脚本以将变量获取到一个数组中,然后可以将该数组传递给实际程序。问题是setenv.sh脚本可能包含if语句块和包含其他shell脚本,所以我真的想在setenv.sh运行结束时捕获环境(通过exec&#39; ing)并传递这个回到父母。

有什么建议值得赞赏吗?

2 个答案:

答案 0 :(得分:3)

如果不使用操作系统的调试工具并深入了解子进程的内存,基本上无法解决这个问题。这基本上要求你写一半调试器。

使用第三方脚本可以获得的最接近的是这样的。假设脚本是/ bin / bash。你可以编写自己的包装脚本:

#!/bin/bash

. setenv.sh

env >&3

其中3是管道的文件描述符编号。您可以为其他shell编写等效的脚本。这种方法的唯一原因是因为“setenv.sh”脚本在包装器脚本中执行而不创建子进程。环境变量只能传达给流程的子项。

在我在工作中使用的系统中,我们有需要在来自各种脚本的许多不同程序之间统一的环境变量,其中许多程序我们无法控制。我们解决这个问题的方法是,我们要求这些脚本输出“KEY = VALUE \ n”行,而不是环境变量,然后通过简单的脚本(如果需要)将它们导入脚本,makefile等。这可能是你能做的最好的事情。

答案 1 :(得分:-1)

您可以使用:

extern char **environ;

environ被定义为Glibc源文件posix / environ.c中的全局变量。