2009-05-22 12 views
41

C'è un modo portatile (POSIX) per ottenere il numero di descrittore di file assegnato più alto per il processo corrente?Ottenere il descrittore di file assegnato più elevato

So che c'è un buon modo per ottenere il numero su AIX, ad esempio, ma sto cercando un metodo portatile.

Il motivo per cui sto chiedendo è che voglio chiudere tutti i descrittori di file aperti. Il mio programma è un server che gira come root e forca ed esegue programmi figlio per utenti non-root. Lasciare i descrittori di file privilegiati aperti nel processo figlio è un problema di sicurezza. Alcuni descrittori di file possono essere aperti da un codice che non posso controllare (la libreria C, le librerie di terze parti, ecc.), Quindi non posso fare affidamento su FD_CLOEXEC.

+3

Si noti che sarebbe preferibile aprire tutti i file con il flag di close-on-exec impostato in modo che vengano automaticamente chiusi da una qualsiasi delle funzioni della famiglia 'exec'. –

+0

Modern glibc supporta il carattere "e" stdio.h FILE * open flag per indicare il trattamento FD_CLOEXEC. – fche

risposta

61

Mentre portatile, la chiusura di tutti i descrittori di file fino a sysconf(_SC_OPEN_MAX) non è affidabile, poiché sulla maggior parte dei sistemi questa chiamata restituisce il limite di descrittore di file corrente, che potrebbe essere stato abbassato sotto il descrittore di file utilizzato più alto. Un altro problema è che su molti sistemi sysconf(_SC_OPEN_MAX) può restituire INT_MAX, il che può causare un approccio inaccettabilmente lento. Sfortunatamente, non esiste un'alternativa affidabile e portatile che non coinvolga l'iterazione su tutti i possibili descrittori di file int non negativi.

Anche se non portatili, la maggior parte dei sistemi in uso comune operativi oggi forniscono una o più delle seguenti soluzioni a questo problema:

  1. una funzione di libreria a vicino descrittori di tutti i file> = fd. Questa è la soluzione più semplice per il caso comune di chiusura di tutti i descrittori di file, sebbene non possa essere utilizzata per molto altro. Per chiudere tutti i descrittori di file ad eccezione di un determinato set, è possibile utilizzare dup2 per spostarli prima in basso e, se necessario, spostarli indietro in seguito.

    • closefrom(fd) (Solaris 9 o successiva, FreeBSD 7.3 o 8.0 e versioni successive, NetBSD 3.0 o versione successiva, OpenBSD 3.5 o versione successiva.)

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

  2. Una funzione di libreria per fornire il descrittore di file massimo attualmente utilizzato dal processo.Per chiudere tutti i descrittori di file sopra un certo numero, chiudili tutti fino a questo massimo, oppure ottieni e chiudi continuamente il descrittore di file più alto in un ciclo finché non viene raggiunto il limite inferiore. Che è più efficiente dipende dalla densità del descrittore di file.

    • fcntl(0, F_MAXFD) (NetBSD)

    • pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
      Restituisce informazioni sul processo, tra cui il più alto descrittore di file aperto in ps.pst_highestfd. (HP-UX)

  3. Un directory contenente una voce per ogni descrittore di file aperto. Questo è l'approccio più flessibile in quanto consente di chiudere tutti i descrittori di file, trovare il descrittore di file più alto o fare qualsiasi altra cosa su ogni descrittore di file aperto, anche quelli di un altro processo (sulla maggior parte dei sistemi). Tuttavia questo può essere più complicato rispetto agli altri approcci per gli usi comuni. Inoltre, può fallire per una serie di motivi come proc/fdescfs non montati, un ambiente di chroot o nessun descrittore di file disponibile per aprire la directory (limite di processo o di sistema). Pertanto l'uso di questo approccio è spesso combinato con un meccanismo di fallback. Example (OpenSSH), another example (glib).

    • /proc/pid/fd/ o /proc/self/fd/ (Linux, Solaris, AIX, Cygwin, NetBSD)
      (AIX non supporta "self")

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

    Può essere difficile gestire tutte le casse d'angolo in modo affidabile con questo approccio. Ad esempio si consideri la situazione in cui tutti i descrittori di file> = FD devono essere chiusi, ma tutti i descrittori di file < fd sono utilizzati, il limite di risorsa attuale processo è fd, e ci sono descrittori di file> = fd in uso. Poiché il limite delle risorse del processo è stato raggiunto, la directory non può essere aperta. Se si chiude ogni descrittore di file da fd attraverso il limite di risorse o sysconf(_SC_OPEN_MAX) viene utilizzato come fallback, non verrà chiuso nulla.

+1

Informazioni sull'approccio 3: ci sono problemi seri nell'usare questo tra fork/exec in un programma multithread perché opendir() può chiamare malloc() che potrebbe bloccarsi in questa situazione. Ho paura che non ci sia modo di fare quello che la domanda posta sotto Linux, e gli sviluppatori non faranno nulla al riguardo: https://sourceware.org/bugzilla/show_bug.cgi?id=10353 – medoc

+0

@ medoc: lo sviluppo di glibc ha subito una profonda riorganizzazione nel 2012, e diverse cose precedentemente respinte sono ora entrate nel nuovo modello di sviluppo. Potrebbe essere utile iniziare una nuova discussione sulla questione. – mark4o

-2

Perché non chiudere tutti i descrittori da 0 a, diciamo, 10000.

Sarebbe piuttosto veloce, e la cosa peggiore che potrebbe accadere è EBADF.

+0

Funzionerà, ma dovrai renderlo configurabile, in quanto semplicemente non sai quanti devono essere chiusi (dipende dal carico). –

12

Il modo POSIX è:

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

(si noti che è la chiusura da 3, per mantenere stdin/output/error aperto)

close() ritorna innocuo EBADF se il descrittore di file non è aperto . Non è necessario sprecare un altro controllo di chiamata di sistema.

Alcuni Unix supportano closefrom(). Ciò evita il numero eccessivo di chiamate a close() in base al numero massimo di descrittori di file possibile. Mentre la migliore soluzione di cui sono a conoscenza, è completamente non portabile.

5

Ho scritto codice per gestire tutte le funzionalità specifiche della piattaforma. Tutte le funzioni sono sicure per segnale asincrono. Le persone pensate potrebbero trovare ciò utile. Solo testato su OS X in questo momento, sentitevi liberi di migliorare/correggere.

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

Proprio quando il programma è iniziato e non ha aperto nulla. Per esempio. come l'inizio di main(). pipe e fork avviano immediatamente un server executer. In questo modo è la memoria e altri dettagli è pulito e si può semplicemente dare le cose alla forchetta & 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); 
} 

Se si vuole fare IO sul programma eseguito il server esecutore dovrà fare redirect presa ed è possibile utilizzare i socket UNIX.

Problemi correlati