fsnotify - это библиотека уведомлений файловой системы, написанная на Go, которая может отслеживать изменения файлов и каталогов в файловой системе и уведомлять приложение о возникших изменениях. Она поддерживает кросс-платформенную работу и может работать на различных операционных системах, таких как Linux, macOS и Windows. fsnotify использует механизмы уведомлений файловой системы различных операционных систем, такие как inotify в Linux, FSEvents в macOS и ReadDirectoryChangesW в Windows.

Требуется использование Go версии 1.17 или более поздней; полная документация может быть найдена на https://pkg.go.dev/github.com/fsnotify/fsnotify

Поддержка различных операционных систем:

Backend Операционная система Статус
inotify Linux Поддерживается
kqueue BSD, macOS Поддерживается
ReadDirectoryChangesW Windows Поддерживается
FEN illumos Поддерживается
fanotify Linux 5.9+ Пока не поддерживается
AHAFS AIX Ветка AIX; Экспериментальная функция из-за отсутствия ведущих и тестовых сред
FSEvents macOS Требуется поддержка x/sys/unix
USN Journals Windows Требуется поддержка x/sys/windows
Polling Все Пока не поддерживается

Android и Solaris, которые должны включать Linux и illumos, пока не были протестированы.

Примеры использования

Примеры использования fsnotify включают, но не ограничиваются следующими ситуациями:

  1. Синхронизация файлов в реальном времени: fsnotify может отслеживать изменения в файловой системе в реальном времени, что делает его подходящим для реализации синхронизации файлов в реальном времени. Когда происходят изменения в исходном файле, изменения могут немедленно синхронизироваться с целевым файлом, обеспечивая согласованность файлов.
  2. Автоматизированная сборка: fsnotify может отслеживать изменения в исходном коде и файлов зависимостей проекта, запуская команды сборки при возникновении изменений, тем самым достигая автоматизированной сборки. Это позволяет экономить время и усилия, затрачиваемые на ручную сборку, и повышает эффективность разработки.
  3. Резервное копирование файлов: fsnotify может отслеживать изменения в файлах или каталогах, которые нужно резервировать, и сразу же начинать резервное копирование при возникновении изменений. Это обеспечивает безопасность данных и предотвращает потерю данных из-за потери или повреждения файлов.
  4. Мониторинг логов в реальном времени: fsnotify может отслеживать операции, такие как создание, изменение и удаление лог-файлов, запуская программы мониторинга логов при изменении в лог-файлах, что позволяет эффективно отслеживать изменения в содержании логов в реальном времени.
  5. Мониторинг безопасности файловой системы: fsnotify может отслеживать события безопасности в файловой системе, такие как доступ к файлам, изменение и удаление. Это позволяет мониторить безопасность файловой системы, своевременно выявляя и регистрируя небезопасные события.

Пример использования

Базовый пример:

package main

import (
    "log"

    "github.com/fsnotify/fsnotify"
)

func main() {
    // Создаем новый наблюдатель.
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    // Начинаем слушать события.
    go func() {
        for {
            select {
            case event, ok := <-watcher.Events:
                if !ok {
                    return
                }
                log.Println("Событие:", event)
                if event.Has(fsnotify.Write) {
                    log.Println("Измененный файл:", event.Name)
                }
            case err, ok := <-watcher.Errors:
                if !ok {
                    return
                }
                log.Println("Ошибка:", err)
            }
        }
    }()

    // Добавляем путь, который нужно отслеживать.
    err = watcher.Add("/tmp")
    if err != nil {
        log.Fatal(err)
    }

    // Блокируем основную горутину.
    <-make(chan struct{})
}

Более подробные примеры можно найти в cmd/fsnotify и запустить с помощью следующей команды:

% go run ./cmd/fsnotify

Более подробная документация доступна в godoc: https://pkg.go.dev/github.com/fsnotify/fsnotify

Часто задаваемые вопросы

Будет ли осуществляться мониторинг, если файл перемещен в другой каталог?

Нет, если вы не отслеживаете расположение, куда он был перемещен.

Он отслеживает подкаталоги?

Нет, вы должны добавить отслеживание для каждого каталога, который вы хотите отслеживать (рекурсивное отслеживание запланировано: #18).

Нужно ли одновременно отслеживать как ошибки, так и события в горутине?

Да. Вы можете использовать select для чтения из обоих каналов в той же горутине (вам не нужно запускать горутину отдельно для каждого канала; см. пример).

Почему уведомления не работают в NFS, SMB, FUSE, /proc или /sys?

Для работы fsnotify требуется поддержка со стороны операционной системы. В текущих протоколах NFS и SMB нет поддержки уведомлений о файлах на уровне сети, а также виртуальные файловые системы /proc и /sys также не предоставляют поддержки.

Это можно исправить, используя наблюдение через опрос (см. #9), но это пока не было реализовано.

Почему я получаю много событий Chmod?

Некоторые программы могут генерировать большое количество изменений атрибутов, таких как Spotlight на macOS, антивирусные программы, резервные копии и некоторые другие известные приложения. В целом, игнорирование событий Chmod обычно является лучшей практикой, так как они часто бесполезны и могут вызывать проблемы.

Индексирование Spotlight на macOS может вызывать несколько событий (см. #15). Временным решением является добавление ваших папок в Настройки конфиденциальности Spotlight, пока не будет реализовано нативное использование FSEvents (см. #11).

Отслеживание файлов плохо работает

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

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

Отслеживайте родительский каталог и используйте Event.Name для фильтрации файлов, которые вас не интересуют. В файле cmd/fsnotify/file.go есть пример.

Примечания для конкретных платформ

Linux

При удалении файла событие REMOVE не будет выдано до тех пор, пока все дескрипторы файлов не будут закрыты; в этот момент будет выдано событие CHMOD:

fp := os.Open("file")
os.Remove("file")        // CHMOD
fp.Close()               // REMOVE

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

Переменная sysctl fs.inotify.max_user_watches указывает максимальное количество наблюдений на пользователя, а fs.inotify.max_user_instances указывает максимальное количество экземпляров inotify на пользователя. Каждый созданный вами Watcher - это "экземпляр", а каждый добавленный вами путь - это "наблюдение".

Эти переменные также можно найти в /proc по путям /proc/sys/fs/inotify/max_user_watches и /proc/sys/fs/inotify/max_user_instances.

Чтобы увеличить их, вы можете использовать sysctl или записать значения в файл proc:

sysctl fs.inotify.max_user_watches=124983
sysctl fs.inotify.max_user_instances=128

Чтобы изменения вступили в силу после перезагрузки, отредактируйте /etc/sysctl.conf или /usr/lib/sysctl.d/50-default.conf (детали могут отличаться для каждого дистрибутива Linux, пожалуйста, обратитесь к документации вашего дистрибутива):

fs.inotify.max_user_watches=124983
fs.inotify.max_user_instances=128

Достижение лимита приведет к ошибкам "Нет свободного места на устройстве" или "Слишком много открытых файлов".

kqueue (macOS, все системы BSD)

Для kqueue требуется открытие дескриптора файла для каждого наблюдаемого файла; поэтому, если вы наблюдаете каталог, содержащий пять файлов, это будет шесть дескрипторов файлов. На этих платформах вы быстрее достигнете предела "максимальное количество открытых файлов" системы.

Вы можете использовать переменные sysctl kern.maxfiles и kern.maxfilesperproc, чтобы контролировать максимальное количество открытых файлов.