Как восстановить удаленные файлы в Linux пока они не исчезли навсегда?

Вы удалили файл журнала, но запущенный процесс по-прежнему записывает в него, ls ничего не отображает, однако данные по-прежнему находятся там, и вы можете восстановить их до того, как процесс завершится.

Linux удаляет файлы так, но на самом деле команда rm удаляет запись в каталоге и уменьшает счетчик ссылок на файл, и если какой-либо процесс все еще имеет этот файл открытым, ядро сохраняет базовый инод до тех пор, пока не будет закрыт последний файловый дескриптор, указывающий на него.

Это окно между rm и завершением процесса — ваше окно восстановления, и на рабочей системе оно часто достаточно продолжительное, чтобы спасти вас.

Как на самом деле работает удаление файлов в Linux

Когда вы запускаете rm на файле, ядро удаляет его из дерева каталогов, поэтому ls перестает его показывать, но оно не освобождает блоки диска сразу, потому что inode все еще имеет счетчик ссылок больше нуля, пока какой-либо процесс держит открытый файловый дескриптор на него.

В тот момент, когда последний процесс закрывает этот дескриптор или завершается, счетчик ссылок падает до нуля, и ядро помечает эти блоки как свободные — именно тогда данные действительно исчезают.

Поэтому, если ваш веб-сервер, база данных или Сервер логов активно записывает в файл, который вы только что удалили, данные на диске остаются нетронутыми и доступны через специальный путь, который ядро предоставляет в /proc.

Найдите процесс, удерживающий файл в открытом состоянии

Вам понадобится команда lsof, которая означает list open files (список открытых файлов) и показывает все файловые дескрипторы, удерживаемые в данный момент запущенными процессами.

Если она не установлена, сначала установите ее.

sudo apt install lsof         [On Debian, Ubuntu and Mint]
sudo dnf install lsof         [On RHEL/CentOS/Fedora and Rocky/AlmaLinux]

Префикс sudo запускает команду с правами root, что здесь необходимо, поскольку файловые дескрипторы, принадлежащие другим процессам, не видны пользователям без привилегий.

Теперь найдите удаленный файл по имени или по строке «deleted» в выводе:

sudo lsof | grep deleted

Вывод:

nginx     1423  www-data   4w   REG  253,1  204800  131074 /var/log/nginx/access.log (deleted)
rsyslogd  1201      root   7w   REG  253,1  819200  131075 /var/log/syslog (deleted)

Вас интересуют второй столбец (PID), четвертый столбец (номер файлового дескриптора, здесь 4w и 7w) и последний столбец, который подтверждает, что файл помечен (deleted).

Если у удаленного файла нет слова deleted, запустите вместо этого lsof +L1, которая явно перечисляет все файлы с количеством ссылок меньше 1.

sudo lsof +L1

Вывод:

COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF NLINKS     NODE NAME
nginx    1423 www-data    4w   REG  253,1   204800      0   131074 /var/log/nginx/access.log (deleted)

Столбец NLINKS с значением 0 подтверждает, что запись в каталоге исчезла, но ядро по-прежнему хранит данные.

Восстановление файла через /proc/fd

Ядро предоставляет доступ к каждому открытому файловому дескриптору для каждого процесса в /proc/<PID>/fd/, где каждый дескриптор отображается в виде символьной ссылки, указывающей на исходный путь к файлу, даже после удаления.

Из приведенного выше вывода lsof видно, что процесс nginx имеет PID 1423 и файловый дескриптор 4, поэтому путь к сохранившимся данным — /proc/1423/fd/4.

Скопируйте их с помощью команды cp:

sudo cp /proc/1423/fd/4 /var/log/nginx/access.log.recovered

Если вывода нет, это означает успех.

Убедитесь, что восстановленный файл содержит данные:

ls -lh /var/log/nginx/access.log.recovered

Вывод:

-rw-r--r-- 1 root root 200K May  6 03:14 /var/log/nginx/access.log.recovered

Если вы видите размер файла, соответствующий ожидаемому, данные не повреждены. Теперь вы можете переместить его обратно в исходный путь или передать любому инструменту, которому он нужен.

Если запущенный процесс по-прежнему записывает в удаленный дескриптор, он будет продолжать записывать в /proc/<PID>/fd/4, но ничего не попадёт в восстановленный файл, который вы только что скопировали, поскольку это моментальный снимок на момент копирования.

Поэтому, если этот процесс — записывает в журнал, который вам нужен, вам также следует перезапустить его после восстановления файла, чтобы он снова открыл нужный связанный файл.

Если при доступе к /proc/<PID>/fd/4 вы видите Permission denied, либо запустите команду от имени root с sudo, либо проверьте, существует ли PID, с помощью:

ps aux | grep PID

Быстрое восстановление удаленных файлов с помощью скрипта оболочки

Когда вы в полночь смотрите на сломанную ранее рабочую систему, ввод нескольких команд подряд чреват ошибками, поэтому вот небольшая функция оболочки, которая объединяет весь поиск и копирование в один шаг.

Укажите ей имя удаленного файла, а она позаботится об остальном.

recover_deleted() {
  local filename="$1"
  local output="${2:-/tmp/recovered_file}"
  local result
  result=$(sudo lsof +L1 2>/dev/null | grep "$filename")
  if [[ -z "$result" ]]; then
    echo "No process holds $filename open. Data may already be gone."
    return 1
  fi
  local pid fd
  pid=$(echo "$result" | awk 'NR==1{print $2}')
  fd=$(echo "$result" | awk 'NR==1{print $4}' | tr -d 'rwu')
  echo "Found: PID=$pid FD=$fd"
  sudo cp /proc/"$pid"/fd/"$fd" "$output" && echo "Recovered to $output"
}

Вставьте это в свой ~/.bashrc или общий файл системного администратора и запустите его.

Затем вызовите его так:

recover_deleted /var/log/nginx/access.log /var/log/nginx/access.log.recovered

Вывод:

Found: PID=1423 FD=4
Recovered to /var/log/nginx/access.log.recovered

awk NR==1 выбирает первый совпадающий результат, если несколько процессов открыли один и тот же файл, а tr -d 'rwu' удаляет суффикс чтения/записи/блокировки из поля FD, так что у вас остается чистое целое число для пути /proc

Совет: Если у вас есть несколько процессов, удерживающих один и тот же удаленный файл в открытом состоянии, восстанавливайте данные из того, у которого размер файла наибольший, так как в нем, скорее всего, содержатся наиболее полные данные. Проверьте размеры с помощью следующей команды для каждого кандидата.

sudo ls -lh /proc//fd/

Что делать, если процесс уже закрыл файл?

Как только все процессы, у которых файл был открыт, завершаются или закрывают дескриптор, ядро освобождает блоки диска, и данные исчезают также из /proc.

В этот момент вы попадаете в область аппаратного восстановления, используя такие инструменты, как extundelete для файловых систем ext4 или testdisk и photorec для более широкой поддержки файловых систем, но эти инструменты восстанавливают данные из необработанных блоков диска и не дают гарантии успеха после интенсивной записи.

Метод /proc быстр и надежен именно потому, что ядро возвращает вам «живые» данные, в то время как инструменты для криминалистической экспертизы собирают их из фрагментов.

Предупреждение: Никогда не запускайте extundelete или любой другой инструмент восстановления на смонтированной файловой системе с доступом для чтения и записи. Сначала размонтируйте раздел или загрузитесь с Live-USB, иначе вы рискуете, что файловая система перезапишет именно те блоки, которые вы пытаетесь восстановить.

Предотвращение случайного удаления с помощью жестких ссылок или привязок

Если вы управляете системой, в которой процесс записывает журналы в один путь, и вам нужно, чтобы эти журналы сохранились даже при случайном rm, самым чистым решением будет жесткая ссылка, которая сохраняет дополнительную запись в каталоге, указывающую на тот же инод, так что количество ссылок никогда не упадет до нуля из-за одного rm.

ln /var/log/nginx/access.log /var/log/nginx/access.log.hardlink

Убедитесь, что обе записи указывают на один и тот же инод:

ls -li /var/log/nginx/access.log /var/log/nginx/access.log.hardlink

Вывод:

131074 -rw-r--r-- 2 www-data www-data 204800 May  6 03:14 /var/log/nginx/access.log
131074 -rw-r--r-- 2 www-data www-data 204800 May  6 03:14 /var/log/nginx/access.log.hardlink

Обе записи показывают один и тот же номер инода 131074 и количество ссылок 2, что означает, что rm /var/log/nginx/access.log снизит счетчик до 1 и оставит данные полностью нетронутыми по пути жесткой ссылки.

Жесткие ссылки работают только в пределах одной файловой системы, поэтому, если вам нужна защита между файловыми системами, монтирование с помощью команды mount --bind дает аналогичный эффект.

Заключение

Путь /proc/<PID>/fd/ — это один из тех механизмов Linux, который при первом использовании кажется «чит-кодом», и как только вы спасете рабочую среду с его помощью, вы никогда больше не будете смотреть на rm прежним взглядом.

Теперь вы знаете, как найти нужный PID и дескриптор файла с помощью lsof +L1, скопировать актуальные данные до завершения процесса и обернуть всё это в функцию оболочки, чтобы это была одна команда, когда вы работаете на адреналине в 3 часа ночи.

Прямо сейчас откройте Терминал в вашей Linux-системе и попробуйте это сами: создайте тестовый файл, откройте его с помощью tail -f в одном терминале, удалите его с помощью rm в другом, затем выполните run lsof +L1 | grep test и найдите активный дескриптор.

Скопируйте его с помощью следующей команды и убедитесь, что содержимое соответствует тому, что вы поместили в исходный файл. Запустив это один раз в некритичной среде, вы обеспечите себе уверенность: когда вам понадобится это в реальности, ваши руки уже будут знать, что делать.

Зарубин Иван Эксперт по Linux и Windows

Парашютист со стажем. Много читаю и слушаю подкасты. Люблю посиделки у костра, песни под гитару и приближающиеся дедлайны. Люблю путешествовать.

Похожие статьи

Комментарии (0)