Deskripsi di open(2)
halaman manual memberikan beberapa petunjuk untuk memulai dengan:
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.
Terkadang, kami tidak ingin membuka file atau direktori. Sebagai gantinya, kami hanya ingin referensi ke objek sistem file itu untuk melakukan operasi tertentu (misalnya, ke fchdir()
direktori yang dirujuk oleh deskriptor file yang kami buka menggunakan O_PATH
). Jadi, titik sepele: jika ini adalah tujuan kita, maka pembukaan dengan O_PATH
harus sedikit lebih murah, karena file itu sendiri sebenarnya tidak dibuka.
Dan poin yang kurang sepele: sebelum keberadaan O_PATH
, cara mendapatkan referensi seperti itu ke objek sistem file adalah dengan membuka objek O_RDONLY
. Tetapi penggunaan O_RDONLY
mengharuskan kita telah membaca izin pada objek. Namun, ada berbagai kasus penggunaan di mana kita tidak perlu membaca objek: misalnya, menjalankan biner atau mengakses direktori ( fchdir()
) atau menjangkau melalui direktori untuk menyentuh objek di dalam direktori.
Penggunaan dengan panggilan sistem "* at ()"
Umum, tetapi bukan satu-satunya, penggunaan O_PATH
adalah untuk membuka direktori, untuk memiliki referensi ke direktori untuk digunakan dengan "* di" panggilan sistem, seperti openat()
, fstatat()
, fchownat()
, dan sebagainya. Ini keluarga panggilan sistem, yang kita kira-kira bisa anggap sebagai penerus modern untuk panggilan sistem yang lebih tua dengan nama yang mirip ( open()
, fstat()
, fchown()
, dan sebagainya), melayani beberapa tujuan, yang pertama Anda menyentuh ketika Anda meminta " mengapa saya ingin menggunakan deskriptor file alih-alih jalur direktori? " Jika kita melihat lebih jauh ke bawah di open(2)
halaman manual, kita menemukan teks ini (di bawah judul dengan alasan untuk panggilan sistem "* at"):
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().
Untuk membuat ini lebih konkret ... Misalkan kita memiliki program yang ingin melakukan beberapa operasi di direktori selain direktori kerjanya saat ini, artinya kita harus menentukan beberapa awalan direktori sebagai bagian dari nama file yang kita gunakan. Misalkan, misalnya, bahwa nama pathnya /dir1/dir2/file
dan kami ingin melakukan dua operasi:
- Lakukan beberapa pemeriksaan
/dir1/dir2/file
(misalnya, siapa yang memiliki file, atau jam berapa terakhir kali diubah).
- Jika kita puas dengan hasil pemeriksaan itu, mungkin kita kemudian ingin melakukan beberapa operasi sistem file lain di direktori yang sama, misalnya, membuat file bernama
/dir1/dir2/file.new
.
Sekarang, misalkan kita melakukan semuanya menggunakan panggilan sistem berbasis pathname tradisional:
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 */
}
Sekarang, lebih jauh lagi anggaplah bahwa dalam awalan direktori /dir1/dir2
salah satu komponen (katakanlah dir2
) sebenarnya adalah tautan simbolis (yang merujuk ke direktori), dan bahwa antara panggilan ke stat()
dan panggilan keopen()
orang jahat dapat mengubah target dari tautan simbolis dir2
untuk menunjuk ke direktori yang berbeda. Ini adalah kondisi balapan waktu-of-check-of-use klasik. Program kami memeriksa file di satu direktori tetapi kemudian tertipu untuk membuat file di direktori yang berbeda - mungkin direktori yang sensitif terhadap keamanan. Poin kunci di sini adalah bahwa nama path /dir/dir2
tampak sama, tetapi apa yang dirujuknya berubah sepenuhnya.
Kita dapat menghindari masalah semacam ini menggunakan panggilan "* at". Pertama-tama, kami memperoleh pegangan yang merujuk ke direktori tempat kami akan melakukan pekerjaan kami:
dirfd = open("/dir/dir2", O_PATH);
Titik penting di sini adalah bahwa dirfd
adalah stabil referensi ke direktori yang disebut oleh jalan /dir1/dir2
pada saat open()
panggilan. Jika target tautan simbolik dir2
kemudian diubah, ini tidak akan memengaruhi apa yang dirfd
dimaksud. Sekarang, kita dapat melakukan operasi pemeriksaan + menggunakan panggilan "* at" yang setara dengan stat()
dan open()
panggilan di atas:
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 */
}
Selama langkah-langkah ini setiap manipulasi tautan simbolik dalam pathname tidak /dir/dir2
akan berdampak: check ( fstatat()
) dan operasi ( openat()
) dijamin akan terjadi di direktori yang sama.
Ada tujuan lain untuk menggunakan panggilan "* at ()", yang berhubungan dengan gagasan "direktori kerja saat ini per-thread" dalam program multithreaded (dan sekali lagi kita bisa membuka direktori menggunakan O_PATH
), tapi saya pikir penggunaan ini mungkin kurang relevan dengan pertanyaan Anda, dan saya meninggalkan Anda untuk membaca open(2)
halaman manual jika Anda ingin tahu lebih banyak.
Penggunaan dengan deskriptor file untuk file biasa
Salah satu penggunaan O_PATH
dengan file biasa adalah membuka biner yang izinnya kami jalankan (tetapi tidak harus membaca izin, sehingga kami tidak dapat membuka file itu dengan O_RDONLY
). Deskriptor file itu kemudian dapat diteruskan ke fexecve(3)
untuk menjalankan program. Semua yang fexecve(fd, argv, envp)
dilakukan dengan fd
argumennya pada dasarnya adalah:
snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);
(Meskipun, dimulai dengan glibc 2.27, implementasi akan menggunakan execveat(2)
system call, pada kernel yang menyediakan system call itu.)