Inode-ovi i linkovi — kako sistem zapravo čuva fajl

Ime fajla nije fajl. To je jedna od onih rečenica koje zvuče kao filozofija dok ne naiđeš na situaciju u kojoj si obrisao ogroman log, a disk je i dalje pun. Ili dok ti dva različita imena ne pokažu potpuno isti sadržaj, pa se pitaš gde je greška.

Greške nema. Linux fajl i ime fajla drži razdvojeno, a spona između njih zove se inode. Kad razumeš tu podelu, hard i simbolički linkovi prestaju da budu magija, a nekoliko čestih administratorskih problema dobija očigledno rešenje.

Šta je inode

Kad se fajl upiše na disk, sistem napravi zapis koji sadrži sve o tom fajlu osim imena:

  • tip fajla i dozvole
  • vlasnika i grupu
  • veličinu
  • vremenske oznake
  • broj imena koja pokazuju na njega
  • adrese blokova na disku gde je sadržaj

Taj zapis je inode i ima svoj broj. Vidiš ga sa ls -i:

$ ls -li
1310721 -rw-r--r-- 1 alen alen 42 Aug 22 10:15 original.txt

Ceo zapis pokazuje komanda stat:

$ stat original.txt
  File: original.txt
  Size: 42          Blocks: 8          IO Block: 4096   regular file
Device: 8,2   Inode: 1310721     Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/ alen)   Gid: ( 1000/ alen)
Access: 2026-08-22 10:15:03 +0200
Modify: 2026-08-22 10:15:03 +0200
Change: 2026-08-22 10:15:03 +0200

Pogledaj spisak još jednom: imena nema nigde. Ime živi u direktorijumu.

Direktorijum je tabela

Direktorijum nije kutija u kojoj su fajlovi. To je fajl koji sadrži spisak parova ime → broj inode-a:

ime            inode
----------------------
.              1310720
..             1179649
original.txt   1310721
slika.png      1310730

Odatle sledi sve ostalo. Kad kucaš cat original.txt, sistem u direktorijumu potraži to ime, dobije broj 1310721, pročita inode i sa njega sazna gde su blokovi sa sadržajem.

Odatle sledi i nešto što početnike redovno iznenadi: za brisanje ili preimenovanje fajla nisu ti potrebne dozvole nad samim fajlom, nego nad direktorijumom u kome se nalazi. Menjaš tabelu, ne fajl. Zato možeš da obrišeš fajl koji ne smeš ni da otvoriš — o čemu ima više na stranici Pregled prava pristupa.

Hard link: drugo ime za isti inode

Komanda ln bez opcija dodaje novo ime u tabelu, koje pokazuje na već postojeći inode:

$ ln original.txt kopija.txt
$ ls -li
1310721 -rw-r--r-- 2 alen alen 42 Aug 22 10:15 kopija.txt
1310721 -rw-r--r-- 2 alen alen 42 Aug 22 10:15 original.txt

Dve stvari u ovom ispisu su bitne. Broj inode-a je isti — to nisu dva fajla, nego jedan fajl sa dva imena. A broj u drugoj koloni skočio je sa 1 na 2; to je broj linkova, odnosno koliko imena pokazuje na taj inode.

Pošto je fajl jedan, promena je odmah vidljiva „na oba mesta":

$ echo "nova linija" >> kopija.txt
$ cat original.txt
prvi red
nova linija

Isto važi i za dozvole i vlasništvo, jer i oni sede na inode-u:

$ chmod 600 kopija.txt
$ ls -li
1310721 -rw------- 2 alen alen 54 Aug 22 10:22 kopija.txt
1310721 -rw------- 2 alen alen 54 Aug 22 10:22 original.txt

Nema „originala" i „kopije". Imena su potpuno ravnopravna — sistem ne pamti koje je nastalo prvo.

Dva ograničenja

Hard link ne može preko granice fajl sistema, jer su brojevi inode-a jedinstveni samo unutar jednog fajl sistema:

$ ln original.txt /mnt/backup/original.txt
ln: failed to create hard link '/mnt/backup/original.txt': Invalid cross-device link

I ne može na direktorijum:

$ ln dokumenti dok2
ln: dokumenti: hard link not allowed for directory

Kad bi bilo dozvoljeno, stablo direktorijuma prestalo bi da bude stablo i lako bi se dobio ciklus iz koga find ne bi znao da izađe.

Usput: zašto direktorijumi imaju bar 2 linka

$ ls -ld dokumenti
drwxr-xr-x 4 alen alen 4096 Aug 22 10:30 dokumenti

Broj 4 nije broj fajlova unutra. Direktorijum ima jedan link od svog imena u roditelju, jedan od sopstvenog ., i po jedan od .. u svakom poddirektorijumu. Znači: 2 + broj poddirektorijuma. Ovde su to dva poddirektorijuma.

Zašto brisanje ne briše

Sad dolazi ono zbog čega cela priča ima praktičnu vrednost.

rm ne briše fajl. On uklanja ime iz direktorijuma i umanji broj linkova za jedan. Sistemski poziv iza toga i zove se unlink.

$ rm original.txt
$ ls -li
1310721 -rw------- 1 alen alen 54 Aug 22 10:22 kopija.txt

$ cat kopija.txt
prvi red
nova linija

Sadržaj je netaknut, jer je ostalo još jedno ime. Blokovi na disku oslobađaju se tek kada su ispunjena oba uslova:

  1. broj linkova je pao na nulu, i
  2. nijedan proces nema taj fajl otvoren

Drugi uslov je onaj koji ume da pojede popodne.

Disk je pun, a obrisao sam sve

Tipičan scenario — servis je popunio disk ogromnim logom:

$ df -h /var
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3        20G   20G     0 100% /var

$ sudo rm /var/log/aplikacija.log
$ df -h /var
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3        20G   20G     0 100% /var

Ni bajta nazad. Razlog je što servis i dalje drži fajl otvoren, pa sistem ne sme da oslobodi blokove. Takve fajlove izlistava lsof +L1 — „otvoreni, a imaju manje od jednog imena":

$ sudo lsof +L1
COMMAND    PID USER   FD   TYPE DEVICE   SIZE/OFF NLINK    NODE NAME
aplikacij 1842 root    7w   REG    8,3 8589934592     0 1441795 /var/log/aplikacija.log (deleted)

Oznaka (deleted) i NLINK 0 su potpis ovog problema. Rešenja su dva:

$ sudo systemctl restart aplikacija

ili, ako restart nije opcija, isprazni fajl kroz deskriptor koji proces i dalje drži otvorenim (PID i broj FD su iz lsof ispisa):

$ sudo truncate -s 0 /proc/1842/fd/7
$ df -h /var
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3        20G   12G  7,2G  63% /var

Pouka: log fajl se ne prazni komandom rm, nego sa truncate -s 0 fajl ili > fajl. Tako inode ostaje isti, proces nastavlja da piše u njega, a prostor se stvarno oslobodi.

Ista logika u drugom smeru

Obrnuto: ako neki program radi nad fajlom koji si u međuvremenu obrisao, on to neće ni primetiti. Zato tail -f ume da „zamrzne" — prati inode, a ne ime, pa kad logrotate zameni fajl novim, tvoj tail i dalje gleda stari, obrisani inode. Rešenje je tail -F, koji prati ime. Više o tome na stranici tail.

Simbolički link: putokaz, a ne ime

Simbolički link (skraćeno symlink) je sasvim drugačija stvar. To je poseban, mali fajl sa sopstvenim inode-om, čiji je sadržaj — tekst putanje ka nečemu drugom.

$ ln -s /var/www/sajt/index.html pocetna
$ ls -li
1310721 -rw------- 1 alen alen 54 Aug 22 10:22 kopija.txt
1310755 lrwxrwxrwx 1 alen alen 27 Aug 22 10:40 pocetna -> /var/www/sajt/index.html

Tip je l, inode je drugačiji, a veličina je 27 — tačno broj znakova u putanji koju link sadrži. Dozvole lrwxrwxrwx su uvek takve i ništa ne znače; važe dozvole fajla na koji se pokazuje.

Kuda link vodi, pokazuje readlink:

$ readlink pocetna
/var/www/sajt/index.html
$ readlink -f pocetna
/var/www/sajt/index.html

Za razliku od hard linka, symlink sme preko fajl sistema, sme na direktorijum, i sme da pokazuje na nešto što uopšte ne postoji:

$ ln -s /nema/ovoga.txt slomljen
$ ls -l slomljen
lrwxrwxrwx 1 alen alen 15 Aug 22 10:45 slomljen -> /nema/ovoga.txt
$ cat slomljen
cat: slomljen: No such file or directory

Sve slomljene linkove u stablu pronalaziš ovako:

$ find . -xtype l
./slomljen

Zamka: relativna putanja u symlinku

Ovo je najčešća greška sa simboličkim linkovima:

$ cd /home/alen
$ ln -s skripte/backup.sh /usr/local/bin/backup
$ backup
bash: /usr/local/bin/backup: No such file or directory

Link čuva tekst skripte/backup.sh doslovno, a taj tekst se razrešava u odnosu na direktorijum u kome link stoji — dakle /usr/local/bin/skripte/backup.sh, čega nema. Nije bitno odakle si kucao ln.

Zato: symlink na sistemsko mesto uvek pravi sa apsolutnom putanjom.

$ sudo ln -sf /home/alen/skripte/backup.sh /usr/local/bin/backup

Relativne putanje su korisne unutar jednog stabla koje se seli u komadu — tada linkovi ostaju ispravni i na novoj lokaciji. Podsetnik na to kako se putanje računaju ima u lekciji Apsolutne i relativne putanje.

Zamka: kosa crta na kraju

Ako trenutni pokazuje na direktorijum:

$ rm trenutni      # brise link, direktorijum ostaje
$ rm trenutni/     # rm: cannot remove 'trenutni/': Not a directory

Kosa crta na kraju znači „kroz link, u sam direktorijum". Zato ls trenutni prikazuje link, a ls trenutni/ njegov sadržaj. Ista razlika ume da napravi štetu sa rsync i cp -r.

Hard vs. simbolički — kad koji

  • Hard link — isti inode, ravnopravna imena, ne može preko fajl sistema ni na direktorijum, preživljava brisanje „originala“.
  • Symlink — sopstveni inode, sadrži putanju, može bilo gde, pukne ako meta nestane ili se premesti.

U praksi: symlink u devet od deset slučajeva, jer je vidljiv i jasan — odmah vidiš u ls -l šta na šta pokazuje. Hard linkovi su korak za sebe i najčešće ih srećeš u alatima za backup (rsync sa --link-dest), gde se nepromenjeni fajlovi hard-linkuju umesto da se kopiraju, pa deset snimaka zauzima prostor jednog.

Kad ponestane inode-ova

Broj inode-a se kod ext4 fiksira pri formatiranju. Moguće je da ih potrošiš, a da na disku ima mesta:

$ touch novi.txt
touch: cannot touch 'novi.txt': No space left on device

$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2        50G   18G   30G  38% /

$ df -i /
Filesystem      Inodes   IUsed IFree IUse% Mounted on
/dev/sda2      3276800 3276800     0  100% /

Poruka govori o prostoru, ali df -h kaže da ga ima. Kad ovo vidiš, odmah proveri df -i. Krivac su obično milioni sitnih fajlova — sesije, keš, neisporučena pošta. Gde ih ima najviše:

$ sudo find /var -xdev -type f | cut -d/ -f1-4 | sort | uniq -c | sort -rn | head
 892431 /var/lib/php/sessions
  41102 /var/spool/postfix/maildrop

Ovo je i lep primer spajanja komandi iz lekcije Piping i redirekcija. Napomena: du ti ovde ne pomaže, jer meri zauzeće u bajtovima, a problem je u broju fajlova.

Praktični trikovi sa brojem inode-a

Brisanje fajla čije ime je problem

$ ls -li
1310745 -rw-r--r-- 1 alen alen 0 Aug 22 11:02 -rf
$ find . -maxdepth 1 -inum 1310745 -delete

Radi za imena sa crticom, razmacima, navodnicima ili nevidljivim znakovima, gde bi normalno trebalo pažljivo štititi ime navodnicima (Džokeri i navodnici).

Pronalaženje svih imena jednog fajla

$ find /home -samefile kopija.txt
/home/alen/kopija.txt
/home/alen/arhiva/stara-kopija.txt

Provera da li su dva fajla zapravo isti fajl

$ ls -i a.txt b.txt
1310721 a.txt  1310721 b.txt

Isti broj — isti fajl. Ovo je pouzdanije od poređenja sadržaja komandom diff, koje samo kaže da su identični.

Jedna napomena o editorima

Mnogi editori, uključujući vim u podrazumevanoj postavci, ne pišu u postojeći fajl nego naprave novi i preimenuju ga preko starog. Rezultat:

$ ls -li kopija.txt
1310721 -rw-r--r-- 2 alen alen 54 Aug 22 10:22 kopija.txt

$ vim kopija.txt        # izmena i :wq

$ ls -li kopija.txt
1310902 -rw-r--r-- 1 alen alen 61 Aug 22 11:15 kopija.txt

Inode je drugačiji, broj linkova pao na 1 — hard link je pukao i drugo ime i dalje pokazuje na stari sadržaj. Symlink u istoj situaciji preživljava, jer prati ime. Još jedan razlog zašto su symlinkovi u svakodnevnom radu manje opasan izbor.

Kratka referenca

  • ls -li — broj inode-a i broj linkova
  • stat fajl — ceo inode zapis
  • ln meta ime — hard link
  • ln -s meta ime — simbolički link
  • ln -sf meta ime — zameni postojeći link
  • readlink -f link — kuda link zaista vodi
  • find . -xtype l — slomljeni linkovi
  • find . -samefile fajl — sva imena istog inode-a
  • df -i — potrošnja inode-ova
  • lsof +L1 — obrisani fajlovi koje procesi drže otvorene
  • truncate -s 0 fajl — pravilan način da se isprazni log

Vežba

$ mkdir ~/vezba-inode && cd ~/vezba-inode
$ echo "prvi red" > podaci.txt
$ ln podaci.txt hard.txt
$ ln -s podaci.txt soft.txt
$ ls -li

Pre nego što izvršiš, pokušaj da predvidiš odgovore:

  1. Koliki je broj linkova na podaci.txt i zašto soft.txt nije uticao na njega?
  2. Šta se dešava sa hard.txt, a šta sa soft.txt posle rm podaci.txt?
  3. Posle brisanja, napravi ponovo echo "drugi red" > podaci.txt. Da li soft.txt proradi? Da li hard.txt vidi novi sadržaj? Proveri brojeve inode-a i objasni sebi zašto.

Treći zadatak je cela lekcija u tri komande.


Osnovne komande za linkove i njihove opcije su na stranici ln.

Cela lekcija je deo vodiča Linux osnove.

Comments

Popular posts from this blog

Početak u Linuxu — šta je i zašto se uči

Konverzija tipova podataka u Pythonu

groupadd