Proses yang bersiap menjalankan execve() sering memerlukan invariant sederhana: descriptor di atas allowlist kecil tidak boleh bertahan ke program baru. Menutup nomor descriptor satu per satu mengubah invariant tersebut menjadi masalah enumerasi. Linux close_range() menyatakannya langsung sebagai operasi pada interval inklusif di tabel file descriptor milik task pemanggil.

Interface ini khusus Linux. Perilakunya berasal dari semantik file table dan system call Linux, bukan dari bahasa C atau jaminan POSIX yang portabel.

Range bersifat numerik, bukan berbasis object

Pemanggilan dasar menutup descriptor terbuka yang nomornya berada di antara first dan last, termasuk kedua batas:

if (close_range(3, ~0U, 0) == -1)
    /* tangani error pada level pemanggilan */;

Pemakaian ~0U sebagai last menghilangkan kebutuhan mencari nomor descriptor maksimum saat ini. Slot yang sudah tertutup di dalam interval tidak memerlukan penanganan khusus. Kernel saat ini mengabaikan error yang terkait dengan penutupan descriptor individual; return value melaporkan error dari operasi range itu sendiri.

Kontrak tersebut berbeda dari iterasi melalui /proc/self/fd. Enumerasi direktori membuat descriptor sendiri, mengamati view yang dapat berubah ketika thread lain memodifikasi tabel, dan bergantung pada ketersediaan procfs. close_range() tidak meminta userspace membangun snapshot nomor descriptor yang terbuka.

Pendekatan ini juga berbeda dari loop yang dibatasi RLIMIT_NOFILE. Resource limit bukan daftar descriptor yang sedang terbuka, sedangkan kebijakan “semua dari 3 ke atas” tidak memerlukan pencarian setiap slot yang terisi.

CLOSE_RANGE_CLOEXEC menunda penutupan sampai exec

Dengan CLOSE_RANGE_CLOEXEC, pemanggilan menetapkan close-on-exec pada descriptor dalam interval, bukan langsung menutupnya. Descriptor tetap dapat dipakai program saat ini sampai execve() yang berhasil menutupnya.

if (close_range(3, ~0U, CLOSE_RANGE_CLOEXEC) == -1)
    /* tangani error */;

/* setup yang masih dapat memakai descriptor tersebut */

execve(path, argv, envp);

Mekanisme ini memisahkan dua event yang sering dicampur: menetapkan descriptor mana yang boleh melewati batas exec dan menghancurkan descriptor pada image proses saat ini. Jika execve() gagal, descriptor bertanda close-on-exec tetap terbuka karena transisi exec tidak berhasil.

Flag ini berguna ketika setup sebelum exec masih memerlukan descriptor. Program dapat menetapkan kebijakan inheritance lebih dahulu, menjalankan setup berikutnya, lalu membiarkan exec yang berhasil menerapkan penutupan. Ini adalah kebijakan descriptor, bukan jaminan bahwa kode setup arbitrer tidak dapat mengubah flag lagi.

Tabel descriptor bersama membentuk batas concurrency

Thread biasanya berbagi file descriptor table. Penutupan descriptor oleh satu thread karena itu dapat memengaruhi operasi pada thread lain. Urutan pembersihan sebelum exec yang memodifikasi tabel bersama dapat berkonflik dengan pekerjaan concurrent.

CLOSE_RANGE_UNSHARE mengubah batas tersebut. Linux terlebih dahulu melakukan unshare pada descriptor table untuk task pemanggil, kemudian menerapkan operasi range. Secara konseptual, pemanggil memisahkan tabel descriptor-nya dari task lain yang sedang membagikannya sebelum pembersihan.

if (close_range(3, ~0U, CLOSE_RANGE_UNSHARE) == -1)
    /* tangani error */;

Jaminan ini terbatas pada descriptor table. Mekanisme tersebut tidak menghentikan thread lain, menserialisasi state aplikasi, atau membuat seluruh urutan sebelum exec menjadi atomik. Resource bersama lainnya tetap mengikuti aturan masing-masing.

Linux dapat mengoptimalkan kasus ini ketika batas atas melampaui descriptor table yang dialokasikan: tabel baru dapat dibangun hanya untuk prefix yang harus dipertahankan. Kode sebaiknya bergantung pada efek yang dispesifikasikan, bukan pada strategi penyalinan internal tertentu.

Unshare dan close-on-exec menyatakan kebijakan berbeda

Kedua flag menangani perhatian yang berbeda. CLOSE_RANGE_CLOEXEC mengatur apa yang terjadi pada descriptor terpilih saat exec berhasil. CLOSE_RANGE_UNSHARE mengatur apakah pemanggil terlebih dahulu memisahkan descriptor table dari task yang saat ini membagikannya.

Desain yang membutuhkan kedua aspek dapat menggabungkan flag yang didukung. Semantik akhirnya tetap perlu memperhitungkan pembuatan descriptor setelah pemanggilan. Descriptor yang dibuka sesudah operasi range tidak otomatis tercakup hanya karena nomornya berada dalam interval sebelumnya.

Batas waktu tersebut penting pada kode sebelum exec yang multithread atau banyak memakai callback. Pembersihan range menetapkan properti tabel pada saat pemanggilan; operasi itu bukan aturan permanen bagi alokasi descriptor berikutnya.

Close-on-exec tetap merupakan flag per descriptor

FD_CLOEXEC terkait dengan file descriptor, bukan dengan open file description yang mendasarinya. Descriptor hasil duplikasi karena itu dapat memiliki state close-on-exec yang berbeda. close_range(..., CLOSE_RANGE_CLOEXEC) menerapkan flag pada entry descriptor dalam interval numerik terpilih.

Perbedaan ini mencegah asumsi keliru bahwa penandaan satu descriptor mengubah semua duplikat yang merujuk open file description sama. Operasi range berorientasi pada tabel: entry dipilih berdasarkan nomor descriptor.

Batas yang sama menjelaskan perannya sebelum exec. Kernel mengevaluasi state close-on-exec pada entry descriptor selama transisi exec yang berhasil. State open file yang dibagi melalui duplikat memiliki semantik lifetime terpisah.

Operasi range mempersempit kontrak inheritance

Masalah inheritance descriptor sering muncul dari kontrak implisit: setiap descriptor yang tidak sengaja ditutup dapat melewati batas ke program baru. close_range() memungkinkan kontrak dinyatakan dari arah sebaliknya. Sekumpulan kecil nomor rendah dapat dipertahankan, sementara interval yang memuat seluruh nomor lebih tinggi ditutup atau ditandai close-on-exec.

Mekanisme ini tetap tidak menggantikan pembuatan descriptor yang cermat. API seperti open() dengan O_CLOEXEC dan pemanggilan pembuat descriptor dengan flag atomik setara tetap penting karena menetapkan state inheritance pada saat pembuatan, tanpa interval terpisah untuk memasang flag.

Kedua mekanisme bekerja pada batas berbeda. Pembuatan atomik dengan close-on-exec mengontrol descriptor baru saat masuk ke tabel. close_range() menerapkan kebijakan pada interval yang sudah ada, secara opsional setelah memisahkan tabel bersama. Pemisahan batas ini menghasilkan kontrak exec yang presisi tanpa memperlakukan enumerasi descriptor sebagai state proses yang persisten.