fsnotify to biblioteka powiadomień o systemie plików napisana w Go, która może monitorować zmiany w plikach i katalogach w systemie plików oraz powiadamiać aplikację o wystąpieniu tych zmian. Obsługuje operacje na różnych systemach operacyjnych, takich jak Linux, macOS i Windows. fsnotify wykorzystuje mechanizmy powiadamiania o systemie plików różnych systemów operacyjnych, takie jak inotify w systemie Linux, FSEvents w systemie macOS oraz ReadDirectoryChangesW w systemie Windows.

Wymaga użycia Go w wersji 1.17 lub nowszej; pełna dokumentacja dostępna jest pod adresem https://pkg.go.dev/github.com/fsnotify/fsnotify

Obsługa różnych systemów operacyjnych:

Back-end System operacyjny Status
inotify Linux Obsługiwane
kqueue BSD, macOS Obsługiwane
ReadDirectoryChangesW Windows Obsługiwane
FEN illumos Obsługiwane
fanotify Linux 5.9+ Jeszcze nieobsługiwane
AHAFS AIX Gałąź AIX; Funkcja eksperymentalna ze względu na brak opiekunów i środowiska testowego
FSEvents macOS Wymaga wsparcia x/sys/unix
USN Journals Windows Wymaga wsparcia x/sys/windows
Polling Wszystkie Jeszcze nieobsługiwane

Android i Solaris, które powinny obejmować Linuxa i illumos, nie zostały jeszcze przetestowane.

Przykłady użycia

Przykłady użycia fsnotify obejmują, ale nie są ograniczone do, następujących sytuacji:

  1. Synchronizacja plików w czasie rzeczywistym: fsnotify może monitorować zmiany w systemie plików w czasie rzeczywistym, co sprawia, że nadaje się do implementacji synchronizacji plików w czasie rzeczywistym. W przypadku zmian w pliku źródłowym zmiany mogą zostać natychmiast zsynchronizowane z plikiem docelowym, zapewniając spójność plików.
  2. Automatyczna budowa: fsnotify może monitorować zmiany w kodzie źródłowym projektu oraz plikach zależności, uruchamiając polecenia budowania w przypadku wystąpienia zmian, co pozwala na osiągnięcie automatycznej budowy. Może to zaoszczędzić czas i wysiłek związany z ręczną budową oraz poprawić wydajność rozwoju.
  3. Tworzenie kopii zapasowych plików: fsnotify może monitorować zmiany w plikach lub katalogach, które należy zabezpieczyć poprzez natychmiastowe tworzenie kopii zapasowej po wystąpieniu zmian. Zapewnia to bezpieczeństwo danych i zapobiega utracie danych spowodowanej utratą lub uszkodzeniem plików.
  4. Monitorowanie logów w czasie rzeczywistym: fsnotify może monitorować operacje, takie jak tworzenie, modyfikacja i usuwanie plików dziennika, uruchamiając programy monitorujące logi w przypadku zmian w plikach dziennika, efektywnie monitorując zmiany w treści logów w czasie rzeczywistym.
  5. Monitorowanie bezpieczeństwa systemu plików: fsnotify może monitorować zdarzenia związane z bezpieczeństwem w systemie plików, takie jak dostęp do pliku, modyfikacja i usunięcie. Pozwala to na monitorowanie bezpieczeństwa systemu plików, szybkie identyfikowanie i rejestrowanie zdarzeń niespójnych.

Przykład użycia

Przykład podstawowy:

package main

import (
    "log"

    "github.com/fsnotify/fsnotify"
)

func main() {
    // Utwórz nowy obserwator.
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    // Rozpocznij nasłuchiwanie zdarzeń.
    go func() {
        for {
            select {
            case event, ok := <-watcher.Events:
                if !ok {
                    return
                }
                log.Println("Zdarzenie:", event)
                if event.Has(fsnotify.Write) {
                    log.Println("Zmodyfikowany plik:", event.Name)
                }
            case err, ok := <-watcher.Errors:
                if !ok {
                    return
                }
                log.Println("Błąd:", err)
            }
        }
    }()

    // Dodaj ścieżkę, która ma być monitorowana.
    err = watcher.Add("/tmp")
    if err != nil {
        log.Fatal(err)
    }

    // Zablokuj główny wątek goroutine.
    <-make(chan struct{})
}

Więcej przykładów można znaleźć w cmd/fsnotify i uruchomić za pomocą polecenia:

% go run ./cmd/fsnotify

Szczegółową dokumentację można znaleźć w godoc: https://pkg.go.dev/github.com/fsnotify/fsnotify

Najczęściej zadawane pytania

Czy plik nadal będzie monitorowany, jeśli zostanie przeniesiony do innego katalogu?

Nie, chyba że monitorujesz lokalizację, do której został przeniesiony.

Czy monitoruje podkatalogi?

Nie, musisz dodać monitorowanie dla każdego katalogu, który chcesz obserwować (planowane jest monitorowanie rekurencyjne: #18).

Czy potrzebuję jednocześnie monitorować kanały błędów i zdarzeń w goroutine?

Tak. Możesz użyć select do odczytu z obu kanałów w tym samym goroutine (nie musisz uruchamiać osobno goroutine dla każdego kanału; patrz przykład).

Dlaczego powiadomienia nie działają na NFS, SMB, FUSE, /proc lub /sys?

fsnotify wymaga wsparcia ze strony działającego systemu operacyjnego, aby działać poprawnie. Obecne protokoły NFS i SMB nie zapewniają wsparcia na poziomie sieciowym dla powiadomień o plikach, a wirtualne systemy plików /proc i /sys również nie zapewniają wsparcia.

Można to naprawić, używając obserwatora do odpytywania (#9), ale nie zostało to jeszcze zaimplementowane.

Dlaczego otrzymuję wiele zdarzeń Chmod?

Niektóre programy mogą generować dużą liczbę zmian atrybutów, takich jak Spotlight w systemie macOS, programy antywirusowe, aplikacje do tworzenia kopii zapasowych i niektóre inne znane aplikacje. Ogólnie rzecz biorąc, ignorowanie zdarzeń Chmod jest zazwyczaj najlepszą praktyką, ponieważ są one często nieprzydatne i mogą powodować problemy.

Indeksowanie Spotlight w systemie macOS może powodować występowanie wielu zdarzeń (patrz #15). Tymczasowym rozwiązaniem jest dodanie folderów do Ustawień prywatności Spotlight do czasu wprowadzenia natywnej implementacji FSEvents (patrz #11).

Obserwowanie plików nie działa dobrze

Zazwyczaj nie zaleca się obserwowania pojedynczych plików (zamiast katalogów), ponieważ wiele programów, zwłaszcza edytorów, będzie atomowo aktualizować pliki: napisze do pliku tymczasowego, a następnie przeniesie go do docelowej lokalizacji, nadpisując oryginalny plik (lub jego wariant). Obserwowanie oryginalnego pliku zostaje teraz utracone, ponieważ plik już nie istnieje.

W rezultacie awaria zasilania lub awaria nie spowoduje otrzymania napisanego w połowie pliku.

Obserwuj katalog nadrzędny i użyj Event.Name do filtrowania plików, które cię nie interesują. Istnieje przykład w cmd/fsnotify/file.go.

Uwagi dotyczące konkretnych platform

Linux

Gdy plik zostanie usunięty, zdarzenie REMOVE nie zostanie wydane, dopóki wszystkie deskryptory plików nie zostaną zamknięte; w tym momencie zostanie wydane zdarzenie CHMOD:

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

Jest to zdarzenie wysyłane przez inotify, więc nie powinno być dokonywanych zbyt wielu zmian.

Zmienna sysctl fs.inotify.max_user_watches określa maksymalną liczbę obserwacji na użytkownika, a fs.inotify.max_user_instances określa maksymalną liczbę instancji inotify na użytkownika. Każdy utworzony Watcher jest "instancją", a każda ścieżka dodana jest "obserwacją".

Można je również znaleźć w /proc, pod ścieżkami /proc/sys/fs/inotify/max_user_watches i /proc/sys/fs/inotify/max_user_instances.

Aby je zwiększyć, można użyć sysctl lub zapisać wartości do pliku proc:

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

Aby zmiany zaczęły obowiązywać po ponownym uruchomieniu, edytuj plik /etc/sysctl.conf lub /usr/lib/sysctl.d/50-default.conf (szczegóły mogą się różnić w zależności od dystrybucji Linuxa, proszę zajrzeć do dokumentacji swojej dystrybucji):

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

Osiągnięcie limitu spowoduje pojawienie się błędów "Brak miejsca na urządzeniu" lub "Zbyt wiele otwartych plików".

kqueue (macOS, wszystkie systemy BSD)

kqueue wymaga otwarcia deskryptora pliku dla każdego obserwowanego pliku; dlatego, jeśli obserwujesz katalog zawierający pięć plików, to jest sześć deskryptorów plików. Na tych platformach szybciej osiągniesz limit "maksymalnej liczby otwartych plików" systemu.

Możesz użyć zmiennych sysctl kern.maxfiles i kern.maxfilesperproc do kontrolowania maksymalnej liczby otwartych plików.