Autor zadání | Miroslav Jaroš |
---|
V tomto cvičení si ukážeme, jak přistupovat k souborům v POSIXu.
POSIX je rozhraní UN*Xových operačních systémů. Jedná se tedy o velmi
nízkoúrovňové rozhraní, pomocí kterého je např. na Linuxu nebo macOS
implementována standardní knihovna (stdlib
).
Na rozdíl od stdlib
, kde hlavní identifikátor souboru je struktura FILE
,
používá POSIX pro přístup k souboru tzv. file descriptory, česky řečeno
popisovače souborů.
File descriptor je celé číslo, které odkazuje do tabulky otevřených souborů udržované operačním systémem a na základě popisovače požaduje po OS akci na souboru (např. čtení, zápis, přejmenování, smazání, …).
Pro přístup k souborům existují funkce:
-
open(2)
→ alternativa kfopen(3)
-
close(2)
→ alternativa kfclose(3)
-
read(2)
→ alternativa kfread(3)
-
write(2)
→ alternativa kfwrite(3)
Přístup k souboru je tedy velmi podobný jako v stdlib
, s tím rozdílem, že
využití POSIXu nám umožňuje i další zásahy do souboru, jako je změna majitele,
změna uživatele, čtení adresáře atd.
Po stažení kostry zjistíte, že tento úkol neobsahuje přímo konfigurační soubor
pro program Doporučujeme vám pro psaní kódu používat terminálové rozhraní, protože se v něm bude kód lépe překládat a budete mít snazší přístup k dokumentaci. Nicméně, pokud nevěříte svým schopnostem práce v shellu, můžete si ve svém IDE vytvořit vlastní projekt. |
Řešení všech úkolů pište do souboru |
Úkol 1: cat
Upravte následující kód tak, aby používal POSIXové funkce pro přístup k souborům.
#include <stdio.h>
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "Invalid amount of arguments\n");
return 1;
}
FILE *file = fopen(argv[1], "r");
if (file == NULL) {
perror(argv[1]);
return 1;
}
char buffer[1024] = { '\0' };
size_t size = 0;
while ((size = fread(buffer, 1, 1024, file)) > 0) {
if (fwrite(buffer, 1, size, stdout) != size) {
perror("fwrite");
if (fclose(file) != 0) {
perror(argv[1]);
}
return 1;
}
}
if (ferror(file) != 0) {
perror(argv[1]);
if (fclose(file) != 0) {
perror(argv[1]);
}
return 1;
}
return 0;
}
Využijte manuálové stránky pro zjištění, jak se tyto funkce chovají: open(2)
,
close(2)
, read(2)
, write(2)
. Pro otevření manuálové stránky využijte
příkazu man
, například pro manuál k open(2)
použijte příkaz man 2 open
.
Navíc upravte konstanty, které funkce main()
vrací, na makra EXIT_SUCCESS
a EXIT_FAILURE
.
Všimněte si rozdílu v zadávaní módu. Na rozdíl od |
Dejte si pozor na možné návratové hodnoty funkce |
Úkol 2: Informace o souboru
Vaším úkolem je rozšířit předchozí program o výpis informací o souboru, které
zjistíte pomocí funkce fstat(2)
po vypsaní souboru. Informace zapište v
následujícím formátu:
File size: SIZE Date of last access: DATE UID of owner: UID GID of owner: GID
Pro vypsání data použijte funkci ctime(3)
.
Úkol 3: Refactoring
Vytvořte funkce
int print_file(const char *path);
int print_stats(const char *path);
Do těchto funkcí přesuňte funkcionalitu vaší funkce main()
. Upravte váš program,
aby přijímal z příkazové řádky přepínače -s
a -p
, které budou vždy jako
první argument, a 1
až n
souborů. Na základě zvoleného přepínače použijte
nad danými soubory funkce:
-
-s
print_stats()
-
-p
print_file()
Pokud funkce selže, pomocí perror(3)
vypište na stderr
jaká chyba nastala a
pokračujte dál. Nezapomeňte, že funkce fstat(2)
bere file descriptor, existuje
však i funkce stat(2)
, která přijímá místo file descriptoru cestu k souboru.
Úkol 4: Výpis adresářů
Nyní máte program, který dokáže vypsat statistiky a obsah daných souborů. Vaším úkolem bude rozšířit jeho funkcionalitu o práci nad adresáři.
Vytvořte funkci
int read_directory(const char *path, int (*func)(const char *));
Tato funkce přečte obsah zadaného adresáře – argument path
– a na jednotlivé
potomky aplikuje funkci func
, pokud jsou soubory. Pokud nejsou, vypíše na
standardní výstup informaci, že byl nalezen neregulární soubor (například
adresář), a pokračuje dále bez zanoření.
Budete potřebovat funkce stat(2)
, readdir(3)
, opendir(3)
, closedir(3)
a
makra S_ISREG
apod. Tato makra jsou popsaná v manuálové stránce pro hlavičkový
soubor sys/stat.h
(man sys_stat.h
).
Jednoduché procházení složky může vypadat například takto. Kód je převzatý z přednášky.
#include <dirent.h>
void posix_print_files(const char* path) {
DIR *dir = NULL;
if ((dir = opendir(path)) != NULL) { // connect to directory
struct dirent *dir_entry = NULL;
while ((dir_entry = readdir(dir)) != NULL) { // obtain next item
printf("File %s\n", dir_entry->d_name); // get d_name
}
closedir(dir); // finish work with directory
}
}
Mějte na paměti, že ve struktuře dirent
je uložen pouze samotný název souboru,
nikoliv jeho absolutní či relativní cesta.
Na závěr rozšiřte svůj main()
o detekci adresáře nad argumenty z příkazové
řádky. Pokud byl na příkazové řádce předán adresář, váš program vypíše:
Scanning directory %s -------------------------------- /* obsah vypisu, zavolani funkce read_directory */ --------------------------------
Teorie - Rozdíl mezi oddíly manuálových stránek
Jistě jste si všimli, že v průběhu tohoto cvičení nekonzistentně vybíráme oddíly manuálových stránek zdánlivě nahodile. Jaký je tedy pro to důvod?
Obecně manuálové stránky jsou rozděleny do oddílů dle toho, co popisují. V našem případě jsou použity následující oddíly:
-
Oddíl 2., který popisuje systémová volání,
-
Oddíl 3., který popisuje funkce POSIX a standardní knihovny.
Důvodem, proč v rámci tohoto cvičení nepoužíváme pouze třetí oddíl, ačkoliv
například man 3 open
je taktéž validní, je detail popisu funkcí. V tomto
případě totiž třetí oddíl pouze popisuje chování funkcí dle standardu jazyka C
respektive dle POSIXu, což je sice hezké, ale pro konkrétní použití je vhodné
mít detailní popis chování funkce na cílové platformě. Porovnejme například
rozdíl popisu funkce stat()
ve třetím oddíle:
DESCRIPTION Refer to fstatat().
která vypadá následovně:
DESCRIPTION The stat() function shall obtain information about the named file and write it to the area pointed to by the buf argument. The path argument points to a pathname naming a file. Read, write, or exe‐ cute permission of the named file is not required. An implementation that provides additional or alternate file access control mechanisms may, under implementation-defined conditions, cause stat() to fail. In particular, the system may deny the existence of the file specified by path. If the named file is a symbolic link, the stat() function shall continue pathname resolution using the contents of the symbolic link, and shall return information pertaining to the resulting file if the file exists. The buf argument is a pointer to a stat structure, as defined in the <sys/stat.h> header, into which information is placed concerning the file.
a ve druhém oddíle:
DESCRIPTION These functions return information about a file, in the buffer pointed to by statbuf. No permissions are required on the file itself, but—in the case of stat(), fstatat(), and lstat()—execute (search) permission is required on all of the directories in pathname that lead to the file. stat() and fstatat() retrieve information about the file pointed to by pathname; the differences for fstatat() are described below. lstat() is identical to stat(), except that if pathname is a symbolic link, then it returns information about the link itself, not the file that the link refers to. fstat() is identical to stat(), except that the file about which information is to be retrieved is specified by the file descriptor fd. The stat structure All of these system calls return a stat structure, which contains the following fields: struct stat { dev_t st_dev; /* ID of device containing file */ // and many more
Jak jste si jistě všimli, druhý oddíl obsahuje daleko přesnější popis funkcí pro jejich použití, protože je vázán již na implementaci POSIX v Linuxu.
Poznámka k formátování
Zdrojové kódy a referenční implementace jsou mírně nezvykle naformátované a neodpovídají zavedeným konvencím předmětu. U tohoto cvičení se jedná o náš záměr, protože je zaměřeno na práci s POSIX rozhraním primárně v OS Linux. Z tohoto důvodu jsme se rozhodli využít Linux Kernel Coding Style jako způsob formátování.
Při podrobnějším zkoumání zjistíte, že styl PB071 se tomuto stylu velmi blíží s jednou velice podstatnou výjimkou: LKCS vyžaduje odsazení tabulátorem šířky 8. Abychom citovali samotného Linuse Torvaldse:
The whole idea behind indentation is to clearly define where a block of control starts and ends. Especially when you’ve been looking at your screen for 20 straight hours, you’ll find it a lot easier to see how the indentation works if you have large indentations."
Linux Kernel Coding Style
A s tímto prohlášením nelze z pozice cvičícího než souhlasit.[1]