Elrendelő Fájllezárás |
(Mandatory File Locking) |
a Linux Operációs Rendszeren |
Andy Walker < andy@lysaker.kvaerner.no> |
15 April 1996 |
Magyar fordítás: Murzsa Norbert |
A mandatory locking - a továbbiakban elrendelő lezárás - egy a kernel által kierőszakolt fájllezárás, szemben a szokásos kooperatív lezárásokkal, amelyek a processzek fájlhozzáférési szekvenciáját garantálják. Az alkalmazott fájllezárások az flock() és az fcntl() rendszer hívásokat (és a lockf() programkönyvtár rutint, amely az fnctl() öleli magában) használják. Általában az adott folyamat felelőssége, hogy ellenőrizze a frissíteni kívánt fájl lezárásait - mielőtt alkalmazná rá a saját zárolását -, a frissítés, majd a fájl zárolásának megszüntetése. Az egyik legáltalánosabban használt példa (a sendmail esetében talán ez az egyik legzavaróbb is) a felhasználói mailbox-okhoz való hozzáférés kérdése. A levél felhasználói közvetítőjének (mail user agent) és átviteli közvetítőjének (mail transfer agent) védekeznie kell a maibox-ok egyazonidejű frissítése ellen, és meg kell akadályoznia a mailbox olvasását addíg, amíg annak frissítése be nem fejeződött.
Egy tökéletes világban valamennyi folyamatnak alkalmaznia kellene és el kellene fogadnia egy kooperatív, vagy egy tanácsolt lezárási sémát. Ám a világ természetesen nem tökéletes és így egy rakás igen szegényesen megírt kód forog közkézen.
Próbálva nevet adni e problémának, a System V UNIX tervezői jelentkeztek egy "elrendelő" lezárási sémával, amely által az operációs rendszer kernelje blokkolni tudná a folyamatok azon fájlírási kísérleteit, amikor azt egy masik folyamat éppen olvassa vagy egy megosztásból adódóan zárolja, illetve blokkolná mindazon fájlolvasási és -írási kísérleteket is, amely fájlokat pillanatnyilag egy másik folyamat (process) tart írás vagy exkluzív lezárás alatt.
A System V elrendelő lezárási sémáját a már létező felhasználói kódok (user code) lehetséges kisebb káros hatásainak ellentételezésére szánták. Ez a séma az - egyes - elrendelő lezárásra kijelőlt fájlok megjelölésén alapult, felhasználva a már meglévő fcntl()/flockf() felületet a fájlok zárolására úgy, mint azt a hétköznapi, tanácsolt lezárások tették.
1. Megjegyzés: Az előző bekezdésben a "fájl" szó valójában nem fedi a teljes igazságot. A System V zárolási mechanizmusa az fcntl() rendszer híváson alapul. Az fnctl() alkotóelemei engedélyezik a fájlokban lévő adott byte területek zárolását, továbbá a teljes fájlét is, így az elrendelő lezárás szabályai szintén rendelkeznek byte szintű elaprozással.
2. Megjegyzés: A POSIX.1 nem határoz meg semmilyen követendő példát az elrendelő lezárás számára dacára annak, hogy az fcntl() zárolási séma a System V -ből ered. Az elrendelő lezárás sémájának definiciója a System V Felületi Meghatározás /System V Interface Definition=SVID/ 3-as Verziójában történt meg.
Egy fájl kijelölt az elrendelő lezárás számára, beállítva a csoportazonosító bitet magán a fájl információin belül, de törölve a futtatási jogot a csoport részére. Ez különben egy értelmetlen kombináció, amelyet a System V kivitelezői választottak azért, hogy döntésükkel ne idézzenek elő futási hibákat a már meglévő felhasználói programokban. Megjegyezzük, hogy a csoportazonosító bitet (group-id bit), általában automatikusan törli a kernel, amikor egy setgid-s fájl írása történik. Ez egy biztonsági intézkedés. A kernel változtatásokon ment keresztül, hogy felismerje a elrendelő lezárásra való kijelölés speciális esetét és tartózkodjon ezen bit törlésétől. Hasonképpen változtatásra került a kernel az irányban is, hogy ne futhasson elrendelő lezárási kijelölés setgid privilégiumokkal.
Számba vettem az elrendelő lezárás megvalósításait, amelyek elérhetők SunOS 4.1.x, SOlaris2.x és HP-UX 9.x rendszerek alatt.
Általánosan véve próbáltam megérteni e három hivatkozott rendszer kimutatott viselkedését. Ám igen széles anomáliákat találtam.
Valamennyi - a hivatkozásban megjelölt - rendszer megtagad minden - az open()-hez érkező -
rendszer hívást egy olyan fájl számára, amelyik felett egy másik folyamat (process)
rendelkezik be nem fejezett elrendelő lezárással. Ez a SVID 3 nem közvetlen megsértése, amely
azt jelenti, hogy csak azokat az open()-hez érkező hívásokat kellene megtagadni,
amelyek rendelkeznek az O_TRUNC
flag beállítással. A Linux megvalósítása az SVID definícióját követi,
amely a ,,Jó Dolog'' kategóriába esik, mióta csak a O_TRUNC
-kal érkező hívások képesek
módosítani a fájl tartalmát.
A HP-UX nem csak az elrendelő lezárásoknál (mandatory locks), de még az
O_TRUNC
flag-ü open() hívásoknál is tiltja a hozzáférést
a tanácsolt lezárásokkal (advisory locks) rendelkező fájlhoz, ami úgy tűnhet, hogy
ellentmond a POSIX.1-nek.
Az mmap() a másik érdekes eset. Valamennyi operációs rendszer megemlíti az mmap()-olt fájlra alkalmazott, meghíúsított elrendelő lezárásokat, ám a HP-UX tiltja az ilyen tipusú fájlok tanácsolt lezárásait (advisory locks) is. Valójában az SVID meghatározza ezt a paranoid HP-UX viselkedést.
Véleményem szerint csak a MAP_SHARED
tipusú mappolásokat kellene immunissá tenni
a lezárásokkal szemben és ebben az esetben csak az elrendelő lezárásoktól érkezők lennének
pillanatnyilag megvalósítva.
A SunOS reménytelen, hiszen nem becsüli még az O_NONBLOCK
flag-et sem
az elrendelő lezárások esetében, így olvassa és írja a zárolt fájlok valamennyi blokkját,
amikor azoknak EAGAIN
-t kellene visszaadniuk.
Attól tartok, hogy ez is csak egy olyan ezoterikus tér, mint bármi egyéb, ami csak annyira érvényes, amennyire az alábbi jelentéstan meghatározza.
1. Az elrendelő lezárások csak az fcntl()/lockf() zárolási felületeken keresztül valósíthatók meg - másszóval a System V/POSIX felületen keresztül. A BSD tipusú lezárások - használva az flock() függvényt -, sohasem vezetnek eredményre egy elrendelő lezárásban. 2. Amennyiben egy folyamat lezárt egy fájlterületet elrendelő olvasási lezárással, más folyamatok (processes) számára engedélyezett ezen terület olvasása. Ha ezen folyamatok közül bármelyik is megkísérli írni az adott területet, az blokkolásra kerül mindaddíg, amíg a lezárás meg nem szűnik, hacsak a folyamat (process) - amely megnyitotta a fájlt - nem az O_NONBLOCK
flag-gel lett megnyitva, ugyanis ebben az esetben a rendszer hívás haladéktalanul visszaérkezik egyAEAGAIN
hibastátusszal.3. Amennyiben egy folyamat lezárt egy fájlterületet elrendelőírási lezárással, valamennyi ezen területre vonatkozó olvasási és írási kisérlet blokkolásra kerül mindaddig, amíg a lezárás meg nemszűnik, hacsak a folyamat (process) - amely megnyitotta a fájlt - nem az O_NONBLOCK
flaggel lett megnyitva, mert ezesetben a rendszer hívás haladéktalanul azEAGAIN
hibastátusszal fog visszatérni.4. Megvívva az open() függvényt az O_TRUNC
változóval (flag) - vagy a create() függvényt - egy olyan - már létező - fájlhoz, amelyre elrendelő lezárás van érvényben más folyamatok (processes) tulajdonlásából kifolyólag, a kérés megtagadásra fog kerülni azEAGAIN
hibastátusszal.5. Megkísérelve alkalmazni az elrendelő lezárást egy olyan fájlra, amely memória mappolt (memoria mapped) és megosztott (shared) a MAP_SHARED
flag-ű mmap() függvénnyel, a kérés megtagadára fog kerülni azEAGAIN
hibastátusszal.6. Megkísérelve létrehozni egy olyan fájl osztott memória térképét (shared memory map) - a MAP_SHARED
flag-ű mmap() függvényen keresztül -, amelyre éppen elrendelő lezárás van érvényben, a kérés megtagadásra fog kerülni azEAGAIN
hibastátusszal.
Azok a rendszer hívások, amelyek magát a fájl tartalmát is módosítják és nem csak az inode-ot. Ilyen például a read(), a write(), a readv(), a writev(), az open(), a create(), az mmap(), a truncate() és az ftruncate() hívás. A truncate() és az ftruncate() függvények használatát fontolóra kellene venni az írást megvalósítani kívánó elrendelő lezárás szempontjából.
Az érintett terület általában egy fájlpoziciótól definiált az adott byteméretű olvasásra vagy írásra. A csonkoló (truncate) hívások számára ez a fájlhoz hozzáadott, illetve törölt byte-ok formájában kerül megadásra (tekintetbe kell vennünk a hozzáadott byte-okat, mint egy lezárást, amely kizárólag csak a "teljes fájl"-ra határozható meg inkább, mint egy meghatározott byteméretű területre.)
3. Megjegyzés: Természetesen, mohóságomban elkerülhette a figyelmemet néhány rendszer hívás, amelyet esetleg még szükséges megvizsgálni az elrendelő lezárás szempontjából. Kérlek értesíts erről, vagy amennyiben egy még tökéletesítésre szoruló rendszer hívást magad is javítottál, küldj egy patch-et nekem, vagy Linusnak.
Még a root sem képes hatástalanítani egy elrendelő lezárást, így az elszabadult processzek bosszút állhatnak, amennyiben kritikus fájlokat zárolnak. Ezt megelőzendő, változtassuk meg a fájl jogosultságait (kikapcsolva a setgid bitet), mielőtt megpróbálnánk olvasni vagy írni azt. Természetesen lehet trükközgetni akkor is, ha már egyszer a rendszer lefagyott :-(
A fordító megjegyzése:
Az itt olvasott leírás eredeti nyelvű, illetve frissített verziójához bárki szabadon hozzáférhet az éppen aktuális kerneldokumentációban.
Magyar fordítás: Murzsa Norbert