2015-07-10 39 views
1

我有一個C函數來做一個fork和exec,它會被調用兩次。如何在fork和exec之後捕獲環境c?

第一次調用執行shell腳本(稱爲setenv.sh),它可以是任何類型的外殼(bash中/科恩/ 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運行結束時捕獲環境(通過執行它)並將其傳遞迴父母。

任何建議表示讚賞?

+0

你可以做的事情不多 - 你想要獲取運行腳本後得到的環境,這意味着子進程已經終止。內核不存儲殭屍進程的環境,所以你無法獲取它。你可以做的最好的是寫一個包裝腳本,如Art的答案中所述。 –

回答

-1

您可以使用:

extern char **environ; 

ENVIRON被定義爲Glibc的源文件POSIX/environ.c一個全局變量。

+0

是的 - 這是我的想法,但我如何捕獲它在父母當孩子在不同的過程中執行?我在函數中使用了environ,但子代碼被shell腳本替換,所以原來的程序和我的調用來編寫environ到管道不再在內存中,我認爲命名管道將有同樣的問題,因爲我是不執行我的代碼 - 除非可能不是直接執行shell腳本,而是執行我的代碼的另一個副本,即fork/exec shell腳本 - 但我仍然無法看到如何在執行後捕獲子節點的環境。 –

+0

這根本不回答問題*。問題是關於在終止時捕獲子進程的環境,而不是如何訪問當前進程的環境。 –

+0

抱歉,我錯過了解。 –

3

基本上,如果不使用操作系統的調試功能並挖掘您的子進程的內存,通常無法解決這個問題。這基本上要求你寫一半調試器。

用第三方腳本可以得到最接近的東西就像這樣。假設腳本是用於/ bin/bash的。您可以這樣編寫自己的包裝腳本:

#!/bin/bash 

. setenv.sh 

env >&3 

其中3是管道的文件描述符編號。您可以爲其他shell編寫等效的腳本。這個工作的唯一原因是因爲「setenv.sh」腳本在你的包裝腳本中執行而不創建子進程。環境變量只能傳遞給進程的子進程。

在我使用的系統中,我們有許多環境變量需要在來自各種腳本的許多不同程序之間統一,其中很多程序我們沒有任何控制權。我們解決混亂的方式是,我們不需要環境變量,而是需要這些腳本輸出「KEY = VALUE \ n」行,然後通過簡單腳本(如果需要)將它們導入到腳本,makefile等中。這可能是你能做的最好的。

+1

看起來很合理,但是如何在不知道管道fd的情況下編寫包裝腳本?每次執行時可能會有所不同(如果父進程動態地打開和關閉其他文件) –

+1

@FilipeGonçalves在運行中生成它。 'FILE * tmp = fopen(「/ path/to/temporary/script」,...); fprintf(tmp,「#!/ bin/bash \ n。setenv.sh \ nenv>&%d \ n」,fd [1]);'沒有說這很漂亮。 – Art

+1

是的,差不多。要麼是這樣,要麼創建一個帶有已知的同意名稱的命名管道,並將腳本寫入那裏。這兩種方法都不是很優雅,但我想沒有選擇。 –