2009-05-22 11 views
41

Gibt es einen portablen Weg (POSIX), um die höchste zugewiesene Dateideskriptornummer für den aktuellen Prozess zu erhalten?Abrufen des höchsten zugeordneten Dateideskriptors

Ich weiß, dass es eine nette Möglichkeit gibt, die Nummer auf AIX zum Beispiel zu bekommen, aber ich suche nach einer tragbaren Methode.

Der Grund, den ich frage, ist, dass ich alle offenen Dateideskriptoren schließen möchte. Mein Programm ist ein Server, der als root ausgeführt wird und untergeordnete Programme für Nicht-root-Benutzer ausführt. Es ist ein Sicherheitsproblem, die privilegierten Dateideskriptoren im untergeordneten Prozess geöffnet zu lassen. Einige Dateideskriptoren können mit Code geöffnet werden, den ich nicht kontrollieren kann (die C-Bibliothek, Bibliotheken von Drittanbietern usw.), so dass ich mich auch nicht auf FD_CLOEXEC verlassen kann.

+3

Beachten Sie, dass es besser wäre, alle Ihre Dateien mit dem Close-on-Exec-Flag so zu öffnen, dass sie automatisch von einer der 'exec'-Familienfunktionen geschlossen werden. –

+0

Moderne glibc unterstützt das "e" stdio.h FILE * open-Flag-Zeichen, um FD_CLOEXEC Behandlung anzuzeigen. – fche

Antwort

61

Während das Schließen aller Dateideskriptoren bis sysconf(_SC_OPEN_MAX) nicht zuverlässig ist, führt dieser Aufruf auf den meisten Systemen den aktuellen Dateideskriptor-Softlimit zurück, der unter den höchsten verwendeten Dateideskriptor gesenkt werden konnte. Ein anderes Problem ist, dass auf vielen Systemen sysconf(_SC_OPEN_MAX)INT_MAX zurückgeben kann, was dazu führen kann, dass dieser Ansatz inakzeptabel langsam ist. Leider gibt es keine zuverlässige, tragbare Alternative, bei der nicht alle möglichen nicht negativen int-Dateideskriptoren durchlaufen werden müssen.

Obwohl es nicht tragbar, die meisten Betriebssysteme heute gebräuchlichen ein liefern oder mehrere der folgenden Lösungen für dieses Problem:

  1. Eine Bibliothek Funktion schließen Sie alle Datei-Deskriptoren> = fd. Dies ist die einfachste Lösung für den häufigen Fall, dass alle Dateideskriptoren geschlossen werden, obwohl sie nicht für viel mehr verwendet werden können. Um alle Dateideskriptoren bis auf eine bestimmte Menge zu schließen, kann dup2 verwendet werden, um sie vorher auf das untere Ende zu verschieben und sie später wieder zurück zu verschieben.

    • closefrom(fd) (Solaris 9 oder höher, FreeBSD 7.3 oder 8.0 und höher, NetBSD 3.0 oder höher, OpenBSD 3.5 oder höher.)

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

  2. Eine Bibliotheksfunktion, die den maximalen Dateideskriptor bereitstellt, der derzeit vom Prozess verwendet wird.Um alle Dateideskriptoren oberhalb einer bestimmten Anzahl zu schließen, schließen Sie alle bis zu diesem Maximum oder rufen Sie fortlaufend den höchsten Dateideskriptor in einer Schleife ab, bis die untere Grenze erreicht ist. Was effizienter ist, hängt von der Datei-Deskriptordichte ab.

    • fcntl(0, F_MAXFD) (NetBSD)

    • pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
      Gibt Informationen über den Prozess, einschließlich der höchsten Dateideskriptors derzeit offen in ps.pst_highestfd. (HP-UX)

  3. A Verzeichnis einen Eintrag für jede offene Dateideskriptor enthält. Dies ist der flexibelste Ansatz, da alle Dateideskriptoren geschlossen werden können, der höchste Dateideskriptor gefunden werden kann oder einfach alles andere auf jedem geöffneten Dateideskriptor ausgeführt werden kann, sogar die eines anderen Prozesses (auf den meisten Systemen). Dies kann jedoch komplizierter sein als die anderen Ansätze für die gemeinsamen Anwendungen. Es kann auch aus verschiedenen Gründen fehlschlagen, wie zum Beispiel proc/fdescfs nicht gemountet, eine Chroot-Umgebung oder keine Dateideskriptoren verfügbar, um das Verzeichnis zu öffnen (Prozess- oder Systemlimit). Daher wird die Verwendung dieses Ansatzes oft mit einem Fallback-Mechanismus kombiniert. Example (OpenSSH), another example (glib).

    • /proc/pid/fd/ oder /proc/self/fd/ (Linux, Solaris, AIX, Cygwin, NetBSD)
      (AIX nicht unterstützt "self")

    • /dev/fd/ (FreeBSD, Darwin, OS X)

    Bei diesem Ansatz kann es schwierig sein, alle Eckgehäuse zuverlässig zu handhaben. Zum Beispiel die Situation betrachten, in der alle Datei-Deskriptoren> = fd geschlossen werden, aber alle Dateideskriptoren < fd verwendet werden, ist der aktuelle Prozess Ressourcengrenze fd, und es gibt Filedeskriptoren> = fd in Benutzung. Da das Prozessressourcenlimit erreicht wurde, kann das Verzeichnis nicht geöffnet werden. Wenn Sie jeden Dateideskriptor von fd durch das Ressourcenlimit oder sysconf(_SC_OPEN_MAX) als Fallback schließen, wird nichts geschlossen.

+1

Über Ansatz 3: Es gibt ernsthafte Probleme bei der Verwendung dieser zwischen fork/exec in einem Multithread-Programm, weil opendir() malloc() aufrufen kann, die in dieser Situation Deadlock kann. Ich habe Angst, dass es einfach keine Möglichkeit gibt, das zu tun, was die Frage unter Linux stellt, und die Entwickler werden nichts dagegen tun: https://sourceware.org/bugzilla/show_bug.cgi?id=10353 – medoc

+0

@ medoc: Die Glibc-Entwicklung wurde 2012 grundlegend umstrukturiert, und einige zuvor zurückgewiesene Dinge haben nun Einzug in das neue Entwicklungsmodell gehalten. Es könnte sich lohnen, eine neue Diskussion zu diesem Thema zu beginnen. – mark4o

-2

Warum nicht schließen Sie alle Deskriptoren von 0 bis etwa 10000.

Es wäre ziemlich schnell sein, und das Schlimmste, was passieren würde, ist EBADF.

+0

Funktioniert, aber Sie müssen das konfigurierbar machen, da Sie einfach nicht wissen, wie viele geschlossen werden müssen (hängt von der Last ab). –

12

Die POSIX-Weg ist:

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

(beachten Sie, dass ist von 3 Verschließen, halten stdin/stdout/stderr offen)

close() überbrückte EBADF zurück, wenn der Dateideskriptor nicht geöffnet ist . Es besteht keine Notwendigkeit, eine weitere Systemanrufprüfung zu verschwenden.

Einige Unixe unterstützen eine closefrom(). Dies vermeidet die übermäßige Anzahl von Aufrufen zum Schließen() abhängig von der maximal möglichen Datei-Deskriptor-Nummer. Während die beste Lösung, die ich kenne, ist es völlig unportabel.

5

Ich habe Code geschrieben, um mit allen plattformspezifischen Funktionen umzugehen. Alle Funktionen sind async-signalsicher. Denkende Menschen könnten das nützlich finden. Nur getestet unter OS X jetzt, fühlen Sie sich frei zu verbessern/zu beheben.

// 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

Richtig, wenn Ihr Programm gestartet wurde und nichts geöffnet hat. Z.B. wie der Start von main(). Pipe und Fork starten sofort einen Executer Server. Auf diese Weise ist es Speicher und andere Details sind sauber und Sie können es nur geben, um Gabel & 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); 
} 

Wenn Sie IO auf das ausgeführte Programm der Testamentsvollstrecker Server-Socket-Umleitungen zu tun zu tun haben, und Sie können Unix-Sockets verwenden.