Обнаружение того, что файл журнала был удален или усечен в системах POSIX?

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

Можно ли предположить, что fstat() сообщит об нулевом счетчике ссылок для удаленного файла?

С усечением, как мне кажется, немного сложнее. Отчасти это зависит от того, работает ли файловый дескриптор в режиме O_APPEND. Если файл журнала не выполняется с O_APPEND, то текущая позиция записи дескриптора журнала программы не изменяется, а усечение удаляет начальные байты, но программа продолжает запись в «конец», оставляя призрачный пробел. нулевые байты (они читаются как нули, но не обязательно занимают место на диске).

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

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


person Jonathan Leffler    schedule 20.01.2009    source источник
comment
Как насчет блокировки вместо обнаружения удаления?   -  person Eduard Wirch    schedule 20.01.2009
comment
@snot: я не понимаю, как это сильно поможет. Консультативная блокировка будет проигнорирована; обязательная блокировка необычна - не невозможна, но я бы предпочел этого не делать. Кроме того, администраторам должно быть разрешено манипулировать журналами в разумных пределах. Может случиться так, что программное обеспечение нуждается в лучшем механизме «файла журнала изменений».   -  person Jonathan Leffler    schedule 20.01.2009
comment
Имена файлов - это просто указатели на файлы, когда ничто не указывает на файл, он удаляется. Поскольку вы всегда можете добавить ссылку, а затем удалить другую, счетчик ссылок может не помочь, лучшее, что вы можете сделать, это проверить, существует ли еще имя файла или ваш процесс единственный со ссылкой на файл.   -  person Robert Gamble    schedule 20.01.2009
comment
@ Роберт: не совсем; файл должен быть закрыт всеми процессами, прежде чем он будет освобожден. Если демон держит файл открытым, то место на диске остается выделенным для файла, и индексный дескриптор не используется повторно, даже если у него нет имени. Вы не можете повторно связать файл без имени, по крайней мере, не в классическом Unix.   -  person Jonathan Leffler    schedule 21.01.2009
comment
Ничто из того, что ты сказал, не противоречит тому, что я сказал.   -  person Robert Gamble    schedule 21.01.2009
comment
@Robert: хорошо, тогда я назову свое непонимание того, что вы сказали, проблемой, вызванной ограничением в 300 символов на комментарии.   -  person Jonathan Leffler    schedule 21.01.2009


Ответы (5)


Проверка того, что fstat() возвращает нулевой счетчик ссылок, завершится ошибкой, если файл жестко связан или переименован. Вместо этого я, вероятно, периодически сравниваю номер инода stat() с номером fstat().

Я не уверен насчет усечения.

tail -F проверяет удаление и, возможно, усечение, поэтому я бы проверил его исходный код, чтобы увидеть, как он это реализует.

person Josh Kelley    schedule 20.01.2009
comment
Хорошо, что количество ссылок не равно нулю, если файл был жестко связан. Для моих целей переименовать не проблема. Мне нужно подумать о том, является ли связывание проблемой — данные по-прежнему доступны через какое-то имя, что является ключевым моментом. - person Jonathan Leffler; 20.01.2009

Предположим, нерадивый системный администратор убивает процесс. Вы действительно хотите защититься от случайных действий администратора? Я думаю, вы просто ищете способ время от времени запускать новый файл журнала, например, используя logrotate. Там достаточно предоставить способ вручную позволить программе повторно открыть файл журнала. Стандартный способ сделать это — прослушать HUP-сигнал в программе и повторно открыть лог-файл, если он появится:

#include <signal.h>

volatile int f_sighup;

void sighup_handler() {
  f_sighup = 1;
}

void trap_sighup() {
  struct sigaction sa;
  int rv;

  memset(&sa, 0, sizeof(struct sigaction));
  sa.sa_handler = &sighup_handler;
  rv = sigaction(SIGHUP, &sa, NULL);
  if (-1 == rv) {
    fprintf(stderr, "warning: setting SIGHUP signal handler failed");
  }
}

int main() {
  f_sighup = 0;
  trap_sighup();
  ...
}

Затем регулярно проверяйте флаг f_sighup в основной программе, чтобы увидеть, нужно ли повторно открывать файл журнала. Это хорошо сочетается с такими инструментами, как logrotate, который может переименовать старый файл журнала, а затем вызвать kill -s HUP $PID. А нерадивый сисадмин может сделать это вручную, предварительно удалив (а лучше переименовав) старый лог-файл.

person sth    schedule 20.01.2009
comment
Должно ли это быть sig_atomic_t volatile f_sighup = 0;? - person Jonathan Leffler; 21.01.2009
comment
Вероятно, это не повредит, хотя я бы предположил, что int всегда будет атомарным. volatile может быть хорошей идеей в любом случае. - person sth; 21.01.2009

Вы можете использовать inotify для просмотра файла журнала, мониторинг событий файловой системы.

person Jeff Bauer    schedule 20.01.2009
comment
Интересное программное обеспечение, но я смотрю на него с точки зрения программы, записывающей файл журнала, а не с точки зрения внешнего «потребителя» файла журнала. - person Jonathan Leffler; 20.01.2009
comment
inotify может помочь вам с любой задачей, но она специфична для Linux. - person R.. GitHub STOP HELPING ICE; 08.03.2011

В ответ на søren-holm ответить

Когда файл закрывается, время модификации изменяется.

это не кажется правильным:

import os
from time import sleep

TMPF = '/tmp/f'

def print_stats():
    print("%s, %s" % (os.stat(TMPF).st_mtime, os.stat(TMPF).st_ctime))
    sleep(1.1)

print("Opening...")
with open(TMPF, 'w') as f:
    print_stats()
    print("Writing...")
    os.write(f.fileno(), 'apple')
    print_stats()
    print("Flushing...")
    f.flush()
    print_stats()
    print("Closing...")

print_stats()

Производит:

Opening...
1483052647.08, 1483052647.08
Writing...
1483052648.18, 1483052648.18
Flushing...
1483052648.18, 1483052648.18
Closing...
1483052648.18, 1483052648.18

По общему признанию, там происходит немного магии Python; что write() не гарантируется автоматический сброс, но суть в том, что mtime обновляется при изменении файла, а не при его закрытии. Поведение ctime будет зависеть от вашей файловой системы и параметров ее монтирования.

person BMDan    schedule 29.12.2016
comment
Глядя на спецификацию POSIX close(), можно утверждать, что close() не не влияет на время модификации файлов. Спецификация POSIX для write(), напротив, предусматривает, что время модификации изменен. Итак, по крайней мере теоретически, если вы записываете файл, скажем, в 09:00:00 и держите файл открытым без дальнейших изменений до 15:00:00, «последнее обновленное время остается 09:00:00». - person Jonathan Leffler; 31.12.2016

Когда файл закрывается, время модификации изменяется. Поэтому периодически проверяйте mtime с помощью stat().

person Søren Holm    schedule 15.06.2016
comment
Глядя на спецификацию POSIX close(), можно утверждать, что close() не не влияет на время модификации файлов. Спецификация POSIX для write(), напротив, предусматривает, что время модификации изменен. Итак, по крайней мере теоретически, если вы записываете файл, скажем, в 09:00:00 и держите файл открытым без дальнейших изменений до 15:00:00, «последнее обновленное время остается 09:00:00». - person Jonathan Leffler; 31.12.2016