2009-05-22 12 views
41

現在のプロセスで割り当てられているファイル記述子番号の中で最高のものを得るための移植可能な方法(POSIX)はありますか?割り当てられた最も高いファイル記述子を取得する

私は、AIX上で番号を取得するのに良い方法があることを知っていますが、私は移植可能な方法を探しています。

私が尋ねる理由は、開いているファイル記述子をすべて閉じたいということです。私のプログラムはroot以外のユーザーのためにルートとフォークとexecsの子プログラムとして動作するサーバーです。特権ファイル記述子を子プロセスで開いたままにしておくことはセキュリティの問題です。一部のファイル記述子は、制御できないコード(Cライブラリ、サードパーティのライブラリなど)で開くことができるので、FD_CLOEXECのどちらにも頼ることはできません。

+3

close-on-execフラグを設定してすべてのファイルを開くだけで、 'exec'ファミリ関数のいずれかによって自動的に閉じられるようにする方がよいでしょう。 –

+0

現代glibcはFD_CLOEXEC処理を示す "e" stdio.h FILE * openフラグ文字をサポートしています。 – fche

答えて

61

ほとんどのシステムでは、この呼び出しによって現在のファイル記述子のソフト制限値が返されるため、ポータブルではすべてのファイル記述子を閉じると信頼性がありません。別の問題は、多くのシステムでsysconf(_SC_OPEN_MAX)INT_MAXを返す可能性があり、このアプローチが許容できないほど遅くなる可能性があることです。残念ながら、可能性のある非負のintファイル記述子すべてを繰り返し処理することを含まない、信頼性の高い可搬性の代替手段はありません。

一般的に使用されている

ではないが、ポータブル、ほとんどのオペレーティングシステムで、今日は、この問題に、次の解決策の1つ以上を提供:

  1. ライブラリ関数にFD> = すべてのファイル記述子を閉じます。これは、他の多くのものには使用できないが、すべてのファイル記述子を閉じる一般的な場合の最も簡単な解決策である。特定のセットを除くすべてのファイル記述子を閉じるには、あらかじめローエンドに移動し、必要に応じて後で戻すようにdup2を使用できます。

    • closefrom(fd)(Solaris 9またはそれ以降、FreeBSDの7.3または8.0以降のNetBSD 3.0以降、OpenBSDの3.5以降)

    • fcntl(fd, F_CLOSEM, 0)(AIX、IRIX、NetBSDの)

  2. 最大ファイル記述子をプロセスで現在使用しているライブラリ関数です。特定の数より上のすべてのファイル記述子を閉じるには、これらのファイル記述子をすべてこの最大値までクローズするか、下限に達するまでループ内で最も高いファイル記述子を連続的に取得して閉じます。どちらがより効率的かは、ファイルディスクリプタの密度に依存します。 ps.pst_highestfdで現在開いている最高のファイルディスクリプタを含む

    • fcntl(0, F_MAXFD)(NetBSDの)

    • pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
      戻りプロセスに関する情報を、。 (HP-UX)

  3. 各オープンファイル記述子のエントリを含むディレクトリ。これは、すべてのファイル記述子を閉じること、最高のファイル記述子を見つけること、または開いているファイル記述子の他のものについて、他のプロセス(たとえば、ほとんどのシステム)のものでさえも可能です。しかし、これは、一般的な使用のための他のアプローチよりも複雑になり得る。また、proc/fdescfsがマウントされていない、chroot環境、またはディレクトリを開くために使用可能なファイル記述子がない(プロセスまたはシステムの制限)など、さまざまな理由で失敗する可能性があります。したがって、このアプローチの使用は、多くの場合、フォールバックメカニズムと組み合わされます。 Example (OpenSSH),another example (glib)

    • /proc/PID/fd//proc/self/fd/する(Linux、Solaris版、AIX、Cygwinの、NetBSDの)
      (AIXは "self" をサポートしていません)

    • /dev/fd/(FreeBSDでは、ダーウィン、OS X)

    この方法では、すべてのコーナーケースを確実に処理することが難しい場合があります。たとえば、すべてのファイル記述子が> = FDを閉じることになっている状況を考えますが、< FDが使用されている全てのファイルディスクリプタは、現在のプロセスのリソース制限は、FDで、ファイルディスクリプタ> = FDがあります使用中で。プロセスリソースの制限に達しているため、ディレクトリを開くことができません。すべてのファイル記述子をfdからリソース制限、またはsysconf(_SC_OPEN_MAX)まで閉じると、何も閉じられません。

+1

アプローチ3:opendir()がmalloc()を呼び出して、この状況でデッドロックが発生する可能性があるため、マルチスレッドプログラムでfork/execの間に重大な問題があります。私はLinux上で質問されたことをする方法がないことを恐れている。開発者はそれについて何もしないだろう:https://sourceware.org/bugzilla/show_bug.cgi?id=10353 – medoc

+0

@ 2012年にメドック:glibcの開発が大幅に改編され、これまでに拒否されたことがいくつかあり、新しい開発モデルの下でこれを実現しました。この問題について新しい議論を始めることは価値があるかもしれません。 – mark4o

-2

なぜあなたはそれがかなり速いだろう、と起こる最悪の事態はEBADFで0〜10000

、たとえば、すべての記述子を閉じないでください。

+0

動作しますが、設定する必要があります。これは、閉鎖する必要があるかどうか分かりません(負荷によって異なります)。 –

12

POSIXの方法は次のとおりです。

int maxfd=sysconf(_SC_OPEN_MAX); 
for(int fd=3; fd<maxfd; fd++) 
    close(fd); 

(つまり標準入力/標準出力/標準エラー出力のオープンを維持するために、最大3から閉じ者注)

近い()ファイル記述子が開いていない場合は無害EBADFを返します。 。別のシステムコールチェックを無駄にする必要はありません。

一部のUnixはclosefrom()をサポートしています。これにより、ファイル記述子の最大数に応じてclose()を呼び出す回数が増えるのを防ぎます。私が知っている最善の解決策ではありますが、それは完全に移植性がありません。

5

プラットフォーム固有のすべての機能を処理するコードを記述しました。すべての機能は非同期信号に安全です。思考の人々はこれが役に立つと思うかもしれません。 OS Xでしかテストされていませんが、改善/修正しても構いません。

// Async-signal safe way to get the current process's hard file descriptor limit. 
static int 
getFileDescriptorLimit() { 
    long long sysconfResult = sysconf(_SC_OPEN_MAX); 

    struct rlimit rl; 
    long long rlimitResult; 
    if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { 
     rlimitResult = 0; 
    } else { 
     rlimitResult = (long long) rl.rlim_max; 
    } 

    long result; 
    if (sysconfResult > rlimitResult) { 
     result = sysconfResult; 
    } else { 
     result = rlimitResult; 
    } 
    if (result < 0) { 
     // Both calls returned errors. 
     result = 9999; 
    } else if (result < 2) { 
     // The calls reported broken values. 
     result = 2; 
    } 
    return result; 
} 

// Async-signal safe function to get the highest file 
// descriptor that the process is currently using. 
// See also http://stackoverflow.com/questions/899038/getting-the-highest-allocated-file-descriptor 
static int 
getHighestFileDescriptor() { 
#if defined(F_MAXFD) 
    int ret; 

    do { 
     ret = fcntl(0, F_MAXFD); 
    } while (ret == -1 && errno == EINTR); 
    if (ret == -1) { 
     ret = getFileDescriptorLimit(); 
    } 
    return ret; 

#else 
    int p[2], ret, flags; 
    pid_t pid = -1; 
    int result = -1; 

    /* Since opendir() may not be async signal safe and thus may lock up 
    * or crash, we use it in a child process which we kill if we notice 
    * that things are going wrong. 
    */ 

    // Make a pipe. 
    p[0] = p[1] = -1; 
    do { 
     ret = pipe(p); 
    } while (ret == -1 && errno == EINTR); 
    if (ret == -1) { 
     goto done; 
    } 

    // Make the read side non-blocking. 
    do { 
     flags = fcntl(p[0], F_GETFL); 
    } while (flags == -1 && errno == EINTR); 
    if (flags == -1) { 
     goto done; 
    } 
    do { 
     fcntl(p[0], F_SETFL, flags | O_NONBLOCK); 
    } while (ret == -1 && errno == EINTR); 
    if (ret == -1) { 
     goto done; 
    } 

    do { 
     pid = fork(); 
    } while (pid == -1 && errno == EINTR); 

    if (pid == 0) { 
     // Don't close p[0] here or it might affect the result. 

     resetSignalHandlersAndMask(); 

     struct sigaction action; 
     action.sa_handler = _exit; 
     action.sa_flags = SA_RESTART; 
     sigemptyset(&action.sa_mask); 
     sigaction(SIGSEGV, &action, NULL); 
     sigaction(SIGPIPE, &action, NULL); 
     sigaction(SIGBUS, &action, NULL); 
     sigaction(SIGILL, &action, NULL); 
     sigaction(SIGFPE, &action, NULL); 
     sigaction(SIGABRT, &action, NULL); 

     DIR *dir = NULL; 
     #ifdef __APPLE__ 
      /* /dev/fd can always be trusted on OS X. */ 
      dir = opendir("/dev/fd"); 
     #else 
      /* On FreeBSD and possibly other operating systems, /dev/fd only 
      * works if fdescfs is mounted. If it isn't mounted then /dev/fd 
      * still exists but always returns [0, 1, 2] and thus can't be 
      * trusted. If /dev and /dev/fd are on different filesystems 
      * then that probably means fdescfs is mounted. 
      */ 
      struct stat dirbuf1, dirbuf2; 
      if (stat("/dev", &dirbuf1) == -1 
      || stat("/dev/fd", &dirbuf2) == -1) { 
       _exit(1); 
      } 
      if (dirbuf1.st_dev != dirbuf2.st_dev) { 
       dir = opendir("/dev/fd"); 
      } 
     #endif 
     if (dir == NULL) { 
      dir = opendir("/proc/self/fd"); 
      if (dir == NULL) { 
       _exit(1); 
      } 
     } 

     struct dirent *ent; 
     union { 
      int highest; 
      char data[sizeof(int)]; 
     } u; 
     u.highest = -1; 

     while ((ent = readdir(dir)) != NULL) { 
      if (ent->d_name[0] != '.') { 
       int number = atoi(ent->d_name); 
       if (number > u.highest) { 
        u.highest = number; 
       } 
      } 
     } 
     if (u.highest != -1) { 
      ssize_t ret, written = 0; 
      do { 
       ret = write(p[1], u.data + written, sizeof(int) - written); 
       if (ret == -1) { 
        _exit(1); 
       } 
       written += ret; 
      } while (written < (ssize_t) sizeof(int)); 
     } 
     closedir(dir); 
     _exit(0); 

    } else if (pid == -1) { 
     goto done; 

    } else { 
     do { 
      ret = close(p[1]); 
     } while (ret == -1 && errno == EINTR); 
     p[1] = -1; 

     union { 
      int highest; 
      char data[sizeof(int)]; 
     } u; 
     ssize_t ret, bytesRead = 0; 
     struct pollfd pfd; 
     pfd.fd = p[0]; 
     pfd.events = POLLIN; 

     do { 
      do { 
       // The child process must finish within 30 ms, otherwise 
       // we might as well query sysconf. 
       ret = poll(&pfd, 1, 30); 
      } while (ret == -1 && errno == EINTR); 
      if (ret <= 0) { 
       goto done; 
      } 

      do { 
       ret = read(p[0], u.data + bytesRead, sizeof(int) - bytesRead); 
      } while (ret == -1 && ret == EINTR); 
      if (ret == -1) { 
       if (errno != EAGAIN) { 
        goto done; 
       } 
      } else if (ret == 0) { 
       goto done; 
      } else { 
       bytesRead += ret; 
      } 
     } while (bytesRead < (ssize_t) sizeof(int)); 

     result = u.highest; 
     goto done; 
    } 

done: 
    if (p[0] != -1) { 
     do { 
      ret = close(p[0]); 
     } while (ret == -1 && errno == EINTR); 
    } 
    if (p[1] != -1) { 
     do { 
      close(p[1]); 
     } while (ret == -1 && errno == EINTR); 
    } 
    if (pid != -1) { 
     do { 
      ret = kill(pid, SIGKILL); 
     } while (ret == -1 && errno == EINTR); 
     do { 
      ret = waitpid(pid, NULL, 0); 
     } while (ret == -1 && errno == EINTR); 
    } 

    if (result == -1) { 
     result = getFileDescriptorLimit(); 
    } 
    return result; 
#endif 
} 

void 
closeAllFileDescriptors(int lastToKeepOpen) { 
    #if defined(F_CLOSEM) 
     int ret; 
     do { 
      ret = fcntl(lastToKeepOpen + 1, F_CLOSEM); 
     } while (ret == -1 && errno == EINTR); 
     if (ret != -1) { 
      return; 
     } 
    #elif defined(HAS_CLOSEFROM) 
     closefrom(lastToKeepOpen + 1); 
     return; 
    #endif 

    for (int i = getHighestFileDescriptor(); i > lastToKeepOpen; i--) { 
     int ret; 
     do { 
      ret = close(i); 
     } while (ret == -1 && errno == EINTR); 
    } 
} 
0

プログラムが開始されていて、何も開いていないとき。例えば。 main()の開始のように。パイプとフォークはすぐにexecuterサーバーを開始します。この方法ではメモリと他の詳細はきれいですし、あなたはそれをフォークすることを与えることができます& exec。

#include <unistd.h> 
#include <stdio.h> 
#include <memory.h> 
#include <stdlib.h> 

struct PipeStreamHandles { 
    /** Write to this */ 
    int output; 
    /** Read from this */ 
    int input; 

    /** true if this process is the child after a fork */ 
    bool isChild; 
    pid_t childProcessId; 
}; 

PipeStreamHandles forkFullDuplex(){ 
    int childInput[2]; 
    int childOutput[2]; 

    pipe(childInput); 
    pipe(childOutput); 

    pid_t pid = fork(); 
    PipeStreamHandles streams; 
    if(pid == 0){ 
     // child 
     close(childInput[1]); 
     close(childOutput[0]); 

     streams.output = childOutput[1]; 
     streams.input = childInput[0]; 
     streams.isChild = true; 
     streams.childProcessId = getpid(); 
    } else { 
     close(childInput[0]); 
     close(childOutput[1]); 

     streams.output = childInput[1]; 
     streams.input = childOutput[0]; 
     streams.isChild = false; 
     streams.childProcessId = pid; 
    } 

    return streams; 
} 


struct ExecuteData { 
    char command[2048]; 
    bool shouldExit; 
}; 

ExecuteData getCommand() { 
    // maybe use json or semething to read what to execute 
    // environment if any and etc..   
    // you can read via stdin because of the dup setup we did 
    // in setupExecutor 
    ExecuteData data; 
    memset(&data, 0, sizeof(data)); 
    data.shouldExit = fgets(data.command, 2047, stdin) == NULL; 
    return data; 
} 

void executorServer(){ 

    while(true){ 
     printf("executor server waiting for command\n"); 
     // maybe use json or semething to read what to execute 
     // environment if any and etc..   
     ExecuteData command = getCommand(); 
     // one way is for getCommand() to check if stdin is gone 
     // that way you can set shouldExit to true 
     if(command.shouldExit){ 
      break; 
     } 
     printf("executor server doing command %s", command.command); 
     system(command.command); 
     // free command resources. 
    } 
} 

static PipeStreamHandles executorStreams; 
void setupExecutor(){ 
    PipeStreamHandles handles = forkFullDuplex(); 

    if(handles.isChild){ 
     // This simplifies so we can just use standard IO 
     dup2(handles.input, 0); 
     // we comment this out so we see output. 
     // dup2(handles.output, 1); 
     close(handles.input); 
     // we uncomment this one so we can see hello world 
     // if you want to capture the output you will want this. 
     //close(handles.output); 
     handles.input = 0; 
     handles.output = 1; 
     printf("started child\n"); 
     executorServer(); 
     printf("exiting executor\n"); 
     exit(0); 
    } 

    executorStreams = handles; 
} 

/** Only has 0, 1, 2 file descriptiors open */ 
pid_t cleanForkAndExecute(const char *command) { 
    // You can do json and use a json parser might be better 
    // so you can pass other data like environment perhaps. 
    // and also be able to return details like new proccess id so you can 
    // wait if it's done and ask other relevant questions. 
    write(executorStreams.output, command, strlen(command)); 
    write(executorStreams.output, "\n", 1); 
} 

int main() { 
    // needs to be done early so future fds do not get open 
    setupExecutor(); 

    // run your program as usual. 
    cleanForkAndExecute("echo hello world"); 
    sleep(3); 
} 

あなたはエグゼキュータ・サーバは、ソケットリダイレクトを行う必要があります実行されるプログラムでIOを行いたい場合は、あなたがUnixソケットを使用することができます。

関連する問題