Ketika fopen mengembalikan NULL, pertanyaan pertama kita adalah “kenapa?”. File tidak ada? Tidak ada izin? Path terlalu panjang? Disk penuh? Jawabannya ada di variabel global errno yang diset oleh system call yang gagal, dan pesan teks yang mudah dibaca bisa didapat dengan perror atau strerror. Pada chapter ini kita akan membuka file di path yang sengaja tidak ada, melihat bagaimana errno diisi, dan mengapa kita wajib menyimpannya segera sebelum memanggil fungsi lain. Pemahaman ini krusial untuk menulis program yang memberikan pesan error berguna kepada pengguna.
Kontrak errno, perror, strerror #
ISO/IEC 9899 §7.5 mendefinisikan errno sebagai makro yang berkembang menjadi lvalue bertipe int modifiable. Kontrak penting:
errnotidak pernah di-reset ke0oleh library C — bila sebuah fungsi berhasil,errnotidak diubah. Karena itu tidak pernah periksaerrnountuk menentukan keberhasilan—selalu periksa return value fungsi.errnodiset ke nilai non-zero saat fungsi gagal — nilai spesifik bergantung pada kesalahan (lihat<errno.h>dan POSIX<errno.h>).perror(const char *s)mencetaks: <pesan error>\nkestderr, di mana<pesan error>diterjemahkan darierrnosaat ini.strerror(int errnum)mengembalikan string yang berisi pesan untuk kode error yang diberikan, tanpa mencetaknya—berguna bila kita ingin memformat pesan sendiri.
POSIX.1 menambahkan banyak kode errno standar: ENOENT (2, No such file or directory), EACCES (13, Permission denied), EEXIST (17, File exists), EBADF (9, Bad file descriptor), ENOSPC (28, No space left on device), dan seterusnya.
Pola Aman: Cek Return Value, Simpan errno #
Pola yang benar adalah:
- Periksa return value fungsi untuk mengetahui keberhasilan.
- Bila gagal, segera salin
errnoke variabel lokal (misalnyaint saved_errno = errno;). - Cetak pesan error dengan
perrorataustrerror(saved_errno).
Langkah ke-2 sangat penting karena banyak fungsi library (termasuk printf, perror, strerror sendiri) dapat mengubah errno secara internal. Tanpa penyimpanan, pesan yang kita dapat bisa tidak sesuai dengan kesalahan asli.
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
/* Demo 1: fopen gagal membuka file tidak ada */
errno = 0;
FILE *f = fopen("/nonexistent/path/file.txt", "r");
if (f == NULL) {
int saved = errno;
printf("fopen gagal, errno=%d\n", saved);
perror("fopen /nonexistent/path/file.txt");
printf("strerror: %s\n", strerror(saved));
}
if (f) fclose(f);
/* Demo 2: fwrite ke stream read-only */
f = fopen("/tmp/test_errno.txt", "w");
if (f) {
fputs("hello\n", f);
fclose(f);
}
f = fopen("/tmp/test_errno.txt", "r");
if (f) {
int rc = fputs("ini akan gagal\n", f);
if (rc == EOF) {
int saved = errno;
printf("\nfputs ke read-only stream gagal, errno=%d\n", saved);
perror("fputs");
}
fclose(f);
}
/* Demo 3: open(2) langsung dari POSIX */
errno = 0;
int fd = open("/tmp/nonexistent_dir_xyz/file.txt", O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
int saved = errno;
printf("\nopen(2) gagal, errno=%d\n", saved);
perror("open");
} else {
close(fd);
}
return 0;
}Output yang Dihasilkan:
fopen gagal, errno=2
fopen /nonexistent/path/file.txt: No such file or directory
strerror: No such file or directory
fputs ke read-only stream gagal, errno=9
fputs: Bad file descriptor
open(2) gagal, errno=2
open: No such file or directoryTiga demo di atas mengilustrasikan skenario error yang berbeda:
- Demo 1:
fopenmembuka path yang tidak ada di mode"r". Kernel mengembalikanENOENT(errno=2, No such file or directory). Pesan dariperrordanstrerrorkonsisten. - Demo 2: kita menulis ke file yang dibuka dengan mode
"r"(read-only).fputsgagal,errnodiisiEBADF(9, Bad file descriptor). Pesanperrordi sini adalah “Bad file descriptor” karena C standard library menggunakan file descriptor yang ditandai read-only. - Demo 3: kita memanggil system call
open(2)langsung untuk membuat file di direktori yang tidak ada. Kernel mengembalikanENOENT(errno=2).
Mari kita telaah baris-baris kode di atas:
- Baris 9 (
errno = 0;): kita reseterrnosebelum panggilan fungsi yang ingin diuji. Bilafopenberhasil,errnotetap0. Bila gagal,errnoakan diset kernel. (Catatan: standar C tidak menjaminerrnodi-reset pada keberhasilan—praktik reset manual sebelum pemanggilan adalah konvensi yang aman.) - Baris 12 (
int saved = errno;): penyimpananerrnoke variabel lokal. Ini krusial karenaprintfdi baris 13 dapat mengubaherrnosecara internal (misalnya bila buffer penuh). Tanpa penyimpanan,perrordi baris 14 mungkin melaporkan error yang berbeda. - Baris 14 (
perror("fopen /nonexistent/path/file.txt");):perrormencetak argumen +": "+ pesan error darierrnosaat ini +\nkestderr.perrorsendiri dapat mengubaherrno—karena itu kita pakaisavedpadastrerrordi baris 15. - Baris 26 (
int rc = fputs("ini akan gagal\n", f);):fputsmengembalikan non-negatif bila berhasil,EOFbila gagal. Karena stream dibuka dengan mode"r", kernel menolak operasi tulis. - Baris 35 (
int fd = open(..., O_WRONLY | O_CREAT, 0644);):open(2)adalah system call POSIX langsung (bukan C standard library). Mengembalikan file descriptor non-negatif bila berhasil,-1bila gagal denganerrnodiset. - Baris 39 (
perror("open");): kita menyimpanerrnodisavedlebih dulu (baris 38) untuk berjaga-jaga. Dalam demo iniperrorbenar melaporkan No such file or directory.
Bukti: perror Mengubah errno #
Untuk membuktikan bahwa perror (dan banyak fungsi lain) dapat mengubah errno, mari kita jalankan eksperimen sederhana: buka file yang tidak ada, lalu cetak errno sebelum dan sesudah perror.
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void) {
errno = 0;
FILE *f = fopen("/nonexistent/path/file.txt", "r");
if (f == NULL) {
int saved = errno;
printf("errno setelah fopen: %d\n", errno);
perror("perror pertama");
printf("errno setelah perror: %d\n", errno);
printf("strerror(saved_errno): %s\n", strerror(saved));
printf("strerror(errno): %s\n", strerror(errno));
}
if (f) fclose(f);
return 0;
}Output yang Dihasilkan:
perror pertama: No such file or directory
errno setelah fopen: 2
errno setelah perror: 22
strerror(saved_errno): No such file or directory
strerror(errno): Invalid argumentLihat output di atas. Setelah fopen gagal, errno=2 (No such file or directory). Tetapi setelah perror dipanggil, errno=22 (Invalid argument)—perror internal memanggil fungsi yang mengubah errno! Akibatnya, strerror(errno) di baris terakhir melaporkan “Invalid argument” yang bukan error asli dari fopen. Hanya strerror(saved) (menggunakan variabel yang kita simpan lebih awal) yang melaporkan pesan yang benar.
Inilah mengapa aturan “selalu simpan errno segera setelah panggilan yang gagal” wajib diikuti. Bila kita memanggil fungsi lain di antara (termasuk printf, perror, atau bahkan alokasi memori), errno bisa berubah dan pesan error yang kita laporkan menjadi menyesatkan.
Kode errno Umum di Linux #
Beberapa kode errno yang sering ditemui dalam operasi file di Linux:
| errno | Konstanta | Pesan | Penyebab umum |
|---|---|---|---|
| 2 | ENOENT | No such file or directory | File/direktori tidak ada |
| 13 | EACCES | Permission denied | Tidak punya izin |
| 17 | EEXIST | File exists | File sudah ada (mode wx gagal) |
| 9 | EBADF | Bad file descriptor | fclose dua kali, atau operasi tidak sesuai mode |
| 28 | ENOSPC | No space left on device | Disk penuh |
| 24 | EMFILE | Too many open files | Batas file descriptor tercapai |
| 36 | ENAMETOOLONG | File name too long | Path melebihi PATH_MAX |
| 40 | ETIMEDOUT | Connection timed out | Timeout pada network file system |
| 22 | EINVAL | Invalid argument | Argumen fungsi tidak valid |