Hata Durumları, errno
Çağırdığımız sistem ya da POSIX fonksiyonları çeşitli durumlarda bize hata
dönebilirler, isteklerimizi yerine getiremeyebilirler. Birçok sistem fonksiyonun
dokümanında hata durumları için bir kısım bulunmaktadır. write()
fonksiyonundan
devam edelim: [1]
ERRORS
EAGAIN The file descriptor fd refers to a file other than a
socket and has been marked nonblocking (O_NONBLOCK), and
the write would block. See open(2) for further details on
the O_NONBLOCK flag.
EAGAIN or EWOULDBLOCK
The file descriptor fd refers to a socket and has been
marked nonblocking (O_NONBLOCK), and the write would
block. POSIX.1-2001 allows either error to be returned
for this case, and does not require these constants to
have the same value, so a portable application should
check for both possibilities.
EBADF fd is not a valid file descriptor or is not open for
writing.
...
write()
fonksiyonu birçok hata oluşturabiliyor, ben bir kısmını aldım.
Bazı fonksiyonlar ise çok az sayıda hata koşulu var iken bazı fonksiyonlar için
bu sayı fazla oluyor fakat hepsi için hataları yakalama yöntemi aynı: errno
errno
errno
, C dili ile uğraştıysanız duymuş olabileceğiniz bir kelime. errno
, C
standartlarında bulunuyor. Yani ister POSIX uyumlu bir yerde çalışın, ister uyumsuz
olsun, ister Windows üzerinde olsun errno
C dilinde olan bir kavram, POSIX veya
Linux ile sınırlı değil. POSIX standartları da C dilinde var olan errno
hata
bildirim yöntemini tercih etmiştir. [2] O zaman önce C dilindeki errno
kullanımına bakalım.
C89’dan yani ilk C standartından itibaren errno
ve errno.h
standartlar içerisinde
yer alıyor. Standart C kütüphane fonksiyonları, hata bildirimleri için bu yöntemi
kullanabiliyorlar. [3]
errno
yu, int
türden bir değişken gibi düşünebiliriz. Fakat aslında teknik
olarak bu tam doğru değil. C11 standartlarına kadar errno
hakkında şöyle bir
ifade bulunuyordu:
It is unspecified whether errno is a macro or an identifier declared with external linkage.
C11 ile burada bir değişiklik yapıldı ve bu ifade kaldırıldı, errno
nun
aslında bir önişlemci macro’su olması sağlandı. [4] Ama biz kullanıcılar
açısından bu fark çok önemli değil, sonuçta derleyici yazmıyoruz. errno.h
içerisinde extern int errno;
şeklinde bildirimi yapılmış global bir değişken
gibi düşünebiliriz.
errno
ya erişmek için kodumuza errno.h
ı #include
etmemiz gerekiyor. Bu
noktadan sonra errno
yu rezerve edilmiş gibi global değişken gibi düşünebiliriz,
ama arka planda macro olabilir. Problem yaşamamız için errno
yu #undef
etmeye
çalışmamalıyız. Ayrıca POSIX standartları errno
isminde bir şey tanımlamamamız
gerektiğini belirtiyor. [5]
It is unspecified whether errno is a macro or an identifier declared with external linkage. If a macro definition is suppressed in order to access an actual object, or a program defines an identifier with the name errno, the behavior is undefined.
Benzer ifade C standartlarında da var:
If a macro definition is suppressed in order to access an actual object, or a program defines an identifier with the name errno, the behavior is undefined.
Yani özetle errno.h
ı include edin ya da etmeyin errno
isminde kendiniz
bir şeyler tanımlamayın, başka bir isim bulun. (Yani aslında errno.h
ı
kullanmıyorsanız errno
tanımlamak problem de olmayabiliyor bazı durumlarda ama
başka isim mi bulamadınız kardeşim, bela aramayın!)
C’de bir kütüphane fonksiyonu başarısız olduğu zaman, 0 olmayan bir tam sayı ya
da NULL pointer dönebiliyor. İşte bu durumda bu fonksiyon tarafından errno
,
daha detaylı bir hata bilgisi vermek için set edilebiliyor. Biz de bu değere
bakarak hata hakkında detaylı bilgi edinebiliyoruz. Aşağıdaki koda bakalım:
#include <stdio.h>
#include <errno.h>
int main(void)
{
printf("%d\n", errno); //0
FILE* f = fopen("olmayan-dosya.txt","r");
if (!f)
printf("%d\n", errno);
errno = 0; //bizim sorumluluğumuzda
}
Bu kodun çıktısı:
0
2
olmaktadır.
Program başlangıcındaki thread’te errno
nun değerinin 0
olacağı C
standartları tarafından garanti edilmiştir. Çok thread’li programlarda her
thread için ayrı bir errno
vardır. Standartlar, başlangıç thread’i hariç diğer
thread’lerdeki errno
değerinin belirsiz olacağını belirtmektedir. Standart C
fonksiyonları, errno
yu hata olsun olmasın bir pozitif sayıya set edebilirler.
Fakat C kütüphanesi fonksiyonları errno
yu 0’a set etmezler. Dilersek biz elle
0
yazabiliriz.
Yukarıdaki kodda var olmayan bir dosya fopen()
standart C fonksiyonu ile
açılmaya çalışılmış. Dosya olmadığı için fopen()
bize NULL
pointer dönmüş ve
errno
yu set etmiştir.
Aslında bu örnek iyi bir örnek değildir. Çünkü C standartları gereğince
fopen()
fonksiyonunun hata durumunda errno
yu set etme gibi bir zorunluluğu
yoktur. Hata durumunda errno
nun set edilmesi POSIX standartlarında zorunlu
kılınmıştır. [6] Fakat Microsoft C derleyicisi de uyumluluk adına errno
yu
bu durumda set etmektedir. C standartlarında fgetpos()
gibi fonksiyonların
errno
yu set etmesi zorunlu kılınmıştır. Yine de basitlik açısından yukarıdaki
örneğin vermeyi tercih ettim. Ama yine de bu şekilde kullanım C standartları
açısından doğru değildir çünkü errno
, fopen()
tarafından set edilmeyebilir.
Peki 2 nolu hata ne demektir? Bunu anlamak için errno.h
içerisindeki
sembolik sabitlere bakmamız gerekir. errno
yu anlamlandırmak için standart C’de
bulunan bazı yardımcı fonksiyonları kullanabiliriz.
#include <stdio.h>
void perror(const char *s);
stdio.h
içerisinde prototipi bildirilen perror()
fonksiyonu standart hata
akımına, stderr, istediğimiz bir mesaj ile beraber hata bilgisi yazdırmaktadır.
[7] Kodumuzu değiştirelim:
#include <stdio.h>
#include <errno.h>
int main(void)
{
printf("%d\n", errno); //0
FILE* f = fopen("olmayan-dosya.txt","r");
if (!f){
printf("%d\n", errno);
perror("Patladık!");
}
errno = 0; //bizim sorumluluğumuzda
}
Çıktı:
0
2
Patladık!: No such file or directory
Gördüğünüz üzere bizim mesajımız ile beraber hatayı yazdırdı. Demek ki benim
sistemimde 2, bu demekmiş. Dikkat ederseniz perror()
a ayrıca errno
geçmiyoruz
çünkü errno
adeta bir global değişken olduğu için perror()
tarafından
okunabiliyor.
Bir diğer fonksiyon da string.h
içerisindeki strerror()
fonksiyonudur. Bu
fonksiyon da ilgili hata kodunu, string bir mesaja çevirmeye yarar.
for (int i = 0; i < 30; ++i)
printf("error code %2d: %s\n", i, strerror(i));
Dilersek bu şekilde hata kodlarının anlamlarını öğrenebiliriz. Dikkat ederseniz
bu fonksiyon global değişken gibi düşündüğümüz errno
yu okumuyor, argüman olarak
bir hata kodu alıyor. Elbette bunu çağırırken strerror(errno)
dersek, perror()
benzeri bir etki alırız. strerror()
, standart bir C fonksiyonudur.
POSIX ve Sistem Fonksiyonları
Gelelim POSIX ve sistem fonksiyonları ile errno
nun kullanımına. POSIX, kendi
fonksiyonları için sembolik sabit şeklinde hatalar tanımlamıştır. Formatları,
EXXX
şeklindedir. EACCES
, EAGAIN
, EBADF
gibi. Bu sembolik sabitlerin
değerleri ise POSIX standartlarında uspecified olarak bırakılmıştır. [8]
Standart C fonksiyonları gibi POSIX fonksiyonları da errno
yu 0 değerine set
etmezler:
No function in this volume of POSIX.1-2017 shall set errno to zero.
errno
ile ilgili yapılabilecek hatalardan biri de bir fonksiyon hata dönmemiş
olsa bile errno
yu anlamlandırmaya çalışmaktır. errno
, fonksiyon hata
döndüyse anlamlı olacaktır. Elbette her fonksiyonun dokümanına bakmalıyız.
errno
Değerleri
Ubuntu üzerinde sudo apt install moreutils
ile moreutils
paketini yükleyerek
komut satırı üzerinden errno
programını çalıştırıp hata kodlarını
öğrenebiliriz. errno -l
diyerek tüm kodları listeleyebilir, errno 2
diyerek
ise 2
nolu hata hakkında bilgi alabiliriz.
Örnek olarak open()
POSIX fonksiyonu ile aşağıdaki gibi bir kullanım düşünebiliriz:
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
int main(void)
{
int fd;
if ((fd = open("olmayandosya", O_RDONLY)) == -1) {
fprintf(stderr, "open failed: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("success\n");
return 0;
}
Çıktı:
open failed: No such file or directory
Ya da istersek kendimize boilerplate bir kod hazırlayabiliriz:
void exit_sys(const char* msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
//...
exit_sys("Elveda zalim dünya");