DTGaraGe | Автомобили, Офроуд, Заваряване, Linux, AI

Регистрирайте безплатен акаунт днес, за да станете член! След като влезете, ще можете да участвате в този сайт, като добавяте свои собствени теми и публикации, както и да се свързвате с други членове чрез вашата лична пощенска кутия!

linux rm -rf не унищожава това, което мислиш

rm -rf не унищожава това, което мислиш


Тайните следи след rm -rf.png


Какво реално остава след изтриването, защо мястото понякога не се освобождава и как файл без име може още да е жив


Всеки, който е работил достатъчно дълго с Linux, рано или късно стига до един от онези моменти, в които системата изглежда сякаш нарушава собствената си логика. Изтриваш файл. ls го няма. Пускаш df и дисковото пространство не се е върнало. Или махаш файл, а след минута той пак е на същото място. В друг случай човек е убеден, че rm -rf е унищожил чувствителни данни, а всъщност е премахнал само текущия път до тях.

Проблемът не е в rm. Проблемът е в модела, с който човек си представя файла.

За повечето хора файлът е име: /var/log/application.log. За Unix това име е само един от начините да се стигне до файловия обект. И ако не разделим pathname, directory entry, inode, open file descriptor и физическото съдържание върху носителя, думата "изтрит" започва да означава пет различни неща едновременно.

Тази тема е за разглобяването точно на това объркване.




Името е адресът, не къщата


Едно изречение държи цялата тема:

Името на файла не е самият файлов обект.

При типична Unix/Linux файлова система опростеният модел изглежда така:


Code:
application.log
|
v
directory entry
|
v
inode / filesystem object
|
+---- owner
+---- permissions
+---- timestamps
+---- size
+---- link count
|
+----> информация за разположението на съдържанието

Това е учебна схема, не физически чертеж на всяка съществуваща файлова система. Съвременните файлови системи могат да използват extents, Copy-on-Write дървета, checksums, snapshots и различни нива на индирекция. Но идеята остава: името в директорията е вход към обекта, не самият обект.

Най-близката аналогия е адресът на къща. Ако махнеш табелата с адреса, не следва автоматично, че сградата се е изпарила. Просто един конкретен начин да стигнеш до нея вече не съществува.

Оттук следват три отделни въпроса, които обикновено се задават като един: съществува ли pathname, съществува ли файловият обект и съществуват ли старите физически данни някъде в storage слоя.

Ако ги смесиш, Linux изглежда противоречив. Ако ги разделиш, поведението му става почти досадно логично.




Какво всъщност прави rm


При обикновен файл rm в крайна сметка иска от операционната система да премахне името от файловия namespace чрез unlink семантика. Ако това е последното име към обекта, link count пада до нула.

Но дори тогава обектът не е задължително готов за незабавно освобождаване, защото може да съществуват други активни референции. Най-познатият случай е процес, който вече е отворил файла и държи file descriptor. Друг случай е memory mapping.

Затова по-точният модел не е:


Code:
rm -> файлът изчезва

а:

Code:
премахва се име
|
v
намалява link count
|
v
проверява се дали обектът още има активни references
|
v
ако няма -> ресурсите могат да бъдат освободени

При директориите механизмът е различен и rm -r извършва рекурсивна работа по цялото дърво. Тоест rm -rf не е специална ядрена команда "унищожи всичко". Това е потребителски инструмент, който извършва поредица от файлови операции според структурата, която му подадеш.



Изтрих 20 GB лог, но мястото не се върна


Това е класическият production случай. Имаш 20 GB лог, махаш го, ls го няма, а df -h показва почти същото свободно място както преди.

Най-честата причина е, че процесът, който е писал в този лог, все още го държи отворен. След като файлът е отворен, процесът има file descriptor към съответния open file object и не е длъжен при всяко записване отново да търси pathname-а в директорията.

Опростено:


Code:
application
|
v
file descriptor 7
|
v
open file object
|
v
filesystem object

Directory entry вече може да е изчезнал, но процесът още има валидна референция.

Това е моментът, в който разговорното "файлът го няма" и техническото "файловият обект е освободен" се разделят.


В Unix един файл може да изгуби името си, без веднага да изгуби живота си.



lsof +L1: кой държи изтритото отворено


Първата практическа проверка е:

Code:
lsof +L1

Примерен резултат:

Code:
COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NLINK NODE     NAME
nginx     1842  root   5w   REG  8,1      20G      0   393221   /var/log/nginx/access.log (deleted)

Две полета са важни. NLINK = 0 означава, че вече няма directory link към този файлов обект. FD = 5w означава, че процесът държи file descriptor 5, отворен за запис.

Именно затова блоковете още се отчитат като използвани.

Това не е дефект на rm. Не е кеш, който някой ден ще се оправи. Не е файловата система, която е забравила да освободи диска. Процесът още използва обекта.




/proc/<PID>/fd: виждаш файла след като името му е умряло


Linux expose-ва file descriptor-ите на процесите през /proc/<PID>/fd/. При горния пример:

Code:
ls -l /proc/1842/fd/

може да покаже:

Code:
5 -> /var/log/nginx/access.log (deleted)

Pathname вече няма, но kernel-ът знае към кой отворен файлов обект сочи descriptor 5. При обикновен regular file и подходящи права понякога можем дори да копираме съдържанието през него:

Code:
cp /proc/1842/fd/5 recovered.log

Това е полезен recovery трик с точна уговорка: не е универсален метод за възстановяване на изтрити файлове. Резултатът зависи от типа на файловия обект, правата, състоянието на процеса и начина, по който descriptor-ът е отворен.

Но ако важен regular file е unlink-нат по грешка и процес все още го държи, /proc може да се окаже последната отворена врата към съдържанието му.




Как да си върнеш мястото, без да рестартираш половината сървър


Диагностиката е хубаво нещо, но в три през нощта въпросът е друг: как да се освободи дисковото пространство сега. Три подхода, подредени от най-безопасния към най-грубия.

Първо: кажи на услугата да преотвори логовете си. Това е правилният начин, защото процесът сам затваря стария descriptor и създава нов файл:


Code:
nginx -s reopen
systemctl reload apache2
systemctl kill -s HUP rsyslog

Старият файлов обект остава без референции и файловата система освобождава блоковете.

Второ: нулирай съдържанието през самия descriptor. Ако процесът няма механизъм за reopen:


Code:
truncate -s 0 /proc/1842/fd/5

Мястото се връща веднага, а процесът продължава да работи, без да забележи нищо.

Тук има уговорка, която си струва да се знае предварително. Ако файлът е отворен с O_APPEND, всичко е чисто. Ако не е, процесът пази собствен offset и следващият запис ще се случи на старата позиция, при което файлът става sparse и ls -l отново показва огромен размер. Заетите блокове остават малко, но гледката е объркваща.

Трето: рестарт на процеса. Работи винаги, но е най-скъпото решение и точно затова е последно.

И една дума за причината. Ако това се повтаря периодично, проблемът обикновено не е в rm, а в logrotate конфигурация, при която услугата не получава сигнал да преотвори файловете си. В такива случаи copytruncate е компромисът, който спасява ситуацията, с цената на малък прозорец, в който могат да се загубят редове.




Hard links: две имена, един файлов обект


Hard links са най-чистият начин да се види защо pathname и файл не са едно и също:

Code:
echo "DTGarage" > file1
ln file1 file2
ls -li file1 file2

Резултат:

Code:
527881 -rw-r--r-- 2 toni users 9 file1
527881 -rw-r--r-- 2 toni users 9 file2

И двете имена сочат към един и същ inode. Сега:

Code:
rm file1
cat file2
DTGarage

Нищо не е възкръснало и нищо не е възстановено. Премахнато е едното име, второто продължава да сочи към същия файлов обект.

Ако link count е по-голям от единица, а не знаеш къде е другото име, търсиш по inode номер:


Code:
find /mount/point -xdev -inum 527881

Търсенето е в рамките на една файлова система, защото inode номерата не са уникални между отделните filesystems. Точно затова -xdev тук не е излишна предпазливост.

Метафората е проста: имаш две карти към едно и също място. Изгарянето на едната карта не премахва терена.




mmap: референция може да остане и без отворен descriptor


Файл може да бъде memory-mapped чрез mmap(). След успешното създаване на mapping-а първоначалният file descriptor може да бъде затворен, без mapping-ът да изчезне.

Затова твърдението:


Code:
няма отворени file descriptors
=======================

файлът задължително е освободен

не е достатъчно точно. По-добрият модел е:

Code:
няма directory links
+
няма активни references
=======================

файловият обект може да бъде освободен

При обикновен огромен лог файл най-често ще срещнеш open descriptor, не сложен memory mapping. Но ако ще обясняваме механизма сериозно, тази уговорка трябва да я има.

Точността не е да сложиш повече термини. Точността е да не превърнеш най-честия случай в универсален закон.




df срещу du: двама свидетели, два различни въпроса


Друг класически симптом:

Code:
du:  40 GB използвани
df:  90 GB използвани

На пръв поглед единият инструмент трябва да греши. Всъщност двата отговарят на различни въпроси. du обхожда файловата йерархия и смята файловете, които може да достигне през directory tree. df пита файловата система за нейното собствено счетоводство на блоковете.

При голям unlink-нат файл, който още се държи от процес:


Code:
du -> pathname вече липсва от дървото
df -> блоковете още не са освободени

Точно затова при съществено разминаване една от първите проверки е lsof +L1.

Но тук е важно да не превръщаме полезния симптом в диагноза. Разлика между df и du не означава автоматично open-deleted file. Други причини могат да бъдат snapshots, reserved blocks, различни mount points, filesystem metadata, bind mounts, storage abstraction и network filesystems.

Добрата диагностика не казва "виждал съм това преди". Тя казва "това е първата хипотеза, която ще проверя".




Snapshots: текущото състояние не е цялата история


Тук думата "изтрит" става още по-опасна. След:

Code:
rm -rf important/

директорията вече не съществува в текущия filesystem namespace. Но ако системата използва snapshot технология - Btrfs, ZFS, LVM, storage-level или VM snapshots, backup системи - старото състояние може да продължи да пази същите данни.

Затова изречението "файлът го няма" не е технически завършено. Правилният въпрос е:

Няма го на кой слой?

Може да го няма в текущата директория, но да го има в snapshot. Може да го няма във filesystem-а, но да съществува в backup. Може да е премахнат логически, но storage устройството още да не е презаписало физическите клетки.

Изтриването на настоящето не е автоматично изтриване на историята.




rm -rf не е secure erase


Това е митът, който си струва да се счупи внимателно.

При rm -rf directory/ инструментът рекурсивно обхожда съдържанието и премахва съответните entries и директории според правилата на файловата система:


Code:
directory/
|
+-- file1     -> remove
|
+-- file2     -> remove
|
+-- subdir/
    |
    +-- file3 -> remove

-r означава рекурсивна обработка. -f означава force според поведението на rm. Тези флагове правят командата по-опасна заради мащаба и липсата на част от интерактивните проверки. Но нито един от двата не превръща инструмента в механизъм за физическа санитизация на storage.

Тоест rm -rf secret/ не означава:


Code:
физически презапиши всеки байт,
който някога е принадлежал на тези файлове

Тук трябва да държим разделени три състояния:

  1. Pathname вече не съществува.
  2. Файловата система вече може да счита ресурсите за свободни или освобождаеми.
  3. Старото физическо съдържание вече не съществува или не може да бъде възстановено от носителя.

Това не са синоними. И точно между тях живее по-голямата част от объркването около "сигурното изтриване".

HDD: unlink не означава overwrite


При класически магнитен диск unlink операцията сама по себе си не е команда за презаписване на секторите. Файловата система може да маркира съответното пространство като свободно за бъдеща употреба, докато старото физическо съдържание остане там до последващо презаписване:

Code:
unlink != physical overwrite

Това не означава, че recovery винаги ще е възможно. Означава само, че логическото освобождаване на файлово пространство и физическото унищожаване на старите магнитни данни са различни операции.

SSD, TRIM и контролерът между теб и NAND


При SSD картината е по-сложна. Между логическия block address, който операционната система вижда, и реалната NAND клетка стои контролер с Flash Translation Layer, а към това се добавят wear leveling, garbage collection, over-provisioning и TRIM/discard.

Операционната система може да каже на устройството, че дадени logical blocks вече не съдържат полезни данни. Оттам нататък физическото управление на NAND е работа на контролера и неговия firmware.

Затова и двете крайни твърдения са лоши:


Code:
TRIM значи, че старите данни физически изчезват веднага.

или

След rm данните със сигурност още седят върху NAND.

И двете се опитват да опишат физическо поведение, което потребителската операционна система не контролира толкова директно.

Практическият резултат е, че след TRIM recovery на SSD често става много по-трудно или невъзможно, но причината не е, че rm внезапно се е превърнал в shredder.




shred: полезен инструмент, но не универсална присъда


Командата shred secret.txt е създадена с идеята съдържанието да бъде презаписано. Проблемът е, че тази стратегия предполага нещо важно: логическото презаписване трябва действително да стигне до същото физическо място, където се е намирало предишното съдържание.

Това предположение може да бъде нарушено от SSD wear leveling, Copy-on-Write файлови системи, snapshots, RAID и други storage layers, виртуални block devices и remote storage.

Затова shred не е универсален бутон "унищожи завинаги". За чувствителни данни по-зрелият модел е да се мисли за криптиране още от момента на записване, crypto erase, sanitize възможностите на устройството и политиката за целия носител.

Понякога най-добрият начин да унищожиш една тайна е тя никога да не е лежала некриптирана върху диска.




Изтрих го, а след секунда пак беше там


Това е друг клас проблем и не трябва да се смесва с open-deleted файловете.

Code:
rm /run/application.pid
ls /run/application.pid

Файлът отново съществува. Първата реакция често е "не се е изтрил". Но много по-вероятният сценарий е:

Code:
rm го е премахнал успешно
|
v
процес или услуга го е създала отново

Типични кандидати са systemd services и systemd-tmpfiles, container workloads, cron jobs, watchdog процеси, logrotate и application supervisors.

От гледна точка на човек, който гледа само ls, двата сценария изглеждат еднакво. От гледна точка на системната диагностика са коренно различни. Едното е неуспешно премахване. Другото е успешно премахване, последвано от ново създаване.

За базово следене на директория:


Code:
inotifywait -m /path/to/directory

Виждаш събития от типа CREATE, MODIFY, DELETE, MOVED_TO. При по-сериозен случай вече идва Linux auditing инфраструктурата и въпросът не е "защо файлът пак е тук", а "кой процес го създаде, кога и под кой потребител".

Това е разликата между наблюдение и диагностика.




Operation not permitted: когато unlink изобщо не се случва


Трети различен сценарий. rm file връща:

Code:
Operation not permitted

Кандидатите са няколко: read-only mount, права върху родителската директория, sticky bit, security module като SELinux или AppArmor, ограничени capabilities в container и immutable attribute.

Последният се проверява с lsattr:


Code:
lsattr file
----i--------- file

и при съответните привилегии се маха с chattr -i. Не всички файлови системи поддържат всички attributes, така че този флаг не е универсално обяснение.

Най-важното: този случай няма нищо общо с open-deleted file. Тук unlink операцията не е станала. Файлът не е "изтрит, но жив". Той просто не е бил премахнат.




GNU rm има предпазители - но не може да чете намерения


Също толкова неточно е да се разказва митът, че всяко rm -rf / на модерна GNU/Linux система непременно ще започне безусловно да унищожава root filesystem-а. GNU rm има защита --preserve-root, която по подразбиране блокира някои очевидно катастрофални recursive операции върху root.

Само че тази защита гледа аргумента, който ѝ е подаден. А shell-ът разгъва wildcards преди rm изобщо да бъде стартиран. Затова:


Code:
rm -rf /*

никога не стига до rm като "/". До програмата пристига готов списък от /bin, /etc, /home и всичко останало, като напълно валидни отделни пътища. Същото важи за /srv/project/, /home/toni/data/ или /mnt/production/.

Ако си посочил грешния път и имаш права да го премахнеш, системата няма как да разбере, че ръката ти е изпреварила мозъка.

Linux проверява аргументи и права. Не проверява намерения.

Това е причината rm -rf да заслужава уважение. Не защото командата е зла, а защото е безразлично точна.




Полигонът: направи си файл-призрак


Тази част е безопасна за упражнение в /tmp. В първи terminal:

Code:
echo "DTGarage forensic test" > /tmp/ghost.txt
tail -f /tmp/ghost.txt

tail държи файла отворен. Във втори terminal:

Code:
rm /tmp/ghost.txt
ls -l /tmp/ghost.txt
No such file or directory

После:

Code:
lsof +L1
tail ... /tmp/ghost.txt (deleted)

Това е целият урок в един експеримент. Pathname вече го няма. Процесът обаче държи open file reference.

Сега спри tail с Ctrl+C. Descriptor-ът се затваря и ако няма други активни референции, файловата система вече може да освободи файловия обект и свързаните с него ресурси.

Няма магия. Има жизнен цикъл.




Практическа диагностика


Когато след rm нещо не изглежда логично, не започвай с предположение. Раздели проблема по симптом.

  • Изтрих голям файл, но дисковото пространство не се освободи: провери df -h и lsof +L1.
  • Файлът е с (deleted), но процесът работи: разгледай /proc/<PID>/fd/.
  • Трябва ти мястото веднага: reopen през самата услуга, truncate -s 0 /proc/<PID>/fd/<FD> или, в краен случай, рестарт.
  • Искаш да спасиш съдържание от unlink-нат regular file: провери дали descriptor-ът в /proc/<PID>/fd/<FD> все още дава достъп.
  • Файлът постоянно се появява отново: търси кой го създава, вместо да обвиняваш rm.
  • Получаваш Operation not permitted: провери mount state, права, security module и при подходяща файлова система lsattr.
  • Има друго име към същия обект: ls -li и find /mount/point -xdev -inum <номер>.
  • df и du се разминават: open-deleted files са една хипотеза, не единствената.
  • Става дума за чувствителни данни: не използвай rm като синоним на secure erase.



Честната част: какво не доказва тази тема


Тук е важно да не направим обратната грешка и да превърнем нюанса в нов мит.

Фактът, че rm не е secure erase, не означава, че всеки изтрит файл може да бъде възстановен.

Recovery зависи от файловата система, последващите записи, TRIM/discard, storage контролера, snapshots, криптирането и още куп конкретни фактори.

Също така lsof +L1 не обяснява всяко разминаване между df и du. /proc/<PID>/fd не е универсален recovery механизъм. А наличието на snapshot не означава автоматично, че той съдържа точно файла, който търсиш.

Целта не е да заменим едно опростяване с друго. Целта е да знаем на кой слой се намираме и какво точно доказва наблюдението ни.




Последният ред


Слабият модел е име = файл. По-полезният разделя слоевете и помни, че процесът стига до същия обект и по друг път:

Code:
PATHNAME -> DIRECTORY ENTRY -> FILESYSTEM OBJECT
PROCESS  -> FILE DESCRIPTOR  -> FILESYSTEM OBJECT
PROCESS  -> MEMORY MAPPING   -> FILESYSTEM OBJECT

Затова изчезналият pathname не означава задължително изчезнал filesystem object. А освободеният filesystem object не означава изтрита физическа история на всеки storage слой.

След rm един pathname може да е изчезнал, докато процес продължава да използва обекта. Файловата система може да е освободила името, докато snapshot пази стара версия. SSD може да е получил discard информация, без операционната система да има директна видимост върху съдбата на всяка NAND клетка.

Затова най-добрият въпрос не е:

"Изтрит ли е файлът?"
А:
"На кой слой вече не съществува?"

Тази разлика изглежда дребна, докато не започнеш да администрираш истински системи. После става основна.


Linux рядко нарушава собствената си логика. По-често ние гледаме един слой и правим заключение за всички останали.


Ключови думи: linux rm, rm -rf, unlink, inode, hard link, open deleted file, lsof +L1, /proc fd, truncate, df du difference, linux filesystem, ext4, xfs, btrfs, linux recovery, SSD TRIM, secure erase, shred, snapshots, logrotate, linux administration

Източници: Linux man-pages project - unlink(2), mmap(2), open(2), proc_pid_fd(5); lsof(8); logrotate(8); GNU Coreutils документация за rm, shred и truncate; документация на Linux kernel и файловите системи за discard/TRIM; документация на съответните файлови системи за snapshots и Copy-on-Write. Командите и примерите са Linux/Unix ориентирани; конкретното поведение на storage слоя зависи от файловата система, ядрото, носителя и контролера.


toni@dtgarage:~$ whoami
Тони Ангелчовски | Ексклузивно за DTGaraGe
toni@dtgarage:~$ cat LICENSE
🔒 Копирането и препубликуването без разрешение не е позволено.
toni@dtgarage:~$ ./support.sh
☕ Подкрепи DTGaraGe и независимото техническо съдържание
toni@dtgarage:~$ █
 
Last edited:
Top Bottom
🛡️ Този сайт използва аналитични инструменти за подобряване на потребителското изживяване. Никакви лични данни не се събират. С продължаването си в Потока приемаш тази философия на прозрачност и уважение.