sobota 9. června 2012

Inotify a vlastní akce založené na událostech ve filesystému

Abych nepsal jen o samých kravinách, zabřednu dnes opět do něčeho zajímavějšího a užitečnějšího. Součástí Linuxového kernelu je subsystém Inotify, který umí sledovat dění v souborovém systému a poskytovat informace o událostech svým klientům. Vzhledem k tomu, že v Linuxu je všechno soubor, nabízí se opravdu široké možnosti využití. Můžete v reálném čase monitorovat dění v části filesystému, nebo na události reagovat spouštěním vlastních skriptů a třeba zálohovat důležité soubory po změně. Například si také velmi jednoduše můžete "vytvořit adresář", ve kterém se každý pdf soubor automaticky rozloží na jednotlivé stránky ve formátu png, nebo změní velikost obrázků a rovnou je odešle mailem...

Inotify

Inotify není žádná novinka, v hlavní větvi Linuxu je od verze 2.6.13, která vyšla v roce 2005 a nahradil starší a hloupější systém dnotify. Inotify nabízí jednoduché API, jehož prostřednictvím můžete zacílit na konkrétní adresáře, nebo přímo soubory (je založený na inodech, od toho to I na začátku) a dostávat informace o událostech, které se v nich dějí. Na základě událostí pak můžete spouštět vlastní aktivity, které budou události zpracovávat. Místo toho abyste v nějakém časovém intervalu kontrolovali soubor, nebo adresář a dívali se, zda se něco nezměnilo (tzv. polling), budete v klidu čekat na zprávu přímo od vykonavatele těch změn, který umí být o poznání efektivnější.
Inotify má ale jistá omezení, funguje pouze na lokálních filesystémech a neumí rekurzivně sledovat podadresáře, to už si musí klienti zajistit sami, rozšířením objednávky. Kapacita není neomezená, maximum sledovaných inodů je 8192. Update: Toto bylo nastavení Ubuntu 10.04, v novějších systémech je tato hodnota zvýšena řádově a je možné ji měnit. Kapacita je ve skutečnosti limitovaná velikostí operační paměti RAM, protože každý sledovaný bod obsadí nějakou paměť na straně kernelu (neswapovatelnou), i na straně aplikace, která data zpracovává, přičemž na 64bit systému je to dvojnásobek. Na nastvení limitu ve vašem systému se můžete podívat do souborů:
/proc/sys/fs/inotify/max_user_instances - pro maximální počet objektů využívajících současně inotify
/proc/sys/fs/inotify/max_user_watches - pro maximální počet sledovaných inodů

Všechny běžné programovací jazyky mají k dispozici patřičnou knihovnu pro využití Inotify (cé žádnou samozřejmě nepotřebuje), ale jsou k mání i efektivní nástroje pro využití z konzole a tím se budu zabývat v tomto příspěvku.

Inotify z konzole

Základními konzolovými nástroji jsou utility inotifywatch a inotifywait, které nainstalujete takto:

sudo apt-get install inotify-tools

Zatímco inotifywatch umí sbírat statistiky, inotifywait je akčnější nástroj, který umí čekat na určitou událost, po které vyplivne požadovaná data a skončí, čímž může fungovat jako spoušť pro další příkazy, které jeho data mohou zpracovat. Umí ale fungovat i jako monitor. Pokud budete chtít v reálném čase sledovat dění v konkrétním adresáři, můžete na to jít takto:

inotifywait -qm /cesta/k/adreasáři/nebo/souboru

Přepínač -q zajistí, že inotifywait nebude pindat zbytečně a -m je pro monitor, nedojde tedy po první události k ukončení, ale pokračuje se dál.

Můžete čekat i na konkrétní událost, třeba vytvoření souboru v konkrétním adresáři a následně vypsat celou cestu k němu:

inotifywait -qe create --format %w%f /cesta/k/adresáři/

Přepínačem -e určujete jaké události vás zajímají. Přepínač --format umí formátovat výstup podobně jako printf, v tomto případě udává %w cestu k monitorovanému objektu a %f jeho jméno. Událost create nastane i při přepisu souboru tím samým, v tomto případě se ale nedočkáte jména souboru, ale kódu začínajícího .goutputstream-. Pokud byste chtěli rekurzivně hlídat i všechny podadresáře, můžete použít přepínač -r.

Příklad - Akční adresář pdf-to-png - 1

Zkusím reálné použití demonstrovat na příkladu, který jsem zmínil v úvodu. Pro převod pdf do png použiju utilitu convert z balíku imagemagick, který nainstalujete příkazem:

sudo apt-get install imagemagick

Vlastní skript pak může vypadat takto:

pdf-to-png-1.sh
#!/bin/bash

dir="$HOME/pdf-to-png/"
inotifywait -qm -e close_write --format %w%f "$dir" | while read file
do
   [[ "$file" =~ \.pdf$ ]] && convert -density 150 "$file" "$file.png" && rm "$file"
done

Dokud skript běží, inotifywait v režimu monitorování (přepínač -m) vypisuje cesty k souborům které vyvolaly událost close_write týkající se zavření souboru otevřeného pro zápis v adresáři ~/pdf-to-png. Cesty putují rourou do smyčky, která jednu po druhé zpracovává - vybírá jen soubory s koncovkou .pdf, ty převádí pomocí utility convert na sérii png obrázků a poté původní pdf smaže. Přepínač -density určuje hustotu výsledného obrázku - čím vyšší, tím bude mít obrázek vyšší rozlišení.
Všiměte si, že událost close_write nastane třeba vytvořením nového souboru, ale pokud soubor do sledovaného adresáře pouze přesunete v rámci stejného filesystému, nebude skript reagovat, protože tato akce vyvolá událost moved_to, kterou nepokrývá. To funguje i jako ochrana před nechtěným smazáním původního pdf, když ho místo zkopírování jen přesunete, nic se mu nestane. Pokud by měl skript zpracovávat i přesunuté soubory, museli byste použít ještě jeden přepínač -e s parametrem moved_to. Událost create jsem nepoužil proto, že je generována ještě předtím, než se zapíší všechna data a to může logicky způsobit problém.

Jinak skript berte čistě ilustračně, podmínka pro detekci přípony je dost omezená a pro nějaké nasazení by to bylo potřeba trochu ošetřit výjimky, atd...

Události, které můžete sledovat (pokud jich potřebujete sledovat více, použijete přepínač -e násobně, pro každý typ události zvlášť):
  • access  Ze sledovaného souboru, nebo souboru ve sledovaném adresáři bylo čteno.
  • modify  Sledovaný soubor, nebo soubor ve sledovaném adresáři byl změněn, bylo do něj zapsáno.
  • attrib  Byla změněna metadata sledovaného souboru, nebo souboru ve sledovaném adresáři. To zahrnuje oprávnění, časové údaje a další atributy.
  • close_write  Sledovaný soubor, nebo soubor ve sledovaném adresáři byl zavřen po předchozím otevření v režimu zápisu. To nutně neznamená, že do něj bylo zapsáno.
  • close_nowrite  Sledovaný soubor, nebo soubor ve sledovaném adresáři byl zavřen po předchozím otevření jen pro čtení.
  • close  Sledovaný soubor, nebo soubor ve sledovaném adresáři byl zavřen., bez ohledu na to, v jakém režimu byl otevřen. Ve skutečnosti tato událost zahrnuje obě předchzí a tak výsledná událost bude vždy close_write, nebo close_nowrite, nikoliv close.
  • open  Sledovaný soubor, nebo soubor ve sledovaném adresáři byl otevřen
  • moved_to  Do sledovaného adresáře byl přesunut soubor, nebo adresář. Tato událost nastane i tehdy, když je výchozí adresář ten samý, jako cílový.
  • moved_from  Ze sledovaného adresáře byl přesunut soubor, nebo adresář. Tato událost nastane i tehdy, když je výchozí adresář ten samý, jako cílový.
  • move  Zahrnuje události moved_to a moved_from, událostí tedy ve skutečnosti není move, ale jedna z jmenovaných.
  • move_self  Sledovaný soubor, nebo adresář byl přesunut. Po této události sledování končí.
  • create  Ve sledovaném adresáři byl vytvořen nový soubor.
  • delete  Ve sledovaném adresáři byl smazán soubor.
  • delete_self  Sledovaný soubor, nebo adresář byl smazán. Po této události sledování samozřejmě končí.
  • unmount  Souborový systém, na kterém se nacházel sledovaný soubor, nebo adresář byl odpojen. Po této události sledování samozřejmě končí také.
Další možnosti viz.  man inotifywait.


Incron

Incron je na Inotify založená služba odvozená od klasického cronu s tím rozdílem, že se neřídí časovým rozvrhem, ale událostmi ve sledovaném adresáři. Skládá se z démona incrond a nástroje pro správu pravidel incrontab. Pro inspiraci, přímo autor Lukáš Jelínek uvádí následující možnosti využití jeho díla a vlastně obecně systému Inotify:

  • hlídání změn v konfiguraci a automatická notifikace programů
  • hlídání kritických souborů před změnou
  • monitoring použití souborů, statistika
  • automatický úklid po spadlých programech
  • automatické zálohování souborů při změně
  • notifikace při příchodu pošty
  • notifikace při uploadu souborů na server
  • monitoring instalací nestandardním způsobem (mimo balíčkovací systém)
  • ...a mnoho dalšího

Instalace:

sudo apt-get install incron

Služba se po instalaci v Ubuntu automaticky spustí a bude se spouštět i po restartu systému.
Po instalaci nemá žádný uživatel, včetně roota, právo incron používat, nejprve je potřeba uživatele, kteří oprávnění mít mají, přidat do souboru /etc/incron.allow. Další možností je tento soubor smazat, pak mohou incron používat všichni lokální uživatelé.

Pro editaci pravidel incronu pro aktuálního uživatele se používá příkaz

incrontab -e

Stejně jako u cronu, i zde si při prvním spuštění vyberete váš oblíbený editor a pak už můžete vesele zapsat svá pravidla. Jednotlivá pravidla se tvoří takto:

<cesta> <maska> <příkaz>


Jako masku můžete použít jednu, nebo více z následujících událostí (oddělují se čárkami):

  • IN_ACCESS        Přístup k souboru - čtení
  • IN_ATTRIB        Změna metadat (práva, časové údaje, atd.)
  • IN_CLOSE_WRITE   Soubor otevřený pro zápis byl zavřen
  • IN_CLOSE_NOWRITE Soubor otevřený pouze pro čtení byl zavřen
  • IN_CREATE        Soubor/adresář byl vytvořen
  • IN_DELETE        Soubor/adresář byl smazán
  • IN_DELETE_SELF   Byl smazán přímo sledovaný adresář
  • IN_MODIFY        Soubor byl změněn
  • IN_MOVE_SELF     Sledovaný adresář byl přesunut
  • IN_MOVED_FROM    Soubor byl přesunut z adresáře
  • IN_MOVED_TO      Soubor byl přesunut do adresáře
  • IN_OPEN          Soubor byl otevřen

Proměnné, které v zápisu pravidel incrontabu můžete použít jako argumenty pro vaše skripty:

  • $$ - znak $
  • $@ - cesta ke sledovanému adresáři. Zde pozor, je to řetězec, který jste zadali do vašeho pravidla vy a pokud budete chtít jednoduše spojit tuto proměnnou s tou následující (↓), dáte na konec cesty i lomítko
  • $# - jméno souboru, kterého se událost týká
  • $% - typ události textově
  • $& - typ události numericky


Příklad - Akční adresář pdf-to-png - 2 

Pravidlo pro výše zmíněný příklad s rozkladem pdf souborů může vypadat takto:

$HOME/pdf-to-png/  IN_CLOSE_WRITE  /cesta/k/pdf-to-png-2.sh $@$#

Vlastní skript:

pdf-to-png-2.sh
#!/bin/bash

if grep -iqE '\.pdf$' <<< $@
  then nice convert -density 150 "$@" "$@.png" && rm "$@"
fi


Schválně jsem použil jinou podmínku, než minule, tahle je lepší, univerzálnější. Také jsem přidal příkaz nice, který zajistí, aby proces konverze měl nižší prioritu, než běžné procesy a tak když bude CPU potřeba pro důležitější záležitosti, nebude ostatní blokovat.


Závěrem

Vytvořit si třeba na pracovní ploše speciální adresáře, které by automaticky zpracovávaly vložené soubory různými způsoby, to je lákavá záležitost. Takový adresář nemusí být jednoúčelový, může podle koncovek souborů rozhodovat, co se souborem provede. Pomocí systémových notifikací se mužete nechat informovat o změnách v důležitých souborech, zkrátka spousta zábavy a v neposlední řadě i užitku :)

V repozitářích je ještě pár dalších aplikací založených na Inotify, můžete se podívat třeba příkazem

apt-cache search inotify


Odkazy k tématu:

http://www.kernel.org/doc/man-pages/online/pages/man7/inotify.7.html
http://inotify.aiken.cz/
http://www.ibm.com/developerworks/linux/library/l-ubuntu-inotify/

4 komentáře:

  1. Jejda, tak tohle je pěkný povídání, po všech těch kravinách ;-)

    OdpovědětVymazat
  2. Konečně jsem našel alternativu k Macovskému automatoru :o)

    Mám ale jeden problém pramenící čistě z neznalosti. Rád bych si udělal skript pro akci složky, která bude konvertit obrázky. Chápu, že [[ $file... definuje proměnnou s názvem souboru, když ale udělám [[ $file =~ \.jpg$ ]], soubor to prostě ve složce nenajde, v tom zápisu bude nějaká bota, ne?

    Díky

    OdpovědětVymazat
    Odpovědi
    1. Ten test není napsán příliš univerzálně, rozhodně bere jen přípony s malými písmeny, ale jinak funguje. =~ znamená 'obsahuje' daný řetězec a $ určuje, že musí být na konci řetězce. Jinak by se to dalo napsat lépe třeba takto:
      if grep -iq '.jpg$' <<< $file; then ...; else ...; fi

      Pak ještě pro jistotu dodám - ten skript musí běžet, jedině tak si může všimnout, že se do adresáře něco zapisuje. Nefunguje to na soubory, které již v adresáři jsou.

      Pro nějaké trvalejší používání by bylo vhodné to napsat lépe celé a ošetřit případné chyby při zpracování, aby se to při prnví příležitosti nerozpadlo..

      Vymazat
    2. A pravda [[ $file... mělo být [[ "$file"..., jinak to s názvy s mezerami bude nefunkční

      Vymazat

Zkuste prosím při komentováni používat místo volby Anonymní volbu Název/adresa URL, kde vyplníte nějakou přezdívku, adresu zadávat nemusíte. Vědět, které příspěvky jsou od jednoho člověka, je fajn. Díky.

Pokud by se vám náhodou odeslaný komentář na stránce nezobrazil, vytáhnu ho z koše hned jak si toho všimnu. I Google spam filter se občas sekne.