Tôi nên sử dụng `O_PATH` để làm gì và làm thế nào?


8

Tôi sử dụng phân phối dựa trên Linux 4.x và gần đây tôi nhận thấy open()lệnh gọi hệ thống của kernel hỗ trợ O_PATHcờ mở.

Mặc dù mantrang dành cho nó có một danh sách các cuộc gọi hệ thống về mặt lý thuyết có thể được sử dụng, tôi không hiểu ý tưởng đó là gì. Tôi open(O_PATH)chỉ thư mục, chứ không phải tập tin? Và nếu tôi làm vậy, tại sao tôi muốn sử dụng một bộ mô tả tệp thay vì đường dẫn của thư mục? Ngoài ra, hầu hết các cuộc gọi hệ thống được liệt kê ở đó dường như không đặc biệt cho các thư mục; Vì vậy, tôi cũng mở các tệp thông thường O_PATHđể bằng cách nào đó lấy thư mục của họ làm mô tả tệp? Hoặc để có được một mô tả tập tin cho họ nhưng với chức năng hạn chế?

Ai đó có thể đưa ra một lời giải thích chung về những gì O_PATHvề và làm thế nào, và để làm gì, chúng ta nên sử dụng nó?

Ghi chú:

  • Không cần mô tả lịch sử phát triển của nó (các trang hướng dẫn liên quan đề cập đến các thay đổi trong Linux 2.6.x, 3.5 và 3.6) trừ khi cần thiết - Tôi chỉ quan tâm đến việc mọi thứ hiện tại như thế nào.
  • Xin đừng nói với tôi là chỉ sử dụng libc hoặc các cơ sở cấp cao khác, tôi biết điều đó.


@sebasth: Nó thực sự có liên quan, nhưng: 1. Bây giờ nó hơi cũ và mọi thứ có thể đã thay đổi. 2. Thành thật mà nói, tôi không nhận được ý chính của câu trả lời.
einpoklum

1
Bạn có thể gửi bình luận trong câu hỏi đó hỏi xem có gì thay đổi không.
Barmar

Câu trả lời:


8

Mô tả trong open(2)trang man cung cấp một số manh mối để bắt đầu:

   O_PATH (since Linux 2.6.39)
          Obtain a file descriptor that can be used for two purposes:
          to  indicate  a location in the filesystem tree and to per‐
          form operations that act  purely  at  the  file  descriptor
          level.  The file itself is not opened, and other file oper‐
          ations  (e.g.,  read(2),  write(2),  fchmod(2),  fchown(2),
          fgetxattr(2), ioctl(2), mmap(2)) fail with the error EBADF.

Đôi khi, chúng tôi không muốn mở một tập tin hoặc một thư mục. Thay vào đó, chúng tôi chỉ muốn một tham chiếu đến đối tượng hệ thống tập tin đó để thực hiện các hoạt động nhất định (ví dụ: fchdir()đến một thư mục được gọi bởi một bộ mô tả tệp mà chúng tôi đã mở bằng cách sử dụng O_PATH). Vì vậy, một điểm tầm thường: nếu đây là mục đích của chúng tôi, thì việc mở bằng O_PATHsẽ rẻ hơn một chút, vì bản thân tệp không thực sự được mở.

Và một điểm ít tầm thường hơn: trước khi tồn tại O_PATH, cách để có được một tham chiếu như vậy đến một đối tượng hệ thống tập tin là mở đối tượng với O_RDONLY. Nhưng việc sử dụng O_RDONLYđòi hỏi chúng ta phải đọc quyền trên đối tượng. Tuy nhiên, có nhiều trường hợp sử dụng khác nhau mà chúng ta không thực sự cần phải đọc đối tượng: ví dụ: thực thi nhị phân hoặc truy cập một thư mục ( fchdir()) hoặc truy cập qua một thư mục để chạm vào một đối tượng bên trong thư mục.

Sử dụng với các cuộc gọi hệ thống "* at ()"

Phổ biến, nhưng không phải là duy nhất, sử dụng O_PATHlà để mở một thư mục, để có một tham chiếu đến thư mục đó để sử dụng với "* tại" cuộc gọi hệ thống, chẳng hạn như openat(), fstatat(), fchownat(), và vân vân. This family các cuộc gọi hệ thống, mà chúng tôi đại khái có thể nghĩ đến là các kế hiện đại với các cuộc gọi hệ thống cũ với tên tương tự ( open(), fstat(), fchown(), và vân vân), phục vụ cho một vài mục đích, là người đầu tiên trong số đó bạn chạm vào khi bạn hỏi " tại sao tôi muốn sử dụng một bộ mô tả tập tin thay vì đường dẫn của thư mục? ". Nếu chúng ta nhìn sâu hơn vào open(2)trang man, chúng ta sẽ tìm thấy văn bản này (dưới tiêu đề phụ với lý do cho các cuộc gọi hệ thống "* tại"):

   First,  openat()  allows  an  application to avoid race conditions
   that could occur when using open() to open  files  in  directories
   other  than  the current working directory.  These race conditions
   result from the fact that some component of the  directory  prefix
   given  to  open()  could  be  changed in parallel with the call to
   open().  Suppose, for example, that we wish  to  create  the  file
   path/to/xxx.dep  if  the  file path/to/xxx exists.  The problem is
   that between the existence check and the file creation step,  path
   or  to  (which might be symbolic links) could be modified to point
   to a different location.  Such races can be avoided by  opening  a
   file descriptor for the target directory, and then specifying that
   file descriptor as the dirfd argument of (say) fstatat(2) and ope‐
   nat().

Để làm cho điều này cụ thể hơn ... Giả sử chúng ta có một chương trình muốn thực hiện nhiều thao tác trong một thư mục khác với thư mục làm việc hiện tại của nó, nghĩa là chúng ta phải chỉ định một số tiền tố thư mục là một phần của tên tệp mà chúng ta sử dụng. Ví dụ, giả sử rằng tên đường dẫn là /dir1/dir2/filevà chúng tôi muốn thực hiện hai thao tác:

  1. Thực hiện một số kiểm tra /dir1/dir2/file(ví dụ: ai sở hữu tệp hoặc thời gian sửa đổi lần cuối).
  2. Nếu chúng tôi hài lòng với kết quả của kiểm tra đó, có lẽ sau đó chúng tôi muốn thực hiện một số thao tác hệ thống tệp khác trong cùng thư mục, ví dụ: tạo một tệp có tên /dir1/dir2/file.new.

Bây giờ, trước tiên, giả sử chúng tôi đã làm mọi thứ bằng cách sử dụng các cuộc gọi hệ thống dựa trên tên đường truyền thống:

struct stat stabuf;
stat("/dir1/dir2/file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
    fd = open("/dir1/dir2/file.new", O_CREAT | O_RDWR, 0600);
    /* And then populate file referred to by fd */
}

Bây giờ, hơn nữa giả sử rằng trong tiền tố thư mục, /dir1/dir2một trong các thành phần (giả sử dir2) thực sự là một liên kết tượng trưng (liên quan đến một thư mục) và giữa cuộc gọi đến stat()và cuộc gọi đếnopen() người độc hại có thể thay đổi mục tiêu của liên kết tượng trưng dir2để trỏ đến một thư mục khác. Đây là một điều kiện đua thời gian cổ điển kiểm tra thời gian sử dụng. Chương trình của chúng tôi đã kiểm tra một tệp trong một thư mục nhưng sau đó bị lừa tạo một tệp trong một thư mục khác - có lẽ là thư mục nhạy cảm với bảo mật. Điểm mấu chốt ở đây là tên đường dẫn /dir/dir2trông giống nhau, nhưng những gì nó đề cập đã thay đổi hoàn toàn.

Chúng ta có thể tránh các loại vấn đề này bằng cách sử dụng các cuộc gọi "* tại". Trước hết, chúng tôi có được một điều khiển tham chiếu đến thư mục nơi chúng tôi sẽ thực hiện công việc của mình:

dirfd = open("/dir/dir2", O_PATH);

Điểm quan trọng ở đây là dirfdmột tham chiếu ổn định đến thư mục được tham chiếu bởi đường dẫn /dir1/dir2tại thời điểm open()cuộc gọi. Nếu mục tiêu của liên kết tượng trưng dir2sau đó được thay đổi, điều này sẽ không ảnh hưởng đến những gì dirfdđề cập đến. Bây giờ, chúng ta có thể thực hiện thao tác kiểm tra + bằng cách sử dụng các cuộc gọi "* tại" tương đương với các cuộc gọi stat()open()cuộc gọi ở trên:

fstatat(dirfd, ""file", &statbuf)
struct stat stabuf;
fstatat(dirfd, "file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
    fd = openat(dirfd, "file.new", O_CREAT | O_RDWR, 0600);
    /* And then populate file referred to by fd */
}

Trong các bước này, mọi thao tác liên kết tượng trưng trong tên đường dẫn /dir/dir2sẽ không có tác động: kiểm tra ( fstatat()) và thao tác ( openat()) được đảm bảo diễn ra trong cùng một thư mục.

Có một mục đích khác để sử dụng các cuộc gọi "* at ()", liên quan đến ý tưởng "thư mục làm việc hiện tại trên mỗi luồng" trong các chương trình đa luồng (và một lần nữa chúng ta có thể mở các thư mục bằng cách sử dụng O_PATH), nhưng tôi nghĩ rằng việc sử dụng này có lẽ là ít liên quan đến câu hỏi của bạn và tôi để bạn đọc open(2)trang người đàn ông nếu bạn muốn biết thêm.

Sử dụng với mô tả tệp cho các tệp thông thường

Một cách sử dụng O_PATHvới các tệp thông thường là mở tệp nhị phân mà chúng tôi có quyền thực thi (nhưng không nhất thiết phải đọc quyền, do đó chúng tôi không thể mở tệp với O_RDONLY). Mô tả tập tin đó sau đó có thể được thông qua fexecve(3)để thực hiện chương trình. Tất cả những fexecve(fd, argv, envp)gì đang làm với fdđối số của nó về cơ bản là:

snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);

(Mặc dù, bắt đầu với glibc 2.27, việc triển khai thay vào đó sẽ sử dụng execveat(2)lệnh gọi hệ thống, trên các hạt nhân cung cấp lệnh gọi hệ thống đó.)


The problem is that between the existence check and the file creation step, path or to ... could be modified - không thể phân tích câu này. Nhưng tôi nghĩ ý chính của nó, tôi nghĩ vậy. Vì vậy, nó phục vụ như một loại cơ chế khóa trên một thư mục. Nhưng tại sao sử dụng open()kết quả chứ không phải là một khóa thực tế?
einpoklum

@einpoklum vấn đề là 'đường dẫn' và 'đến' không có định dạng được hiển thị trong trang man gốc. Đây là các thành phần của tên đường dẫn giả định "/ path / to / xxx". Và, nó không giống như một khóa: đó là một tham chiếu ổn định đến một đối tượng hệ thống tập tin; một số chương trình có thể có tham chiếu như vậy đến cùng một đối tượng.
mtk
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.