2009-05-22 32 views
41

Có cách di động (POSIX) để nhận số mô tả tệp được phân bổ cao nhất cho quy trình hiện tại không?Lấy bộ mô tả tệp được phân bổ cao nhất

Tôi biết rằng có một cách hay để lấy số trên AIX, ví dụ, nhưng tôi đang tìm một phương pháp di động.

Lý do tôi hỏi là tôi muốn đóng tất cả các mô tả tệp đang mở. Chương trình của tôi là một máy chủ chạy như root và forks và thực hiện các chương trình con cho người dùng không phải root. Việc để lại các mô tả tập tin đặc quyền mở trong tiến trình con là một vấn đề an ninh. Một số mô tả tệp có thể được mở bằng mã tôi không thể kiểm soát (thư viện C, thư viện của bên thứ ba, v.v.), vì vậy tôi cũng không thể dựa vào FD_CLOEXEC.

+3

Lưu ý rằng sẽ tốt hơn nếu bạn chỉ mở tất cả các tệp của mình bằng bộ cờ đóng-để-exec để chúng tự động bị đóng bởi bất kỳ hàm nào trong số các hàm 'exec'-family. –

+0

glibc hiện đại hỗ trợ "e" nhân vật stdio.h FILE * mở cờ để chỉ điều trị FD_CLOEXEC. – fche

Trả lời

61

Trong khi di động, đóng tất cả các bộ mô tả tệp lên đến sysconf(_SC_OPEN_MAX) không đáng tin cậy, vì trên hầu hết các hệ thống, cuộc gọi này trả về giới hạn mềm mô tả tệp hiện tại, có thể được hạ thấp xuống dưới mô tả tệp được sử dụng cao nhất. Một vấn đề khác là trên nhiều hệ thống sysconf(_SC_OPEN_MAX) có thể trả về INT_MAX, điều này có thể khiến phương pháp này không thể chấp nhận chậm. Thật không may, không có thay thế đáng tin cậy, di động nào không liên quan đến việc lặp qua tất cả các bộ mô tả tập tin int không âm.

Mặc dù không di động, hầu hết các hệ điều hành được sử dụng phổ biến hiện nay cung cấp một hoặc nhiều trong những giải pháp sau đây để vấn đề này:

  1. Một chức năng thư viện để gần mô tả tất cả các tập tin> = fd. Đây là giải pháp đơn giản nhất cho trường hợp phổ biến của việc đóng tất cả các bộ mô tả tập tin, mặc dù nó không thể được sử dụng cho nhiều thứ khác. Để đóng tất cả các bộ mô tả tập tin ngoại trừ một tập hợp nhất định, dup2 có thể được sử dụng để di chuyển chúng xuống mức thấp trước và sau đó di chuyển chúng trở lại nếu cần.

    • closefrom(fd) (Solaris 9 hoặc mới hơn, FreeBSD 7.3 hoặc 8.0 và sau, NetBSD 3.0 hoặc mới hơn, OpenBSD 3,5 hoặc sau đó.)

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

  2. Chức năng thư viện để cung cấp mô tả tệp tối đa hiện đang được sử dụng bởi quy trình.Để đóng tất cả các bộ mô tả tập tin ở trên một số nào đó, hãy đóng tất cả chúng lên đến mức tối đa này, hoặc liên tục nhận và đóng bộ mô tả tệp cao nhất trong vòng lặp cho đến khi đạt tới giới hạn thấp. Đó là hiệu quả hơn phụ thuộc vào mật độ mô tả tập tin.

    • fcntl(0, F_MAXFD) (NetBSD)

    • pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
      Returns thông tin về quá trình, trong đó có mô tả tập tin cao nhất hiện đang mở trong ps.pst_highestfd. (HP-UX)

  3. Một thư mục chứa một mục nhập cho mỗi bộ mô tả tập tin mở. Đây là cách tiếp cận linh hoạt nhất vì nó cho phép đóng tất cả các bộ mô tả tập tin, tìm bộ mô tả tập tin cao nhất, hoặc làm bất cứ điều gì khác trên mọi bộ mô tả tập tin mở, thậm chí là của một quá trình khác (trên hầu hết các hệ thống). Tuy nhiên, điều này có thể phức tạp hơn các cách tiếp cận khác cho việc sử dụng thông thường. Ngoài ra, nó có thể thất bại vì nhiều lý do như proc/fdescfs không được gắn kết, môi trường chroot hoặc không có bộ mô tả tệp nào để mở thư mục (quá trình hoặc giới hạn hệ thống). Do đó việc sử dụng phương pháp này thường được kết hợp với một cơ chế dự phòng. Example (OpenSSH), another example (glib).

    • /proc/pid/fd/ hoặc /proc/self/fd/ (Linux, Solaris, AIX, Cygwin, NetBSD)
      (AIX không hỗ trợ "self")

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

    Có thể khó xử lý tất cả các trường hợp góc một cách đáng tin cậy với phương pháp này. Ví dụ xem xét các tình huống mà tất cả các file descriptor> = fd đang được đóng lại, nhưng tất cả các file descriptor < fd được sử dụng, giới hạn tài nguyên quá trình hiện nay là fd, và có những file descriptor> = fd đang sử dụng. Bởi vì giới hạn tài nguyên quy trình đã đạt đến thư mục không thể mở được. Nếu đóng mỗi bộ mô tả tệp từ fd thông qua giới hạn tài nguyên hoặc sysconf(_SC_OPEN_MAX) được sử dụng làm dự phòng, không có gì sẽ bị đóng.

+1

Về phương pháp 3: có những vấn đề nghiêm trọng trong việc sử dụng này giữa ngã ba/exec trong một chương trình đa luồng vì opendir() có thể gọi malloc() có thể bế tắc trong tình huống này. Tôi e rằng không có cách nào để làm những gì câu hỏi được hỏi trong Linux, và các nhà phát triển sẽ không làm một điều về nó: https://sourceware.org/bugzilla/show_bug.cgi?id=10353 – medoc

+0

@ Medoc: phát triển glibc trải qua một sự cải tổ lớn trong năm 2012, và một vài điều trước đó từ chối nay đã thực hiện nó trong theo mô hình phát triển mới. Nó có thể là đáng giá để bắt đầu một cuộc thảo luận mới về vấn đề này. – mark4o

-2

Tại sao bạn không đóng tất cả các mô tả từ 0 đến, nói, 10000.

Nó sẽ là khá nhanh, và điều tồi tệ nhất có thể xảy ra là EBADF.

+0

Sẽ làm việc, nhưng bạn sẽ phải thực hiện mà có thể cấu hình, như bạn chỉ đơn giản là không biết có bao nhiêu cần phải được đóng lại (phụ thuộc vào tải). –

12

Cách POSIX là:

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

(lưu ý đó là đóng cửa từ 3 lên, để giữ cho stdin/stdout/stderr mở)

close() không trúng trả EBADF nếu mô tả tập tin không mở cửa . Không cần phải lãng phí một cuộc gọi kiểm tra hệ thống khác.

Một số Unix hỗ trợ cận cảnh(). Điều này tránh số lượng cuộc gọi quá nhiều để đóng() tùy thuộc vào số mô tả tệp tối đa có thể có. Trong khi giải pháp tốt nhất mà tôi biết, nó hoàn toàn không thể di chuyển được.

5

Tôi đã viết mã để xử lý tất cả các tính năng dành riêng cho nền tảng. Tất cả các chức năng đều an toàn-tín hiệu an toàn. Nghĩ rằng mọi người có thể thấy điều này hữu ích. Chỉ được thử nghiệm trên OS X ngay bây giờ, hãy cải thiện/sửa lỗi.

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

Ngay khi chương trình của bạn bắt đầu và chưa mở bất kỳ thứ gì. Ví dụ. như bắt đầu chính(). ống và nĩa ngay lập tức bắt đầu một máy chủ executer. Bằng cách này, nó là bộ nhớ và các chi tiết khác là sạch sẽ và bạn chỉ có thể cung cấp cho nó những thứ để ngã ba & 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); 
} 

Nếu bạn muốn thực hiện IO trên chương trình thực thi, máy chủ thực thi sẽ phải thực hiện chuyển hướng ổ cắm và bạn có thể sử dụng ổ cắm unix.

Các vấn đề liên quan