Можно ли в UNIX обнулять (truncate) файл, в который пишет некоторый процесс?

в 10:30, , рубрики: bash, linux, shell, truncate, UNIX, операционные системы, перенаправление, метки: , , , , ,

Довольно популярный и актуальный вопрос, который возникает, например, при чистке интенсивно растущих журналов постоянно запущенных процессов.

Ответ в двух словах

Обнулять можно, но для этого пишущий процесс должен открывать этот файл с использованием флага O_APPEND. Ниже приводится соответствующий отрывок из open(2):


O_APPEND
The file is opened in append mode. Before each write(2), the file offset is positioned at the end of the file, as if with lseek(2). O_APPEND may lead to corrupted files on NFS file systems if more than one process appends data to a file at once. This is because NFS does not support appending to a file, so the client kernel has to simulate it, which can't be done without a race condition.

Несколько процессов, пишущих в один файл на NFS — это отдельный клинический случай. Пусть у нас есть один-единственный процесс, пишущий в файл.

Как же быть, если наш процесс не использует флаг O_APPEND?

Есть два варианта решения в зависимости от того, как создается этот файл:

  1. Путем перенаправления стандартных потоков вывода (stdout/stderr):
    $ /path/binary >/path/log 2>&1
    

    Здесь при открытии файла флаг O_APPEND будет использоваться в том случае, если вместо оператора перенаправления > использовать оператор перенаправления с добавлением >>:

    $ /path/binary >>/path/log 2>&1
    

  2. Процесс пишет напрямую в предопределенный файл /path/log_predefined. В этом случае перед запуском процесса создается связка из именованного канала (named pipe) и, например, cat(1), приводящая этот случай к предыдущему:
    $ mkfifo /path/log_predefined && cat </path/log_predefined >>/path/log &
    

    Кстати, при завершении нашего процесса «фоновый» cat будет также завершаться. Как вариант решения этой проблемы можно запускать cat в бесконечном цикле:

    $ bash -c "while :; do cat <log_predefined >>log; done" &
    

Теперь можно безболезненно обнулить файл журнала в любой момент, например, так:

$ : > /path/log

Однако следует заметить, что флаг O_APPEND не поддерживается некоторыми старыми оболочками, например, Bourne shell (/bin/sh), все еще использующимся в большинстве скриптов.

Проверим это утверждение, например, в Solaris. При этом предупрежу заранее, что пользователям Linux проблема со старой /bin/sh не грозит, т.к.:

$ uname -o && ls -l /bin/sh
GNU/Linux
lrwxrwxrwx 1 root root 4 2008-11-10 17:16 /bin/sh -> bash

Открытие файлов оператором '>>' в разных оболочках

Приведённый ниже скрипт выполняет команду echo c перенаправлением вывода в новый или существующий файл оператором '>>' в разных оболочках, отслеживая при этом интересующие нас системные вызовы creat(2), open(2), lseek(2) и их варианты:

     1  #!/bin/sh
     2
     3  trace() {
     4      eval "truss -tcreat,open,lseek $* 2>&1 | sed -ne '/my_test/,$p;'"
     5  }
     6
     7  for shell in sh csh tcsh ksh bash zsh; do
     8       printf "nRedirecting with '>>' to a non-existing file in '$shell'n"
     9       [ -f my_test ] && rm my_test
    10      trace "$shell -c 'echo test >> my_test'"
    11
    12      printf "nRedirecting with '>>' to an existing file in '$shell'n"
    13      [ -f my_test ] || : > my_test
    14      trace "$shell -c 'echo test >> my_test'"
    15  done

В следующей таблице приводятся результаты нашего исследования — флаги системного вызова open(2), использовавшиеся оболочками для открытия нового или существующего файла оператором '>>'. При этом учитывается, что системный вызов creat(2) является синонимом open(), вызванным с флагами O_WRONLY | O_CREAT | O_TRUNC.

Оболочка Файл
новый существующий
sh
csh
O_WRONLY | O_CREAT | O_TRUNC O_WRONLY
tcsh O_WRONLY | O_CREAT | O_TRUNC O_WRONLY | O_APPEND
bash
ksh
O_WRONLY | O_APPEND | O_CREAT
zsh O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY

Флаг O_NOCTTY в zsh используется на тот случай, если в качестве имени файла будет указано терминальное устройство, см. open(2):


O_NOCTTY
If set and path identifies a terminal device, open() does not cause the terminal device to become the controlling terminal for the process.

Таким образом, оболочки sh и csh, а также tcsh при открытии нового файла, не используют флаг O_APPEND, а описанный выше способ обнуления используемых файлов в этих оболочках использовать не получится. В более современных оболочках (ksh, bash, zsh) этой проблемы нет.

В следующей статье я постараюсь разобраться подробнее в нюансах работы со стандартными потоками и каналами в UNIX применительно к задаче ротации журналов.

Автор: nfubh

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js