> İşletim Sistemlerinin Dosya Sistemleri; >> İki gruba ayrılmıştır. Bir grup, disk üzerindeki dosya organizasyonundan sorumlu iken diğer grup işletim sistemi çalıştırıldığında, çekirdek alanının içerisindeki organizasyonlardan sorumludur. Yani işletim sistemlerinde dosya sistemi iki ayrı ayaktan meydana gelmektedir. >> Beş adet sistem fonksiyonları ile işletilmektedir. Bunlar, "sys_open", "sys_close", "sys_read", "sys_write" ve "sys_lseek" isimli sistem fonksiyonlarıdır. Bunlara karşılık gelen POSIX fonksiyonları ise sırasıyla, "open", "close", "read", "write" ve "lseek" fonksiyonlarıdır. İş bu fonksiyonlar ise sırasıyla dosya açmak için, dosya kapatmak için, dosyadan okuma yapmak için, dosyaya yazmak için ve dosya göstericisini konumlandırmak için kullanılırlar. Hangi programlama dilini kullanırsak kullanalım, günün sonunda dosya işlemleri iş bu beş fonksiyon çağrılarak işimiz görülmektedir. Bu POSIX fonksiyonlarının kaynak kodlarına da "elixir.bootlin.com" adresinden ulaşabiliriz. Bu fonksiyonlardan, >>> "open" isimli POSIX fonksiyonu, UNIX türevi sistemlerde bir dosyayı açmak için kullanılmaktadır. Prototipi şu şekildedir; #include int open(const char* path, int flags, ...); Her ne kadar imzasından istediğimiz kadar argüman ile çağırabileceğimiz anlaşılsa da, işin özünde bizler bu fonksiyonu ya iki argüman ile ya da üç argüman ile çağırmalıyız. Eğer üç argüman ile çağıracaksak, bu üçüncü argüman "mode_t" türünden olmak zorundadır. "mode_t" türü herhangi bir "integral" tür olabilir. Bu fonksiyonu üçten fazla argüman ile çağırmak "Tanımsız Davranış" a neden olur. Bu fonksiyonun birinci parametresi, açılacak olan dosyaya ilişkin yol ifadesidir. İkinci parametre ise dosyayı açış bayraklarını belirtmektedir. Yani dosyayı hangi amaç için açtığımızın bilgisini bu argüman ile geçiyoruz. Bu bayrak, TEK BİTİ bir olan sayılar şeklindedir. Dolayısıyla birden fazla bayrağı "|" (Bit-wise OR) işlemine soktuğumuz zaman, iki farklı amaca dair bilgiyi argüman olarak geçebiliriz. C dilindeki maskeler gibi. Bu bayraklar "O_" ile başlamakta olup, önemlilerden bazıları şu isimdedirler; "O_RDONLY", "O_WRONLY", "O_RDWR". Fakat bu üç bayraktan yalnızca BİR TANESİNİ argüman olarak geçebiliriz ve bu bayrakların anlamları ise sırasıyla şu şekildedir; "Okuma", "Yazma" ve "Okuma & Yazma". Fakat unutulmamalıdır ki ilgili dosyanın, bu dosyayı açacak prosese, sırasıyla şu hakları da vermesi gerekmektedir; "r", "w" veya "rw" Bu hak konusunda bir uyuşmazlık olursa "open" fonksiyonu BAŞARISIZ OLUR. Bunlara ek olarak, "open" fonksiyonu yeni bir dosya oluşturmak için de kullanılabilinir fakat bunun için "O_CREAT" bayrağını kullanmalıyız. Bu bayrağı, yukarıdaki üç bayraktan bir tanesi ile "|" (Bit-wise OR) işlemine sokarsak, ilgili dosyanın olmaması durumunda, yeni bir dosya oluşturulacak fakat dosyanın olması durumunda OLAN dosya açılacak eğer yetki konusunda da bir terslik yoksa. Fakat bu "O_CREAT" bayrağını kullanmazsak, dosyanın olmaması durumunda "open" fonksiyonu başarısız olacaktır. Buradaki "O_CREAT" bayrağı, dosyanın var olması durumunda ilgili dosyanın sıfırlanacağı ANLAMINA GELMEMEKTEDİR. Sadece "Dosya yok ise yeni bir dosya oluştur" anlamına gelmektedir. Eğer dosya var ise bu "O_CREAT" bayrağı işlevsiz hale gelir. Fonksiyonun üçüncü parametresi ise ilgili dosyanın sunacağı erişim haklarının bilgisidir. Üçüncü argümanı geçmemiz, dosyanın var olması durumunda, ilgili dosyanın erişim haklarının değişeceği anlamına gelmez. SADECE YENİ BİR DOSYA OLUŞTURDUĞUMUZ ZAMAN İŞ BU DOSYANIN HAKLARINI BELİRTMEKTEDİR. Dolayısıyla "O_CREAT" BAYRAĞINI KULLANIYORSAK, ÜÇÜNCÜ PARAMETRE OLARAK BU HAKLARI DA fonksiyona geçmeliyiz. Eğer "O_CREAT" bayrağı girilmemiş ise bu üçüncü parametreyi geçmemiz anlamsız olacaktır. Yine bu üçüncü parametre de, tıpkı ikinci parametrede olduğu gibi bir takım bayraklardan meydana gelmektedir ve "|" (Bit-wise OR) işlemi uygulayabiliriz. Bu bayraklar ise "sys/stat.h" başlık dosyasında bildirilmiş olup isimleri "S_" ile başlamaktadır. Bu isimlerin isimlendirme konvensiyonu şu şekildedir; -> "S_" karakterlerinden sonra ya "R" ya "W" ya da "X" karakteri gelmekte. -> İş bu karakterden sonra ya "USR", ya "GRP" ya da "OTH" kelimelerinden bir tanesi gelmektedir. Bu şekilde dokuz adet bayrak tanımlanmıştır. Bu bayrak ise şu şekildedir; "S_IRUSR", "S_IWUSR", "S_IXUSR", "S_IRGRP", "S_IWGRP", "S_IXGRP", "S_IROTH", "S_IWOTH", ve "S_IXOTH" Örneğin, "rw-r--r--" haklarını veren bayrak kombinasyonu şu şekildedir; (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) Bu dokuz bayrağa ek olarak üç tane de bayrak daha vardır ki bu dokuz bayrağın kombine edilmiş halledir; "S_IRWXU", "S_IRWXG" ve "S_IRWXO". EĞER "O_CREAT" BAYRAĞINI İKİNCİ PARAMETRE OLARAK KULLANMIŞSAK FAKAT ÜÇÜNCÜ PARAMETRE OLARAK "S_I" İLE BAŞLAYAN ARGÜMANLARI KULLANMAMIŞSAK "Tanımsız Davranış" OLUŞACAKTIR. * Örnek 1, #include "stdio.h" #include "stdlib.h" #include "string.h" #include "fcntl.h" #include "sys/stat.h" void exit_sys(const char* msg); int main(void) { /* # INPUT # ~/Desktop/LearnLinux/Examples/wd$ ls /Desktop/LearnLinux/Examples/wd$ ./wd */ /* # OUTPUT # test.txt wd wd.c Success!.. */ if (open("test.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) { exit_sys("open"); } puts("Success!.."); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Şimdi de iş bu bayrakları inceleyelim: >>>> "O_TRUNC" isimli bir açış moduna da sahiptir. Bu açış modunu "O_WRONLY" ya da "O_RDWR" ile birlikte kullanmalıyız, fakat "O_RDONLY" modu ile birlikte KULLANAMAYIZ. Bu mod, açılan dosyanın sıfırlanacağı anlamına da gelmektedir. "O_TRUNC" bayrağını kullanabilmek için dosyanın illaki yeniden oluşturulması gerekmez, halihazırda var olan dosyaları da açabiliriz. Fakat böyle bir dosya mevcut değil ise fonksiyon başarısız olacaktır. >>>>> Aşağıdaki tabloda ise Standart C fonksiyonlarındaki açış modları ile POSIX fonksiyonlarının açış modlarının karşılaştırmasını bulabilirsiniz: Standart C <=> POSIX "w" O_WRONLY | O_CREAT | O_TRUNC "W+" O_RDWR | O_CREAT | O_TRUNC "r" O_RDONLY "r+" O_RDWR >>>> "O_APPEND" bayrağı, yazma işleminin dosyanın sonuna yapılacağı anlamına gelmektedir. Tıpkı "O_TRUNC" bayrağında olduğu gibi, bu bayrağı kullanabilmek için bizlerin "O_WRONLY" ya da "O_RDWR" bayraklarını da kullanmamız gerekmektedir. Bu bayrak geçildiğinde işletim sistemi, dosya göstericisini EOF konumuna kaydırarak, yazma yapmaktadır. >>>>> Aşağıdaki tabloda ise Standart C fonksiyonlarındaki açış modları ile POSIX fonksiyonlarının açış modlarının karşılaştırmasını bulabilirsiniz: Standart C <=> POSIX "a" O_WRONLY | O_CREAT | O_APPEND "a+" O_RDWR | O_CREAT | O_APPEND >>>> "O_EXCL" bayrağı ise "dosya yok ise yeni bir dosya oluştur, var ise bir şey yapma ve başarısız ol" anlamına gelmektedir. Dosya halihazırda mevcut ise fonksiyon başarısız olmaktadır. Dolayısıyla bu bayrak, "O_CREAT" bayrağı ile birlikte kullanılmalıdır. "O_CREAT" olmadan bu bayrağın kullanılması "Tanımsız Davranış" oluşturur. >>>> Geri dönüş değeri "int" türden olup, ismine Dosya Betimleyicisi("File Descriptor") de denmektedir. Bu geri dönüş değerini "write" ve "read" işlemi yapan diğer POSIX fonksiyonlarına argüman olarak geçiyoruz ki hangi dosya üzerinde işlem yapıldığı belli olsun. "open" fonksiyonunun başarısız olması durumunda, -1 ile geri dönülmektedir ve "errno" değişkeninin değeri o hataya ilişkin bir değere çekilir. Örneğin, dosyamızı "O_RDWR" modunda açmak isteyelim. Bu durumda bu işi yapacak olan prosesin, bu dosya üzerinde "r" ve "w" haklarına sahip olması gerekmektedir. Aksi halde "open" fonksiyonu başarısız olacak ve "errno" nun değeri "EACCESS" değerine çekilecektir. Buradaki kilit nokta, kontrolün ilk başta yapılmasıdır. Yani, prosesimiz ilgili dosya üzerinde "r" ve "w" haklarına sahip değilse, dosya hiç açılmamaktadır, "read" veya "write" fonksiyonlarına daha sıra gelmemiştir. Çünkü prosesimiz dosya üzerinde ne "r" hakkına ne de "w" hakkına sahiptir. AÇMA İŞLEMİNİN BAŞARI DURUMU KESİNLİKLE KONTROL EDİLMELİDİR. * Örnek 1, #include "stdio.h" #include "stdlib.h" #include "fcntl.h" #include "sys/stat.h" void exit_sys(const char* msg); int main(void) { /* # INPUT # //.. */ /* # OUTPUT # //.. */ int fd; if ((fd = open("test.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { exit_sys("open"); } puts("Success!.."); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>> İşletim sistemi veya POSIX nezdinde "text_mode"/"binary_mode" şeklinde bir kavram yoktur. Bu kavramlar C dili nezdinde oluşturulan kavramlardır. >>>> Önceki günlerde de açıklandığı üzere proseslerin kontrol bloğu içerisinde("Process Control Block"), dolaylı yoldan, Dosya Betimleyici Tablosunun("File Descriptor Table") adresi tutulmaktadır. Bu tablonun her bir indeksinde de "file" türünden dosya nesnelerinin adresleri tutulmaktadır. İşte "open" fonksiyonu çağrıldığında sırasıyla şu işlemleri yapmaktadır: -> İlk önce "file" türünden bir dosya nesnesi hayata getiriyor ve diskten aldığı bilgileri bu nesnenin içerisine işliyor. -> Sonrasında bu nesnenin adresini de Dosya Betimleyici Tablosundaki boş bir indise yazar. İlgili indis numarasını da geri döndürüyor eğer başarılı olmuş ise. İşte bu geri dönüş değeri bir nevi "handle" olarak kullanılmaktadır ki ilgili dosyaya erişebilelim. Geri döndürülen bu indis bilgisinin adı da "File Description" olarak geçmektedir. Fakat işin esasında bu işlemler, bir sistem fonksiyonu olan "sys_open" isimli Linux sistem fonksiyonu tarafından yapılmaktadır. "open" sadece bu fonksiyonu çevrelemektedir. >>>>> Dosya Betimleyici Tablosunun ilk üç indisi her zaman için bizlere dolu olarak sunulmaktadır. Sıfırıncı indis "stdin", birinci indis "stdout", ikinci indis ise "stderr" isimli dosya nesnelerini göstermektedir. POSIX standartlarına göre "open" fonksiyonunun Dosya Betimleyici Tablosundaki ilk boş indisi vermesi garanti edilmiştir. >>>>> Dosya Betimleyici Tablosu ise proseslere özgüdür, yani her prosesinki farklıdır. Buradan hareketle diyebiliriz ki "open" fonksiyonunun geri döndürdüğü File Descriptor("fd") değeri, kendi prosesinde anlamlıdır. Bu indis bilgisi, başka prosesinkinde bambaşka bir "file" nesnesine hitap ediyor olabilir. >>>>> Bütün bu süreç aslında şöyle de özetlenebilir; >>>>>> İlk evvel parametrelerin geçerliliği kontrol edilir. Geçerli parametre girilmediğinde herhangi bir dosya açma işlemi yapmadan süreç sonlandırılır. >>>>>> Daha sonra Dosya Betimleyici Tablosunda boş yer olup olmadığına, var ise en düşük indisinin kaçıncı indis olduğuna bakarız. Eğer bu tabloda yer yok ise herhangi bir dosya açma işlemi yapmadan süreç sonlandırılır. >>>>>>> Bütün tabloyu tekrar tekrar silbaştan dolaşmak yerine, ilgili tablodaki toplam indis adedince bitlerden oluşan yeni bir dizi tahsis edilmiş. Bu yeni dizinin her bir indisi aslında Dosya Betimleyici Tablosundaki aynı indisi göstermekte. Eğer Dosya Betimleyici Tablosundaki 10 numaralı indis kullanımdaysa, bitlerden oluşan dizinin de on numaralı indisinin değeri "1" olarak değiştirilmekte. Eğer 15 numaralı indis boş olsaydı, bitlerden oluşan dizinin 15 numaralı indisinin değeri "0" olarak değiştirilmekte. Bunun avantajı ise şundan kaynaklanmaktadır; işlemcilerin bir takım komutları bir bit dizisindeki ilk "1" değerine sahip indisi döndürür. Bu komutlar ile bit-dizisindeki ilk dolu olan indisi öğrenip, Dosya Betimleyici Tablosundaki aynı indise erişebiliyoruz. "find_next_zero_bit" isimli kernel fonksiyonu ilk boş biti bize döndürmektedir. Bu veri yapısına da "bitset" veri yapısı denmektedir. >>>>>> Devamında ise "file" türden bir dosya nesnesi tahsis edip, diske gideceğiz. Diskten uygun bilgileri alıp, bu nesnenin içerisine yazacağız. >>>>>> Sonrasında da bu "file" türden dosya nesnesinin adresini de Dosya Betimleyici Tablosundaki uygun indise yazacağız. >>>>> İşletim sistemi, o an çalışmakta olan prosesin yani "open" fonksiyonunu çağıran prosesin, "Process Control Block" adresini her zaman bir gösterici ile tutmaktadır. Linux sistemlerinde bu göstericinin adı "current" ismindedir. >>>>>> "current" isimli göstericinin tanımlandığı yere ise aşağıdaki linktek ulaşabiliriz: https://elixir.bootlin.com/linux/1.2.5/source/kernel/sched.c#L88 Son olarak "open" fonksiyonu sadece "regular" dosyalar oluşturmaktadır. >>> "close" : Dosyanın kapatılması; >>>> Bir prosesin sonlanması hasebiyle işletim sistemi, bu prosesin açmış olduğu bütün dosyaları, Dosya Betimleyici Tablosundan hareketle kapatmaktadır. Dosya Betimleyici Tablosunun maksimum indis sayısı 1024 tanedir, yani bir proses ile en fazla 1024 adet dosya açabiliriz. >>>> Bir dosya açıkken hem Kernel içerisinde hem de Dosya Betimleyici Tablosunda yer kaplamaktadır. Dolayısıyla işimizin bittiği dosyaları biz kapatmalıyız ki hem Kernel rahatlasın hem de Dosya Betimleyici Tablosunda boş yer sayısı çoğalsın. Nesne Yönelimli programlama dillerinde dosyalar bir sınıf ile temsil edildiklerinden, örneğin C++ dili için "RAII idiom", C# & Java dillerinde "Reference Counter" üzerinden çalışan "Garbage Collector", dosyaların kapatılması otomatik olarak gerçekleştirilmekte. Fakat C dilinde bizler manuel olarak kapatmalıyız. >>>> Dosya Betimleyici Tablosundaki her bir indiste gösterilen dosya nesneleri ("file"), bünyesinde bir sayaç barındırmaktadır. İlgili tablodaki farklı indislerin aynı "file" nesnesini göstermesi durumunda, iş bu sayacın değeri arttırılır. Eğer bu sayacın değeri sıfır olmuş ise dosya artık kapatılır(önce "file" nesnesinin hayatı bitirilir, sonrasında da tablodaki ilgili indis sıfırlanır). İş bu sayacın ismi Linux kaynak kodlarında "f_count" ismindedir. >>>> Dosyanın kapatılması için "close" isimli POSIX fonksiyonu kullanılmakta olup, doğrudan bir sistem fonksiyonu olan "sys_close" isimli fonksiyonu çağırmaktadır. "close" fonksiyonunun tek parametre alır ki bu parametre "open" fonksiyonunun geri döndürdüğü parametredir, fakat başarısızlık durumunda "-1" ile geri dönmektedir. Genel olarak bu fonksiyonun geri dönüş değeri kontrol edilmez çünkü umulur ki dosyayı açarken herhangi bir aksaklık olmadıysa kapatırken de olmayacaktır. Bu fonksiyon, "open" fonksiyonuna nazaran "unistd.h" isimli başlık dosyasında bildirilmiştir. * Örnek 1, #include "stdio.h" #include "stdlib.h" #include "fcntl.h" #include "sys/stat.h" void exit_sys(const char* msg); int main(void) { /* # INPUT # //.. */ /* # OUTPUT # //.. */ int fd; if ((fd = open("test.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { exit_sys("open"); } puts("Success!.."); close(fd); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> "read" : >>>> Günün sonunda dosyadan okuma yapmak için varolan tek POSIX fonksiyonudur. Bu fonksiyon da bir sistem fonksiyon olan "sys_read" isimli fonksiyonu çağırmaktadır. >>>> Bu fonksiyon "unistd.h" isimli başlık dosyasında bildirilmiştir. Fonksiyonun birinci parametresi hangi dosyadan okuma yapacağımızı anlatan bir "file handler" ki bunu bizler "open" fonksiyonunun geri dönüş değeri olarak elde ediyoruz. Fonksiyonun ikinci parametresiyse okunacak bilgilerin bellekte yerleştirileceği adres. Okunulan yazılar bu adrese yazılacaktır. Üçüncü parametre ise okunacak byte sayısını belirtmektedir. Fonksiyonun geri dönüş değeri ise başarı durumunda okunan bayt miktarıdır, başarısızlık durumunda ise "-1" şeklindedir. Burada dikkat edilmesi gereken husus, 0 bayt okumanın da legal bir okuma olduğudur. Yanlış parametre geçilmesi, diskin bozulması gibi durumlarda iş bu "read" fonksiyonu "-1" ile geri dönmektedir. Örneğin, bizler 10 bayt okumak için bir dosyayı açıyor olalım. Fakat dosyanın içerisinde ise 7 karakter olmuş olsun. Bu durumda 7 karakter okunacak, dosya göstericisi EOF konumuna gelecek ve "read" fonksiyonu "7" ile geri dönecektir. "read" fonksiyonunun "0" ile geri dönmesi, EOF konumundan itibaren okuma yapıldığı anlamına gelmekte olup GAYET NORMAL BİR DURUMDUR. "-1" İLE GERİ DÖNMESİ ABNORMAL BİR DURUMDUR. Geri dönüş değerinin türü "ssize_t" türündendir. Bu tür POSIX'e özgü olup, bir kaç başlık dosyasında "typedef" edilmiştir ve İŞARETLİ BİR TAM SAYIYA TEKABÜL EDECEĞİ GARANTİ ALTINDADIR. 32-bit sistemlerde "long", 64-bit sistemlerde ise "long long" türünün eş anlamlıdır. >>> "write" :Dosyaya yazmak için çağrılan bir fonksiyondur. Çoğu sistemde doğrudan sistem fonksiyonu olan "sys_write" isimli fonksiyonu çağırmaktadır. >>>> İmzası ise şu şekildedir; İlk parametre, "open" fonksiyonundan elde edilen "File handler". İkinci parametre ise okumanın yapılacağı veri alanının başlangıç adresi. Bu alandan okuma yapacağı için burasını bir nevi "source" olarak düşünebiliriz. Bundan sebeptir ki ikinci parametre "const" bir göstericidir. Üçüncü parametre ise kaç bayt yazılacağının bilgisidir. Yine 0 bayt yazmak istememiz, normal bir durumdur ve fonksiyon sıfır ile geri döner. Buradan hareketle çok büyük olasılıkla diyebiliriz ki yazmak istediğimiz bayt miktarını geri döndürmektedir. Diskin tam dolu olması ya da kesme sinyallerinin gelmesi gibi durumlarda da fonksiyonumuz "-1" ile geri dönmektedir. Yazma işlemi sırasında diskin dolması durumunda yazabildiği bayt kadarını geri döndürür. >>>> İş bu fonksiyon da "unistd.h" isimli başlık dosyasında bildirilmiştir. >>>> Yazma işlemi de yine bayt bayt şeklindedir. * Örnek 1, #include "stdio.h" #include "stdlib.h" #include "string.h" #include "fcntl.h" #include "sys/stat.h" #include "unistd.h" void exit_sys(const char* msg); int main(void) { /* # INPUT # //.. */ /* # OUTPUT # //.. */ int fd; char buffer[] = "This is a test."; ssize_t result; if ((fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) exit_sys("open"); if( write(fd, buffer, strlen(buffer)) == -1) exit_sys("write"); close(fd); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> Dosya Göstericisi Kavramı: >>>> Dosya göstericisi, "read" ve "write" işlemleri için bir orjin noktasıdır. Dosyadaki her bir bayt için bir adet off-set numarası getirilmiştir. Konum bilgisi için bu off-set bilgisi kullanılmaktadır. >>>> Dosyanın ortasından bir yere ekleme işlemi mümkün değildir, sadece ilgili konumdan itibaren yazma işlemi yapılacaktır. Bu durumda halihazırdaki veri silinebilir. Eğer iş bu göstericiyi EOF konumuna aldıktan sonra yazma işlemi yaparsak, dosyayı büyütüyor olacağız (EOF konumu, dosyanın en sonundaki karakterden bir sonraki konumdur). >>>>> EOF konumundan itibaren bir bayt okuma yaparsak, bir şey okumamış oluruz fakat bu olağan bir durumdur. >>>> Dosya ilk açıldığında dosya göstericisi sıfırıncı off-set'i göstermektedir, daha doğrusu birinci karakteri göstermektedir eğer bir yazı varsa. Yani en baştadır. Üç karakter okuma ya da yazma yaptığımız zaman artık dördüncü off-set'i gösteriyor olacaktır, bir diğer değişle beşinci karakteri gösterecektir. >>>> Dosya göstericisinin konumu EOF konumundan da öteye konumlandırılabilir(okuma ya da yazma yapmaksızın). İlgili konumdan itibaren yazma yapıldığında, EOF konumundan itibarenki konumlar artık Dosya Delikleri olarak geçer(yazmanın başladığı konuma kadarki olanlar) ve bu konumdaki karakterler sıfır karakteri ile doldurulur. Özel bir konu olduğundan şu an için detaylarından bahsedilmeyecektir. >>>> C dilindeki "fopen" fonksiyonu için dosya açılış modu kavramı vardır fakat işletim sistemi için böyle bir kavram söz konusu değildir. İşletim sistemine göre dosyalar baytlardan oluşmaktadır. C dilindeki "binary_mode" a bu yönüyle benzerlik gösterebilir. >>>> Dosya göstericisinin konum bilgisi, dosya nesnesi içerisindedir. Yani bir dosyayı iki defa açsak bile iki farklı dosya nesnesi hayata geleceğinden, iki farklı dosya konum göstericisi elde edeceğiz. "f_pos" isimli değişken bu konum bilgisini tutmaktadır. >>>> "lseek" fonksiyonu : >>>>> "lseek" fonksiyonu da doğrudan "sys_lseek" isimli sistem fonksiyonunu çağırmaktadır. >>>>> Kullanımı, standart bir C fonksiyonu olan, "fseek" fonksiyonuna çok benzemektedir. >>>>> İş bu fonksiyon da "unistd.h" isimli başlık dosyasında bildirilmiş olup imzası şöyledir; -> Birinci parametresi, "open" fonksiyonunun geri dönüş değeri olan "File Handler". -> İkinci parametresi ise "off-set" bilgisi, yani kaçıncı "off-set" e konumlandıracağımızı belirtmektedir. İkinci parametre, POSIX sistemlerinde işaretli bir tam sayının tür eş ismidir. -> Üçüncü parametre ise dosyanın hangi orjin noktasından itibaren konumlandırılmanın yapılacağını bildirmektedir ki bu orjinler dosyanın başı, dosyanın sonu(EOF) noktası ve en son kullanılan orjin şeklindedir. Üçüncü parametre olarak 0, 1 ya da 2 rakamları girilebilir. Pek tabii bu rakamların yerine sırasıyla "SEEK_SET", "SEEK_CUR" ve "SEEK_END" isimli sembolik sabitleri de kullanabiliriz. -> Fonksiyonun geri dönüş değeri ise göstericinin yeni konum bilgisidir. Başarısızlık durumunda da, örneğin dosyanın başından itibaren -5. konum gibi, -1 ile geri dönmektedir. EOF konumundan itibaren, +n. konuma, konumlandırmak için de o sistemde kullanılan dosya sisteminin buna izin vermesi gerekmektedir. Böylesi bir durumda yazma işlemi yapılırsa, aradaki konumlar artık birer dosya delikleri("file holes") olarak anılır. Bu konu ileride ele alınacaktır. Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, #include "stdio.h" #include "stdlib.h" #include "string.h" #include "fcntl.h" #include "sys/stat.h" #include "unistd.h" void exit_sys(const char* msg); #define MyStrLength(buffer) (sizeof(buffer) / sizeof(*buffer)) int main(void) { /* # OUTPUT (destiny.txt) # This is a text file This is a test line. */ const char buffer[] = "\nThis is a test line."; int fd; if((fd = open("destiny.txt", O_WRONLY)) == -1) exit_sys("open"); lseek(fd, 0, SEEK_END); // const unsigned long buffer_size = sizeof(buffer) / sizeof(*buffer); // const unsigned long buffer_size = sizeof buffer; // const unsigned long buffer_size = MyStrLength(buffer); if(write(fd, buffer, strlen(buffer)) == -1) exit_sys("write"); close(fd); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>> Dosya açarkenki "O_APPEND" modu, atomik bir biçimde "write" çağrısından evvel, dosya göstericisinin konumunu EOF konumuna çekmektedir. Aşağıda bu konuya ilişkin örnekler verilmiştir: * Örnek 1, #include "stdio.h" #include "stdlib.h" #include "fcntl.h" #include "sys/stat.h" #include "unistd.h" void exit_sys(const char* msg); int main(void) { /* # INPUT # //.. */ /* # OUTPUT # //.. */ int fd; char buffer[10 + 1]; // 11 karakterlik yerimiz var. ssize_t result; if ((fd = open("read.c", O_RDONLY)) == -1) exit_sys("open"); if((result = read(fd, buffer, 10)) == -1) // 10 karakter okuyacağız. exit_sys("read"); close(fd); buffer[result] = '\0'; // 10. indise, yani 11. karaktere, de ilgili sayıyı işliyoruz. puts(buffer); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include "stdio.h" #include "stdlib.h" #include "fcntl.h" #include "sys/stat.h" #include "unistd.h" void exit_sys(const char* msg); #define BUFFER_SIZE 4096 int main(void) { /* # INPUT # //.. */ /* # OUTPUT # //.. */ int fd; char buffer[BUFFER_SIZE + 1]; // 11 karakterlik yerimiz var. ssize_t result; if ((fd = open("test.txt", O_RDONLY)) == -1) exit_sys("open"); while((result = read(fd, buffer, BUFFER_SIZE)) > 0) { if(-1 == result) exit_sys("read"); buffer[result] = '\0'; printf("%s", buffer); } close(fd); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, Bir dosyanın kopyalanması; UNIX/Linux sistemlerde geleneksel olarak dosya kopyalama işlemi, kaynak dosyadan hedef dosyaya bir döngü içerisinde, baytların kopyalanması yoluyla gerçekleştirilir. Fakat bazı UNIX türevi işletim sistemleri bünyelerinde bu işi gerçekleştiren alt seviyeli sistem fonksiyonları da bulundurmaktadır. Linux işletim sisteminde bu iş "copy_file_range" isimli sistem fonksiyonu tarafından yürütülmektedir. Fakat UNIX ailesi içinde taşınabilirlik olması açısından geleneksel yöntem tercih edilmelidir. Diğer yandan bu işlem sırasında ne kadar büyüklükte bir tampon bölge kullanılmalıdır? Tipik olarak dosya sistemindeki bir blokun büyüklüğü bu iş için tercih edilir. "stat", "fstat" ve "lstat" gibi fonksiyonlar bu büyüklüğün katlarını bizlere vermektedir. Blok uzunlukları ekseriyetle 512 rakamının katları şeklindedir. 512 olmasının sebebi ise diskin en küçük 512 baytlık küçük parçalara ayrılmış olmasıdır (dosya sisteminden dosya sistemine bu rakam değişebilir ama). #include "stdio.h" #include "stdlib.h" #include "fcntl.h" #include "sys/stat.h" #include "unistd.h" #define BUFFER_SIZE 4096 void exit_sys(const char* msg); int main(int argc, char* argv[]) { /* # INPUT # ./main source.txt destiny.txt # INPUT (source.txt) # This is a text file */ /* # OUTPUT (destiny.txt) # This is a text file */ if(argc != 3) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } int fds, fdd; // File Handler for Source, File Handler for Destiny. if((fds = open(argv[1], O_RDONLY)) == -1) exit_sys(argv[1]); if((fdd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) exit_sys(argv[2]); char buffer[BUFFER_SIZE]; ssize_t result; /* * Döngünün her bir turunda kaynak dosyadan 4096 baytlık okuma talep edilecek. * (n-1). turda 4096 bayttan küçük bir bayt okunacak. * n. turde ise 0 bayt okunacak, böylelikle "read" fonksiyonu sıfır ile geri dönecek. * Herhangi bir IO hatasında da -1 ile geri dönecek. */ while((result = read(fds, buffer, BUFFER_SIZE)) > 0) { /* * 4096 bayt okunursa, "result" değişkeninin değeri 4096 olacak. * 4096 bayt yazılırsa, "write" fonksiyonu 4096 ile geri dönecek. * Eğer başka bir değerle dönerse "okuduğumuz bayt miktarını yazamamışız" * olacağız ve programı sonlandıracağız. */ if(write(fdd, buffer, result) != result) { fprintf(stderr, "cannot write to file!...\n"); exit(EXIT_FAILURE); } } if(-1 == result) exit_sys("read"); close(fds); close(fdd); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >> Bir dosya açıldığı zaman, bu dosyayı açan prosesin "Process Control Block" unun içerisinde "task_struct" türünden bir gösterici bulunmaktadır. İş bu gösterici tarafından gösterilen nesne ise bünyesinde "files_struct" türünden gösterici barındırır. Yine bu gösterici tarafından gösterilen nesne ise bünyesinde "fdtable" türünden başka bir yapı bulundurur. İş bu "fdtable" türünden yapı ise içerisinde bir adet gösterici-dizisi barındırmaktadır, yani göstericiyi gösteren gösterici. Bu gösterici ise elemanları "file" türden olan bir dizinin başlangıç adresini göstermektedir. Yani bu dizinin her bir elemanı olan gösterici ise "file" türünden adreslerdir. Bu göstericiyi gösteren göstericinin ismi ise "fd" biçimindedir. İş bu gösterici-dizisine ise Dosya Betimleyici Tablosu denmektedir. İşte, Linux çekirdeği tarafından bir dosya açıldığında, diskten ilgili dosyanın bilgilerini alıp, RAM içerisinde, ilgili prosesin kontrol bloğuna yerleştiriyor. Böylelikle açık dosyanın bilgileri disk yerine memory'de saklanıyor. İş bu "file" türü ise bünyesinde dosyanın açış moduna dair bilgiler, dosya göstericisinin konum bilgisi vb. bir sürü bilgiyi barındırmaktadır. İşin özü, dosyaya ait bilgiler iş bu "file" türünden olan "struct" içerisindedir. "Linux Kernel" kursunda bu konunun detayları açıklanmaktadır fakat şu an için bizim bilmemiz gereken şey şudur: bir dosya açıldığında işletim sistemi, diskten ilgili dosyanın bilgilerini çekerek, "file" türünden olan yapının içerisine yerleştiriyor. Özetle, "task_struct(a) --> files_struct(b) --> fdtable(c) --> file*[](d) --> file(e)" a: https://elixir.bootlin.com/linux/v6.3.1/source/include/linux/sched.h#L737 b: https://elixir.bootlin.com/linux/v6.3.1/source/include/linux/fdtable.h#L57 c: https://elixir.bootlin.com/linux/v6.3.1/source/include/linux/fdtable.h#L57 d: https://elixir.bootlin.com/linux/v6.3.1/source/include/linux/fdtable.h#L57 e: https://elixir.bootlin.com/linux/v6.3.1/source/include/linux/fs.h#L942 Kısca, "task_struct" > "file*[]" > "file" >> Bir dosya kapatıldığında ise "file*[]" olan, yani Dosya Betimleyici Tablosu, dizinin elemanları boşaltılıyor ve ilgili "file" türden değişken de yok ediliyor. Buradan da diyebiliriz ki her bir prosesin Dosya Betimleyici Tablosu birbirinden farklıdır. Her dosya açışımızda ayrı bir "file" türden değişken hayata gelir. Aynı dosyayı iki defa da açsak yine iki adet "file" türünden değişkenimiz olacaktır. >> "pread" & "pwrite" POSIX fonksiyonları: Yukarıda da belirtildiği üzere "read" ve "write" fonksiyonları işlevlerini dosya göstericisinin bulunduğu konumdan itibaren yapmakta, bu işlemler sırasında da iş bu göstericinin yerini ilerletmiş olmaktadırlar. İşte "pread" ve "pwrite" fonksiyonları ise okuma ve yazma işlemleri sırasında dosya göstericisinin gösterdiği yerden değil de argüman olarak aldığı "offset" konumundan itibaren yapmaktadırlar, dolayısıyla dosya göstericisinin konumunu DEĞİŞTİRMEZLER. Fakat bu fonksiyonların kullanımı seyrektir. Fonksiyonların prototipi aşağıdaki gibidir. #include ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset); ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset); Bu fonksiyonların klasik "read" ve "write" fonksiyonundan tek farkı, son parametreleri olan "offset" parametreleridir. >> POSIX standartlarına göre dosyaya yapılan okuma ve yazma işlemleri sistem genelinde atomik yapıdadır; >>> İki farklı proses bir dosyayı açsa ve her ikisi de yazma işlemi yapsa, önce birininki yazılır ve sonrasında diğerininki. Yani baytların yarısı ilk prosesten, diğer yarısı ikinci prosesten gelecek şekilde bir yazma yapılmaz. Ama hangi prosesinkinin önce yazacağını bilemeyiz. Burada iki prosesin de ilgili işlemleri aynı lokasyona yaptığı varsayılmıştır. >>> Benzer şekilde iki farklı prosesten biri okuma biri yazma yapıyor olsun. Ya yazma işleminden evvelki yazı okunur ya da yazma işlemi bittikten sonraki yazı. Burada da iki prosesin de ilgili işlemleri aynı lokasyona yaptığı varsayılmıştır. >>> Yukarıdaki iki senaryoda da senkronizasyon biz kullanıcalara bırakılmıştır. Burada da devreye dosya kilitleme mekanizması girmektedir; ama dosyanın bütününü kilitleriz ama sadece dosyanın belirli bir kısmını. Fakat dosyanın bütününün kilitlenmesi iyi bir teknik değildir. Bunun yerine sadece işlemin yapılacağı bölümler, ilgili "off-set" alanları kilitlenmelidir. Dosyanın geri kalanı erişime açık bırakılmalıdır. Burada önemli olan günün sonunda bir prosesin yazdığı baytların kalmasıdır. İki prosesin baytlarının iç içe geçmesi aslında dosyanın bozulması anlamına gelmektedir. >> Dosya sistemine ilişkin yardımcı fonksiyonlar: >>> "creat" fonksiyonu, "open" fonksiyonunu sarmayalan bir fonksiyondur. İlk UNIX sistemlerinden beridir varolan bir fonksiyondur. Fonksiyonun birinci parametresi, hedef dosyanın yol ifadesini belirtmektedir. İkinci parametresi erişim bilgilerini belirtir. Bu iki argüman ile "open" fonksiyonunu "O_WRONLY | O_CREAT | O_TRUNC" açış modlarıyla açmaktadır. >>> Yukarıda işlenen "open", "close", "read", "write" ve "lseeek" fonksiyonlarına ek olarak pek çok yardımcı dosya fonksiyonları da bulunmaktadır. Bu bölümde bu fonksiyonlardan önemli olanlarını tanıtacağız. İş bu yardımcı fonksiyonlar sırasıyla "stat", "fstat" ve "lstat" fonksiyonlarıdır. Aslında bu üçü aynı şeyi farklı parametrik yapılar ile gerçekleştirmektedir. Bu üç fonksiyon bir dosyaya ilişkin bilgileri elde etmek için kullanılırlar. İş bu fonksiyonlar, "ls -l" komutu çalıştırıldığında ekrana çıkan yazıların elde edilmesini sağlayan bir fonksiyonlardır.Fonksiyonların imzaları da şöyledir; #include int stat(const char* pathname, struct stat* statbuf); int fstat(int fd, struct stat *statbuf); int lstat(const char* pathname, struct stat* statbuf); Şimdi de bu fonksiyonları sırasıyla inceleyelim: >>>> "stat" fonksiyonunun birinci parametresi, bilgisi elde edilecek dosyanın yol ifadesidir. İkinci parametresi ise alınan bilgilerin yerleştirileceği "stat" isimli bir yapı nesnesinin adresidir. Başarı durumunda "0", başarısızlık durumunda "-1" değerine geri dönecektir. * Örnek 1, #include "stdio.h" #include "stdlib.h" #include void exit_sys(const char* msg); int main() { struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); //... return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } "stat" isimli yapının elemanları ise şu şekildedir; struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* i-node number */ mode_t st_mode; /* File type and mode */ nlink_t st_nlink; /* Number of hard links */ uid_t st_uid; /* User ID of owner */ gid_t st_gid; /* Group ID of owner */ dev_t st_rdev; /* Device ID (if special file) */ off_t st_size; /* Total size, in bytes */ blksize_t st_blksize; /* Block size for filesystem I/O */ blkcnt_t st_blocks; /* Number of 512B blocks allocated */ /* Since Linux 2.6, the kernel supports nanosecond precision for the following timestamp fields. For the details before Linux 2.6, see NOTES. */ struct timespec st_atim; /* Time of last access */ struct timespec st_mtim; /* Time of last modification */ struct timespec st_ctim; /* Time of last status change */ #define st_atime st_atim.tv_sec /* Backward compatibility */ #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec }; Bu veri elemanlarından, >>>>> "st_dev" elemanı, dosyanın içinde bulunduğu aygıtın aygıt numarasını belirtir. Türü "dev_t" biçiminde olup, herhangi bir tam sayı türü biçiminde olabilir. Genellikle programcılar pek gereksinim duyulmaz. >>>>> "st_ino" elemanı, ilgili dosyanın bilgilerini barındıran "i-node" elemanının, "i-node" tablosundaki indis numarasını belirtir. Her dosya için bir "i-node" elemanı vardır ki bu "i-node" elemanları da "i-node" tablosu içerisindedir. Fakat ömrü biten dosyalara ilişkin indis numaraları, başka dosyalar için kullanılabilir. Bütün bu organizasyon ise dosya sistemlerinin disk organizasyonları kapsamındadır. Artık bellekte değil, disk üzerindeki organizasyondan bahsediyoruz. "ls -i" komutu ile bizler bu indis bilgilerine erişebiliriz. Bu elemanın türü "ino_t" biçiminde olup, işaretsiz bir tam sayı biçiminde olmalıdır. >>>>>> "i-node tablosu" ve "dosya betimleyici tablosu" aslında birbirine çok benzemektedir. Bir tanesi disk tarafı ile ilgilenirken, diğeri bellek tarafıyla ilgilenmektedir. "open" fonksiyonu bir dosyayı açarken "i-node" tablosundan ilgili dosyaya ait bilgileri çekiyor ve bunları dosya betimleyici tablosuna yeni eklenecek olan "file" türden yapının içine yazmaktadır. Benzer şekilde "stat" fonksiyonu da yine "i-node" tablosundan ilgili dosyaya ait bilgileri çekmektedir. Aşağıda bu hususa ilişkin bir örnek verilmiştir: * Örnek 1, #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # i-node number: 305691 */ struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); printf("i-node number: %llu", (unsigned long long)file_info.st_ino); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>> "st_mode" isimli eleman, dosyanın erişim haklarını ve dosyanın türünü(regular file, directory etc.) içermektedir. Hangi hakların olup olmadığı öğrenmek için, "st_mode & S_IXXX" şeklinde bir "bitwise-AND" işlemi uygulamamız gerekmektedir. Çünkü "st_mode" elemanı, çeşitli bitleri "1" olan bir elemandır. Dosyanın tür bilgisi de yine bu elemanın içerisinde bitsel olarak kodlanmıştır. Fakat bitlerin konumları sistemden sisteme değişmektedir. Dolayısıyla bu bitlerin rakamsal karşılıklarını değil, bu bitlere karşılık gelen fonksiyonel makroları ya da sembolik sabitleri kullanmalıyız. >>>>>> "sys/stat.h" içerisindeki "S_ISXXX" biçimindeki makroları kullanmaktır. Bu makrolar, eğer dosya ilgili türden ise "non-zero" bir değer, aksi halde "zero" değer döndürmektedir. Bu makrolar şu şekildedir; S_ISBLK(m) - Test for a block special file. - "ls -l" de 'b' olarak gözükür. S_ISCHR(m) - Test for a character special file. - "ls -l" de 'c' olarak gözükür. S_ISDIR(m) - Test for a directory. - "ls -l" de 'd' olarak gözükür. S_ISFIFO(m) - Test for a pipe or FIFO special file. - "ls -l" de 'p' olarak gözükür. S_ISREG(m) - Test for a regular file. - "ls -l" de '-' olarak gözükür. S_ISLNK(m) - Test for a symbolic link. - "ls -l" de 'l' olarak gözükür. S_ISSOCK(m) - Test for a socket. - "ls -l" de 's' olarak gözükür. >>>>>> "sys/stat.h" içerisindeki sembolik sabitleri kullanmaktır. Bu sabitleri KULLANMADAN EVVEL ilk önce "mode_t" türünden olan "mode" değişkenimizi "S_IFMT" ile "bitwise-AND" işlemine sokuyoruz. Çıkan sonuçları da aşağıdaki sembolik sabitlerle "==" işlemine tabii tutuyoruz. S_IFBLK - Block special. S_IFCHR - Character special. S_IFIFO - FIFO special. S_IFREG - Regular. S_IFDIR - Directory. S_IFLNK - Symbolic link. S_IFSOCK - Socket. [Option End] Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, #include #include #include void exit_sys(const char* msg); void display_modes(mode_t mode); int main() { /* # OUTPUT # xwrxwrxwr- */ struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); display_modes(file_info.st_mode); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } void display_modes(mode_t mode) { // ACCESS RIGHTS ON THE FILE static mode_t modes[] = { S_IXOTH, S_IWOTH, S_IROTH, S_IXGRP, S_IWGRP, S_IRGRP, S_IXUSR, S_IWUSR, S_IRUSR }; for(int i = 0; i < 9; ++i) printf("%c", modes[i] & mode ? "xwr"[i % 3] : '-'); // TYPE OF THE FILE - v1 static mode_t f_types[] = { S_IFBLK, S_IFCHR, S_IFIFO, S_IFREG, S_IFDIR, S_IFLNK, S_IFSOCK }; for(int i = 0; i < 7; ++i) if( (mode & S_IFMT) == f_types[i] ) { printf("%c", "bcp-dls"[i]); break; } /* // TYPE OF THE FILE - v2 if( S_ISBLK(mode) ) printf("%c", 'b'); else if( S_ISCHR(mode) ) printf("%c", 'c'); else if( S_ISDIR(mode) ) printf("%c", 'd'); else if( S_ISFIFO(mode) ) printf("%c", 'p'); else if( S_ISREG(mode) ) printf("%c", '-'); else if( S_ISLNK(mode) ) printf("%c", 'l'); else if( S_ISSOCK(mode) ) printf("%c", 's'); else fprintf(stderr, "SOMETHING WENT WRONG!...\n"); */ } >>>>> "st_nlink" elemanı, dosyanın "hard-link" sayısını belirtmektedir. "hard-link" kavramı ileride ele alınacaktır. Bu tür ("nlink_t") herhangi bir tam sayı türü olabilir. "ls -l" komutundaki, dosya izinlerinden sonra gelen ama "user id" bilgisinden önce gelen kısımdaki, bilgidir bu bilgi. * Örnek 1, #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # hard-link number: 1 */ struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); printf("hard-link number: %llu", (unsigned long long)file_info.st_nlink); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>>> "st_uid" ve "st_gid" elemanları ise dosyanın sırasıyla Kullanıcı ID ve Grup ID bilgilerini tutmaktadır. "ls -l" komutu ile karşımıza yazı olarak çıkan bilgiler, bu elemanlarda rakam olarak tutulmaktadır. Bu rakamların isim karşılığı ise daha önceki derslerde de anlatıldığı üzere "etc/passwd" ve "etc/group" dosyalarından elde etmektedir. "uid_t" türü herhangi bir tam sayı türü olabilir. * Örnek 1, #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # User ID : 0 Group ID : 0 */ struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); printf("User ID : %llu\n", (unsigned long long)file_info.st_uid); printf("Group ID : %llu", (unsigned long long)file_info.st_gid); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>>> "st_rdev" elemanı, dosya bir aygıt dosyası ise, temsil ettiği aygıtın numarasını bize vermektedir. "dev_t" türündendir. >>>>> "st_size" elemanı ise dosyanın uzunluğunu tutmaktadır. "off_t" türü, daha önceden de belirttiğimiz gibi işaretli bir tam sayı türü olabilir. * Örnek 1, #include #include #include void exit_sys(const char* msg); int main() { /* # test.txt # Ulya Yürük */ /* # OUTPUT # File Size : 12 */ struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); printf("File Size : %lld\n", (long long)file_info.st_size); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>>> "st_blksize" elemanı, dosyamızın kapladığı blok sayısının bayt cinsinden değerini tutmaktadır. Dosyalar diskte bloklar halinde tutulurlar. Diyelim ki dosyamızın 1000 byte olsun. Sistemimizdeki blokların en küçük bayt değeri de 512 şeklindedir. Bu durumda dosyamız diskte iki blokluk yer kaplayacaktır. Geriye kalan 24 baytlık alan ölü alan olacaktır. Dolayısıyla bu eleman 1024 baytlık bir değer tutacaktır. (Ek olarak, 512 bayt büyüklüğü, dosyaları kopyalarkenki efektif "buffer" uzunluğu olarak da kullanılmaktadır.) Yine 1 baytlık bir dosya oluştursak bile, bu yine bir blok içerisinde saklanacaktır ve bloklar daha küçük parçalara bölünemezler. Windows sistemlerde bunu şöyle de görebiliriz; "Dosyanın büyüklüğü" ve "Dosyanın diskte kapladığı alan". Buradan hareketle diyebiliriz ki 10 adet 10 baytlık dosyalar, 1 adet 100 baytlık dosyadan daha fazla disk üzerinde yer kaplarlar. Bu elemanın türü işaretli bir tam sayı türü olabilir. Bu eleman 512'nin katları şeklinde değer tutmaktadır. * Örnek 1, #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # Block Size : 4096 */ struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); printf("Block Size : %lld\n", (long long)file_info.st_blksize); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>>> "st_blocks" elemanı, dosyanın kapladığı blok sayısını belirtmektedir. Örneğin, 1000 baytlık bir dosya disk üzerinde 3 blok yer kaplayacaktır eğer her bir blok 512 baytlık ise. İşte 2 rakamını bu eleman tutmaktadır. Bu elemanın türü de işaretli bir tam sayı türü olmalıdır. * Örnek 1, #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # Block Amount : 8 */ struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); printf("Block Amount : %lld\n", (long long)file_info.st_blocks); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>>> UNIX/Linux sistemleri bir dosya için üç tane tarih/zaman bilgisi tutmaktadır. Bunlardan ilki son yazma zamanı, ikincisi son okuma zamanı ve üçüncüsü ise dosyanın "i-node" bilgilerinin en son ne zaman değiştirildiği bilgisidir. Microsoft sistemlerinde dosyanın ilk hayata geldiği tarih/zaman bilgisi tutulurken, UNIX/Linux sistemlerinde böyle bir bilgi tutulmamaktadır. "write" fonksiyonu işini başarılı bir şekilde yaptığında en son yazma zamanı bilgisini, "read" fonksiyonu en son okuma zamanı bilgisini değiştirirken, "chmod" komutu ise dosyanın "i-node" bilgilerinin en son ne zaman değiştirildiği bilgisini değiştirir. Ek olarak "write" fonksiyonu ile dosyanın uzunluğunu değiştirirsek, ilgili dosyanın "i-node" bilgilerini de değiştirmiş olacağız. "stat" yapısının "st_atim", "st_mtim" ve "st_ctim" isimli elemanları sırasıyla "time of last access", "time of modification" ve "time of last status change" bilgilerini tutmaktadır. 2008 öncesi POSIX standartlarda bu elemanların isimleri "st_atime", "st_mtime" ve "st_ctime" şeklindeydi. Eski elemanların türler "time_t" şeklindeyken, yeni standartlarda tür "timespec" türünden. Eskiye dönük uyumluluğu korumak adına da, şu anki isimlerin içindeki bir takım elemanlar, eski isimler ile tekrardan "typedef" edilmiştir. struct timespec st_atim; /* Time of last access */ struct timespec st_mtim; /* Time of last modification */ struct timespec st_ctim; /* Time of last status change */ #define st_atime st_atim.tv_sec /* Backward compatibility */ #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec Eski standartlarda bu üç eleman "01/01/1970" tarihinden itibaren geçen saniye sayısını tutmaktaydılar ki C dilinde tarihin bu şekilde olması bir zorunluluk değildir, yeni standartlardan itibaren, yeni isimleriyle birlikte, çözünürlülüğünü nanosaniye çektiler. * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # Last Modification : 09/12/2022 18:22:52 Last Access : 09/12/2022 18:22:52 Last I-node Changed : 09/12/2022 18:22:52 */ struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); struct tm* time_info; time_info = localtime(&file_info.st_mtim.tv_sec); printf( "Last Modification : %02d/%02d/%04d %02d:%02d:%02d\n", time_info->tm_mday, time_info->tm_mon + 1, time_info->tm_year + 1900, time_info->tm_hour, time_info->tm_min, time_info->tm_sec ); time_info = localtime(&file_info.st_atim.tv_sec); printf( "Last Access : %02d/%02d/%04d %02d:%02d:%02d\n", time_info->tm_mday, time_info->tm_mon + 1, time_info->tm_year + 1900, time_info->tm_hour, time_info->tm_min, time_info->tm_sec ); time_info = localtime(&file_info.st_ctim.tv_sec); printf( "Last I-node Changed : %02d/%02d/%04d %02d:%02d:%02d\n", time_info->tm_mday, time_info->tm_mon + 1, time_info->tm_year + 1900, time_info->tm_hour, time_info->tm_min, time_info->tm_sec ); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>>> "ls -l" komutunun temsili bir implementasyonu, * Örnek 1, #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char* msg); void my_ls_command(struct stat* file_info, const char* file_name); int main() { /* # test.txt # Ahmet Kandemir Pehlivanli */ /* # OUTPUT # [-xwrxwrxwr 1 0 0 25 Dec 9 19:57 test.txt] */ struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); my_ls_command(&file_info, "test.txt"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } void my_ls_command(struct stat* file_info, const char* file_name) { static char buffer[BUFFER_SIZE]; static mode_t modes[] = { S_IXOTH, S_IWOTH, S_IROTH, S_IXGRP, S_IWGRP, S_IRGRP, S_IXUSR, S_IWUSR, S_IRUSR }; static mode_t f_types[] = { S_IFBLK, S_IFCHR, S_IFIFO, S_IFREG, S_IFDIR, S_IFLNK, S_IFSOCK }; int index = 0; for(int i = 0; i < 7; ++i) if( (file_info->st_mode & S_IFMT) == f_types[i] ) { buffer[index++] = "bcp-dls"[i]; break; } for(int i = 0; i < 9; ++i) buffer[index++] = modes[i] & file_info->st_mode ? "xwr"[i % 3] : '-'; index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_nlink); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_uid); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_gid); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_size); struct tm* ptime; ptime = localtime(&file_info->st_mtim.tv_sec); index += strftime(buffer + index, BUFFER_SIZE, " %b %2e %H:%M", ptime); sprintf(buffer + index, " %s", file_name); printf("[%s]", buffer); } >>>> "fstat" fonksiyonu, "stat" fonksiyonunun aksine dosyanın yol ifadesini değil, dosya betimleyicisini argüman olarak almaktadır. "open" fonksiyonu halihazırda ilgili dosyanın yol ifadesini kullanarak, dosyaya ait bilgileri diskten çekmektedir. Dolayısıyla dosyaya ait bilgileri tekrardan yol ifadesi kullanarak almak, dosya betimleyicisi kullanarak almaktan daha yavaş bir yöntemdir. Burada vurgulanmak istenen nokta şudur; başka bir amaç için halihazırda açık olan bir dosyanın bilgilerine "fstat" ile çok daha hızlı bir şekilde erişebiliriz. Ama önce bir dosyayı açıp, sonra ona "fstat" uygulamamız anlamsız olacaktır. * Örnek 1, #include #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char* msg); void my_ls_command(struct stat* file_info, const char* file_name); int main() { /* # test.txt # Ahmet Kandemir Pehlivanli */ /* # OUTPUT # [-xwrxwrxwr 1 0 0 25 Dec 9 19:58 test.txt] */ int fd; if((fd = open("test.txt", O_RDONLY)) == -1) exit_sys("open"); struct stat file_info; if(fstat(fd, &file_info) == -1) exit_sys("stat"); my_ls_command(&file_info, "test.txt"); close(fd); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } void my_ls_command(struct stat* file_info, const char* file_name) { static char buffer[BUFFER_SIZE]; static mode_t modes[] = { S_IXOTH, S_IWOTH, S_IROTH, S_IXGRP, S_IWGRP, S_IRGRP, S_IXUSR, S_IWUSR, S_IRUSR }; static mode_t f_types[] = { S_IFBLK, S_IFCHR, S_IFIFO, S_IFREG, S_IFDIR, S_IFLNK, S_IFSOCK }; int index = 0; for(int i = 0; i < 7; ++i) if( (file_info->st_mode & S_IFMT) == f_types[i] ) { buffer[index++] = "bcp-dls"[i]; break; } for(int i = 0; i < 9; ++i) buffer[index++] = modes[i] & file_info->st_mode ? "xwr"[i % 3] : '-'; index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_nlink); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_uid); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_gid); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_size); struct tm* ptime; ptime = localtime(&file_info->st_mtim.tv_sec); index += strftime(buffer + index, BUFFER_SIZE, " %b %2e %H:%M", ptime); sprintf(buffer + index, " %s", file_name); printf("[%s]", buffer); } >>>> Sembolik Bağlantı Dosyaları aslında bir gösterici dosyasıdır. Bu dosyanın "i-node" elemanı sadece hangi dosyayı gösterdiğini belirtmektedir. Windows sistemlerindeki kısayol uygulamalarına benzemektedir. Diskte bu dosya için bir yer ayrılmamakta, sadece bir adet "i-node" elemanı oluşturuluyor. Eğer "stat" ya da "fstat" fonksiyonlarına bu dosyayı geçersek, bu dosyanın gösterdiği dosyanın bilgilerini çekeriz. "lstat" fonksiyonuna bu dosyayı geçersek, işte bu dosyanın kendisine dair bilgileri çekeriz. Unutulmamalıdır ki "stat" ve "fstat", arada kaç tane sembolik bağlantı dosyayı olursa olsun, her daim en son gösterilen esas dosyaya erişmektedir. "open" fonksiyonu da benzer şekilde bağlantı linkini izleyecektir. Aşağıda bu hususa ilişkin bir örnek verilmiştir: * Örnek 1, Aşağıdaki örnek çevrimiçi gcc derleyicisi kullanılarak çalıştırılmıştır. İlgili dosya bir sembolik bağlantı dosyası DEĞİLDİR. #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char* msg); void my_ls_command(struct stat* file_info, const char* file_name); int main(void) { /* # test.txt # */ /* # OUTPUT # [-xwrxwrxwr 1 0 0 0 Dec 15 13:08 test.txt] */ struct stat file_info; if(lstat("test.txt", &file_info) == -1) exit_sys("stat"); my_ls_command(&file_info, "test.txt"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } void my_ls_command(struct stat* file_info, const char* file_name) { static char buffer[BUFFER_SIZE]; static mode_t modes[] = { S_IXOTH, S_IWOTH, S_IROTH, S_IXGRP, S_IWGRP, S_IRGRP, S_IXUSR, S_IWUSR, S_IRUSR }; static mode_t f_types[] = { S_IFBLK, S_IFCHR, S_IFIFO, S_IFREG, S_IFDIR, S_IFLNK, S_IFSOCK }; int index = 0; for(int i = 0; i < 7; ++i) if( (file_info->st_mode & S_IFMT) == f_types[i] ) { buffer[index++] = "bcp-dls"[i]; break; } for(int i = 0; i < 9; ++i) buffer[index++] = modes[i] & file_info->st_mode ? "xwr"[i % 3] : '-'; index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_nlink); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_uid); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_gid); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_size); struct tm* ptime; ptime = localtime(&file_info->st_mtim.tv_sec); index += strftime(buffer + index, BUFFER_SIZE, " %b %2e %H:%M", ptime); sprintf(buffer + index, " %s", file_name); printf("[%s]", buffer); } >>>>> Sembolik Bağlantı Dosyaları üzerine; >>>>>> Geçen derste de anlatıldığı üzere, bir dosyayı işaret eden dosyalara sembolik bağlantı dosyaları denmektedir. Bu dosyalara aynı zamanda "soft-link" dosyalar da denmektedir. >>>>>> Sembolik bağlantı dosyaları adeta bir "pointer" dosyalardır. >>>>>> İşletim sistemleri, bu tip dosyalar için, diskte yalnızda bir "i-node" elemanı tutmaktadır. Komut satırından "ln -s" ile sembolik bağlantı dosyaları oluşturulabilir. >>>>>> Bu tip dosyalar "ls -l" komutunda "->" işareti ile gösterilmektedir. Yine bu tip dosyaların tipleri, "ls -l" komutunda "l" harfi ile gösterilmektedir. >>>>>> Ek olarak, bir sembolik bağlantı dosyası, başka bir sembolik bağlantı dosyasını da gösterebilir. >>>>>> BİR İŞLETİM SİSTEMİNDE, BİR DOSYANIN YOL İFADESİNİ ARGÜMAN OLARAK ALAN FONKSİYONLARIN HEMEN HEPSİ SEMBOLİK BAĞLANTILARI TAKİP EDER. YANİ SEMBOLİK BAĞLANTI DOSYASININ KENDİSİNİ DEĞİL, GÖSTERDİĞİ DOSYAYI ELE ALIR. Örneğin, "lstat" fonksiyonu izlemez fakat "open" fonksiyonu izlemektedir. >>>>>> Sembolik bağlantı dosyasının gösterdiği dosyayı silmemiz durumunda, bizim sembolik bağlantı dosyamız artık "dangling symbolic-link file" olarak geçer. Böyle bir dosyayı açmaya çalıştığımız zaman da sanki hiç var olmamış bir dosyayı açarken karşılaşacağımız "errno" hatası ile karşılaşırız. Çünkü bağlantının işaret ettiği bir dosya mevcut değildir. >>>>>> Sembolik bağlantı dosyaları, Windows sistemlerindeki kısayol dosyalarına çok benzemektedir. >>>>>> "a" isimli sembolik bağlantı dosyası, "b" dosyasını gösteriyor olsun. "b" de "c" isimli sembolik bağlantı dosyasını gösteriyor olsun. Bu durumuna "loop" durumu denmektedir. Bir dosya fonksiyonuna(ama temel ama yardımcı), bu dosyalardan birisinin yol ifadesi ya da "File Description" değeri argüman olarak geçildiğinde, iş bu fonksiyonlar "errno" değerini uygun değer ile değiştiriyorlar. Çünkü çoğu dosya fonksiyonu, "lseek" fonksiyonu istisnai bir fonksiyondur çünkü bu fonksiyon sembolik bağlantıları İZLEMEMEKTEDİR, sembolik bağlantıları İZLEMEKTEDİR. ORTADA BİR SONSUZ DÖNGÜ SÖZ KONUSU OLDUĞUNDAN, SİSTEM ÇÖKEBİLİR. "errno" değişkeninin değeri de "ELOOP" biçimindedir. >>>> Komut satırından "stat" ve "fstat" komutlarını da çağırarak ilgili dosyalara ait bilgileri çekebiliriz. * Örnek 1, "stat sample.c" Size: 2832, Dosyanın büyüklüğü Blocks: 8, Dosyanın kaç blok kapladığı IO Blocks, 4096, Dosyanın kapladığı blokların byte cinsinden karşılığı Inode: 1207667, Dosyanın "i-node" numarası //... >>> "unlink" ve "remove" fonksiyonları: Bu iki fonksiyon da dosya silmek için kullanılan fonksiyonlardır. Her iki fonksiyon da aynı şeyi yapmaktadır fakat "unlink" fonksiyonu bir POSIX fonksiyonuyken, "remove" fonksiyonu ise standart bir C fonksiyonudur. Her iki fonksiyon da silinecek dosyaya ait bir yol ifadesini argüman olarak almaktadır ve başarı durumunda "0" ile başarısızlık durumunda ise "-1" ile geri dönmektedir. "remove" fonksiyonu "stdio.h" başlık dosyasında bildirilmiş, "unlink" ise "unistd.h" başlık dosyasında.Daha önce de belirtildiği gibi bir dosyayı silebilmek için prosesimizin o dosya üzerinde "w" hakkı değil, ilgili dosyanın içinde bulunduğu dizinde "w" hakkına sahip olması gerekmektedir. Buradan hareketle silinecek dosyanın sahibi konumunda olmayan fakat dosyanın bulunduğu dizinde "w" hakkına sahip olan prosesler de silme işlemini gerçekleştirebilir(Process ID değeri "0", yani "root", olan prosesler herhangi bir kontrole tabii tutulmadan direkt olarak dosyayı silebilirler). "ls -l" komutunu çalıştırdığımız zaman karşımıza çıkan bilgilerden "hard-link" bilgisi, dosyanın ne zaman fiziksel olarak silineceğini göstermektedir. Bu bir sayaç gibidir. "hard-link" adedi sıfıra olduğunda dosya fiziksel olarak silinir. Ek olarak bizler "open" ile açtığımız fakat kapatmadığımız dosyaları da silebiliriz. Bu durumda ilgili dosyamız ilk önce "dizin girişi" denilen bölgeden silinmekte, ilgili dosya tamamiyle kapatıldıktan sonra da fiziksel olarak silinmektedir (Birden fazla prosesin silinecek olan dosyayı açması durumunda, bütün proseslerin bu dosyayı kapatması gerekmektedir. İşte bunları tutan sayaca da "hard-link" sayacı denmektedir). Kapalı olan bir dosyanın silinmesi de aynı şekildedir; ilk önce dizin girişinden silinirler ve "hard-link" sayacı bir eksiltilir. Eğer bu sayaç "0" olursa dosya fiziksel olarak da silinir. "hard-link" sayacının detaylarına ileride değineceğiz. * Örnek 1,Yol İfadelerinde ismi verilen dosyaların silinmesi: //.. #include #include #include int main(int argc, char* argv[]) { /* # INPUT # test.txt x.dat */ /* # OUTPUT # */ if(argc == 1) { fprintf(stderr, "file name(s) must be specified!...\n"); exit(EXIT_FAILURE); } for(int i = 1; i < argc; ++i) { if(unlink(argv[i])== -1) { perror("unlink"); } } return 0; } >>>> Dizin Girişleri(Directory Entry): >>>>> Bu girişler, ilgili dizinin içerisinde bulunmaktadır ve formatları dosya sisteminden dosya sistemine değişebilmektedir. >>>>> Dizin Girişleri, ilgili dizinde bulunan dosyalara ait "Dosya İsmi - I-Node Numaraları" çiftlerini tutmaktadır. >>>>>> Bir dosya silindiği zaman ilk olarak, Dizin Girişindeki ilgili dosyaya "Dosya İsmi - I-Node Numara" çifti silinmektedir. Fakat dosyanın fiziksel olarak silinip silinmeyeceği, ilgili dosyaya ait "hard-link" numarasına bağlıdır. İlgili "i-node" elemanını kaç adet dizin girişi gösterirse, o dosyanın "hard-link" sayacı da o kadar oluyor. * Örnek 1, Directory A I-Node Table (File Name) - (I-Node No) ... a.txt 187181 ... 187181 Directory B ... (File Name) - (I-Node No) ... b.txt 187181 ... İki adet "a.txt" ve "b.txt" adında dosyamız olsun. Görüldüğü üzere bu iki dosyanın da "i-node" numarası aynı. Dosyaların "i-node" numaraları birbiri ile aynı olduğu için, "open" fonksiyonu ile "a.txt" ye ait yol ifadesi geçmekle "b.txt" ye ait yol ifadesini geçmenin arasında bir fark yoktur. İkisi de günün sonunda, "187181" numarasına karşılık gelen dosyayı açacaktır. Bu durumda iki farklı dizin girişi, tek bir "i-node" elemanını gösteriyordur. İşte bu dosyanın "hard-link" rakamı da iki olacaktır. Ama "remove" ile ama "unlink" fonksiyonu ile bir dosya silindiğinde, ilk olarak dizin girişinden silinme yapılmakta ve ilgili dosyaya ait "hard-link" numarası da bir azaltılmakta. Yukarıdaki örneğin baz alırsak, "a.txt" dosyasını silersek, "hard-link" numarası artık bir oldu. "b.txt" dosyası hala aynı "i-node" elemanına sahip olduğundan, ilgili dosya fiziksel olarak silinmedi. Nihayet "b.txt" dosyasını da silersek, "hard-link" numarası sıfır olacağından, dosya tamamiyle siliniyor. Aslında tamamen silinmekten kastedilen şey şudur; Daha önce de belirttiğimiz gibi dosyalar bloklardan oluşmaktadır. Bir dosyanın "i-node" elemanı o dosyanın disteki hangi blokları kullandığı bilgisini tutmaktadır. Ek olarak iki blok daha vardır. Bunlardan bir tanesi hangi "i-node" numarasının kullanımda, hangisinin boş olduğunu bilgisini tutmaktadır ki buna "i-node Bitmap" denir. Diğeri ise diskteki hangi blokların kullanımda, hangilerinin boş olduğu bilgisini tutmaktadır ki buna "Block Bitmap" denir. Bir dosya tamamiyle silindi dediğimizde, kendisine ait "i-node" elemanının içerisi SİLİNMİYOR. SADECE "i-node Bitmap" ve "Block Bitmap" içerisindeki ilgili indeksler boşaltılıyor. Böylelikle işletim sistemi gerçek manada silme işlemi yerine, ilgili alanın üzerine yazma için müsait olduğu bilgisini tutuyor. GERÇEK MANADA SİLMEK İÇİN, İLGİLİ DOSYAYI AÇIP BİZZAT BİZ SİLMELİYİZ. >>>>> Yeni bir dizin oluşturduğumuz zaman genellikle "hard-link" sayacını iki olarak buluruz. Çünkü yeni bir dizin hayata geldiğinde "." ve ".." isminde iki dizin daha oluşturuluyor. Daha önce de anlatıldığı üzere "." dizinin kendisini, ".." ise bir üst dizini göstermektedir (Yani "." dizininin "i-node" numarası ile dizinimizin "i-node" numarası aynı olurken, ".." dizininin "i-node" numarası ile kendi dizinimizin bir üst dizininin "i-node" numarası aynı olmaktadır. Fakat "." ve ".." dizinleri "ls -l" komutu ile görüntülenmeyecektir. "-a" seçeneğini de "ls" komutuna eklememiz gerekiyor ki bu iki dizini görüntüleyebilelim. "-i" seçeneğini de kullanırsak, "i-node" numaralarını da göreceğiz.). Bir dizin içerisinde yeni bir dizin hayata getirdiğimiz zaman, ".." dizininden dolayı "hard-link" sayacını arttıracaktır çünkü ".." dizini bir üst dizinden "i-node" numarasını almaktadır. >>>> "hard-link" ve "soft-link": >>>>> Sembolik link çıkartmak için "ln -s" komutu kullanılmaktadır. Örneğin, "ln -s x.dat y.dat" şeklindeki bir komut çalıştırıldığında, "y.dat" isimli dosya, "x.dat" isimli dosyaya bağlanmış olmaktadır. Burada esas dosya "x.dat" şeklinde, sembolik link dosyası da "y.dat" şeklindedir. Yani "y.dat" dosyası, "x.dat" dosyasını göstermektedir. >>>>> "hard-link" çıkartmak için de "ln" komutunu herhangi bir "switch" olmadan kullanmamız gerekmektedir. Örneğin, "ln sample.c x.c" komutunu çalıştırırsak, "i-node" numaraları aynı iki dosya meydana getiririz. Bu iki dosya birbirinin eşleniği olacaktır çünkü ikisi de aynı "i-node" numarasına sahiptir. Burada yeni oluşturulan dosya bir "pointer" dosya DEĞİLDİR. Canlı kanlı bir dosyadır. >>>>> "hard-link" sayacı, kaç farklı dosyanın aynı "i-node" numarasına sahip olduğunu tutan bir sayaçtır. Fakat "soft-link" ise bir dosyanın diğer dosyası "point" etmesi durumudur. >>> "chmod" fonksiyonu; dosyanın erişim hakları, diskteki ilgili "i-node" elemanı içerisindedir. "stat" fonksiyonu ile bu bilgileri belleğe aktarıp "ls" komutu ile de ekrana yazdırıyoruz. Bir dosya ilk hayata getirilirken de "open" fonksiyonuna bu erişim hakları argüman olarak geçilmektedir. "chmod" POSIX fonksiyonu ile bu erişim haklarını daha sonra değiştirebiliriz. Bu POSIX fonksiyonu ile dosyanın içerisine dokunmadan sadece erişim hakları üzerinde bir değişiklik yapılmaktadır. İş bu fonksiyonumuz "sys/stat.h" içerisinde tanımlanmış olup birinci parametresi ilgili dosyaya ait yol ifadesiyken, ikinci parametresi ise "mode_t" türünden erişim haklarıdır. Başarı durumunda fonksiyon "0", başarısızlık durumunda "-1" ile geri dönmektedir. Fonksiyonun ikinci parametresine, 2008 sonrası sistemlerde, oktal değer de verebiliriz fakat öncesindeki sistemlerde S_IXXX sembolik sabitlerini "bit-wise OR" işlemiyle kombine ederek vermemiz gerekmektedir(tıpkı "open" fonksiyonuna erişim haklarının bilgisini geçer gibi ama tavsiye edilen yöntem yine S_IXXX şeklindeki sembolik sabitlerin kullanılması). "chmod" fonksiyonu ile bir dosyanın erişim haklarını değiştirebilmek için ya o dosya ile aynı "owner" grupta olmalıyız ya "root" kullanıcısı olmalıyız ya da bir takım özel haklara sahip olmalıyız. Aksi halde işlem yapmamız mümkün değildir. "owner" grupta olmak için prosesin ve dosyanın Etkin Kullanıcı ID bilgilerinin birbiri ile aynı olması gerekmektedir. Dosyanın dördüncü üç bitlik erişim hakları olan "S_ISUID", "S_ISGID" ve "S_ISVTX" hakları da yine bu fonksiyon ile değiştirilmeye çalışılabilir. Fakat bazı sistemler "S_ISUID" ve "S_ISGID" erişim hakları değiştirmeye izin vermeyebilir. Yine "chmod" isimli bir "shell" komutu da bulunmaktadır ki bu da aslında "chmod" isimli fonksiyon kullanılarak yazılmıştır. Yine bu "shell" komutunda da erişim hakları oktal dijitler ile belirtilmektedir. Örneğin, "chmod 664 a.txt" şeklinde bir "shell" komutu çalıştıralım. Buradaki "664" oktal sayısının karşılığı, yani üçerli bit halindeki açılmışı, "110 110 100" biçimindedir. Bu da şu anlama gelmektedir;"rw-rw-r--". Fakat buradaki kritik nokta, prosesimizin Etkin Kullanıcı ID değeri ile "a.txt" dosyasının id değeri birbirinin aynısı olmalıdır ya da bizler "root" kullanıcısı olmalıyız (sudo chmod 664 a.txt). Bu komutu örnekte olduğu gibi oktal sayılar ile birlikte kullanırsak, prosesin "umask" değeri DEVREYE GİRMEYECEKTİR. Bu "shell" komutunun diğer kullanım biçiminde de "owner", "group" ya da "other" gruplarından birisine ya da bir kaçına spesifik bir yetki vermede ya da çıkarmada kullanılır. Örneğin, "chmod u+w x.dat" dediğimiz sadece "user" grubuna "w" hakkı ver demektir fakat bu şekilde kullanırsak prosesin "umask" değeri de DEVREYE GİRECEKTİR. "chmod u-w x.dat" ise sadece "user" grubundan "w" hakkını kaldır demektir. "group" ve "other" için ekleme ve çıkarma için sırasıyla "g+w" / "g-w" ve "o+w" / "o-w" şeklinde kullanmalıyız. "a+w" dersek "owner", "group" ve "other" kısımlarının hepsine "w" hakkı ver demektir. "a-w" ise bütün herkesten "w" hakkının silinmesi anlamına gelmektedir. "chmod +w x.dat" şeklindeki bir komut ise bütün gruplara "w" hakkı eklemektedir. Yine bu komutu kullanırken diğer hakları da kombine edebiliriz. Örneğin, "chmod ug+wr x.dat" dediğimiz zaman "user" ve "group" kişilerine "w" ve "r" hakkı verilecektir. Son olarak "chmod" komutunu "=" ile birlikte kullanabiliriz. Örneğin, "chmod g=rw x.dat" dediğimiz zaman "x.dat" dosyasının sadece grup bilgileri için "r" ve "w" hakkı verilecektir. BU ŞEKİLDEKİ BİR KULLANIMDA DA PROSESİN "umask" DEĞERİ DEVREYE GİRMEZ. "chmod" fonksiyonu, prosesin "umask" değerlerinden etkilenmemektedir. Bu nedenle, bu fonksiyona geçilen haklar doğrudan ilgili dosyaya yansıtılmaktadır. Fakat "open" fonksiyonunda durum böyle değildir; arada "umask" değerleri ile filtreleme yapılmaktadır. * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int check_umask_arg(const char* str); int main(int argc, char* argv[]) { if(argc < 3) { fprintf(stderr, "too few arguments!...\n"); exit(EXIT_FAILURE); } /* * "argv[1]", yeni "umask" değerleri. */ if(!check_umask_arg(argv[1])) { fprintf(stderr, "invalid mode: %s\n", argv[1]); exit(EXIT_FAILURE); } int mode; sscanf(argv[1], "%o", &mode); for(int i = 2; i < argc; ++i) if(chmod(argv[i], mode) == -1) fprintf(stderr, "cannot change mode: %s\n", argv[i]); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } int check_umask_arg(const char* str) { if(strlen(str) > 4) return 0; for(int i = 0; str[i] != '\0'; ++i) if(str[i] < '0' || str[i] > '7') return 0; return 1; } * Örnek 2, #include #include #include #include void exit_sys(const char* msg); int check_umask_arg(const char* str); void display_modes(mode_t mode); int main(int argc, char* argv[]) { // error handling section if(argc < 3) { fprintf(stderr, "too few arguments!...\n"); exit(EXIT_FAILURE); } if(!check_umask_arg(argv[1])) { fprintf(stderr, "invalid mode: %s\n", argv[1]); exit(EXIT_FAILURE); } // print the initial "st_mode" struct stat file_info; if(stat("test.txt", &file_info) == -1) exit_sys("stat"); display_modes(file_info.st_mode); // change the "st_mode" int modeval; mode_t mode; mode_t modes[] = { S_ISUID, S_ISGID, S_ISVTX, S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH }; sscanf(argv[1], "%o", &modeval); for(int i = 11; i >= 0; --i) if(modeval >> i & 1) mode |= modes[11 - i]; for(int i = 2; i < argc; ++i) if(chmod(argv[i], mode) == -1) fprintf(stderr, "cannot change mode: %s\n", argv[i]); // print the last "st_mode" if(stat("test.txt", &file_info) == -1) exit_sys("stat"); display_modes(file_info.st_mode); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } int check_umask_arg(const char* str) { if(strlen(str) > 4) return 0; for(int i = 0; str[i] != '\0'; ++i) if(str[i] < '0' || str[i] > '7') return 0; return 1; } void display_modes(mode_t mode) { // TYPE OF THE FILE - v2 if( S_ISBLK(mode) ) printf("%c", 'b'); else if( S_ISCHR(mode) ) printf("%c", 'c'); else if( S_ISDIR(mode) ) printf("%c", 'd'); else if( S_ISFIFO(mode) ) printf("%c", 'p'); else if( S_ISREG(mode) ) printf("%c", '-'); else if( S_ISLNK(mode) ) printf("%c", 'l'); else if( S_ISSOCK(mode) ) printf("%c", 's'); else fprintf(stderr, "SOMETHING WENT WRONG!...\n"); } >>> "chown" fonksiyonu, bir dosyanın Kullanıcı ID ve Grup ID değerlerini değiştirmek için kullanılır. Örneğin, "x.dat" isminde bir dosyamız olsun. Bu dosyanın Kullanıcı ID değeri "kaan", Grup ID değeri ise "study" olsun. Bu dosyanın Kullanıcı ID değerini "ali" olarak değiştirmemiz, aslında dosyayı oluşturan kişinin "ali" olduğu algısı oluşturmaktadır. Bu da "ali" kullanıcısını zan altında bırakabilir. Böyle bir kötü niyetli kullanım sezdiklerinden, bazı sistemler bu fonksiyonun kullanılmasına müsaade etmemektedir. "_POSIX_CHOWN_RESTRICTED" makrosunun ilgili sistemde tanımlı olup olmadığını sorgulayarak, hangi sistemlerin bu fonksiyonun kullanımına izin verdiğini öğrenebiliriz fakat günümüzdeki çoğu sistemde bu makro TANIMLIDIR(iş bu makro da "unistd.h" başlık dosyasında tanımlanmıştır). Eğer prosesimizin Etkin Kullanıcı ID değeri, ilgili dosyanın Kullanıcı ID değeri ile aynıysa, bu fonksiyon çağrısı sonucunda dosyanın Grup ID değeri prosesin kendi Grup ID değeri ya da prosesin kendi ek Grup ID değerlerinden birisi olarak DEĞİŞTİRİLİR. BAMBAŞKA BİR Grup ID DEĞERİ İLE YİNE DEĞİŞTİREMEYİZ. BAHSİ GEÇEN KISITLAMA SADECE PROSESİN ETKİN KULLANICI ID DEĞERİ İLE DOSYANIN KULLANICI ID DEĞERİNİN FARKLI OLMASI DURUMUNDA GEÇERLİDİR. Bu fonksiyon da yine "unistd.h" isimli başlık dosyasında bildirilmiştir. Birinci parametresi ilgili dosyaya ait olan yol ifadesi, ikinci parametresi ve üçüncü parametresi ise sırasıyla yeni Kullanıcı ID ve yeni Grup ID değerleridir. Eğer prosesimizin Etkin Kullanıcı ID değeri "0" ise, herhangi bir kısıtlamaya maruz kalmadan dosyanın kullanıcı ve Grup ID değerlerini rahatlıkla değiştirebilir. Ek olarak bu fonksiyon ile yalnızda kullanıcı ya da Grup ID değeri de değiştirilebilir. Bu durumda değiştirmek İSTEMEDİKLERİMİZ için "-1" değerini geçmeliyiz. Fonksiyonumuz başarı durumunda "0", başarısızlık durumunda ise "-1" ile geri dönmektedir. POSIX fonksiyonuna ek olarak "chown" isimli bir "shell" komutu daha vardır. Bu komut ile yine dosyaların Kullanıcı ID ve Grup ID değerlerini değiştirebiliriz. "sudo chown kaan:studt test.txt" komutu ile "test.txt" dosyasının Kullanıcı ID ve Grup ID değerlerini sırasıyla "kaan" ve "studt" haline getirmiş oluruz. "sudo chown kaan test.txt" komutu ile sadece Kullanıcı ID, "sudo chown :studt test.txt" ile de sadece Grup ID değerlerini DEĞİŞTİRİRİZ. * Örnek 1, #include #include int main(int argc, char* argv[]) { /* # OUTPUT # chown IS RESTRICTED */ #ifdef _POSIX_CHOWN_RESTRICTED printf("chown IS RESTRICTED\n"); #else printf("chown IS [NOT] RESTRICTED\n"); #endif } * Örnek 2, (AŞAĞIDAKİ PROGRAMI "sudo" İLE ÇALIŞTIRIRSAK, İŞLEM BAŞARILI OLACAKTIR) #include #include #include void exit_sys(const char* msg); int main(int argc, char* argv[]) { /* # OUTPUT # chown: Operation not permitted */ if(chown("test.txt", 1001, -1) == -1) exit_sys("chown"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> "fchown" fonksiyonu, "chown" fonksiyonunun "File Description" alan versiyonudur. Dolayısıyla halihazırda açılmış bir dosya var ise onun "fd" değerini bu fonksiyona geçebiliriz. "chown" fonksiyonundaki kısıtlamalar da yine bu fonksiyon için de geçerlidir. Her iki fonksiyon arasındaki tek fark, birisinin "id" değeri almasıyken diğerinin "fd" değeri alması. Buradan "fd" ile kastedilen şey "open" fonksiyonunun geri dönüş değeridir. >>> "fchmod" fonksiyonu, "chmod" fonksiyonunun "File Description" alan versiyonudur. Dolayısıyla halihazırda açılmış bir dosya var ise onun "fd" değerini bu fonksiyona geçebiliriz. Yine "chmod" ile aynı başlık dosyasında bildirilmişlerdir. >>> "mkdir" fonksiyonu, bir dizin oluşturmak için kullanılan bir fonksiyondur. Çünkü "open" fonksiyonu ile bir dizin değil, sadece "regular file" oluşturabiliriz. "sys/stat.h" başlık dosyasında bildirilmiş olup, ilk parametresi oluşturulacak dizinin yol ifadesidir. İkinci parametresi de ilgili dizinin erişim haklarıdır. Tıpkı "creat" fonksiyonunda olduğu gibi, bu fonksiyona geçilen erişim hakları da ilgili prosesin "umask" değerinden etkilenecektir. Eğer prosesin "umask" değerinden etkilenmemesini istiyorsak, ilgili prosesin "umask" değerini evvelce sıfıra çekmeliyiz. Başarı durumunda iş bu fonksiyon "0" ile, hata durumunda ise "-1" ile dönmektedir. Anımsayacağımız üzere dizinlerdeki "x" hakkı, yol ifadelerinin açılması sırasında ilgili dizinin içinden geçilebilirlik anlamına gelmektedir. Dolayısıyla bir dizin oluştururken bu dizine "x" hakkının verilmesi önem arz etmektedir. Bir dizin hayata getirdiğimiz zaman, yeni oluşturulan bu dizinin içinde iki tane daha dizin otomatik olarak oluşturulur. Bu dizinle "." ve ".." dizinleri. "." dizini yeni oluşturduğumuz dizinin bulunduğu dizinin "i-node" elemanını işaret ederken, ".." dizini ise yeni oluşturulan dizinin bulunduğu dizinin bir üst dizininin "i-node" elemanını işaret eder. Böylelikle "." ve ".." dizinleri ilgili "i-node" elemanlarındaki "hard-link" sayaçlarını (yani dizinimizin bulunduğu dizinin ve onun bir üst dizininin "hard-link" sayaçları) da bir arttıracaktır. "mkdir" POSIX fonksiyonun ek olarak "mkdir" isimli bir "shell" komutu daha vardır ki bu komut da yine aynı isimli fonksiyon kullanılarak yazılmıştır. İş bu komut ile yeni dizinler oluşturabiliriz. Prosesin "umask" değeri, bu komutun varsayılan kullanım biçiminde, dizinin erişim haklarını etkilemektedir. Bunu önlemek için ilgili komutu "-m" ya da "--mode" seçenekleri ile birlikte kullanabiliriz. İş bu seçenekler ile birlikte kullandığımız zaman, erişim haklarını oktal sayılar ile belirtebiliriz. Örneğin, "mkdir xxx" ve "mkdir -m 777 yyy". * Örnek 1, //.. #include #include #include #include void exit_sys(const char* msg); int main(int argc, char* argv[]) { /* # OUTPUT # Command line arguments: test Success!... */ if(argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if(mkdir(argv[1], S_IRWXU | S_IRWXG | S_IRWXO) == -1) exit_sys("mkdir"); printf("Success!...\n"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, Aşağıdaki örnekte prosesin "umask" değeri sıfıra ÇEKİLMEMİŞTİR. #include #include #include #include void exit_sys(const char* msg); void display_modes(mode_t mode); mode_t get_umask_value(void); int main(int argc, char* argv[]) { /* # OUTPUT # Command line arguments: Ahmopasa umask : [22] permissions : [777] Success!... drwxr-xr-x */ if(argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } /* @ umask: 000 010 010 */ printf("umask : [%o]\n", get_umask_value()); /* @ S_IRWXU: 111 000 000 @ S_IRWXG: 000 111 000 @ S_IRWXO: 000 000 111 @ directory_permissions: 111 111 111 */ mode_t directory_permissions = S_IRWXU | S_IRWXG | S_IRWXO; printf("permissions : [%o]\n", directory_permissions); /* @ umask : 000 010 010 @ umask' : 111 101 101 @ directory_permissions : 111 111 111 @ (umask') & (directory_permissions) : 111 101 101 @ : rwx r-x r-x */ if(mkdir(argv[1], directory_permissions) == -1) exit_sys("mkdir"); printf("Success!...\n"); struct stat file_info; if(stat(argv[1], &file_info) == -1) exit_sys("stat"); display_modes(file_info.st_mode); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } void display_modes(mode_t mode) { // TYPE OF THE FILE - v1 static mode_t f_types[] = { S_IFBLK, S_IFCHR, S_IFIFO, S_IFREG, S_IFDIR, S_IFLNK, S_IFSOCK }; for(int i = 0; i < 7; ++i) if( (mode & S_IFMT) == f_types[i] ) { printf("%c", "bcp-dls"[i]); break; } /* // TYPE OF THE FILE - v2 if( S_ISBLK(mode) ) printf("%c", 'b'); else if( S_ISCHR(mode) ) printf("%c", 'c'); else if( S_ISDIR(mode) ) printf("%c", 'd'); else if( S_ISFIFO(mode) ) printf("%c", 'p'); else if( S_ISREG(mode) ) printf("%c", '-'); else if( S_ISLNK(mode) ) printf("%c", 'l'); else if( S_ISSOCK(mode) ) printf("%c", 's'); else fprintf(stderr, "SOMETHING WENT WRONG!...\n"); */ // RIGHTS ON THE FILE static mode_t modes[] = { S_IXOTH, S_IWOTH, S_IROTH, S_IXGRP, S_IWGRP, S_IRGRP, S_IXUSR, S_IWUSR, S_IRUSR }; for(int i = 8; i >= 0; --i) printf("%c", modes[i] & mode ? "xwr"[i % 3] : '-'); } mode_t get_umask_value(void) { mode_t mode = umask(0); umask(mode); return mode; } * Örnek 3, Aşağıdaki örnekte prosesin "umask" değeri SIFIRLANMIŞTIR. #include #include #include #include void exit_sys(const char* msg); void display_modes(mode_t mode); mode_t get_umask_value(void); int main(int argc, char* argv[]) { /* # OUTPUT # Command line arguments: Ahmopasa umask : [0] permissions : [777] Success!... drwxrwxrwx */ if(argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } umask(0); /* @ umask: 000 000 000 */ printf("umask : [%o]\n", get_umask_value()); /* @ S_IRWXU: 111 000 000 @ S_IRWXG: 000 111 000 @ S_IRWXO: 000 000 111 @ directory_permissions: 111 111 111 */ mode_t directory_permissions = S_IRWXU | S_IRWXG | S_IRWXO; printf("permissions : [%o]\n", directory_permissions); /* @ umask : 000 000 000 @ umask' : 111 111 111 @ directory_permissions : 111 111 111 @ (umask') & (directory_permissions) : 111 111 111 @ : rwx rwx rwx */ if(mkdir(argv[1], directory_permissions) == -1) exit_sys("mkdir"); printf("Success!...\n"); struct stat file_info; if(stat(argv[1], &file_info) == -1) exit_sys("stat"); display_modes(file_info.st_mode); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } void display_modes(mode_t mode) { // TYPE OF THE FILE - v1 static mode_t f_types[] = { S_IFBLK, S_IFCHR, S_IFIFO, S_IFREG, S_IFDIR, S_IFLNK, S_IFSOCK }; for(int i = 0; i < 7; ++i) if( (mode & S_IFMT) == f_types[i] ) { printf("%c", "bcp-dls"[i]); break; } /* // TYPE OF THE FILE - v2 if( S_ISBLK(mode) ) printf("%c", 'b'); else if( S_ISCHR(mode) ) printf("%c", 'c'); else if( S_ISDIR(mode) ) printf("%c", 'd'); else if( S_ISFIFO(mode) ) printf("%c", 'p'); else if( S_ISREG(mode) ) printf("%c", '-'); else if( S_ISLNK(mode) ) printf("%c", 'l'); else if( S_ISSOCK(mode) ) printf("%c", 's'); else fprintf(stderr, "SOMETHING WENT WRONG!...\n"); */ // RIGHTS ON THE FILE static mode_t modes[] = { S_IXOTH, S_IWOTH, S_IROTH, S_IXGRP, S_IWGRP, S_IRGRP, S_IXUSR, S_IWUSR, S_IRUSR }; for(int i = 8; i >= 0; --i) printf("%c", modes[i] & mode ? "xwr"[i % 3] : '-'); } mode_t get_umask_value(void) { mode_t mode = umask(0); umask(mode); return mode; } >>> "rmdir" fonksiyonu, bir dizin silmek için çağrılması gereken bir fonksiyondur. Bir dizin silmek için "unlink" ya da "remove" isimli FONKSİYONLARI KULLANAMAYIZ. Bu fonksiyon da standart bir POSIX fonksiyonudur. İş bu fonksiyon "unistd.h" içerisinde bildirilmiştir. Tek parametre olarak silinecek dizinin yol ifadesini alır. Başarı durumunda "0" ile, başarısızlık durumunda "-1" ile geri döner. Fakat içi dolu bir dizini BU FONKSİYON İLE SİLEMEYİZ. BU FONKSİYON İLE DİZİN SİLME İŞLEMİ YAPABİLMEK İÇİN İLGİLİ DİZİNİN İÇİNİN BOŞ OLMASI GEREKMEKTEDİR("." ve ".." DİZİNLERİ GENELLİKLE SİLİNEMEDİĞİ İÇİN BUNLAR KAPSAM DIŞIDIR). Bir dizinin tamamen silinebilmesi için, tıpkı "regular file" da olduğu gibi, "hard-link" sayacının sıfıra düşmesi gerekmektedir. Fakat bir dizinin "hard-link" sayacının ÇIKARTILMASI TAVSİYE EDİLMEZ. ÇÜNKÜ BU DURUM, DİZİN AĞACI DOLAŞAN KODLARIN SONSUZ DÖNGÜYE GİRMESİNE NEDEN OLABİLİR. Bunun yerine "soft-link" çıkartmalıyız. İş bu fonksiyona argüman olarak bir sembolik bağlantı dosyasının yol ifadesi geçilirse, iş bu fonksiyon bağlantıyı TAKİP ETMEYECEKTİR ve "errno" değerini de "ENOTDIR" biçiminde değiştirecektir. İş bu fonksiyonun başarılı olabilmesi için, prosesin silinecek dizin üzerinde "w" hakkına sahip olması gerekli değildir. Sadece silinecek dizinin bulunduğu dizin üzerinde bu hakka sahip olması gerekmektedir. Daha önce de bu konuya değinmiştik. "shell" programı üzerinden bir dizin silmek için de "rmdir" isimli bir komut da vardır. Bu komut da aynı isimli POSIX fonksiyon kullanılarak yazılmıştır. Yine bu komut ile silme yapabilmek için de dizinimizin boş olması gerekmektedir. Fakat içi dolu bir dizini tek hamlede silmek için "rm" komutunu "-r" seçeneği ile birlikte kullanılmalıdır. Örneğin, "rm -r xxx". * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int main(int argc, char* argv[]) { /* # OUTPUT # Command line arguments: Ahmopasa Success! The directory is being created... Success! The directory is being deleted... */ if(argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if(mkdir(argv[1], S_IRWXU | S_IRWXG | S_IRWXO) == -1) exit_sys("mkdir"); else printf("Success! The directory is being created...\n"); if(rmdir(argv[1]) == -1) exit_sys("rmdir"); else printf("Success! The directory is being deleted...\n"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> "etc/passwd" ve "etc/group" dosyaları içerisindeki bilgilerin çekilmesi: >>>> "etc/passwd" dosyası içerisindeki bilgilerin çekilmesi: >>>>> Tipik bir "etc/passwd" dosyası aşağıdaki biçimdedir (tipik bir "etc/passwd" dosyasının her bir satırı bir kullanıcıya tekabül etmektedir ve her bir satır ":" ile ayrılmış kelimelerde meydana gelmektedir): ... ahmopasa:x:1000:1000:Ahmet Kandemir PEHLİVANLI,,,:/home/ahmopasa:/bin/bash ahmo:$6$DcUtZx5u4mUVFQfA$wSGIC2szvgP5zeos4YoaVEbVPbnAZxsSuydIUl9K0xy8DhfPo4hWROBFGm7.xWu5hq/uPVKDIvADYsX6q905Q0:18736:0:99999:7::: ali:x:1001:1001::/home/ali:/bin/my"shell" vali:x:1002:1002::/home/vali:/bin/my"shell" ... Buradan hareketle diyebiliriz ki; -> Satır başı ile ilk ":" ayracı arasındaki kısım kullanıcının ismi, -> Birinci ":" ile ikinci ":" arasındaki kısım kullanıcının şifresi ki 'x' olması durumunda ilgili şifre "etc/shadow" dosyasında kaydedilmiştir, -> İkinci ":" ve üçüncü ":" arasındaki kısım kullanıcının id bilgisi ki bu kullanıcının çalıştıracağı prosesler bu id değerine sahip olacaktır, -> Üçüncü ":" ile dördüncü ":" arası kullanıcının Grup ID bilgisi ki bu kullanıcının çalıştıracağı prosesler bu Grup ID değerine sahip olacaktır fakat grup isminin ne olduğu "etc/group" dosyası içerisindedir, -> Dördüncü ":" ile beşinci ":" arasındaki kısım kullanıcının kişisel bilgilerinin olduğu alan, -> Beşinci ":" ile altıncı ":" arasındaki alan ise çalıştırılacak "bash" programının "Current Working Directory" bilgisi, -> Altıncı ":" ile satır sonu arasındaki alan ise çalıştırılacak "bash" programını belirtmektedir. >>>> "etc/group" dosyası içerisindeki bilgilerin çekilmesi: >>>>> Aynı mekanizma ve aynı mantık grup bilgileri için de oluşturulmuştur. Tipik bir "etc/group" dosyası aşağıdaki biçimdedir (tipik bir "etc/group" dosyasının her bir satırı bir gruba tekabül etmektedir ve her bir satır ":" ile ayrılmış kelimelerde meydana gelmektedir): ... adm:x:4:syslog,ahmopasa ahmopasa:x:1000: sambashare:x:132:ahmopasa ... Buradan hareketle diyebiliriz ki; -> Satır başı ile ilk ":" ayracı arasındaki kısım grubun ismi, -> Birinci ":" ile ikinci ":" arasındaki kısım grubun şifresi, -> İkinci ":" ve üçüncü ":" arasındaki grubun id numarası, -> Üçüncü ":" ile dördüncü ":" arası iş bu gruba dahil olan kullanıcıların isimleir (yani "supplementary group" bölümü). >>>>> Grup bilgilerini çeken fonksiyonlar "getgrnam", "getgrgid", "getgrent", "setgrent" ve "endgrent" fonksiyonlarıdır ve "grp.h" başlık dosyasında bildirilmişlerdir. Bu fonksiyonlardan, >>>>>> "getgrnam" ve "getgrgid" fonksiyonları argüman olarak sırasıyla grup ismi ve Grup ID değerini alırlar ve geriye "group" türünden statik ömürlü bir yapının başlangıç adresini döndürürler. >>>>>>> "struct group" yapısı aşağıdaki elemanlarda oluşmaktadır ve her bir eleman "etc/group" içerisindeki ilgili satırdaki ilgili bölüme dair elemanları tutar. struct group{ char *gr_name, /* The name of the group. */ char *gr_passwd, /* The password of the group. */ gid_t gr_gid, /* Numerical group ID. */ // Pointer to a NULL-terminated array of character pointers to member names. // (Bu dizinin son indeksi NULL) char **gr_mem, }; >>>>>> "getgrent" fonksiyonu ise her çağırmada sıradaki grubun bilgilerini geri döndürmektedir. >>>>>> "endgrent" fonksiyonu ise "etc/group" dosyasını kapatmak için kullanılır. İşimiz bittikten sonra bu fonksiyonu da çağırmalıyız. >>>>>> "setgrent" ise tekrardan baştan okuma yapmak için çağırmamız gereken bir fonksiyondur. Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # OUTPUT # root x 0 ------------------------------------------- ... ------------------------------------------- */ struct group* pass; while((errno = 0, pass = getgrent()) != NULL) { printf("%s\n", pass->gr_name); printf("%s\n", pass->gr_passwd); printf("%llu\n", (unsigned long long)pass->gr_gid); for(int i = 0; pass->gr_mem[i] != NULL; ++i) { if(i != 0) printf(", "); printf("%s", pass->gr_mem[i]); } puts("-------------------------------------------"); } if(errno != 0) exit_sys("getpwent"); endgrent(); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Diğer yandan UNIX/Linux sistemlerinde kullanıcı bilgilerinin "etc/passwd" ve "etc/group" içerisinde saklanması standart değildir fakat yaygın biçimi bu şekildedir. Bunun üstesinden gelebilmek için de standart POSIX fonksiyonları geliştirilmiştir. İş bu fonksiyonlar "pwd.h" başlık dosyası içerisinde bildirilmiştir. İş bu fonksiyonlar, >>>> "getpwnam" fonksiyonu, bir kullanıcının ismini argüman olarak alır ve o kullanıcı hakkındaki bilgileri "etc/passwd" dosyasından çeker. Başarı durumunda fonksiyonun geri dönüş değeri, statik ömürlü bir yapının başlangıç adresidir. Statik ömürlü olması hasebiyle bellekte yer tahsisi ile uğraşmak durumunda değiliz (YAPININ KENDİSİNİ DÖNDÜRMEMEKTEDİR). İş bu yapının türü "passwd" türüdür. İlgili kullanıcı bulunamadığında veya herhangi başka bir hata durumunda fonksiyon NULL ile dönmektedir. Fakat ilgili kullanıcı kaydı bulunamazsa, "errno" değişkenini DEĞİŞTİRİLMEZ. Buradan hareketle öncelikle "errno" değişkeni sıfıra çekilir ve iş bu fonksiyonunun geri dönüş değerinin NULL olup olmadığına bakılır. Sonrasında da "errno" değişkeninin değerinin sıfır olup olmadığı kontrol edilir. Eğer geri dönüş değeri NULL ise ama "errno" sıfır değil ise patolojik bir sorun vardır, aksi halde ilgili kullanıcı bulunamamıştır. >>>>>> "struct passwd" türü aşağıdaki elemanlardan oluşmaktadır. Her bir eleman, sırasıyla "etc/passwd" içerisindeki ilgili satırda ":" ile ayrılan bölümlere hitap etmektedir. struct passwd{ char *pw_name, // User's login name. char *pw_passwd, // User's password. uid_t pw_uid, // Numerical user ID. gid_t pw_gid, // Numerical group ID. char *pw_gecos, // User Personal Information char *pw_dir, // Initial working directory of the program that will be used as "shell". char *pw_"shell", // Program to use as "shell". }; Aşağıda kullanıma ilişkin bir örnek verilmiştir: * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int main(int argc, char* argv[]) { /* # OUTPUT # Command line arguments: Ahmopasa ------------------------------------------- root x 0 0 root /root /bin/bash ------------------------------------------- */ if(argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } struct passwd* pass; errno = 0; if((pass = getpwnam(argv[1])) == NULL) { if(errno == 0) { fprintf(stderr, "such user name cannot be found!...\n"); exit(EXIT_FAILURE); } else { exit_sys("getpwnam"); } } puts("-------------------------------------------"); printf("%s\n", pass->pw_name); printf("%s\n", pass->pw_passwd); printf("%llu\n", (unsigned long long)pass->pw_uid); printf("%llu\n", (unsigned long long)pass->pw_gid); printf("%s\n", pass->pw_gecos); printf("%s\n", pass->pw_dir); printf("%s\n", pass->pw_"shell"); puts("-------------------------------------------"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>> "getpwuid" fonksiyonu ise kullanıcının id bilgisini bizden almaktadır. "getpwnam" fonksiyonundan başka bir farklılığı yoktur. * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int main(int argc, char* argv[]) { /* # OUTPUT # Command line arguments: 0 ------------------------------------------- root x 0 0 root /root /bin/bash ------------------------------------------- */ /* # OUTPUT # Command line arguments: 1 ------------------------------------------- daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin ------------------------------------------- */ /* # OUTPUT # Command line arguments: 100 ------------------------------------------- _apt x 100 65534 /nonexistent /usr/sbin/nologin ------------------------------------------- */ if(argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } struct passwd* pass; errno = 0; if((pass = getpwuid(atoi(argv[1]))) == NULL) { if(errno == 0) { fprintf(stderr, "such user name cannot be found!...\n"); exit(EXIT_FAILURE); } else { exit_sys("getpwnam"); } } puts("-------------------------------------------"); printf("%s\n", pass->pw_name); printf("%s\n", pass->pw_passwd); printf("%llu\n", (unsigned long long)pass->pw_uid); printf("%llu\n", (unsigned long long)pass->pw_gid); printf("%s\n", pass->pw_gecos); printf("%s\n", pass->pw_dir); printf("%s\n", pass->pw_"shell"); puts("-------------------------------------------"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>>> "getpwent" fonksiyonu ise her çağrımda sıradaki kullanıcının bilgilerini geri döndürmektedir. Bu fonksiyonu kullanarak bir döngü içerisinde bütün kullanıcılara dair bilgiyi çekebiliriz. Fakat işimiz bittiğinde, "endpwent" fonksiyonunu çağırmalıyız ki "etc/passwd" dosyasını kapatabilelim. Eğer tekrardan dosyanın başından okumak istiyorsak, "setpwent" fonksiyonunu çağırmalıyız. "getpwent" fonksiyonu da "getpwnam" fonksiyonunun geri dönüş özelliklerine sahiptir. İşin başında "setpwent" fonksiyonunun çağrılması GEREKMEMEKTEDİR. * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # OUTPUT # root x 0 0 root /root /bin/bash ------------------------------------------- ... ------------------------------------------- */ struct passwd* pass; /* ',' operatörü önce sol tarafı işletir fakat sonucu ıskarta eder. Daha sonra sağ tarafı işletir ve elde ettiği sonuc operatörün geri döndürdüğü değerdir. Dolayısıyla iş bu operatörün geri dönüş değeri, ilgili sağ operanttır. Yani 'NULL' karşılaştırmasına "pass" değişkeni girecektir fakat aynı zamanda her turda "errno" değişkeni sıfıra çekilmektedir. */ while((errno = 0, pass = getpwent()) != NULL) { printf("%s\n", pass->pw_name); printf("%s\n", pass->pw_passwd); printf("%llu\n", (unsigned long long)pass->pw_uid); printf("%llu\n", (unsigned long long)pass->pw_gid); printf("%s\n", pass->pw_gecos); printf("%s\n", pass->pw_dir); printf("%s\n", pass->pw_"shell"); puts("-------------------------------------------"); } if(errno != 0) exit_sys("getpwent"); endpwent(); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Öte yandan "etc/passwd" ve "etc/group" dosyalarından kullanıcıların id bilgilerinin çekilerek, daha önce yazdığımız "my_ls_command" isimli fonksiyonda kullanılması: Normal şartlarda bir kullanıcının bilgileri "etc/passwd" ve "etc/group" dosyalarından silindiğinde, bu kullanıcının oluşturduğu dosyaların da silinmesi GEREKMEZ. Komut satırı üzerinden silme işlemi yaparken ekstra seçenek kullanmalı, el ile silme işlemi yaparken de bizler el ile ilgili dosyaları silmeliyiz. Tabii silinmesi gerekiyorsa. Bu durumda eğer kullanıcıya dair bir bilgi "etc/passwd" içerisinde bulunamaz ise ekrana ilgili Kullanıcı ID'nin rakamsal değerini, bulunması durumunda ise bu id'ye karşılık gelen kullanıcı ismini ekrana yazdırmalıyız. * Örnek 1, "my_ls_command" isimli fonksiyonun güncellenmiş hali: #include #include #include #include #include #include #include #define BUFFER_SIZE 4096 void exit_sys(const char* msg); void my_ls_command(struct stat* file_info, const char* file_name); int main(void) { /* # test.txt # */ /* # OUTPUT # [-rwxrwxrwx 1 0 0 0 Jan 6 23:16 test.txt] */ struct stat file_info; if(lstat("test.txt", &file_info) == -1) exit_sys("stat"); my_ls_command(&file_info, "test.txt"); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } void my_ls_command(struct stat* file_info, const char* file_name) { static char buffer[BUFFER_SIZE]; static mode_t modes[] = { S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH }; static mode_t f_types[] = { S_IFBLK, S_IFCHR, S_IFIFO, S_IFREG, S_IFDIR, S_IFLNK, S_IFSOCK }; int index = 0; for(int i = 0; i < 7; ++i) if( (file_info->st_mode & S_IFMT) == f_types[i] ) { buffer[index++] = "bcp-dls"[i]; break; } for(int i = 0; i < 9; ++i) buffer[index++] = modes[i] & file_info->st_mode ? "rwx"[i % 3] : '-'; struct passwd* pw; char uname[BUFFER_SIZE]; if((pw = getpwuid(file_info->st_uid)) == NULL) sprintf(uname, "%llu", (unsigned long long)file_info->st_uid); else strcpy(uname, pw->pw_name); struct group* gr; char gname[BUFFER_SIZE]; if((gr = getgrgid(file_info->st_gid)) == NULL) sprintf(gname, "%llu", (unsigned long long)file_info->st_gid); else strcpy(gname, gr->gr_name); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_nlink); index += sprintf(buffer + index, " %s", uname); index += sprintf(buffer + index, " %s", gname); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info->st_size); struct tm* ptime; ptime = localtime(&file_info->st_mtim.tv_sec); index += strftime(buffer + index, BUFFER_SIZE, " %b %2e %H:%M", ptime); sprintf(buffer + index, " %s", file_name); printf("[%s]", buffer); } * Örnek 2, Our Bash Program(version 4): #include #include #include #include #include #include #include #include #include #include #include #define MAX_CMD_LINE 4096 #define MAX_CMD_PARAMS 128 #define BUFFER_SIZE 4096 char parse_cmd_line(void); void dir_proc(void); void clear_proc(void); void pwd_proc(void); void cd_proc(void); void ls_proc(void); void exit_sys(const char* msg); typedef struct tagCMD{ char* name; void (*proc)(void); } CMD; CMD g_cmds[] = { {"dir", dir_proc}, {"clear", clear_proc}, {"pwd", pwd_proc}, {"cd", cd_proc}, {"ls", ls_proc}, {NULL, NULL} }; char g_cmdline[MAX_CMD_LINE]; char* g_params[MAX_CMD_PARAMS]; int g_nparams; char g_cwd[PATH_MAX]; int main(void) { char* str; int i; if(!getcwd(g_cwd, PATH_MAX)) exit_sys("getcwd"); for (;;) { fprintf(stdout, "CSD: %s> ", g_cwd); if (fgets(g_cmdline, MAX_CMD_LINE, stdin) == NULL) continue; if ((str = strchr(g_cmdline, '\n')) != NULL) *str = '\0'; parse_cmd_line(); if(g_nparams == 0) continue; if(!strcmp(g_params[0], "exit")) exit(EXIT_SUCCESS); for(i = 0; g_cmds[i].name != NULL; ++i) if(!strcmp(g_params[0], g_cmds[i].name)) { g_cmds[i].proc(); break; } if(g_cmds[i].name == NULL) fprintf(stderr, "bad command: %s\n", g_params[0]); } return 0; } char parse_cmd_line(void) { char* str; g_nparams = 0; for(str = strtok(g_cmdline, " \t"); str != NULL; str = strtok(NULL, " \t")) { g_params[g_nparams++] = str; } g_params[g_nparams] = NULL; } void dir_proc(void) { fprintf(stdout, "dir command executing...\n"); } void clear_proc(void) { system("clear"); } void pwd_proc(void) { if (g_nparams > 1) { fprintf(stdout, "pwd command must be used w/o an argument!\n"); return; } fprintf(stdout, "%s\n", g_cwd); } void cd_proc(void) { // DEFAULT if (g_nparams > 2) { printf("Too mang arguments!..\n"); return; } // APPROACH - I if (g_nparams == 1) { printf("Too few arguments!..\n"); return; } if (chdir(g_params[1]) == -1) { printf("%s!\n", strerror(errno)); return; } // APPROACH - II /* char* dir; if (g_nparams == 1) { if((dir = getenv("HOME")) == NULL) exit_sys("getenv"); } else dir = g_params[1]; if (chdir(dir) == -1) { printf("%s!\n", strerror(errno)); return; } */ // DEFAULT if(!getcwd(g_cwd, PATH_MAX)) exit_sys("getcwd"); } void ls_proc(void) { if(g_nparams != 1) { fprintf(stderr, "wrong number of arguments!...\n"); return; } DIR* dir; if( (dir = opendir(g_cwd)) == NULL) exit_sys("opendir"); int fd; if( (fd = dirfd(dir)) == -1 ) exit_sys("dirfd"); struct dirent* de; struct stat file_info; while(errno = 0, (de = readdir(dir)) != NULL) { /* * Dördüncü parametreye "0" geçilmesi durumunda, "stat" semantiği uygulanacaktır. "AT_SYMLINK_NOFOLLOW" sembolik sabiti "fcntl.h" içerisinde bildirilmiştir. */ if(fstatat(fd, de->d_name, &file_info, AT_SYMLINK_NOFOLLOW) == -1) exit_sys("fstatat"); // my_ls_command(&info, de->d_name); static char buffer[BUFFER_SIZE + 1]; static mode_t modes[] = { S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH }; static mode_t f_types[] = { S_IFBLK, S_IFCHR, S_IFIFO, S_IFREG, S_IFDIR, S_IFLNK, S_IFSOCK }; int index = 0; for(int i = 0; i < 7; ++i) if( (file_info.st_mode & S_IFMT) == f_types[i] ) { buffer[index++] = "bcp-dls"[i]; break; } for(int i = 0; i < 9; ++i) buffer[index++] = modes[i] & file_info.st_mode ? "rwx"[i % 3] : '-'; struct passwd* pw; char uname[BUFFER_SIZE]; if((pw = getpwuid(file_info.st_uid)) == NULL) sprintf(uname, "%llu", (unsigned long long)file_info.st_uid); else strcpy(uname, pw->pw_name); struct group* gr; char gname[BUFFER_SIZE]; if((gr = getgrgid(file_info.st_gid)) == NULL) sprintf(gname, "%llu", (unsigned long long)file_info.st_gid); else strcpy(gname, gr->gr_name); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info.st_nlink); index += sprintf(buffer + index, " %s", uname); index += sprintf(buffer + index, " %s", gname); index += sprintf(buffer + index, " %llu", (unsigned long long)file_info.st_size); struct tm* ptime; ptime = localtime(&file_info.st_mtim.tv_sec); index += strftime(buffer + index, BUFFER_SIZE, " %b %2e %H:%M", ptime); sprintf(buffer + index, " %s", de->d_name); printf("[%s]\n", buffer); } if(errno != 0) exit_sys("readdir"); closedir(dir); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> Dizin dosyalarının "open" fonksiyonu ile açılması: Anımsayacağımız üzere dizinler de birer dosya olarak ele alınmaktadır. Dosya sistemleri de dizinlerin "open" fonksiyonu ile açılmasını mümkün kılmışlar fakat açılan bu dizinlerde okuma ve yazma işlemlerini işletim sistemlerinin insifiyatına bırakmışlardır. Günümüzde çoğu Unix türevi işletim sistemi, dizinlerin "open" ile açılmasına izin vermekte fakat bu dizinlerden "read" ve "write" fonksiyonlarını kullanarak okuma ve yazma işlemi yapmamıza izin vermemektedir. Fakat "lseek" işleminin, açılan bu dosya üzerinde yapılmasına MÜSAADE VARDIR. (Dizin dosyalarının gerçek formatları dosya sisteminden dosya sistemine değişkenlik göstermektedir. Kursun sonlarına doğru Ext-2 dosya sistemi de incelenecektir.) Bu anlatımda, proseslerin dizin üzerinde gerekli izinlere sahip olduğu varsayılmıştır. Dizin dosyalarını "open" ile açarken "read" ve "write" yapamayacağımızdan bahsetmiştik. Peki "open" ile açarken hangi açış modunu kullanacağız? İşte POSIX standartlarında bunun için "O_SEARCH" isimli bir mod geliştirilmiştir. Aslında bu mod, ileride ele alınacak olan, at'li POSIX (openat vs.) fonksiyonları için düşünülmüştür. Bu açış modu kullanılarak açılan dizinler üzerinde "read" ve "write" işlemleri yapılamaz sadece iş bu at'li fonksiyonlarda kullanılabilir ("open" fonksiyonunun geri dönüş değeri olan "fd" yi bu tip fonksiyonlara geçerek). BU MOD LINUX & macOS İŞLETİM SİSTEMLERİNDE DESTEKLENMEDİĞİNDEN, BU İŞLETİM SİSTEMİNDE "O_RDONLY" MODUNU KULLANMALIYIZ. NOT: İŞLETİM SİSTEMLERİ "open" İLE AÇILAN DİZİNLERDE OKUMA VE YAZMA İŞLEMLERİNE "read" VE "write" FONKSİYONLARI ÜZERİNDEN İZİN VERMEMEKTEDİR. BAŞKA FONKSİYONLARI BU İŞLEMLER İÇİN KULLANABİLİRİZ. * Örnek 1, "open" fonksiyonunun "0_RDONLY" modu ile açılması: #include #include #include #include void exit_sys(const char* msg); int main(void) { // OUTPUT => Success!... int fd; if( (fd = open(".", O_RDONLY)) == -1) { exit_sys("open"); } printf("Success!...\n"); close(fd); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, "open" fonksiyonunun "O_WRONLY" modu ile açılması: #include #include #include #include void exit_sys(const char* msg); int main(void) { // OUTPUT => open: Is a directory int fd; if( (fd = open(".", O_WRONLY)) == -1) { exit_sys("open"); } printf("Success!...\n"); close(fd); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, "open" fonksiyonunun "O_SEARCH" modu ile açılması: #include #include #include #include void exit_sys(const char* msg); int main(void) { // OUTPUT => error: ‘O_SEARCH’ undeclared (first use in this function) int fd; if( (fd = open(".", O_SEARCH)) == -1) // Bu açış modu Linux'ta desteklenmiyor. { exit_sys("open"); } printf("Success!...\n"); close(fd); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> UNIX sistemlerdeki bazı at'li fonksiyonlar: Anımsayacağımız üzere yol ifadesini argüman olarak alan POSIX dosya fonksiyonlarının, argüman olarak "file descriptor" alan versiyonları vardı ki bu versionların isimlerli "f" harfi ile başlamaktaydı. Örneğin, "stat" ve "lstat" fonksiyonları yol ifadesi alırken, "fstat" fonksiyonu bir "file descriptor" alır. Benzer şekilde "chmod" ve "fchmod" fonksiyonlarını, "chown" ve "fchown" fonksiyonlarını örnek gösterebiliriz. İşte bu "f" li fonksiyonların birde "at" li versiyonları bulunmaktadır. Örneğin, "fstatat", "fchmodat", "fchownat" gibi. Aslında bu "at" li versiyonlar seyrek kullanılan fonksiyonlardır. Bu "at" li versiyonlar, argüman olarak bir "file descriptor" alırlar ve bu "fd", "open" ile açılmış bir DİZİNE AİT OLMALIDIR. AKSİ HALDE İLGİLİ FONKSİYON BAŞARISIZ OLACAKTIR. Eğer bu "at" li fonksiyon bir yol ifadesi de alıyorsa, bunun göreli bir yol ifadesi olması gerekiyor. Mutlak bir yol ifadesi geçtiğimiz zaman, birinci parametre işlevsiz hale geliyor ve "at" siz versiyondan bir farkı kalmıyor. Peki yol ifadesini göreli geçtiğimiz zaman ne olur? Prosesin "current working directory" konumu yerine, birinci parametrede geçilen "file descriptor" tarafından belirtilen dizin konum olarak ele alınır. POSIX standartlarına göre biz dizin "O_SEARCH" modunda açılmışsa, prosesimizin ilgili dizin üzerinde "x" hakkına sahip olmadığı kontrol edilmez. Başka modlar ile açılmışsa bu kontrol işlemi gerçekleştirilir. * Örnek 1, aşağıda "open" ve "openat" fonksiyonlarının karşılaştırılması yapılmıştır: int open(const char *path, int oflag, ...); int openat(int fd, const char *path, int oflag, ...); "openat" fonksiyonunun; -> Birinci parametresi, "open" ile açılmış bir DİZİNE AİT "fd" olmalıdır. Aksi halde "openat" fonksiyonu kafadan başarısız olacaktır. -> İkinci parametresi, GÖRELİ BİR YOL İFADESİ OLMALIDIR. MUTLAK BİR YOL İFADESİ KULLANDIĞIMIZ ZAMAN BİRİNCİ PARAMETRE İŞLEVSİZ HALE GELECEK OLUP, BU FONKSİYONUN "open" FONKSİYONUNDAN BİR FARKI KALMAYACAKTIR. * Örnek 2, #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # OUTPUT # Success!... openat: No such file or directory */ int fddir; if( (fddir = open("/usr/include", O_RDONLY)) == -1 ) exit_sys("open"); printf("Success!...\n"); int fd; if( (fd = openat(fddir, "test.txt", O_RDONLY)) == -1 ) exit_sys("openat"); printf("Success!...\n"); close(fd); close(fddir); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 3, #include #include #include #include void exit_sys(const char* msg); int main(void) { /* # OUTPUT # Success!... Success!... */ int fddir; if( (fddir = open("/usr/include", O_RDONLY)) == -1 ) exit_sys("open"); printf("Success!...\n"); int fd; if( (fd = openat(fddir, "/home/test.txt", O_RDONLY)) == -1 ) exit_sys("openat"); printf("Success!...\n"); close(fd); close(fddir); } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4, aşağıda "chmod" fonksiyonu, diğer versiyonları ile birlikte karşılaştırılmıştır. int chmod(const char *path, mode_t mode); int fchmod(int fildes, mode_t mode); int fchmodat(int fd, const char *path, mode_t mode, int flag); * Örnek 5, aşağıda "chown" fonksiyonu, diğer versiyonları ile birlikte karşılaştırılmıştır. int chown(const char *path, uid_t owner, gid_t group); int fchown(int fildes, uid_t owner, gid_t group); int fchownat(int fd, const char *path, uid_t owner, gid_t group, int flag); >>> Programlama yoluyla "hard-link" ve "soft-link" oluşturulması: Komut satırından "hard-link" ve "soft-link" çıkarmak için "ln" komutunu kullanmaktayız. "-s" seçeneğini de eklersek, "soft-link" meydana getiririz. Aksi halde "hard-link" oluşacaktır. Disk üzerinde, dosyalara ait bilgiler "i-node table" denilen bir tabloda tutulmaktadır. Memory üzerindeyse dizin girişleri "dosya_ismi - i_node_number" ikilisi biçiminde oluşmaktadır. Eğer iki farklı dizin girişi, "i-node table" üzerindeki aynı indisi gösteriyorsa, ilgili dizin girişinin "hard-link" sayacı iki olmaktadır. "soft-link" dosyası ise "i-node table" üzerinde farklı bir indise sahip fakat içerisindeki "i-node" elemanı sadece kendisi ile bağlı olan esas dosyaya ait "i-node" numarasını tutmaktadır. Windows sistemlerindeki "kısayol dosyaları" gibi düşünebiliriz. >>>> "hard-link" çıkartılması "link" fonksiyonu ile mümkündür ve fonksiyonumuz aşağıdaki parametrik yapıya sahiptir; #include int link(const char *path1, const char *path2); Fonksiyonun, -> Birinci parametre, "hard-link" i çıkartılacak dosyaya ait yol ifadesidir. Eğer bu ifade bir sembolik link dosyasına aitse, sembolik link dosyasının kendisinin mi yoksa gösterdiği dosyanın mı "hard-link" inin çıkartılacağı işletim sistemine göre değişmektedir. -> İkinci parametresi, yeni dizin girişinin ismini belirtmektedir. -> Yeni bir dizin girişi oluşturacağımız için, ilgili dizin üzerinde prosesimizin "w" hakkı yok ise fonksiyon başarısız olacaktır. Eğer bir dizine dair "hard-link" çıkartacaksak prosesimiz yeteri kadar yetkiye sahip olmalı ve işletim sistemi de buna izin vermelidir. Aksi halde DİZİNLERE "hard-link" ÇIKARTILAMAZ. Aşağıda bu konuya ilişkin örnek verilmiştir: * Örnek 1, #include #include #include void exit_sys(const char* msg); int main(int argc, char** argv) { /* Command Line Argument => test.txt mest.txt */ /* # OUTPUT # *A new file called 'mest.txt' has been created.* */ if (argc != 3) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if(link(argv[1], argv[2]) == -1) exit_sys("link"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Aynı zamanda "link" fonksiyonunun "linkat" isimli "at" li versiyonu da vardır. Diğer "at" li versiyonlardan bir farkı yoktur. Yani ilk parametre bir "file descriptor". İkinci parametre ise, ilk parametreden itibaren göreli olan bir yol ifadesi. Üçüncü ve dördüncü parametreler de bu mantıkta. İş bu yol ifadeleri mutlak bir yol ifadesiyse, birinci ve üçüncü parametreler işlev görmeyecektir. Beşinci parametre ise sembolik bağlantının izlenip izlenmeyeceğini belirtmektedir. Varsayılan durumda sembolik bağlantı izlenmemektedir fakat izlemek için bu parametreye "AT_SYMLINK_FOLLOW" sembolik sabitini geçmeliyiz. Bu beşinci parametreye "0" girilmesi, varsayılan durumu gerçekleştirecektir. >>>> "soft-link" çıkartılması, "symlink" isimli POSIX fonksiyonu ile mümkündür. Aşağıdaki parametrik yapıya sahiptir; #include int symlink(const char *path1, const char *path2); -> Fonksiyonun birinci parametresi, sembolik bağlantısı çıkartılacak dosyanın yol ifadesidir. -> Fonksiyonun ikinci parametresi, sembolik bağlantı dosyasının yol ifadesidir. -> Başarı durumunda "0" değerine, başarısızlık durumunda "-1" değerine geri dönecektir. Sistemlerde, sembolik bağlantı dosyasının kendisine ait erişim haklarının bir önemi YOKTUR. Erişim hakları kontrol edilirken sembolik bağlantı dosyasının kendisi yerine gösterdiği esas dosyaya bakılır. Hem komut satırından sembolik bağlantı oluştururken hem de "symlink" fonksiyonunu kullanırken "dangling" durumda olan sembolik bağlantı dosyası oluşturabiliriz. Yani kaynak bir dosyanın olması mecburi değildir. Her ne kadar dizinlerin "hard-link" leri çıkartılması sorunlu bir durum olduğundan yukaruda bahsetmiştik. Halbuki aynı durum "soft-link" için geçerli değildir. Yani bir dizinin sembolik bağlantısını oluşturabiliriz. * Örnek 1, #include #include #include void exit_sys(const char* msg); int main(int argc, char** argv) { /* Command Line Argument => q.txt mest.txt */ /* # OUTPUT # *A new file called 'mest.txt' has been created.* */ if (argc != 3) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if(symlink(argv[1], argv[2]) == -1) exit_sys("link"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Aynı zamanda "symlink" versiyonu da "at" li bir versiyona sahiptir. İsmi "symlinkat". Fonksiyonun ikinci parametresi bir "fire descriptor". Üçüncü parametre ise, ikinci parametreden itibaren göreli bir yol ifadesi. Birinci parametre ise kaynak olan dosyanın yol ifadesidir. >>> "readlink" POSIX fonksiyonu: "lstat" fonksiyonu ile bir dosyanın sembolik bağlantı dosyası olduğunu öğrenebiliyoruz fakat bu fonksiyon iş bu sembolik bağlantı dosyasının neyi gösterdiğinin bilgisini tutmamaktadır. İşte "readlink" fonksiyonu ile bizler sembolik bağlantı dosyalarının neyi gösterdiğini öğrenebiliyoruz. "ls" komutu da "lstat" fonksiyonunun yanında bu fonksiyonu da kullanmaktadır. >>>> "readlink" fonksiyonu aşağıdaki parametrik yapıya sahiptir; #include ssize_t readlink( const char * path, char * buf, size_t bufsize ); Fonkisyonun, -> Birinci parametre, içi okunacak sembolik bağlantı dosyasının yol ifadesidir. -> İkinci parametre, gösterilen esas dosyanın yol ifadesinin yazılacağı "buffer" ın başlangıç adresi. -> Üçüncü parametre, iş bu "buffer" bölgesinin büyüklüğü. -> Eğer birinci parametredeki ifade, ikinci ve üçüncü parametrelerde bilgileri verilen "buffer" alanına sığmaz ise, fonksiyon BAŞARISIZ OLMUYOR. SADECE BİLGİYİ KIRPMAKTADIR. Yol ifadesinin baş kısımları ilgili "buffer" alanına yazılırken, son kısımlar kırpılmaktadır. Eğer birinci parametredeki ifade "buffer" alanından küçükse, geriye kalan alanların ne ile doldurulacağı kesin değil. "buffer" alanını doldururken, en sona "\0" karakteri EKLENMEMEKTEDİR. Dolayısıyla bu alandaki bilgileri işlerdek dikkatli olmalıyız. Fonksiyon geri dönüş değeri olarak "buffer" alanına yazdığı karakterlerin adedini döndürmektedir. Fakat herhangi bir aksili olması durumunda "-1" ile geri döner ve "buffer" alanına ellenmez. Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, görüldüğü üzere yeni alanın bittiği yere "\0" karakteri eklenmedi. #include #include #include void exit_sys(const char* msg); #define PATH_MAX_II 4096 int main(int argc, char** argv) { /* Command Line Argument => q.txt mest.txt */ /* # INPUT # q.txt */ /* # OUTPUT # {xxxxxXXXXXxxxxxXXXXX} [5] => {q.txtXXXXXxxxxxXXXXX} */ if (argc != 3) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if (symlink(argv[1], argv[2]) == -1) exit_sys("link"); char buffer[PATH_MAX_II] = "xxxxxXXXXXxxxxxXXXXX"; printf("{%s}\n", buffer); ssize_t result; if ( (result = readlink(argv[2], buffer, PATH_MAX_II)) == -1 ) exit_sys("readlink"); printf("[%lld] => ", (long long)result); printf("{%s}\n", buffer); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include #include #include void exit_sys(const char* msg); #define PATH_MAX_II 4096 int main(int argc, char** argv) { /* Command Line Argument => q.txt mest.txt */ /* # INPUT # q.txt */ /* # OUTPUT # {xxxxxXXXXXxxxxxXXXXX} [5] => {q.txt} [5] => q.txt [5] => {q.txt} */ if (argc != 3) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if (symlink(argv[1], argv[2]) == -1) exit_sys("link"); char buffer[PATH_MAX_II + 1] = "xxxxxXXXXXxxxxxXXXXX"; printf("{%s}\n", buffer); ssize_t result; if ( (result = readlink(argv[2], buffer, PATH_MAX_II)) == -1 ) exit_sys("readlink"); // Approach - I if ( result < PATH_MAX_II ) { buffer[result] = '\0'; printf("[%lld] => ", (long long)result); printf("{%s}\n", buffer); } else { /* * Bizler "buffer" alanımızı 4096 + 1 karakter uzunluğunda belirledik. Yol ifadesinin * bu uzunluktan da büyük olması çok çok uç bir durumdur. Eğer "buffer" alanı daha küçük * belirlenmiş olsaydı, bu durumda bu "buffer" alanını adım adım büyütme yapmamız * gerekmektedir ki bu sebeple tam yol ifadesini kayıt altına alabilelim. */ fprintf(stderr, "the path maybe truncated!...\n"); } // Approach - II printf("[%lld] => ", (long long)result); for(ssize_t i = 0; i < result; ++i) { putchar(buffer[i]); } puts(""); // Approach - III printf("[%lld] => ", (long long)result); printf("{%.*s}\n", (int)result, buffer); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> "access" fonksiyonu: Bir dosyayı açmadan, o dosya üzerinde bir takım işlemlerin yapılıp yapılamayacağını sorgulamak için kullanılan bir POSIX fonksiyonudur. Bu fonksiyon, prosesin gerçek Kullanıcı ID ve gerçek Grup ID bilgilerini kullanarak sorgulama yapmaktadır fakat daha önceki fonksiyon örneklerinde Etkin Kullanıcı ID ve Etkin Grup ID bilgileri kullanılmıştır. Anımsayacağımız üzere Etkin Kullanıcı ID ile gerçek Kullanıcı ID değerleri ekseriyetle birbirine eşitti. Benzer şekilde etkin Grup ID ile gerçek Grup ID değerleri de. Öyle bir senaryo düşünelim ki prosesimizin Etkin Kullanıcı ID değeri "root", yani "0". Fakat gerçek Kullanıcı ID değeri "kaan". Bir dosya üzerinde de "kaan" ın bir yetkisi yok. İşte "access" fonksiyonu bu durumda başarısız olacaktır çünkü kendisi işleme gerçek Kullanıcı ID değerini sokmaktadır. Ek olarak bu fonksiyon atomik bir yapıda değildir. Yani bizler bu fonksiyon ile bir dosyaya "yazma" yapıp yapamayacağımıza dair bir sorgulama yapalım. Fakat yazma işlemine başlamadan evvel başka bir proses, bu dosyanın haklarını değiştirmiş olsun. Sorgulama ile yazma işlemi arasına başka bir prosesin müdahalesi olduğu için bizim "yazma" işlemimiz başarısız olacaktır. Dolayısıyla bu fonksiyon ile yaptığımız sorgulamaların %100 doğru olmayabilir. Fonksiyonun imzası aşağıdaki gibidir; #include int access(const char *path, int amode); Fonksiyonun, -> Birinci parametre, sorgulama yapılacak dosyanın yol ifadesidir. -> İkinci parametre ise sorgulama yapılacak erişim haklarını belirtmektedir ve aşağıda belirtilen sembolik sabitlerin "bit-wise OR" işlemine sokulması ile elde edilebilir. R_OK - Okuma yapılabilir mi? W_OK - Yazma yapılabilir mi? X_OK - Çalıştırılabilir mi? F_OK - İlgili dosya var mı? -> Test olumlu ise "0" değerine, olumsuz ise "-1" değerine geri dönmektedir. Başarısızlık durumunda "errno" değişkeni uygun değer almaktadır. İş bu fonksiyonun GNU libc kütüphanesinde tanımlı iki verisyonu daha vardır ve bunlar sırasıyla "euidaccess" ve "eaccess" ismindedirler. Bu iki fonksiyon da yine "access" ile aynı işi yapmaktadır fakat prosesin Etkin Kullanıcı ID ve Etkin Grup ID bilgilerini işleme sokmaktadır. Fakat bu iki fonksiyon POSIX standartlarında YOKTUR. Bu iki fonksiyonu da kullanabilmek için "_GNU_SOURCE" makrosunu da en tepede tanımlamamız gerekiyor. Diğer yandan "access" fonksiyonu birde "at" li bir versiyona sahiptir ve ismi "faccessat" biçimindedir. Aşağıdaki parametrik yapıya sahiptir; #include int faccessat(int fd, const char *path, int amode, int flag); Fonksiyonun, -> Birinci parametre bir "file descriptor". -> İkinci parametre, birinci parametredeki "fd" ye bağlı göreli bir yol adresi. Göreli bir yol ifadesi geçildiğinde, birinci parametre başlangıç noktası sayılacaktır. Mutlak bir yol ifadesi geçildiğinde birinci parametre dikkate alınmayacaktır. -> Üçüncü parametre, sorgulama yapılacak erişim haklarını belirtmektedir. "access" fonksiyonuna geçilen sembolik sabitler bu parametreye geçilir. -> Dördüncü parametre ise etkin id değerlerinin mi yoksa gerçek id değerlerinin mi kullanılacağını belirtmektedir. Buraya "AT_EACCESS" geçilmesi durumunda etkin id değerleri kullanılacaktır. "0" geçilmesi durumunda da gerçek id değerleri kullanılacaktır. Aşağıda örnek bir kullanım verilmiştir: * Örnek 1, Aşağıdaki örnekte "/" dizini açılmış ve bu dizine ait "fd" ilgili "faccessat" fonksiyonuna argüman olarak geçilmiştir: #include #include #include #include void exit_sys(const char* msg); int main(int argc, char** argv) { /* Command Line Argument => home/q.txt */ /* # OUTPUT # file exists!... */ if (argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } int fd; if( (fd = open("/", O_RDONLY)) == -1) exit_sys("open"); if(faccessat(fd, argv[1], F_OK, AT_EACCESS) == 0) fprintf(stdout, "file exists!...\n"); else fprintf(stdout, "file does not exist!...\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, Aşağıdaki örnekte prosesin o anki "current working directory" noktası kullanılmış. Arama bu noktadan itibaren yapılmıştır: #include #include #include #include void exit_sys(const char* msg); int main(int argc, char** argv) { /* Command Line Argument => home/q.txt */ /* # OUTPUT # file does not exist!... */ if (argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if(faccessat(AT_FDCWD, argv[1], F_OK, AT_EACCESS) == 0) fprintf(stdout, "file exists!...\n"); else fprintf(stdout, "file does not exist!...\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Şimdi de pekiştirici örneklere bakalım: * Örnek 1, İş bu örnekte "errno" değerinin kontrolü yapılmamıştır: #include #include #include void exit_sys(const char* msg); int main(int argc, char** argv) { /* Command Line Argument => q.txt */ /* # OUTPUT # q.txt is readable, writable and executable!... */ if (argc != 2) { fprintf(stderr, "wrong number of arguments!...\n"); exit(EXIT_FAILURE); } if(!access(argv[1], F_OK)) { if( access(argv[1], R_OK | W_OK | X_OK) == 0) fprintf(stdout, "%s is readable, writable and executable!...\n", argv[1]); } else { fprintf(stderr, "%s does not exists!...\n", argv[1]); } return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> "fileno" isimli bu fonksiyon, argüman olarak bir "FILE" türden yapı nesnesinin adresini almakta ve iş bu nesnenin içerisindeki "int" türden "fd" değişkenini geri döndürmektedir. Böylelikle C standart fonksiyonunu kullanarak açtığımız bir dosyayı, POSIX fonksiyonları kullanarak işleyebiliriz. İş bu fonksiyonun prototipi şöyledir: #include int fileno(FILE *stream) POSIX standartlarına göre bu fonksiyon başarısız olabilir ve bu durumda "-1" ile geri dönecektir. Fakat bu fonksiyon, tam randımanlı bir kontrol yapamadığı için geçersiz "FILE" türden nesnelerde de başarılı olabilir. * Örnek 1, #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # fileno: Bad file descriptor */ FILE* f; f = malloc(sizeof(FILE)); if(fileno(f) == -1) exit_sys("fileno"); printf("OK\n"); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, "FILE" yapısı içerisindeki "fd" çekilmiştir: #include #include #include void exit_sys(const char* msg); int main() { /* # q.txt # Ahmet Kandemir Pehlivanli */ /* # OUTPUT # Ahmet Kand */ FILE* f; if((f = fopen("q.txt", "r")) == NULL) { fprintf(stderr, "cannot open the file!...\n"); exit(EXIT_FAILURE); } /* * Unutmamalıyız ki "fileno" fonksiyonu ile "FILE" içerisindeki "fd" yi çekerek işlem yaptığımız * zaman, iş bu "fd" tarafından gösterilen "file" yapısına ait "file pointer" da değiştirilmiş * olur. Yani okuma işlemine yukarıdaki "f" değişkeni üzerinden devam edersek, başka konumdan * itibaren okumaya başlayabiliriz. Bu konuda temkinli olmalıyız. Fakat genellikle bir soruna * yol açmamaktadır. */ int fd; if((fd = fileno(f)) == -1) exit_sys("fileno"); char buffer[10 + 1]; ssize_t result; if((result = read(fd, buffer, 10)) == -1) exit_sys("read"); buffer[result] = '\0'; puts(buffer); fclose(f); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> "fdopen" fonksiyonu, "fileno" fonksiyonunun yaptığının tam tersini yapmaktadır. POSIX fonksiyonu ile başladığımız yola, C fonksiyonları ile devam edebilmemize olanak sağlamaktadır. Yani parametre olarak bir "fd" almakta, geri dönüş değeri de "FILE" türden yapının adresini oluşturmaktadır. İlgili fonksiyonunun imzası aşağıdaki gibidir; #include FILE *fdopen(int fildes, const char *mode); Fonksiyonun birinci parametresi ilgili "fd", ikinci parametresi ise dosyanın açış modlarına ilişkindir. Başarısızlık durumunda "NULL" adresine geri dönerken, başarı durumunda "FILE" nesnesinin adresini döndürür. Unutulmamalıdır ki ikinci parametre ile "open" fonksiyonu açarken geçilen açış modlarının birbiri ile uyumlu olması gerekmektedir. * Örnek 1, #include #include #include #include void exit_sys(const char* msg); int main() { /* # q.txt # Ahmet Kandemir Pehlivanli */ /* # OUTPUT # Ahmet Kandemir Pehlivanli */ int fd; if((fd = open("q.txt", O_RDONLY)) == -1) exit_sys("open"); FILE* f; if((f = fdopen(fd, "r")) == NULL) exit_sys("fdopen"); int ch; while((ch = fgetc(f)) != EOF) putchar(ch); if(ferror(f)) { fprintf(stderr, "cannot read file!...\n"); exit(EXIT_FAILURE); } fclose(f); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } >>> "fcntl" fonksiyonu: İş bu fonksiyon, halihazırda açılmış olan bir dosyanın ki bu dosya "regular" dosya olabileceği gibi boru dosyası da olabilir, çeşitli açım özelliklerini değiştirmek için kullanılır. Yani bir dosyayı açarken parametre olarak girdiğimiz bayrakları daha sonra değiştirmek istediğimizde bu fonksiyondan faydalanırız. Bu fonksiyonu kullanabilmek için ilgili dosyanın "open" ile açılmasına gerek yoktur. Örneğin, bir boru hayata getirirken bizler "open" fonksiyonunu kullanmayız. Çağırdığımız "mkfifo" fonksiyonu ise bizlere iki elemanlı ve elemanları bir "fd" olan dizinin başlangıç adresini döndürür. İşte bu "fd" leri kullanarak ilgili boru dosyalarının bir takım özelliklerini değiştirmek istiyorsak, "fcntl" fonksiyonunu çağırmalıyız. Anımsanacağınız üzere bu fonksiyonu, bir prosesin "close on exec" bayrağını değiştirmek için de kullanmıştık. Ayrıca iş bu fonksiyon bayrakların "get" edilmesi için de kullanılmaktadır. Fonksiyonumuz aşağıdaki parametrik yapıya sahiptir; #include int fcntl(int fildes, int cmd, ...); Fonksiyonun birinci parametresi, hangi "fd" üzerinde işlem yapılacağını belirtir. İkinci parametre ise bizim ne yapmak istediğimizi belirten parametredir. Bu parametrelere sadece bir takım sembolik sabitleri geçebiliriz. Üçüncü parametre ise sadece "set" işlemi sırasında yeni değeri geçmek için kullanılır. Fonksiyon başarısızlık durumunda "-1" ile geri döner. Başarı durumunda ise "get" ettiğimiz değer ile geri dönmektedir. Fonksiyonun ikinci parametresine: >>>> "F_GETFL" ya da "F_SETFL" sembolik sabitlerinden birisini geçmemiz durumunda, fonksiyon "Dosya Durum Bayraklarını (file status flags)" ve "Dosya Erişim Modunu (file access mode)" sırasıyla "get" ve "set" etmektedir. Dosya Durum Bayrakları şunlardır: O_APPEND O_DSYNC O_NONBLOCK O_RSYNC O_SYNC Biz şimdiye kadar bu bayraklardan sadece "O_APPEND" bayrağını "open" fonksiyonu sırasında gördük. Dosya Erişim Modları da şunlardır: O_EXEC O_RDONLY O_RDWR O_SEARCH O_WRONLY Eğer programcı Dosya Durum Bayraklarını "F_GETFL" ile elde etmek istemişse, ilgili fonksiyonun geri dönüş değerini yukarıdaki durum bayrakları ile "bitwise-AND" işlemine sokması gerekmektedir. Takribi olarak aşağıdaki biçimde bir çözüm üretmeliyiz: //... result = fcntl(fd, F_GETFL); if(result & O_APPEND) { /* "O_APPEND" bayrağı "set" edilmiş demektir. */ } Öte yandan Dosya Erişim Modlarını "get" etmek istiyorsa yukarıdaki çözümü UYGULAMAMALIDIR. Bunun için önce "fcntl" fonksiyonunun geri dönüş değeri "O_ACCMODE" ile "bitwise-AND" işlemine sokulmalı. Çıkan sonuç ile yukarıdaki erişim modları "==" sorgulaması yapılmalıdır. Takribi olarak aşağıdaki biçimde bir çözüm üretmeliyiz: //.. result = fcntl(fd, F_GETFL); if((result & O_ACCMODE) == O_RWWR) { /* "O_RWWD" bayrağı "set" edilmiş demektir. */ } Şimdi de "O_NONBLOCK" bayrağını "set" etmek isteyelim. Bu sefer de aşağıdaki gibi bir çözüm üretmeliyiz; //... fcntl(fd, F_SETFL, O_NONBLOCK); Fakat bu şekilde kullanırsak, bütün bayraklar (Dosya Durum Bayrakları ve Dosya Erişim Modu) "set" edilecektir. Dolayısıyla bizler ilk önce "get", sonra "set" yapmalıyız ki sadece bizim istediğimiz "set" edilsin. Bunun için de: //.. result = fcntl(fd, F_GETFL); if(fcntl(fd, F_SETFL, result | O_NONBLOCK) == -1) exit_sys("fcntl); Şimdi de "O_NONBLOCK" bayrağını "clear" edelim: //.. result = fcntl(fd, F_GETFL); if(fcntl(fd, F_SETFL, result & ~O_NONBLOCK) == -1) exit_sys("fcntl); >>>> "F_GETFD" ya da "F_SETFD" sembolik sabitlerinden birisini geçmemiz durumunda fonksiyonumuz "Dosya Betimleyici Bayraklarını" sırasıyla "get" ve "set" edecektir. POSIX standartları, Dosya Betimleyici Bayrakları olarak, sadece bir adet bayrak tanımlamıştır. Bu bayrak ise şudur: FD_CLOEXEC Dolayısıyla biz bu sembolik sabitler ile sadece "FD_CLOEXEC" bayrağını değiştirebiliriz. Anımsanacağınız üzere bizler daha evvelinde dosyanın "close on exec" bayrağı üzerinde oynamalar yapmıştık. Hatırlamak gerekirse; "get" etmek için aşağıdaki yöntemi kullanabiliriz. //... result = fcntl(fd, F_GETFD); if(result & FD_CLOEXEC) { /* "close on exec" bayrağı "set" edilmiş. */ } İlgili bayrağı "set" etmek için de yine aşağıdaki yöntemi kullanabiliriz: //... result = fcntl(fd, F_GETFD); if(fcntl(fd, F_SETFD, result | FD_CLOEXEC) == -1) exit_sys("fcntl); İlgili bayrağı "clear" etmek için de yine aşağıdaki yöntemi kullanabiliriz: //... result = fcntl(fd, F_GETFD); if(fcntl(fd, F_SETFD, result & ~FD_CLOEXEC) == -1) exit_sys("fcntl); >>>> "F_DUPFD" sembolik sabitini geçmemiz durumunda fonksiyonumuz argüman olarak aldığı "fd" değerini çiftleyecektir. Daha önce bizler bu çiftleme işlemi için "dup" ve "dup2" fonksiyonunu kullanmıştık. "fcntl" fonksiyonu ile çiftlemek için üçüncü bir parametre daha girmemiz gerekmektedir. Böylelikle üçüncü parametredeki "fd" değişkeninden büyük eşit olan ilk boş "fd" değişkeni ile çiftleme yapılacaktır. Karşılaştırırsak, //... fd2 = dup(fd); // İlk boş betimleyici bize verilecek. //... fd3 = dup2(fd, 10); // 10 numaralı betimleyici bize verilecek. //... fd4 = fcntl(fd, F_DUPFD, 15); // 15 numaralıdan büyük eşit ilk betimleyici. İlgili fonksiyonun kullanım biçimi de aşağıdaki gibidir; //... if((fd2 = fcntl(fd, F_DUPFD, 50)) == -1) exit_sys("fcntl); Burada "fd" nin çiftlenmiş betimleyicisi 50 ya da ilk büyük betimleyicidir. Aşağıdaki gibi bir kullanım ise "dup" fonksiyonunun kullanımı aynıdır: //... if((fd2 = fcntl(fd, F_DUPFD, 0)) == -1) exit_sys("fcntl); >>>> "F_DUPFD_CLOEXEC" sembolik sabiti kullanırsak, "F_DUPFD" ve "close on exec" bayrağının "set" edilmesini birlikte yapmaktadır. Yani özetle bu komut kodu hem dosya betimleyicisini çiftler hem de çiftlenmiş olan yeni betimleyicinin "close on exec" bayrağını "set" eder. //... if((fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 0)) == -1) exit_sys("fcntl); Artık "fd2" betimleyicisinin aynı zamanda "close on exec" bayrağı da "set" edilmiştir. İş bu fonksiyonun ikinci parametreye geçilen diğer bayrakları ise o konular işlendiği zamanda anlatılacaktır. > Hatırlatıcı Notlar: >> Linux Kernel dökümantasyonu için "kernel.org" internet sitesini kullanabiliriz. >> Tek hamlede yazılabilecek maksimum karakter alanı için pratik bir limit söz konusu değildir. Zaman zaman da döngünün her anında büyük büyük okumalar yerine biraz da ideal büyüklükteki okumalar yaparsak daha hızlı sonuç alabiliriz. >> Bir C/C++ programcısı olarak UNIX/Linux türevi sistemlerde dosya işlemleri yapmak için üç adet seçenek söz konusu olabilir; >>> C/C++ dillerinin dosya fonksiyonlarını kullanmak. Fakat bu fonksiyonlar spesifik bir sistem gereksinimini karşılayacak şekilde tasarlanmamışlardır. Tavsiye edilen, işimizi bu fonksiyonlar görüyor ise bu fonksiyonlar ile görmektir. >>> POSIX fonksiyonlarını çağırmak. >>> Sistem fonksiyonlarını çağırmak. Fakat çoğu sistemde POSIX fonksiyonları doğrudan sistem fonksiyonlarını çağırdığından, özel özel sistem fonksiyonlarını çağırmamıza gerek yoktur. >> İşlemlerin atomik olması, ilgili işlemlerin "task-switch" e maruz kalmadan işlenmesi anlamına gelmektedir. Bir diğer anlamı ise sadece o anda bir prosesin işleminin yapılıyor oluşudur. >> "IO-Scheduler" : Proseslerden gelen okuma ve yazma işlem taleplerinin, diskin o anki okuma başlığının konumuna göre yeniden sıralanması işlemine denkmektedir. >> "internal fragmantation", dosyaların disk üzerinde kapladıkları alanlardaki ölü alanlardır. Örneğin, 512 bayt büyüklüğünde bloklarımız olsun ve 12 baytlık bir dosya oluşturalım. Geriye kalan 500 baytlık alan ölü alan olacaktır ve bu isimle anılacaktır. Örneğin, 10 adet 10 bayt büyüklüğünde dosyamız olsun. Blok büyüklüğü de 512 bayt olsun. Normal şartlarda bu on dosya, on adet bloğu kaplayacaktır. Her bir blok içerisindeki 502 baytlık kısım ölü alan olacaktır. İşte, bu on dosyayı uç uca getirerek tek bir dosya haline getirilmesine "tar" işlemi denmektedir. Böylece bu yeni dosya sadece bir blok kaplayacağı için, disk üzerinde yeni yerler kazanmış olacağız. Fakat bu on dosya uç uca getirildiğinde, bunların bilgilerini tutmak için de yeni alanlar tahsis edilebilinir. Bu neden ile bu yeni dosyanın büyüklüğü 100 bayttan geçkin olacaktır. "zip" işlemi ise var olan dosyaların sıkıştırılmasına denir. Genel olarak önce "tar", sonra "zip" işlemi uygulanır. "zip" işlemi aslında dosyayı daha az bayt ile temsil etmektir. >> Dizin Girişinden silinen bir dosyanın "hard-link" sayacını görmek için üçüncü parti "utility" programlar kullanmamız gerekiyor. Eğer silinen bu dosya o "i-node" numarasına sahip son dosya ise, dosya fiziksel olarak da silineceği için, iş bu sayacı görmemiz imkansızdır. Aynı "i-node" numarasına sahip dosyaları görüntülemek için pratik bir yöntem mevcut değildir. Öyle ya da böyle, diskte tek tek dosyalar aranarak bulunmaktadır. >> Editör programları üzerinden "Save As" dediğimiz zaman "hard-link" oluşturulmamaktadır. Çünkü "Save As" demekle aslında birbirinden bağımsız iki dosya oluşturmak istemekteyiz. >> "du" komutu ile gösterilen rakam 1024 baytlık bloklar şeklinde hesaplanması sonucu elde edilen rakamdır. >> İsminin başında "f" dosya işlem fonksiyonları, "f" olmayanların, parametre olarak "File Description" alan versiyonlarıdır. Örneğin, "chown" ve "fchown", "stat" ve "fstat". Bu tip "File Description" alan fonksiyonlar, almayan versiyonlarına nazaran daha hızlı bir çalışma sunmaktadır eğer halihazırda açılmış bir dosya varsa. Fakat "f" li versiyonlar daha az kullanılırlar. >> Bu derse kadar gördüğümüz dosya fonksiyonlarını iki gruba ayırabiliriz; >>> Temel Dosya Fonksiyonları; "open", "close", "read", "write", "lseek" fonksiyonlarıdır. >>> Yardımcı Dosya Fonksiyonları; "unlink" / "remove", "chmod" / "fchmod", "stat" / "fstat" / "lstat", "chown" / "fchown", "mkdir" / "rmdir" isimli fonksiyonlarıdır. >> UNIX/Linux, macOS ve Windows sistemlerinde "." ve ".." dizinleri SİLİNEMEMEKTEDİR. >> Bir dizinin boş olması, içinde sadece "." ve ".." dizinlerinin olması demektir. >> "getpwent" fonksiyonunu implemente ederken bir adet statik ömürlü 4096 karakter uzunluğunda dizi oluşturalım. "fgets" ile "etc/passwd" içerisindeki her bir satırı sırayla okuyup ilgili bu diziye yazalım. "strtok" ile bu diziden de "parse" işlemi yapalım. Elde ettiğimiz her bir bilgisi de statik ömürlü "struct passwd" nin elemanlarına atayalım. İlgili bu yapının başlangıç adresi, statik ömürlü dizinin başlangıç adresini göstermeli. >> Linux işletim sisteminde dosya işlemleri önce "sys_open" sistem fonksiyonu ile dizin'in açılması, "sys_getdents" sistem fonksiyonu ile dizin girişlerinin okunması ve nihayet "sys_close" sistem fonksiyonu ile dizin'in kapatılması yoluyla yapılmaktadır. Fakat POSIX standartlarında taşınabilirlik adına bu işlemler sırasıyla "opendir", "readdir" ve "closedir" fonksiyonlarına devredilmiştir. Şüphesiz bu POSIX fonksiyonları da aslında dizini açıp, ona ait betimleyiciyi "DIR" yapısının içerisinde saklamaktadır. Eğer elimizde bir "DIR" yapısı varsa, bizler de açık bir dizine ait dizin betimleyicisini elde etmek istiyorsak, "dirfd" isimli fonksiyonu kullanabiliriz. >>> "dirfd" isimli fonksiyon parametre olarak sadece "DIR" yapısının adresini alır, geri dönüş olarak ilgili betimleyiciyi döndürür. Başarısızlık durumunda "-1" ile geri dönmektedir. >> "unlink" ve "remove" isimli fonksiyonlar sembolik bağlantı dosyalarını İZLEMEMEKTEDİR. Dolayısıyla bu fonksiyonlar ile bir silme işlemi yaptığımız zaman sembolik bağlantı dosyasının kendisini silmiş olacağız. Öte yandan "open" fonksiyonu sembolik bağlantıları izlemektedir. Genel olarak POSIX fonksiyonlarının çoğu sembolik bağlantı dosyalarını izlemektedir. >> Linux sistemlerinde maksimum yol ifadesi uzunluğu 4096 karakterdir. Fakat POSIX sistemlerde yol ifadesinin en fazla kaç karakter uzunlukta olacağı, "limits.h" içerisinde belirtilen, "PATH_MAX" isimli sembolik sabit üzerinden öğrenilebilir. Fakat bu sembolik sabitin tanımlı olması da bir ZORUNLULUK DEĞİLDİR. Bu konu teferruatlı olduğu için ileride ele alınacaktır. >> Sembolik bağlantı dosyalarını kullanarak bir "loop" meydana getirebiliriz. "open" ile bu durumdaki bir sembolik bağlantı dosyasını açmaya çalıştığımız vakit, "errno" değeri "ELOOP" değerini alacaktır (burada sonsuz döngünün belli miktar kadar dönmesi gerekmektedir). >> "at" li fonksiyonların birinci parametresine bir "file descriptor" geçmek yerine "AT_FDCWD" sembolik sabitini kullanmamız durumunda prosesin "current working directory" konumu başlangıç noktası olarak ele alınacaktır. Dolayısıyla bir "fd" elde etmek için "open" ile bir dizin açmamız gerek kalmamaktadır. Eğer ikinci parametre mutlak yol ifadesi olursa, birinci parametre ele alınmayacağı için günün sonunda "at" siz versiyonlarını kullanmış gibi olacağız. Velevki ikinci parametre göreli bir yol ifadesi olsaydı, birinci parametredeki "fd" başlangıç noktası olarak ele alınacaktır. Eğer birinci parametreye de "AT_FDCWD" geçerksek, prosesin o anki "current working directory" konumu başlangıç olarak ele alınacaktır. İşte bu durum sonucunda "at" siz versiyonu kullanmaktan yine bir farkı kalmamaktadır. Çünkü "at" siz versiyonlara geçilen yol ifadesi göreli ise prosesin o anki "current working directory" konumu başlangıç olarak ele alınmaktadır. Pekiyi neden "at" li fonksiyonları kullanmalıyız? "at" li versiyonların almış olduğu diğer parametrelerden de faydalanmak için. AYRICA UNUTULMAMALIDIR Kİ "at" Lİ FONKSİYONLARIN BİRİNCİ PARAMETRESİNE GEÇİLEN "fd", İKİNCİ PARAMETRESİNİN MUTLAK OLMASI DURUMUNDA HERHANGİ BİR KONTROLE TABİİ DEĞİLDİR. GEÇERSİZ BİR "fd" GEÇİLEBİLİR. >> Aygıt sürücüler "open" fonksiyonu ile açılmaktadır ve parametre olarak aygıt sürücüyü temsil eden bir dizin girişi belirtilir. Örneğin, fd = open("/dev/NULL", O_WRONLY); Ancak bu dizin girişi gerçek bir dosyaya ait değildir. Bu giriş için sadece bir adet "i-node" elemanı bulundurulmaktadır. İşletim sistemi böyle bir dosya açılmaya çalışıldığında, aslında bir aygıt sürücü ile işlem yapılmak istediğini anlamaktadır. Yani aygıt sürücü bir dosya gibi açılıyor olsada, bir dosya ile ilgisi yoktur. Aygıt dosyaları, "dummy" bir dosyadır ve "kernel" içerisindeki aygıt sürücüsünü temsil etmektedir. Buradaki dizin girişleri aygıt sürücüye ulaşmak için kullanılan ara bir dosyadır. >> "write" fonksiyonu ile blokeli modda yazarken "Partial Write" oluşmazken, blokesiz modda OLUŞUR. >> "open" gibi fonksiyonlar ile açtığımız dosyalar için bir "file" türden nesne oluşturulur ve prosesin "Dosya Betimleyici Tablosu" ndaki ilgili indislere bu "file" dosyalarına ilişkindir.