Di sistem operasi Unix, setiap berkas membawa daftar izin yang menentukan siapa boleh membaca, menulis, atau mengeksekusinya — dan PHP berjalan di bawah satu akun pengguna tertentu yang tunduk pada aturan itu. Ketika fopen() gagal tanpa alasan yang jelas, atau berkas upload tidak bisa dibaca oleh web server, hampir selalu permission-lah biang keladinya. Pada chapter ini kita akan membedah anatomi permission Unix, membacanya lewat fileperms(), mengubahnya lewat chmod(), memeriksa kelayakan operasi lewat is_readable() dan is_writable(), serta memahami bagaimana umask membentuk permission berkas baru. Ditutup dengan pembahasan konvensi aman — dan alasan mengapa 0777 adalah ide buruk.
Anatomi Permission Unix #
Permission sebuah berkas terbagi ke dalam tiga kelas subjek: owner (pemilik berkas), group (kelompok pengguna), dan other (semua pihak lainnya). Setiap kelas mendapat tiga bit izin: r untuk baca, w untuk tulis, dan x untuk eksekusi. Notasi oktal merangkum kesembilan bit itu menjadi tiga digit — setiap digit adalah penjumlahan nilai r=4, w=2, x=1 — sehingga rw- menjadi 6, r-x menjadi 5, dan rwx menjadi 7:
| Bit | Nilai oktal | Owner | Group | Other |
|---|---|---|---|---|
rwxr-xr-x | 0755 | baca, tulis, eksekusi | baca, eksekusi | baca, eksekusi |
rw-r--r-- | 0644 | baca, tulis | baca | baca |
rw------- | 0600 | baca, tulis | tanpa izin | tanpa izin |
rwx------ | 0700 | semua izin | tanpa izin | tanpa izin |
Dua konvensi menyusun sebagian besar sistem: berkas biasa memakai 0644 (pemilik boleh menulis, semua orang boleh membaca), sedangkan direktori dan skrip yang dieksekusi memakai 0755 (ditambah bit eksekusi untuk semua pihak). Bit x pada direktori memiliki makna khusus: izin untuk menelusuri masuk (membaca isi) direktori tersebut. Angka nol di depan seperti pada 0644 bukan hiasan — pada kode PHP, angka yang diawali 0 adalah literal oktal, dan inilah satu-satunya cara menuliskan permission dengan benar.
Membaca Permission dengan fileperms #
Fungsi fileperms() mengembalikan seluruh bit status berkas sebagai integer, termasuk bit jenis berkas di posisi tinggi — 100 di depan menandakan berkas reguler, 40 menandakan direktori. Karena itu, untuk melihat permission murni kita biasanya menyaringnya dengan operasi bitwise & 0777:
<?php
$berkas = __DIR__ . '/dokumen.txt';
file_put_contents($berkas, "isi dokumen\n");
echo "Nilai mentah fileperms: " . sprintf('%o', fileperms($berkas)) . "\n";
echo "Hanya bit permission: " . sprintf('%04o', fileperms($berkas) & 0777) . "\n";
unlink($berkas);Output yang Dihasilkan:
Nilai mentah fileperms: 100664
Hanya bit permission: 0664Nilai mentah 100664 pada baris pertama output terbaca sebagai: 100 (berkas reguler) diikuti 664 (permission). Format %o pada sprintf() mencetak integer dalam basis oktal, dan format %04o memaksa empat digit agar angka seperti 644 tampil sebagai 0644. Perlu diketahui bahwa permission 0664 — bukan 0644 — muncul karena umask lingkungan eksekusi chapter ini bernilai 0002; hubungan ini kita bedah pada bagian khusus di bawah.
Mengubah Permission dengan chmod #
Fungsi chmod() menerima path dan permission oktal baru, lalu mengubah permission berkas di sistem operasi. Hanya owner berkas (atau superuser) yang boleh melakukan chmod(). Contoh berikut menunjukkan efek mencabut seluruh izin, lalu memberikannya kembali hanya untuk pemilik:
<?php
$berkas = __DIR__ . '/rahasia.txt';
file_put_contents($berkas, "data rahasia\n");
chmod($berkas, 0000);
clearstatcache();
var_dump(is_readable($berkas), is_writable($berkas));
chmod($berkas, 0600);
clearstatcache();
var_dump(is_readable($berkas), is_writable($berkas));
unlink($berkas);Output yang Dihasilkan:
bool(false)
bool(false)
bool(true)
bool(true)Pada panggilan pertama, chmod($berkas, 0000) mencabut seluruh izin dari semua kelas — hasilnya is_readable() dan is_writable() sama-sama false, karena PHP berjalan sebagai pengguna biasa yang kini tidak punya hak apa pun atas berkas itu. Panggilan kedua memulihkan 0600: hanya pemilik yang boleh baca-tulis, dan kedua pemeriksaan kembali true. Perhatikan pemanggilan clearstatcache() setelah setiap chmod(): PHP menyimpan hasil panggilan stat sebelumnya demi kecepatan, dan cache tersebut harus dibersihkan agar pemeriksaan berikutnya melihat kondisi berkas yang baru.
Menghapus berkas tetap berhasil di akhir contoh meskipun permission sempat 0000, karena hak menghapus (unlink) dikendalikan oleh permission direktori, bukan berkasnya — dan owner juga selalu berhak melakukan chmod() atas berkas miliknya, itulah mengapa pemulihan 0600 tidak tersangkut.
Memeriksa Permission dengan is_readable, is_writable, is_executable #
Sebelum operasi penting, tiga fungsi pemeriksa menjawab pertanyaan “apakah PHP (dengan pengguna yang menjalankannya sekarang) boleh melakukan operasi ini?”: is_readable(), is_writable(), dan is_executable(). Berikut ketiganya beraksi pada berkas yang dibuat baru, sebelum dan sesudah diberi bit eksekusi:
<?php
$skrip = __DIR__ . '/alat.php';
file_put_contents($skrip, "<?php echo 'alat siap';\n");
printf("bisa dibaca: %s\n", is_readable($skrip) ? 'ya' : 'tidak');
printf("bisa ditulis: %s\n", is_writable($skrip) ? 'ya' : 'tidak');
printf("bisa dieksekusi: %s\n", is_executable($skrip) ? 'ya' : 'tidak');
chmod($skrip, 0755);
clearstatcache();
printf("bisa dieksekusi setelah chmod 0755: %s\n", is_executable($skrip) ? 'ya' : 'tidak');
unlink($skrip);Output yang Dihasilkan:
bisa dibaca: ya
bisa ditulis: ya
bisa dieksekusi: tidak
bisa dieksekusi setelah chmod 0755: yaBerkas baru dengan permission 0664 langsung bisa dibaca dan ditulis, tetapi tidak bisa dieksekusi — bit x memang tidak diberikan oleh permission dasar 0666 yang dijelaskan pada chapter Menulis File. Setelah chmod($skrip, 0755), bit eksekusi menyala untuk semua kelas dan is_executable() menjadi ya. Perlu diingat bahwa untuk benar-benar menjalankan skrip PHP dari terminal tetap butuh interpreter, misalnya php alat.php; bit x hanya membuka jalan bagi sistem operasi untuk mengeksekusi berkas, misalnya lewat baris shebang #!/usr/bin/env php di baris pertama skrip.
Efek umask terhadap File Baru #
Kita telah melihat bahwa berkas baru tidak selalu mendapat permission 0644. Penjelasannya adalah umask: nilai oktal yang menentukan bit mana yang otomatis dicabut dari permission dasar setiap kali berkas (dasar 0666) atau direktori (dasar 0777) dibuat. Fungsi umask() tanpa argumen membaca nilai saat ini, dan memanggilnya dengan argumen mengganti nilai tersebut untuk proses PHP yang sedang berjalan:
<?php
$berkas = __DIR__ . '/konfig.txt';
echo "umask awal: " . sprintf('%04o', umask()) . "\n";
file_put_contents($berkas, "isi\n");
echo "Berkas baru: " . sprintf('%04o', fileperms($berkas) & 0777) . "\n";
unlink($berkas);
umask(0022);
echo "umask setelah diubah: " . sprintf('%04o', umask()) . "\n";
file_put_contents($berkas, "isi\n");
echo "Berkas baru: " . sprintf('%04o', fileperms($berkas) & 0777) . "\n";
unlink($berkas);Output yang Dihasilkan:
umask awal: 0002
Berkas baru: 0664
umask setelah diubah: 0022
Berkas baru: 0644Dengan umask 0002, permission dasar 0666 kehilangan hanya bit tulis milik other, menghasilkan 0664. Setelah umask diubah menjadi 0022, bit tulis dicabut dari group sekaligus other, dan berkas baru lahir dengan permission 0644 yang klasik. Perubahan umask lewat umask(0022) hanya berlaku di dalam proses PHP tersebut — umask adalah warisan lingkungan, bukan pengaturan PHP, sehingga nilai bawaannya bisa berbeda antara terminal, web server, dan container.
Memeriksa Permission Sebelum Operasi #
Menggabungkan pemeriksaan izin ke dalam alur kerja membuat kegagalan berkas bisa ditangani dengan pesan yang jelas alih-alih Warning mentah dari fopen(). Pola berikut memeriksa keberadaan dan kebolehan menulis sebelum menyentuh berkas:
<?php
$berkas = __DIR__ . '/laporan.txt';
if (!is_file($berkas)) {
file_put_contents($berkas, "Laporan penjualan mingguan\n");
}
if (is_writable($berkas)) {
file_put_contents($berkas, "Diperbarui oleh Budi\n");
echo "Laporan berhasil diperbarui.\n";
} else {
echo "Berkas tidak dapat ditulis — periksa permission.\n";
}
unlink($berkas);Output yang Dihasilkan:
Laporan berhasil diperbarui.Pemeriksaan semacam ini paling bernilai di aplikasi yang berjalan sebagai pengguna berbeda-beda: skrip CLI yang dijalankan lewat SSH mungkin menjadi owner penuh, sementara proses web server yang sama hanya “other” atas berkas itu. Catatan penting: selalu ada celah waktu kecil antara pemeriksaan dan operasi (konsep TOCTOU — time-of-check to time-of-use), jadi perlakukan hasil is_writable() sebagai sinyal awal, tetapi tetap siapkan penanganan kegagalan pada operasi tulisnya sendiri.
Keamanan: Hindari 0777 #
Saat aplikasi gagal menulis karena permission, solusi yang paling sering diambil pemula adalah chmod($berkas, 0777) — memberi baca-tulis-eksekusi kepada semua pengguna di mesin itu. Tindakan ini menyelesaikan gejala dengan membuka pintu: berkas 0777 dapat ditulis oleh akun layanan lain, pengguna lain di server bersama, bahkan proses yang dibajak penyerang. Di lingkungan shared hosting, 0777 pada direktori upload praktis sama dengan mengundang penyerang menaruh skrip backdoor ke dalamnya.
Pendekatan yang benar adalah menyelaraskan kepemilikan: jalankan PHP (php-fpm atau web server) sebagai pengguna yang sama dengan pemilik direktori aplikasi, atau gunakan grup bersama dengan permission 0664/0775 bila kolaborasi antar-pengguna memang diperlukan. Direktori yang harus ditulis aplikasi — upload, cache, log — idealnya ditempatkan di luar document root dan dimiliki oleh pengguna proses PHP dengan permission 0755 atau 0775.
Ringkasan #
Permission Unix terbaca sebagai tiga digit oktal untuk owner, group, dan other, dengan nilai baca 4, tulis 2, dan eksekusi 1. fileperms() mengembalikannya (disaring dengan & 0777), chmod() mengubahnya dengan literal oktal, dan trio is_readable()/is_writable()/is_executable() memeriksa kelayakan operasi dari sudut pandang pengguna yang menjalankan PHP. umask membentuk permission berkas baru dengan mencabut bit dari dasar 0666/0777, dan konvensi minimal — 0644/0600 untuk berkas, 0755 untuk direktori, dan tidak pernah 0777 — adalah pondasi higienitas keamanan. Topik direktori yang barusan muncul di beberapa contoh menjadi fokus penuh chapter berikutnya: membuat, menelusuri, dan membersihkan direktori dengan PHP.