Örneklerle birlikte Linux'ta dosya tanıtıcı

Bir keresinde, bir röportaj sırasında bana, diskte yer kalmaması nedeniyle bir hizmetin çalışmadığını fark ederseniz ne yapacağınız soruldu.

Tabii ben de buranın ne işgal ettiğini göreceğimi ve mümkünse burayı temizleyeceğimi söyledim.
Daha sonra görüşmeci şunu sordu: Bölümde boş alan yoksa ama aynı zamanda tüm alanı kaplayacak herhangi bir dosya da göremiyorsanız ne olur?

Bunun için, örneğin lsof komutuyla her zaman açık dosya tanımlayıcılarına bakabileceğinizi ve hangi uygulamanın mevcut tüm alanı kapladığını anlayabileceğinizi ve ardından verilere ihtiyaç olup olmadığına bağlı olarak koşullara göre hareket edebileceğinizi söyledim. .

Görüşmeci son kelimede sözümü kesti ve sorusuna şunu ekledi: "Diyelim ki verilere ihtiyacımız yok, bu sadece bir hata ayıklama günlüğü, ancak uygulama çalışmıyor çünkü hata ayıklama yazamıyor"?

"Tamam" diye yanıtladım, "uygulama yapılandırmasında hata ayıklamayı kapatıp yeniden başlatabiliriz."
Görüşmeyi yapan kişi itiraz etti: "Hayır, uygulamayı yeniden başlatamıyoruz, hafızamızda hâlâ önemli veriler kayıtlı ve önemli istemciler hizmetin kendisine bağlı ve bunu yeniden bağlanmaya zorlayamayız."

“tamam,” dedim, “eğer uygulamayı yeniden başlatamazsak ve veriler bizim için önemli değilse, o zaman ls komutunda görmesek bile bu açık dosyayı dosya tanımlayıcı aracılığıyla temizleyebiliriz. dosya sisteminde.”

Görüşmeyi yapan kişi memnundu ama ben değildim.

Sonra düşündüm, neden bilgimi test eden kişi daha derine inmiyor? Peki ya veriler sonuçta önemliyse? Bir işlemi yeniden başlatamazsak ve işlem, boş alanı olmayan bir bölümdeki dosya sistemine yazıyorsa ne olur? Peki ya yalnızca önceden yazılmış olan verileri değil, aynı zamanda bu sürecin yazdığı veya yazmaya çalıştığı verileri de kaybedemezsek?

sandal

Kariyerimin başlarında kullanıcı bilgilerini depolamak için gereken küçük bir uygulama oluşturmaya çalıştım. Sonra kullanıcıyı verileriyle nasıl eşleştirebilirim diye düşündüm. Mesela bende Ivanov Ivan Ivanovich var ve onun da bazı bilgileri var ama onlarla nasıl arkadaş olabilirim? “Tuzik” isimli köpeğin de bu İvan'a ait olduğunu doğrudan belirtebilirim. Peki ya ismini değiştirirse ve Ivan yerine örneğin Olya olursa? O zaman Olya Ivanovna Ivanova'mızın artık bir köpeği olmayacağı ve Tuzik'imizin hala var olmayan Ivan'a ait olacağı ortaya çıkacak. Her kullanıcıya benzersiz bir tanımlayıcı (ID) veren bir veritabanı bu sorunun çözülmesine yardımcı oldu ve Tuzik'im aslında sadece bir seri numarası olan bu kimliğe bağlıydı. Böylece asın sahibi 2 numaralı kimlik numarasına sahipti ve bir noktada Ivan bu kimliğin altındaydı ve ardından Olya da aynı kimlik altındaydı. İnsanlığın ve hayvancılığın sorunu fiilen çözüldü.

Dosya tanımlayıcı

Dosyanın ve bu dosyayla çalışan programın sorunu köpeğimiz ve erkeğimizle yaklaşık olarak aynıdır. Diyelim ki ivan.txt adında bir dosya açtım ve içine tuzik kelimesini yazmaya başladım, ancak dosyaya yalnızca ilk "t" harfini yazmayı başardım ve bu dosya birisi tarafından örneğin olya.txt olarak yeniden adlandırıldı. Ancak dosya aynı kalıyor ve ben yine de asımı ona kaydetmek istiyorum. Bir dosya sistem çağrısıyla her açıldığında açık herhangi bir programlama dilinde beni bir dosyaya yönlendiren benzersiz bir kimlik alırım; bu kimlik, dosya tanımlayıcıdır. Ve bu dosyayla bundan sonra ne ve kimin yapacağı hiç önemli değil, silinebilir, yeniden adlandırılabilir, sahibi değiştirilebilir veya okuma ve yazma hakları elinden alınabilir, yine de erişime sahip olacağım çünkü dosyayı açtığım sırada onu okuma ve/veya yazma haklarım vardı ve onunla çalışmaya başlamayı başardım, bu da bunu yapmaya devam etmem gerektiği anlamına geliyor.

Linux'ta libc kütüphanesi, çalışan her uygulama (işlem) için 3 numaralı 0,1,2 tanımlayıcı dosya açar. Daha fazla bilgiyi bağlantılarda bulabilirsiniz adam stdio и adam stdout

  • Dosya tanımlayıcı 0, STDIN olarak adlandırılır ve uygulama girişiyle ilişkilendirilir
  • Dosya tanımlayıcı 1'e STDOUT adı verilir ve uygulamalar tarafından yazdırma komutları gibi verilerin çıktısını almak için kullanılır
  • Dosya tanımlayıcı 2'ye STDERR adı verilir ve uygulamalar tarafından hata mesajlarının çıktısını almak için kullanılır.

Programınızda herhangi bir dosyayı okumak veya yazmak için açarsanız, büyük olasılıkla ilk ücretsiz kimliği alacaksınız ve bu 3 numara olacaktır.

PID'sini biliyorsanız herhangi bir işlem için dosya tanımlayıcıların listesi görüntülenebilir.

Örnek olarak bash konsolunu açalım ve sürecimizin PID’sine bakalım.

[user@localhost ]$ echo $$
15771

İkinci konsolda koşalım

[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

Bu makalenin amaçları doğrultusunda 255 numaralı dosya tanımlayıcıyı güvenle göz ardı edebilirsiniz; bu, bağlantılı kitaplık tarafından değil, bash'ın kendisi tarafından ihtiyaçları için açılmıştır.

Artık 3 tanımlayıcı dosyanın tümü sahte terminal cihazıyla ilişkilendirilmiştir /dev/puan, ancak yine de bunları değiştirebiliriz; örneğin, onları ikinci bir konsolda çalıştırabiliriz.

[user@localhost ]$ echo "hello world" > /proc/15771/fd/0

Ve ilk konsolda göreceğiz

[user@localhost ]$ hello world

Yönlendirme ve Yönlendirme

Bu 3 tanımlayıcı dosyayı bash dahil olmak üzere herhangi bir işlemde, örneğin iki işlemi birbirine bağlayan bir boru aracılığıyla kolayca geçersiz kılabilirsiniz, bkz.

[user@localhost ]$ cat /dev/zero | sleep 10000

Bu komutu kendiniz çalıştırabilirsiniz. strace -f ve içeride neler olduğuna bakın, ama size kısaca anlatacağım.

PID 15771'e sahip ebeveyn bash sürecimiz komutumuzu ayrıştırır ve tam olarak kaç tane komut çalıştırmak istediğimizi anlar, bizim durumumuzda bunlardan iki tanesi vardır: cat ve uyku. Bash, iki alt süreç oluşturması ve bunları tek bir kanalda birleştirmesi gerektiğini biliyor. Toplamda bash'ın 2 alt sürece ve bir kanala ihtiyacı olacak.

Bash, alt süreçleri oluşturmadan önce bir sistem çağrısı çalıştırır boru ve geçici kanal arabelleğindeki yeni dosya tanımlayıcılarını alır, ancak bu arabellek henüz iki alt işlemimizi birbirine bağlamaz.

Ana süreç için zaten bir kanal var gibi görünüyor, ancak henüz alt süreç yok:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

Daha sonra sistem çağrısını kullanarak clone bash iki alt süreç yaratır ve üç sürecimiz şöyle görünecektir:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
PID    command
9004  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21
PID    command
9005  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21

Klonlamanın, süreci tüm dosya tanımlayıcılarla birlikte klonladığını, dolayısıyla bunların ana süreçte ve alt süreçlerde aynı olacağını unutmayın. PID 15771'e sahip ana sürecin görevi alt süreçleri izlemektir, dolayısıyla yalnızca çocuklardan yanıt bekler.

Bu nedenle pipe'a ihtiyaç duymaz ve 3 ve 4 numaralı dosya tanımlayıcılarını kapatır.

PID 9004 ile ilk çocuk bash işleminde sistem çağrısı dup2, 1 numaralı STDOUT dosya tanımlayıcımızı boruya işaret eden bir dosya tanımlayıcıya dönüştürür, bizim durumumuzda bu 3 numaradır. Böylece, PID 9004'e sahip ilk alt işlemin STDOUT'a yazdığı her şey otomatik olarak boru arabelleğine düşecektir.

PID 9005'li ikinci alt süreçte bash, dosya tanımlayıcı STDIN numarası 2'ı değiştirmek için dup0'yi kullanır. Artık PID 9005'li ikinci bash'ımızın okuyacağı her şey kanaldan okunacaktır.

Bundan sonra 3 ve 4 numaralı dosya tanımlayıcıları da artık kullanılmadığından alt süreçlerde kapatılır.

Dosya tanımlayıcı 255'i kasıtlı olarak görmezden geliyorum; bash'ın kendisi tarafından dahili amaçlar için kullanılıyor ve ayrıca alt süreçlerde de kapatılacak.

Daha sonra PID 9004'e sahip ilk alt süreçte bash bir sistem çağrısı kullanmaya başlar exec komut satırında belirttiğimiz yürütülebilir dosya, bizim durumumuzda /usr/bin/cat'tir.

PID 9005'li ikinci alt süreçte bash, /usr/bin/sleep durumumuzda belirttiğimiz ikinci yürütülebilir dosyayı çalıştırır.

Exec sistem çağrısı, açık çağrının yapıldığı sırada O_CLOEXEC bayrağıyla açılmadıkça dosya tanıtıcılarını kapatmaz. Bizim durumumuzda, yürütülebilir dosyaları başlattıktan sonra mevcut tüm dosya tanımlayıcıları kaydedilecektir.

Konsolda kontrol edin:

[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
[user@localhost ]$ ls -lah /proc/9004/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
l-wx------ 1 user user 64 Oct  7 15:57 1 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lr-x------ 1 user user 64 Oct  7 15:57 3 -> /dev/zero
[user@localhost ]$ ls -lah /proc/9005/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lr-x------ 1 user user 64 Oct  7 15:57 0 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
[user@localhost ]$ ps -up 9004
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9004  0.0  0.0 107972   620 pts/21   S+   15:57   0:00 cat /dev/zero
[user@localhost ]$ ps -up 9005
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9005  0.0  0.0 107952   360 pts/21   S+   15:57   0:00 sleep 10000

Gördüğünüz gibi her iki işlemde de borumuzun benzersiz numarası aynıdır. Dolayısıyla aynı ebeveyne sahip iki farklı süreç arasında bir bağlantımız var.

Bash'ın kullandığı sistem çağrılarına aşina olmayanlar için, komutları strace aracılığıyla çalıştırmanızı ve dahili olarak neler olduğunu görmenizi şiddetle tavsiye ederim, örneğin:

strace -s 1024 -f bash -c "ls | grep hello"

Düşük disk alanı sorunumuza ve işlemi yeniden başlatmadan veri kaydetmeye çalışmamıza dönelim. Diske saniyede yaklaşık 1 megabayt veri yazacak küçük bir program yazalım. Üstelik herhangi bir nedenden dolayı diske veri yazamıyorsak, bunu görmezden gelip bir saniye sonra verileri yeniden yazmayı deneyeceğiz. Python kullandığım örnekte, başka herhangi bir programlama dilini kullanabilirsiniz.

[user@localhost ]$ cat openforwrite.py 
import datetime
import time

mystr="a"*1024*1024+"n"
with open("123.txt", "w") as f:
    while True:
        try:
            f.write(str(datetime.datetime.now()))
            f.write(mystr)
            f.flush()
            time.sleep(1)
        except:
            pass

Programı çalıştıralım ve dosya tanımlayıcılarına bakalım

[user@localhost ]$ python openforwrite.py &
[1] 3762
[user@localhost ]$ ps axuf | grep [o]penforwrite
user  3762  0.0  0.0 128600  5744 pts/22   S+   16:28   0:00  |   _ python openforwrite.py
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt

Gördüğünüz gibi 3 tane standart dosya tanımlayıcımız ve açtığımız bir tane daha var. Dosya boyutunu kontrol edelim:

[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 117M Oct  7 16:30 123.txt

Veriler yazılıyor, dosyadaki izinleri değiştirmeye çalışıyoruz:

[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 168M Oct  7 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 172M Oct  7 16:31 123.txt

Kullanıcımızın dosyaya yazma izni olmamasına rağmen verilerin hala yazıldığını görüyoruz. Bunu kaldırmayı deneyelim:

[user@localhost ]$ sudo rm 123.txt 
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory

Veriler nerede yazılıyor? Peki bunlar hiç yazılı mı? Kontrol ediyoruz:

[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt (deleted)

Evet, dosya tanımlayıcımız hala mevcut ve bu dosya tanımlayıcıya eski dosyamız gibi davranabilir, okuyabilir, temizleyebilir ve kopyalayabiliriz.

Dosya boyutuna bakalım:

[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5   19923457   2621522 /home/user/123.txt

Dosya boyutu 19923457. Dosyayı temizlemeye çalışalım:

[user@localhost ]$ truncate -s 0 /proc/31083/fd/3
[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5  136318390   2621522 /home/user/123.txt

Gördüğünüz gibi dosya boyutu giderek artıyor ve bagajımız çalışmıyor. Sistem çağrısı belgelerine bakalım açık. Bir dosyayı açarken O_APPEND bayrağını kullanırsak, her yazma işleminde işletim sistemi dosya boyutunu kontrol eder ve verileri dosyanın en sonuna yazar ve bunu atomik olarak yapar. Bu, birden fazla iş parçacığının veya işlemin aynı dosyaya yazmasına olanak tanır. Ancak kodumuzda bu bayrağı kullanmıyoruz. Trunk'tan sonra lsof'ta farklı bir dosya boyutunu ancak dosyayı ek yazmak için açarsak görebiliriz, yani bunun yerine kodumuzda

with open("123.txt", "w") as f:

koymak zorundayız

with open("123.txt", "a") as f:

“w” bayrağıyla kontrol etme

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

ve "a" bayrağıyla

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3

Halihazırda çalışan bir işlemin programlanması

Çoğu zaman programcılar, programları oluştururken ve test ederken hata ayıklayıcıları (örneğin GDB) veya uygulamada çeşitli düzeylerde günlük kaydı kullanırlar. Linux, halihazırda çalışan bir programı gerçekten yazma ve değiştirme yeteneği sağlar; örneğin, değişkenlerin değerlerini değiştirmek, bir kesme noktası ayarlamak vb.

Dosya yazmak için yeterli disk alanı olmamasıyla ilgili orijinal soruya dönersek, sorunu simüle etmeye çalışalım.

Ayrı bir disk olarak bağlayacağımız bölümümüz için bir dosya oluşturalım:

[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$

Bir dosya sistemi oluşturalım:

[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd is not a block special device.
Proceed anyway? (y,n) y
...
Writing superblocks and filesystem accounting information: done
[user@localhost ~]$

Dosya sistemini bağlayın:

[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] password for user: 
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  172K  7.9M   3% /mnt

Sahibimizle bir dizin oluşturuyoruz:

[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs

Dosyayı programımızda sadece yazmak için açalım:

with open("/mnt/logs/123.txt", "w") as f:

koşmak

[user@localhost ]$ python openforwrite.py 

Birkaç saniye bekliyoruz

[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

Yani, bu makalenin başında açıklanan sorunla karşı karşıyayız. Boş alan 0, %100 dolu.

Görevin şartlarına göre kaybedilmeyecek çok önemli verileri kaydetmeye çalıştığımızı hatırlıyoruz. Ve aynı zamanda süreci yeniden başlatmadan hizmeti düzeltmemiz gerekiyor.

Diyelim ki hala disk alanımız var, ancak farklı bir bölümde, örneğin /home'da.

Kodumuzu "anında yeniden programlamaya" çalışalım.

Tüm disk alanını tüketen sürecimizin PID'sine bakalım:

[user@localhost ~]$ ps axuf | grep [o]penfor
user 10078 27.2  0.0 128600  5744 pts/22   R+   11:06   0:02  |   _ python openforwrite.py

Sürece gdb aracılığıyla bağlanın

[user@localhost ~]$ gdb -p 10078
...
(gdb) 

Açık dosya tanımlayıcılarına bakalım:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt

Bizi ilgilendiren 3 numaralı dosya tanımlayıcı hakkındaki bilgilere bakıyoruz

(gdb) shell cat /proc/10078/fdinfo/3
pos:    8189952
flags:  0100001
mnt_id: 482

Python'un hangi sistem çağrısını yaptığını aklımızda tutarak (yukarıda strace'yi çalıştırdığımız ve açık çağrıyı bulduğumuz yere bakın), bir dosyayı açmak için kodumuzu işlerken, sürecimiz adına aynısını kendimiz de yaparız, ancak O_WRONLY|O_CREAT| O_TRUNC bitleri sayısal bir değerle değiştirilir. Bunu yapmak için çekirdek kaynaklarını açın, örneğin burada ve hangi bayrakların neden sorumlu olduğuna bakın

#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000

Tüm değerleri tek bir yerde birleştiriyoruz, 00001101 elde ediyoruz

Çağrımızı gdb'den yürütüyoruz

(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4

Böylece 4 numaralı yeni bir dosya tanımlayıcımız ve başka bir bölümde yeni bir açık dosyamız var, kontrol ediyoruz:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

Borulu örneği hatırlıyoruz - bash'ın dosya tanımlayıcılarını nasıl değiştirdiği ve dup2 sistem çağrısını zaten öğrenmiştik.

Bir dosya tanımlayıcıyı diğeriyle değiştirmeye çalışıyoruz

(gdb) call dup2(4,3)
$2 = 3

Kontrol ediyoruz:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

İhtiyacımız olmadığı için dosya tanımlayıcı 4'ü kapatıyoruz:

(gdb) call close (4)
$1 = 0

Ve gdb'den çıkın

(gdb) quit
A debugging session is active.

    Inferior 1 [process 10078] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 10078

Yeni dosyayı kontrol etmek:

[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M Oct  8 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M Oct  8 11:18 /home/user/123.txt

Gördüğünüz gibi veriler yeni bir dosyaya yazılıyor, eskisini kontrol edelim:

[user@localhost ~]$ ls -lah /mnt/logs/123.txt 
-rw-rw-r-- 1 user user 7.9M Oct  8 11:08 /mnt/logs/123.txt

Hiçbir veri kaybolmaz, uygulama çalışır, günlükler yeni bir konuma yazılır.

Görevi biraz karmaşıklaştıralım

Verilerin bizim için önemli olduğunu ancak hiçbir bölümde disk alanımız olmadığını ve diski bağlayamadığımızı düşünelim.

Yapabileceğimiz şey, verilerimizi bir yere, örneğin boruya yönlendirmek ve ardından verileri, örneğin netcat gibi bir program aracılığıyla borudan ağa yönlendirmektir.
mkfifo komutu ile isimli bir kanal oluşturabiliriz. Üzerinde boş alan olmasa bile dosya sisteminde sahte bir dosya oluşturacaktır.

Uygulamayı yeniden başlatın ve şunları kontrol edin:

[user@localhost ]$ python openforwrite.py 
[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 72.9  0.0 128600  5744 pts/22   R+   11:27   0:20  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

Disk alanı yok, ancak orada başarıyla adlandırılmış bir kanal oluşturduk:

[user@localhost ~]$ mkfifo /mnt/logs/megapipe
[user@localhost ~]$ ls -lah /mnt/logs/megapipe 
prw-rw-r-- 1 user user 0 Oct  8 11:28 /mnt/logs/megapipe

Şimdi bu kanala giren tüm verileri bir şekilde ağ üzerinden başka bir sunucuya sarmamız gerekiyor, aynı netcat bunun için uygundur.

Remote-server.example.com sunucusunda çalıştırıyoruz

[user@localhost ~]$ nc -l 7777 > 123.txt 

Sorunlu sunucumuzda ayrı bir terminalde başlatıyoruz

[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe 

Artık boruya giren tüm veriler otomatik olarak netcat'teki stdin'e gidecek ve bu da onu 7777 numaralı bağlantı noktasındaki ağa gönderecek.

Tek yapmamız gereken verilerimizi bu isimli kanala yazmaya başlamak.

Uygulamayı zaten çalıştırıyoruz:

[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 99.8  0.0 128600  5744 pts/22   R+   11:27 169:27  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt

Tüm işaretler arasında yalnızca O_WRONLY'ye ihtiyacımız var çünkü dosya zaten mevcut ve onu temizlememize gerek yok

[user@localhost ~]$ gdb -p 5946
...
(gdb) call open("/mnt/logs/megapipe", 00000001,0666)
$1 = 4
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call dup2(4,3)
$2 = 3
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call close(4)
$3 = 0
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
(gdb) quit
A debugging session is active.

    Inferior 1 [process 5946] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 5946

Remote-server.example.com uzak sunucusunu kontrol etme

[user@localhost ~]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 38M Oct  8 14:21 123.txt

Veriler geliyor, sorunlu sunucuyu kontrol ediyoruz

[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K Oct  8 11:28 .
drwxr-xr-x 4 root     root     1.0K Oct  8 10:55 ..
-rw-rw-r-- 1 user user 7.9M Oct  8 14:17 123.txt
prw-rw-r-- 1 user user    0 Oct  8 14:22 megapipe

Veriler kaydedilir, sorun çözülür.

Bu fırsatı Degiro'daki meslektaşlarıma merhaba demek için değerlendiriyorum.
Radyo-T podcast'lerini dinleyin.

Herkese iyi.

Ev ödevi olarak, aşağıdaki komutu çalıştırırsanız cat ve uyku süreç dosyası tanımlayıcılarında ne olacağını düşünmenizi öneririm:

[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000

Kaynak: habr.com

Yorum ekle