# Základy práce se soubory Soubor je základní jednotkou práce s perzistentními daty. Rozhraní pro práci se soubory je ve většině operačních systémů velmi důležité, a často pokrývá mnohem víc, než jen obyčejné soubory. Více o souborech a jejich organizaci se dovíte ve třetí kapitole skript. Z pohledu programátora je důležité rozhraní, které práci se soubory zprostředkuje – centrálním prvkem je zde «popisovač otevřeného souboru». Jedná se o hodnotu (číslo), která je pevně svázaná s «otevřeným souborem».¹ Samotný popisovač nemá žádnou vnitřní strukturu, kterou bychom mohli zkoumat, je definován pouze operacemi, které je nad ním možné provádět (jedná se v tomto smyslu o abstraktní datový typ). Těmi hlavními jsou čtení (‹read›) a zápis (‹write›) bajtů. Pozor, na jeden otevřený soubor může odkazovat více než jeden popisovač (s různými číselnými hodnotami). Zároveň může být stejný soubor otevřen více než jednou. Některé typy «otevřených souborů» (zejména obyčejné soubory) si pamatují «pozici» – místo, na kterém proběhne další operace ‹read› nebo ‹write›. Konečně se v této kapitole budeme zabývat tím, že podprogramy, které zprostředkují služby operačního systému, mohou v naprosté většině případů «selhat». Tuto skutečnost obvykle indikují speciální «návratovou hodnotou» a konkrétní problém upřesňuje proměnná ‹errno›.³ Ukázky: 1. ‹copy› – čtení a zápis do obyčejného souboru, 2. ‹filter› – kopírujeme pouze některé záznamy, 3. ‹hello› – standardní vstup a výstup. Přípravy: 1. ‹count› – počítání zadaného bajtu na vstupu, 2. ‹bwconv› – převod obrázku ze stupňů šedi na černobílý, 3. ‹catfd› – použití ‹read› a ‹write› 4. ‹pick› – vykopíruje zadaný rozsah bajtů z každého záznamu, 5. ‹align› – zarovnaný zápis „zubaté“ matice, 6. ‹split› – rozdělení záznamů ze vstupu na dva výstupy. Rozšířené: 1. ‹min› – výpis záznamu s nejmenším jednobajtovým klíčem, 2. ‹bcount› – počítání různých bajtů které se objeví na vstupu, 3. ‹blimit› – výpis každého bajtu nejvýše ⟦n⟧-krát, 4. ‹otp› – „one time pad“ na dvou vstupech, 5. ‹grep› – přepis záznamů, které obsahují zadanou hodnotu, 6. ‹togray› – † konverze barevného obrázku na černobílý. ¹ Nikoliv s cestou, nebo jiným nepřímým pojmenováním nebo označením. Identita již otevřeného souboru se nám nemůže „změnit pod rukama“. ² Tuto proměnnou nelze striktně vzato označit za globální, protože každé vlákno má vlastní verzi. Implementace takové proměnné je komplikovaná a jde mimo rámec tohoto kurzu. ## Systémová volání Pro čtení a zápis dat (sekvencí bajtů) slouží operace ‹read› a ‹write›. Pro textový (tzv. formátovaný) zápis je pak určena knihovní procedura ‹dprintf›¹ – vnitřně používá pro výstup na zadaný popisovač volání ‹write›. Bude se nám hodit také volání ‹lseek›. ### ‹write› Protože výstup je jednodušší než vstup, začneme systémovým voláním ‹write›. Má tři parametry: • ‹fd› – popisovač, do kterého hodláme zapisovat, • ‹buf› – ukazatel na paměť, kde jsou uložena data určená k zápisu, • ‹nbytes› – počet bajtů, které si přejeme zapsat. Volání ‹write› pak provede zápis bajtů uložených v polouzavřeném rozsahu adres od ‹buf› po ‹buf + nbytes› (všechny tyto adresy musí být platné, a je velice žádoucí, aby na nich uložené bajty byly inicializované). Návratová hodnota volání ‹write› je počet skutečně zapsaných bajtů. Pro «blokující popisovače» (to budou všechny, které v prvních třech kapitolách potkáme) může být výsledek: • -1 značí chybu zápisu, • kladné číslo menší než ‹nbytes› – pro kompletní zápis nebyl dostatek místa a další volání ‹write› selže s výsledkem -1, • přesně ‹nbytes› – zápis proběhl bez problémů, • konečně 0 může nastat pouze pro nulové ‹nbytes›. První dva případy bychom měli rozlišovat – pro nekompletní zápis totiž není nastaveno ‹errno› a případný výpis důvodu selhání (např. pomocí ‹err› nebo ‹warn›) by byl přinejlepším matoucí – to se týká i situace, kdy ukončujeme podprogram (volající bude očekávat platné ‹errno›). Nejrobustnějším řešením je pokusit se v takovém zápisu pokračovat, nicméně pro účely tohoto kurzu to není nutné a vystačíme si s indikací chyby bez nastavené hodnoty ‹errno›. ### ‹read› Vstup ze souborů je zprostředkován systémovým voláním ‹read›. Použití ‹read› je poněkud komplikovanější, protože (v obecném případě) nemůžeme dopředu vědět, kolik dat se v souboru nachází. Proto musíme být připraveni data zpracovávat po částech. Volání má tři parametry: • ‹fd› – popisovač, ze kterého hodláme číst, • ‹buf› – ukazatel na paměť, kam budou uložena přečtená data, • ‹nbytes› – maximální počet bajtů, které si přejeme načíst. Volání ‹read› načte zadaný počet bajtů (nebo méně, není-li zadaný počet k dispozici) do předané paměti. Dosavadní obsah takto předané paměti bude voláním přepsán. Pro «blokující popisovače» může být výsledek: • -1 značí chybu čtení, • kladné číslo ⟦n⟧ menší nebo rovno ‹nbytes› – ze souboru bylo načteno ⟦n⟧ bajtů, • 0 znamená, že soubor již žádné další bajty neobsahuje. Pro «obyčejné soubory» je navíc zaručeno, že ⟦n⟧ bude ostře menší než ‹nbytes› pouze na konci souboru (tzn. následovné volání ‹read› vrátí nulu – za předpokladu, že soubor není souběžně modifikován). «Pozor», toto neplatí obecně – v pozdějších kapitolách budeme muset brát v potaz i tzv. «krátké čtení». ¹ Podobá se funkci ‹printf›, kterou již možná znáte z předchozího programování v jazyce C. Prozatím ‹dprintf› nepotřebujeme k řešení příkladů (a samozřejmě vždy se lze bez ‹dprintf› obejít, máme-li ‹write›), ale může se hodit pro ladicí výpisy atp. ### ‹lseek› Součástí otevřeného souboru je tzv. ukazatel pozice – adresa (offset) prvního bajtu, který bude následovným voláním ‹read› načten (resp. následovným voláním ‹write› přepsán). Volání ‹read› a ‹write› tento ukazatel automaticky posunou na konec přečtené (zapsané) oblasti. Sekvence volání ‹read› (resp. ‹write›) tak postupně načítá (zapisuje) po sobě jdoucí bloky dat. Může ale nastat situace, kdy je potřeba soubor načítat v jiném pořadí (např. je potřeba po přečtení celého souboru podruhé načíst data z jeho začátku). Pro posuv ukazatele pozice slouží systémové volání ‹lseek›. Má tři parametry: • ‹fd› – popisovač souboru, • ‹offset› – počet bajtů, o které má být pozice posunuta, • ‹whence› – jedna z těchto hodnot: ◦ ‹SEEK_SET› – ‹offset› se počítá od začátku souboru, ◦ ‹SEEK_END› – ‹offset› se počítá od konce souboru, ◦ ‹SEEK_CUR› – ‹offset› se počítá od aktuální pozice. Návratová hodnota je pak výsledná pozice «od začátku souboru» (bez ohledu na předanou hodnotu ‹whence›). Není-li možné ukazatel posunout, výsledek je -1. ## Knihovní podprogramy Krom systémových volání budeme používat standardní knihovní podprogramy (ať už dle standardu jazyka C nebo dle normy POSIX). Detailní popis naleznete jako vždy v manuálových stránkách. Tento týden si vystačíme s těmito: • ‹memcpy› – kopíruje zadaný počet bajtů z jedné adresy na jinou (zdrojová a cílová oblast se nesmí překrývat), • ‹memcmp› – srovná obsah dvou zadaných oblastí paměti stejné velikosti (vrací 0 jsou-li identické), • ‹memchr› – vrátí ukazatel na první výskyt zadaného bajtu v zadané oblasti paměti (nebo nulový ukazatel, neexistuje-li takový).