Найти и удалить большие файлы, которые открыты, но были удалены

120

Как найти большие файлы, которые были удалены, но все еще открыты в приложении? Как удалить такой файл, даже если процесс открыт?

Ситуация такова, что мы запускаем процесс, который заполняет файл журнала с невероятной скоростью. Я знаю причину, и я могу это исправить. До тех пор я хотел бы rm или очистить файл журнала без остановки процесса.

Простое выполнение rm output.logудаляет только ссылки на файл, но оно продолжает занимать место на диске, пока процесс не будет завершен. Хуже того, после того, rmкак я не смог найти файл или его размер! Есть ли способ найти файл и, возможно, очистить его, даже если он еще открыт в другом процессе?

Я имею в виду операционные системы на основе Linux, такие как Debian или RHEL.

dotancohen
источник
2
Если вы знаете pid, вы можете использовать его lsof -p <pid>для отображения открытых файлов и их размеров. Удаленный файл будет иметь (deleted)рядом с ним. Удаленный файл будет связан, /proc/<pid>/fd/1вероятно, в. Я не знаю, как заставить процесс прекратить запись в свой файловый дескриптор, не завершив его. Я думаю, что это будет зависеть от процесса.
успешно,
Благодарю. Как можно получить PID всех rmфайлов ed, которые все еще открыты?
dotancohen
@donothingsuccessfully Тег «удален», сообщаемый lsof, относится только к Solaris, в действительности только для Solaris 10 или более поздней версии. ОП не указал, какую операционную систему он использует. @dotancohen В Solaris вы можете направить вывод lsof для поиска удаленных, например lsof | grep "(deleted)". Когда больше нет процессов, удерживающих открытый файл открытым, ядро ​​освобождает блоки inode и disk. Процессы не имеют «обработчиков», с помощью которых они могут получать уведомления о том, что открытый, по существу заблокированный файл был удален с диска.
Йохан
2
@Johan, lsof | grep '(deleted)'работает и в Linux. В Linux вы можете получать уведомления об удалении файлов (даже файлы, которые уже не имеют записей в каком-либо каталоге, кроме / proc / some-pid / fd) с помощью механизма inotify (событие IN_DELETE_SELF)
Stéphane Chazelas
Я создал somefileи открыл его в VIM, а затем rmредактировал в другом процессе bash. Затем я запускаю, lsof | grep somefileи его там нет, хотя файл открыт в VIM.
dotancohen

Ответы:

141

Если вы не можете убить свое приложение, вы можете усечь его вместо удаления файла журнала, чтобы освободить место. Если файл не был открыт в режиме добавления (с помощью O_APPEND), то этот файл будет иметь такой же большой размер, как и в следующий раз, когда приложение записывает в него (хотя с лидирующей частью, разреженной и выглядящей так, как будто она содержит NUL-байты), но с пробелом будет исправлено (это не относится к файловым системам HFS + в Apple OS / X, которые не поддерживают разреженные файлы).

Чтобы усечь это:

: > /path/to/the/file.log

Если он уже был удален, в Linux вы можете обрезать его, выполнив:

: > "/proc/$pid/fd/$fd"

Где $pidнаходится идентификатор процесса, в котором открыт файл, и $fdодин дескриптор файла, в котором он был открыт (в котором вы можете проверить lsof -p "$pid".

Если вы не знаете pid и ищете удаленные файлы, вы можете сделать:

lsof -nP | grep '(deleted)'

lsof -nP +L1, как упомянуто @ user75021, является еще лучшей (более надежной и более переносимой) опцией (перечислять файлы, которые имеют менее 1 ссылки).

Или (в Linux):

find /proc/*/fd -ls | grep  '(deleted)'

Или найти большие с zsh:

ls -ld /proc/*/fd/*(-.LM+1l0)

Альтернативой, если приложение динамически связано, является присоединение к нему отладчика и выполнение его вызова с close(fd)последующим новым open("the-file", ....).

Стефан Шазелас
источник
1
Также есть truncateкоманда, которая делает то же самое более явно.
Тобу
1
@dotancohen Stephane отредактирован, чтобы включить информацию о том, как это сделать, когда pid неизвестен.
Диди Кохен
1
@OlivierDulac, lsofвероятно, будет наиболее близким к переносимому решению, которое вы можете получить для просмотра списка открытых файлов. подход отладчика для закрытия fd под ногами приложения также должен быть достаточно переносимым.
Стефан Шазелас
2
@StephaneChazelas: спасибо. Я нашел способ перечислить все PID, в которых есть файлы, открытые на каждом из разделов: df -k | awk 'NR>1 { print $NF }' | xargs fuser -Vud (и затем легко отправлять сигналы нарушителям, чтобы заставить их освободить fd)
Оливье Дюлак
6
Вы также можете использовать lsof +L1. На странице руководства lsof: «Спецификация формы +L1выберет открытые файлы, которые не были связаны. Спецификация формы +aL1 <file_system>выберет несвязанные открытые файлы в указанной файловой системе». Это должно быть немного надежнее, чем grepping.
Синхронно
31

Проверьте быстрый старт здесь: lsofБыстрый старт

Я удивлен, что никто не упомянул файл быстрого запуска lsof (входит в состав lsof). Раздел «3.a» показывает, как найти открытые несвязанные файлы:

lsof -a +L1 *mountpoint*

Например:

[root@enterprise ~]# lsof -a +L1 /tmp
COMMAND   PID   USER   FD   TYPE DEVICE    SIZE NLINK  NODE NAME
httpd    2357 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
mysqld   2588  mysql    4u   REG 253,17      52     0  1495 /tmp/ibY0cXCd (deleted)
mysqld   2588  mysql    5u   REG 253,17    1048     0  1496 /tmp/ibOrELhG (deleted)
mysqld   2588  mysql    6u   REG 253,17       0     0  1497 /tmp/ibmDFAW8 (deleted)
mysqld   2588  mysql    7u   REG 253,17       0     0 11387 /tmp/ib2CSACB (deleted)
mysqld   2588  mysql   11u   REG 253,17       0     0 11388 /tmp/ibQpoZ94 (deleted)
httpd    3457   root   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd    8437 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd    8438 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd    8439 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd    8440 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd    8441 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd    8442 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd    8443 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd    8444 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd   16990 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd   19595 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd   27495 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd   28142 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)
httpd   31478 apache   29u   REG 253,17 3926560     0  1499 /tmp/.NSPR-AFM-3457-9820130.0 (deleted)

В системах Red Hat, чтобы найти локальную копию файла быстрого запуска, я обычно делаю это:

[root@enterprise ~]# locate -i quickstart |grep lsof
/usr/share/doc/lsof-4.78/00QUICKSTART

... или это:

[root@enterprise ~]# rpm -qd lsof
/usr/share/doc/lsof-4.78/00.README.FIRST
/usr/share/doc/lsof-4.78/00CREDITS
/usr/share/doc/lsof-4.78/00DCACHE
/usr/share/doc/lsof-4.78/00DIALECTS
/usr/share/doc/lsof-4.78/00DIST
/usr/share/doc/lsof-4.78/00FAQ
/usr/share/doc/lsof-4.78/00LSOF-L
/usr/share/doc/lsof-4.78/00MANIFEST
/usr/share/doc/lsof-4.78/00PORTING
/usr/share/doc/lsof-4.78/00QUICKSTART
/usr/share/doc/lsof-4.78/00README
/usr/share/doc/lsof-4.78/00TEST
/usr/share/doc/lsof-4.78/00XCONFIG
/usr/share/man/man8/lsof.8.gz
user75021
источник
1

Это зависит от драйвера файловой системы, чтобы фактически освободить выделенное пространство, и это обычно происходит только после освобождения всех файловых дескрипторов, ссылающихся на этот файл. Таким образом, вы не сможете действительно освободить место, если не заставите приложение закрыть файл. Это означает, что нужно либо завершить его, либо «поиграть» с ним в отладчике (например, закрыть файл и убедиться, что он не был открыт / записан снова, или открыть /dev/nullвместо него). Или вы можете взломать ядро, но я бы посоветовал против этого.

Усечение файла, как предполагает Стефан, может помочь, но реальный результат также будет зависеть от вашей файловой системы (например, предварительно выделенные блоки, вероятно, будут освобождены только после закрытия файла в любом случае).

Смысл такого поведения заключается в том, что ядро ​​не будет знать, что делать с запросами данных (как чтение, так и запись, но чтение на самом деле более важно) для такого файла.

peterph
источник
2
Поскольку Linux поддерживает разреженные файлы в большинстве файловых систем, поведение четко определено, и драйвер диска может действительно освободить дисковое пространство. Я проверил его на ext3 и ext4, и он работает так, как написал Стефан.
Джофель
1
Что заставляет вас говорить, что усечение файла не приведет к восстановлению предварительно выделенных блоков? Усечение предназначено для освобождения данных, я не думаю, что в этом есть какая-то двусмысленность.
Стефан Шазелас
1
Файловая система может сохранить блоки, выделенные для экономии времени позже (особенно, если файл все еще остается открытым), особенно когда он был достаточно большим перед усечением. По крайней мере, именно это делает XFS.
Петер
Спасибо, Питер. Я рад, что вы обращаетесь к «почему» в этом посте.
dotancohen
2
Насколько я могу судить, усечение открытых файлов освобождает пространство и в XFS. Протестировано как с обычным файлом, так и с файлом, выделенным fallocateв Linux 4.9. Не могли бы вы уточнить, под какой файловой системой и условием усечение файла не занимает место?
Стефан