UNIX/Linux ve Windows Sistemlerinde Dosyaların ve

advertisement
UNIX/Linux ve Windows Sistemlerinde
Dosyaların ve Dizinlerin Silinmesi
Kaan Aslan
23 Temuz 2007
UNIX/Linux sistemlerinde bir dosyanın katı bağ sayısı 1 ise dosyayı tek bir dizin
girişi göstermektedir. Bu durumda dosyanın silinmesi hem dizin girişinin hem de
düğüm bilgisinin (dolayısıyla da dosya bloklarının) silinmesi anlamına gelir. Bu gerçek
bir silme işlemidir. Fakat silinecek dosyanın katı bağ sayısı 1'den fazlaysa silme
işlemi yalnızca dizin girişinin silinmesine yol açar. Dosyayı kullanan başka girişler
olduğu için dosya gerçek anlamda silinmez. Dosya silme işleminde unlink isimli
POSIX fonksiyonu kullanılmaktadır:
#include <unistd.h>
int unlink(const char *path);
Fonksiyonun parametresi silinecek dosyaya ilişkin yol ifadesidir. Fonksiyon başarı
durumunda sıfır değerine başarısızlık durumunda -1 değerine geri döner. Başarısızlık
durumunda errno değişkeninin aldığı önemli değerler şunlardır:
EACCESS
:
Yol bileşenlerine ilişkin dizinlerden en az birine ‘x’ hakkı yoktur ya
da dosyanın bulunduğu dizine yazma hakkı yoktur.
ENOENT
:
Yol bileşenlerinden biri yoktur ya da yol ifadesi boş bir stringten
oluşmaktadır.
ENOTDIR
:
Yol bileşenlerinden biri dizin olması gerekirken dizin değildir.
…
:
…
unlink fonksiyonu dizin girişini siler ve dosyanın katı bağ sayısını 1 azaltır. Eğer
katı bağ sayacı sıfıra düşmüşse düğüm elemanını ve dosyanın disk bloklarını siler.
Dosyanın silinebilmesi için fonksiyonu çağıran prosesin yol ifadesindeki tüm dizinlere
‘x’ hakkının ve dizin girişinin içinde bulunduğu dizine ‘w’ hakkının olması gerekir.
Silme işlemi için prosesin dizin girişinin içinde bulunduğu dizine ‘r’ hakkının olması
gerekmemektedir.
POSIX standartlarına göre unlink fonksiyonu ile dizinlerin silinip silinemeyeceği
işletim sistemini yazanların isteğine bırakılmıştır. Eğer işletim sistemi unlink
fonksiyonuyla dizinin silinmesine izin veriyorsa fonksiyonu çağıran prosesin etkin
1
Kaan Aslan Makale Arşivi – www.kaanaslan.net
kullanıcı id'sinin 0 olması gerekir. Fakat yaygın olarak kullanılan UNIX türevi
sistemler dizinlerin bu fonksiyonla silinmesine olanak vermiyorlar.
unlink fonksiyonu dosyanın içerisinde bulunduğu dizinde değişiklik yaptığı için
dosyanın içinde bulunduğu dizine ilişkin düğümün (i-node elemanının) st_ctime ve
st_mtime elemanlarını günceller. Tabi eğer unlink işlemi sonucunda yalnızca dizin
girişi silinmişse (katı bağ sayısının düğüm elemanının içinde olduğunu anımsayınız)
silinmek istenen dosyanın st_ctime elemanı da güncellenecektir.
unlink fonksiyonu dosya açıkken de uygulanabilir. Bu durumda dizin girişi yok
edilir ve dosyanın katı bağ sayısı bir azaltılır. Fakat dosyanın katı bağ sayısı sıfıra
düşse bile silme işlemi hemen yapılmaz. Gerçek silme işlemi dosya kapatıldığında
yapılır. Örneğin:
#include
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<unistd.h>
<fcntl.h>
<sys/types.h>
<sys/stat.h>
int main(vodid)
{
int fd;
if ((fd = open("test.dat", O_RDWR|O_CREAT|O_TRUNC,
S_IRUSR|S_IWUSR)) < 0) {
perror("open");
exit(EXIT_FAILURE);
}
if (unlink("test.dat") < 0) {
perror("unlink");
exit(EXIT_FAILURE);
}
/* Dizin girişinden silindi ancak gerçek anlamda silinmedi */
printf("Press ENTER to continue. . . ");
getchar();
/* Dosyanın kapatılmasıyla dosya gerçek anlamda silinecek */
close(fd);
return 0;
}
Yukarıdaki programda dosya yaratılmış ve hemen silinmeye çalışılmıştır. Bu
durumda unlink fonksiyonu başarılı olur. Fakat gerçek silme işlemi close fonksiyonu
çağrıldığında yapılacaktır.
2
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Dosyaların silinmesinde remove isimli fonksiyon da kullanılabilir. remove aynı
zamanda bir standart C fonksiyonu olduğu için yalnızca UNIX/Linux sistemlerinde
değil tüm C derleyicilerinde bulunmaktadır:[1
#include <stdio.h>
int remove(const char *path);
POSIX standartlarına göre remove fonksiyonu ile hem dosyalar hem de dizinler
silinebilirken unlink fonksiyonuyla yalnızca normal dosyalar silinebilmektedir. Fakat C
standartlarında remove fonksiyonunun dizin silmede de kullanılabileceğine ilişkin bir
ifadenin olmadığını belirtelim. (Ayrıca yukarıda da belirttiğimiz gibi POSIX
fonksiyonları unlink fonksiyonuyla dizinlerin silinip silinmeyeceğini işletim sistemini
yazanların isteğine bırakmıştır.)
Windows sistemlerinde dosya silmek için DeleteFile API fonksiyonu
kullanılmaktadır. Windows 95, 98 ve ME sistemlerinde dosya açıkken silme yapılması
salık verilmez. Çünkü DeleteFile bu sistemlerde gerçek anlamda silme yapmaktadır.
Bu durumda önce dosyanın kapatılıp silinmesi uygun ve doğru tekniktir. DeleteFile
fonksiyonunun Windows 200 ve XP sistemlerindeki davranışı POSIX sistemlerindeki
gibidir. Yani dosyanın silinmesi gecikmeli olarak CloseHandle fonksiyonu ile
kapatıldığında gerçekleştirilir. Windows CE sistemlerinde dosya açıkken silinmeye
çalışılırsa DeleteFile fonksiyonu başarısızlıkla geri döner.
Dizinlerin silinmesi için
POSIX sistemlerinde rmdir fonksiyonu kullanılmaktadır. Fonksiyonun protoipini
inceleyiniz:
#include <unistd.h>
int rmdir(const char *path);
Fonksiyon parametere olarak dizine ilişkin yol ifadesini alır. İşlem başarılıysa sıfır
değerine başarısızsa -1 değerine geri döner. Bir dizinin silinmesi için dizinin boş
olması gerekir. Dizinin boş olması dizinde "." ve ".." dışında hiçbir dizin girişinin
olmaması anlamına geliyor. Ayrıca bir prosesin çalışma dizininin rmdir fonksiyonuyla
silinip silinemeyeceği POSIX standartlarında işletim sistemini yazanların isteğine
bırakılmıştır. Ancak pek çok sistemin buna izin verdiğini görüyoruz. Kök dizinin
silinmeye çalışılması ise normal bir durum değildir. Bu durumun da mümkün olup
olamayacağı işletim sistemini yazanların isteğine bırakılmıştır. Sistemlerin neredeyse
hepsinde bu yasaklanmış durumda. Son olarak dizininlerin ilk iki elemanı olan “.” ve
“..” dizinlerinin silinmeye çalışılmasının da geçerli olmadığını belirtelim.[2]
rmdir fonksiyonu da unlink ve remove fonksiyonları gibi katı bağ sayacını dikkate
alarak çalışmaktadır. Yani bu fonksiyon da önce dizinin katı bağ sayacını düşürür,
eğer dizinin katı bağ sayacı sıfıra düşmüşse dizini gerçek anlamda siler. Dizin boşsa
ve bağ sayacı 2 ise dizin "." ve ".." girişleriyle birlikte silinmektedir. Eğer dizin boş
olduğu halde bağ sayacı 2'den büyük ise dizinler için katı bağ oluşturmaya izin
verilen bir sistem söz konusudur. Bu durumda rmdir fonksiyonu yalnızca dizine ilişkin
katı bağ girişini siler ve dizinin bağ sayacını 1 eksiltir. Son olarak dizinin bağ
sayacının hiçbir zaman 1 olmayacağını bir kez daha vurgulayalım. Silinebilecek
durumdaki bir dizine ilişkin bağ sayacı en az 2 olabilir.
3
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Katı bağ sayacı 2 olan bir dizin herhangi bir ya da birden fazla proses tarafından
opendir fonksiyonuyla açılmış olabilir. Bu durumda rmdir fonksiyonu “.” ve ”..”
dizinlerini hemen siler. Ancak dizinin gerçek anlamda silinmesi son proses dizini
kapattığında gerçekleştirilecektir.
rmdir fonksiyonu başarısız olduğunda errno değişkenine yerleştirilen önemli
değerlerden bazıları şunlardır:
EACCESS
:
Yol bileşenlerine ilişkin dizinlerden en az birine “x” hakkı yoktur ya
da dizinin içinde bulunduğu dizine yazma hakkı yoktur.
ENOENT
:
Yol bileşenlerinden biri yoktur ya da yol ifadesi boş bir stringten
oluşmaktadır.
ENOTDIR
:
Yol bileşenlerinden biri dizin olması gerekirken dizin değildir.
EBUSY
:
Dizin sistem tarafından ya da proses tarafından kullanıyor
durumdadır. Örneğin dizin bir prosesin çalışma dizinidir.
EEXIST
ENOEMPTY
:
Silinmeye çalışılan dizin boş değildir.
…
:
…
Dizin silindiğinde dizinin üst dizinine ilişkin düğüm elemanının st_ctime ve st_mtime
elemanları güncellenmektedir.
İşletim sistemlerinin çoğu tek hamlede dizin ağacının silinmesine olanak
sağlayacak bir sistem fonksiyonu barındırmıyor. Bunun nedenlerinden biri dizin
ağacının silinmesinin yüksek seviyeli bir işlem olarak görülmesidir. Çünkü işletim
sisteminin sunduğu temel sistem fonksiyonları kullanılarak dizin ağacının silinmesi
kullanıcı modunda yapılabilir. Ayrıca dizin ağacının tek hamlede tümden silinmesi
sistem tasarımcıları tarafından tehlikeli bir işlem olarak da ele alınmaktadır. Çok
sayıda dosyanın bir hamlede silinmesi geri dönülemez önemli kayıpların oluşmasına
yol açabilmektedir. Ağaç silinirken bir noktada pürüz çıkarsa bir kısmı silinmiş olan bir
ağacın durumu ne olacaktır?
Dizin ağacını silmek için aşağıdaki gibi bir özyinelemeli algoritma kullanılabilir. Bu
özyinelemeli algoritma, dizinin içerisine girilince dizindeki dosyaların hepsini silen,
dizinden çıkıldığında ise dizini silen bir yapıdadır
4
Kaan Aslan Makale Arşivi – www.kaanaslan.net
/* deltree.c */
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<fcntl.h>
<unistd.h>
<sys/types.h>
<sys/stat.h>
<dirent.h>
<errno.h>
#define MAX_PATH_LEN
2048
void err_sys(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
void delwalk(const char *dirpath)
{
DIR *dir;
struct dirent *ent;
struct stat finfo;
int fdcwd;
static int level;
if ((dir = opendir(dirpath)) == NULL) {
perror("opendir");
return;
}
if ((fdcwd = open(".", O_RDONLY)) < 0)
err_sys("getcwd");
if (chdir(dirpath) < 0) {
perror("chdir");
goto NOT_SEARCHABLE;
}
while (errno = 0, (ent = readdir(dir)) != NULL) {
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
continue;
if (lstat(ent->d_name, &finfo) < 0)
err_sys("lstat");
if (S_ISDIR(finfo.st_mode))
delwalk(ent->d_name);
else
if (remove(ent->d_name) < 0)
err_sys("remove");
}
if (errno)
err_sys("readdir");
5
Kaan Aslan Makale Arşivi – www.kaanaslan.net
if (fchdir(fdcwd) < 0)
err_sys("fchdir");
if (rmdir(dirpath) < 0)
err_sys("rmdir");
NOT_SEARCHABLE:
close(fdcwd);
closedir(dir);
}
int main(int argc, char *argv[])
{
if (argc != 2)
err_usr("Wrong number of arguments\n");
delwalk(argv[1]);
return 0;
}
Derleme işlemini şöyle yapabilirsiniz:
gcc –o deltree deltree.c
Windows sistemlerinde dizin ağacını tek hamlede silen bir çekirdek API fonksiyonu
yoktur. Ancak bu işlem SHFileOperation isimli bir kabuk (shell) fonksiyonuyla
yapılabilir. Bildiğiniz gibi kabuk fonksiyonları daha yüksek seviyelidir ve kendi
içerisinde
çekirdek
fonksiyonlarını
çağırarak
işlevlerini
gerçekleştirirler.
SHFileOperation fonksiyonunu kullanarak bir dizini aşağıdaki gibi bir kodla
silebilirsiniz
/* deltree.c */
#include <stdio.h>
#include <windows.h>
#include <shellapi.h>
int main(int argc, char *argv[])
{
SHFILEOPSTRUCT shfo;
char path[MAX_PATH];
int result;
if (argc != 2) {
fprintf(stderr, "Wrong number of arguments!..\n");
exit(EXIT_FAILURE);
}
strcpy(path, argv[1]);
path[strlen(path) + 1] = '\0';
6
Kaan Aslan Makale Arşivi – www.kaanaslan.net
ZeroMemory(&shfo, sizeof(SHFILEOPSTRUCT));
shfo.hwnd = NULL;
shfo.wFunc = FO_DELETE;
shfo.lpszProgressTitle = "Test";
shfo.fFlags = FOF_NOERRORUI;
shfo.pFrom = path;
if ((result = SHFileOperation(&shfo)) != 0) {
fprintf(stderr, "Cannot delete folder or file, "
"ErrCode = %X\n", result);
exit(EXIT_FAILURE);
}
printf("Success...\n");
return 0;
}
Derleme işlemini şöyle yapabilirsiniz:
cl deltree.c shell32.lib
SHFILEOPSTRUCT yapısının pFrom elemanına atanacak yazının sonunda çift null
karakterin bulunması gerekmektedir. SHFileOperation pek çok işlemi yapabilen geniş
bir fonksiyondur. Fonksiyonun kullanımına ilişkin ayrıntılı bilgileri MSDN yardım
sisteminden elde edebilirsiniz. [3]
[1]
C'de ve C++'ta standartlara uyum "hosted" ve "free standing" biçiminde iki gruba
ayrılmıştır. "Hosted" uyum katı uyumdur. Bu uyuma sahip standart C derleyicilerinde
tüm standart C fonksiyonları bulunmak zorundadır. "Free standing" uyumda ise
kütüphanenin küçük bir bölümünün bulunma zorunluluğu vardır.
[2]
POSIX standartları "." ve ".." dizinleri konusunda esnek davranmıştır. Bu iki dizinin
üst dizinin ilk iki girişinde bulunması zorunlu değildir. Taşınabilir programların bunu
göz önüne alması gerekebilir. Fakat uygulamada hemen hemen tüm sistemlerde bu
iki dizin girişi üst dizinin ilk iki elemanında bulunuyorlar.
[3]
Windows Shell programlama için Dino Esposito'nun "Visual C++ Windows Shell
Programming" isimli kitabından faydalanabilirsiniz.
7
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Download