Předchozí kapitola se zabývala základními funkcemi pro operace vstupu a výstupu. Nyní se budeme zabývat dalšími možnostmi systému souborů. Hlavně se budeme zabývat celkovou strukturou, symbolickými linky a metodami procházení hierarchií stromu.
#include <sys/types.h> #include <sys/stat.h> |
int stat (const char *pathname, struct stat *buf); int fstat (int filedes, struct stat *buf); int lstat (const char *pathname, struct stat *buf); |
Všechny tři vrací: 0 když OK, -1 při chybě |
Funkce vracejí informační strukturu o daném souboru. stat získá informace o souboru daném cestou, fstat získá informace o již otevřeném souboru, lstat je podobná stat, ale když se jedná o symbolický link, získá informace o tomto linku, nikoli o souboru, na který link ukazuje.
První argument pathname nebo filedes specifikuje soubor. Druhý argument je ukazatel na informační strukturu, kterou funkce vyplní.
struct stat{ mode_t st_mode; /* typ souboru & přístupová práva */ ino_t st_ino; /* číslo i-nodu */ dev_t st_dev; /* číslo zařízení (filesystem) */ dev_t st_rdev; /* číslo zařízení pro spec. soubory */ nlink_t st_nlink; /* počet odkazů (linků) */ uid_t st_uid; /* user ID */ gid_t st_gid; /* group ID */ off_t st_size; /* velikost v bajtech */ time_t st_atime; /* čas posledního přístupu */ time_t st_mtime; /* čas poslední modifikace */ time_t st_ctime; /* čas poslední změny statutu souboru */ long st_blksize; /* nejlepší velikost I/O bloku */ long st_blocks; /* počet alokovaných 512B bloků */ };
V unixu jsou vlastně všechna zařízení mapována jako speciální soubory -- z toho vyplývá i velké množství typů souborů.
Typ souboru se nejlépe zjistí použitím maker z tabulky 8.
Typ souboru | makro |
---|---|
regulární soubor | S_ISREG() |
adresář | S_ISDIR() |
znakový speciální soubor | S_ISCHR() |
blokový speciální soubor | S_ISBLK() |
FIFO | S_ISFIFO() |
symbolický link | S_ISLNK() |
soket | S_ISSOCK() |
Příklad:
Uvedený příklad tiskne informace o souborech zadaných z příkazového řádku.
#include <sys/types.h> #include <sys/stat.h> int main(int argc, char *argv[]) { int i; struct stat buf; char *ptr; for (i = 1; i < argc; i++) { printf("%s: ", argv[i]); if (lstat(argv[i], &buf) < 0) { err_ret("lstat error"); continue; } if (S_ISREG(buf.st_mode)) ptr = "regular"; else if (S_ISDIR(buf.st_mode)) ptr = "directory"; else if (S_ISCHR(buf.st_mode)) ptr = "character special"; else if (S_ISBLK(buf.st_mode)) ptr = "block special"; else if (S_ISFIFO(buf.st_mode)) ptr = "fifo"; #ifdef S_ISLNK else if (S_ISLNK(buf.st_mode)) ptr = "symbolic link"; #endif #ifdef S_ISSOCK else if (S_ISSOCK(buf.st_mode)) ptr = "socket"; #endif else ptr = "** unknown mode **"; printf("%s\n", ptr); } exit(0); }
Každý proces má šest nebo více identifikačních čísel (viz tab. 9).
real user ID real group ID |
kdo skutečně jsme |
effective user ID effective group ID supplementary group ID |
používané pro test přístupových práv |
saved set-user-ID saved set-group-ID |
uschované funkcí exec |
Hodnota st_mode určuje přístupová práva k souboru. K dispozici jsou opět makra pro test těchto podmínek.
Pro přístup k souborům platí určitá pravidla:
Jádro testuje práva vždy při otevření, smazání nebo vytvoření souboru. Podmínky, za kterých jádro připustí operaci se souborem, jsou následující:
Jak jsme popsali dříve, jádro vykonává testy pro přístup k souboru. Tento test můžeme spustit sami, využitím funkce access.
#include <unistd.h> |
int access (const char *pathname, int *mode); |
Vrací: 0 když OK, -1 při chybě |
První parametr určuje soubor. Druhý parametr specifikuje druh testu práv. Můžete použít něco (nebo pomocí | i vše) z tabulky 10.
mode | Popis |
---|---|
R_OK | test čtení |
W_OK | test pro zápis |
X_OK | test pro spuštění |
F_OK | test existence souboru |
Příklad:
Tento program dostatečně ilustruje funkci access.
#include <sys/types.h> #include <fcntl.h> int main(int argc, char *argv[]) { if (argc != 2) err_quit("usage: a.out <pathname>"); if (access(argv[1], R_OK) < 0) err_ret("access error for %s", argv[1]); else printf("read access OK\n"); if (open(argv[1], O_RDONLY) < 0) err_ret("open error for %s", argv[1]); else printf("open for reading OK\n"); exit(0); }
Funkce umask je obdobná funkci umask shellu. Jedná se o nastavení masky vytvářených souborů. Funkce umask tedy nastaví masku vytvářených souborů a vrátí její předchozí hodnotu.
#include <sys/types.h> #include <sys/stat.h> |
mode_t umask (mode_t *cmask); |
Vrací: předchozí masku |
Většinou se maska nastaví jen jednou při přihlášení a pak se nemění.
Příklad:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { umask(0); if (creat("foo", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) < 0) err_sys("creat error for foo"); umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (creat("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) < 0) err_sys("creat error for bar"); exit(0); }
Pomocí těchto funkcí můžeme změnit přístupová práva existujících souborů. Rozdíl mezi funkcemi je jen ve stavu souboru (otevřený/uzavřený).
#include <sys/types.h> #include <sys/stat.h> |
int chmod (const char *pathname, mode_t mode); int fchmod (int filedes, mode_t mode); |
Obě vrací: 0 když OK, -1 při chybě |
K dispozici jsou opět makra definovaná v <sys/stat.h>.
Příklad:
#include <sys/types.h> #include <sys/stat.h> int main(void) { struct stat statbuf; /* turn on set-group-ID and turn off group-execute */ if (stat("foo", &statbuf) < 0) err_sys("stat error for foo"); if (chmod("foo", (statbuf.st_mode & S_IXGRP) | S_ISGID) < 0) err_sys("chmod error for foo"); /* set absolute mode to "rw-r--r--" */ if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) err_sys("chmod error for bar"); exit(0); }
Tento příznak má zajímavou historii. V prvních verzích Unixu sloužil k uchovávání programu ve swapovací oblasti po ukončení -- další spuštění bylo rychlejší. Proto také zkratka S_ISVTX (save-text). Dnešní systémy ho již v tomto významu nepotřebují. Proto dnes slouží k něčemu jinému. Jestliže je sticky bit nastaven nad adresářem, je možné, aby všichni pracovali se soubory adresáře i bez odpovídajících oprávnění.
Tyto funkce slouží ke změně vlastníka souboru (změně UID, GID)
#include <sys/types.h> #include <unistd.h> |
int chown (const char *pathname, uid_t *owner, gid_t
*group); int fchown (int filedes, uid_t *owner, gid_t *group); int lchown (const char *pathname, uid_t *owner, gid_t *group); |
Všechny tři vrací: 0 když OK, -1 při chybě |
Člen st_size struktury stat udává velikost souboru v bajtech. Pro regulární soubor je přípustná i délka 0 -- prvním znakem je znak konce souboru. Unix také podporuje předávání informací o fyzické velikosti souboru, tj. kolik a jaké bloky obsahuje (st_blksize, st_blocks). Pozor na praktické použití -- velikosti bloků mohou být rozdílné.
V určitých případech můžeme požadovat zaříznutí souboru na určitou délku pomocí funkce truncate.
#include <sys/types.h> #include <unistd.h> |
int truncate (const char *pathname, off_t length); int ftruncate (int filedes, off_t length); |
Obě vrací: 0 když OK, -1 při chybě |
Pro pochopení filozofie linků musíme být obeznámeni se strukturou systému souborů. Různé současné systémy používají různý způsob reprezentace -- pro sjednocení se vrátíme zpět k Verzi 7.
Předpokládejme, že disk je rozdělen do různých partitions, na každé partition je nějaký systém souborů (filesystem), který mj. obsahuje seznam i-nodů a data souborů a adresářů, jak ukazauje obr. 4.
Obrázek 4:
Struktura systému souborů
Na jeden fyzický soubor (tj. na stejný i-node) může ukazovat více adresářových položek. Tyto se vytvoří pomocí tzv. pevného linku.
#include <unistd.h> |
int link (const char *existingpath, const char newpath); |
Vrací: 0 když OK, -1 při chybě |
Funkce vytvoří novou položku v adresáři (newpath), která odkazuje na stávající položku (existingpath). Pouze superuživatel může provést link na adresář.
Pro zrušení odkazu slouží unlink.
#include <unistd.h> |
int unlink (const char *pathname); |
Vrací: 0 když OK, -1 při chybě |
Funkce odstraní položku v adresáři a dekrementuje počítadlo odkazů na daný fyzický soubor.
Příklad:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { if (open("tempfile", O_RDWR) < 0) err_sys("open error"); if (unlink("tempfile") < 0) err_sys("unlink error"); printf("file unlinked\n"); sleep(15); printf("done\n"); exit(0); }
Další funkce už jen stručně:
#include <stdio.h> |
int remove (const char *pathname); |
Vrací: 0 když OK, -1 při chybě |
Funkce remove se pro soubor chová jako unlink, pro adresář jako rmdir (viz rmdir).
#include <stdio.h> |
int rename (const char *oldname, const char *newname); |
Vrací: 0 když OK, -1 při chybě |
Funkce rename funguje jako příkaz rm.
Symbolický link vytvoříme funkcí symlink.
#include <unistd.h> |
int symlink (const char *actualpath, const char *sympath); |
Vrací: 0 když OK, -1 při chybě |
Vytvoří se položka sympath, která bude odkazovat na actualpath. Při vytváření nemusí actualpath existovat.
Protože funkce open následuje symbolický link, potřebuje způsob, jak otevřít link samotný. Na to je funkce readlink.
#include <unistd.h> |
int readlink (const char *pathname, char *buf, int bufsize); |
Vrací: počet přečtených bajtů když OK, -1 při chybě |
Adresáře lze vytvořit pomocí mkdir, zrušit pomocí rmdir.
#include <sys/types.h> #include <sys/stat.h> |
int mkdir (const char *pathname, mode_t *mode); |
Vrací: 0 když OK, -1 při chybě |
Tato funkce vytvoří prázdný adresář. Automaticky se vytvoří položky . (tečka) a .. (tečka-tečka).
Prázdný adresář můžeme zrušit pomocí funkce rmdir.
#include <unistd.h> |
int rmdir (const char *pathname); |
Vrací: 0 když OK, -1 při chybě |
Pro získání základních informací o souborech musíme vědět, jaké soubory se v adresářích nacházejí. K tomu slouží funkce pro čtení adresářů. Konkrétní struktura adresářů je implementačně závislá, ale způsob práce s nimi je obecný.
#include <sys/types.h> #include <dirent.h> |
DIR *opendir (const char *pathname); |
Vrací: ukazatel když OK, jinak NULL |
struct dirent *readdir (DIR *dp); |
Vrací: ukazatel když OK, jinak NULL |
void rewinddir (DIR *dp); |
int closedir (DIR *dp); |
Vrací: 0 když OK, -1 při chybě |
Struktura, ve které jsou uloženy informace vypadá následovně:
struct dirent { ino_t d_ino; /* číslo i-nodu */ char d_name[NAME_MAX + 1]; /* jméno souboru ukončené NULL */ }
Tyto operace jsou sice dostatečné pro zjištění obsahu adresáře, ale potřebujeme také prostředky pro procházení stromovou strukturou. V Systemu V existuje funkce ftw (file-tree-walking), která právě toto provádí. Tato funkce rekurzivně volá uživatelem definovanou funkci pro každou položku v adresáři. Nedostatek této funkce spočívá v tom, že na každou položku aplikuje stat, a tudíž následuje i symbolické linky. Opravená verze této funkce se jmenuje nftw.
Použití nejlépe osvětlí příklad. Mohli bychom sice použít nftw, ale použijeme radši vlastní algoritmus.
Příklad:
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
typedef int Myfunc(const char *, const struct stat *, int);
/* function type that's called for each filename */
static Myfunc myfunc;
static int myftw(char *, Myfunc *);
static int dopath(Myfunc *);
static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot;
int
main(int argc, char *argv[])
{
int ret;
if (argc != 2)
err_quit("usage: ftw <starting-pathname>");
ret = myftw(argv[1], myfunc); /* does it all */
if ( (ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock) == 0)
ntot = 1; /* avoid divide by 0; print 0 for all counts */
printf("regular files = %7ld, %5.2f %%\n", nreg, nreg*100.0/ntot);
printf("directories = %7ld, %5.2f %%\n", ndir, ndir*100.0/ntot);
printf("block special = %7ld, %5.2f %%\n", nblk, nblk*100.0/ntot);
printf("char special = %7ld, %5.2f %%\n", nchr, nchr*100.0/ntot);
printf("FIFOs = %7ld, %5.2f %%\n", nfifo, nfifo*100.0/ntot);
printf("symbolic links = %7ld, %5.2f %%\n", nslink,nslink*100.0/ntot);
printf("sockets = %7ld, %5.2f %%\n", nsock, nsock*100.0/ntot);
exit(ret);
}
/*
* Descend through the hierarchy, starting at "pathname".
* The caller's func() is called for every file.
*/
#define FTW_F 1 /* file other than directory */
#define FTW_D 2 /* directory */
#define FTW_DNR 3 /* directory that can't be read */
#define FTW_NS 4 /* file that we can't stat */
static char *fullpath; /* contains full pathname for every file */
static int /* we return whatever func() returns */
myftw(char *pathname, Myfunc *func)
{
fullpath = path_alloc(NULL); /* malloc's for PATH_MAX+1 bytes */
/* ({Prog pathalloc}) */
strcpy(fullpath, pathname); /* initialize fullpath */
return(dopath(func));
}
/*
* Descend through the hierarchy, starting at "fullpath".
* If "fullpath" is anything other than a directory, we lstat() it,
* call func(), and return. For a directory, we call ourself
* recursively for each name in the directory.
*/
static int /* we return whatever func() returns */
dopath(Myfunc* func)
{
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret;
char *ptr;
if (lstat(fullpath, &statbuf) < 0)
return(func(fullpath, &statbuf, FTW_NS)); /* stat error */
if (S_ISDIR(statbuf.st_mode) == 0)
return(func(fullpath, &statbuf, FTW_F)); /* not a directory */
/*
* It's a directory. First call func() for the directory,
* then process each filename in the directory.
*/
if ( (ret = func(fullpath, &statbuf, FTW_D)) != 0)
return(ret);
ptr = fullpath + strlen(fullpath); /* point to end of fullpath */
*ptr++ = '/';
*ptr = 0;
if ( (dp = opendir(fullpath)) == NULL)
return(func(fullpath, &statbuf, FTW_DNR));
/* can't read directory */
while ( (dirp = readdir(dp)) != NULL) {
if (strcmp(dirp->d_name, ".") == 0 ||
strcmp(dirp->d_name, "..") == 0)
continue; /* ignore dot and dot-dot */
strcpy(ptr, dirp->d_name); /* append name after slash */
if ( (ret = dopath(func)) != 0) /* recursive */
break; /* time to leave */
}
ptr[-1] = 0; /* erase everything from slash onwards */
if (closedir(dp) < 0)
err_ret("can't close directory %s", fullpath);
return(ret);
}
static int
myfunc(const char *pathname, const struct stat *statptr, int type)
{
switch (type) {
case FTW_F:
switch (statptr->st_mode & S_IFMT) {
case S_IFREG: nreg++; break;
case S_IFBLK: nblk++; break;
case S_IFCHR: nchr++; break;
case S_IFIFO: nfifo++; break;
case S_IFLNK: nslink++; break;
case S_IFSOCK: nsock++; break;
case S_IFDIR:
err_dump("for S_IFDIR for %s", pathname);
/* directories should have type = FTW_D */
}
break;
case FTW_D:
ndir++;
break;
case FTW_DNR:
err_ret("can't read directory %s", pathname);
break;
case FTW_NS:
err_ret("stat error for %s", pathname);
break;
default:
err_dump("unknown type %d for pathname %s", type, pathname);
}
return(0);
}
Každý proces má pracovní adresář, který se většinou při přihlášení čte ze souboru /etc/passwd. Pracovní adresář měníme následujícími funkcemi:
#include <unistd.h> |
int chdir (const char *pathname); int fchdir (int filedes); |
Vrací: 0 když OK, -1 při chybě |
Ke zjištění aktuálního pracovního adresáře slouží getcwd.
#include <unistd.h> |
int *getcwd (char *buf, size_t size); |
Vrací: buf když OK, NULL při chybě |
Před voláním funkce je nutné alokovat buffer buf.
Klasická implementace unixu má pro většinu I/O operací v jádře vyrovnávací paměť (cache). V praxi to probíhá tak, že data z příkazu write se zapíší do vyrovnávací paměti. Vyrovnávací paměť pak každých max. 30 sekund démon zapíše na disk. Potřebujeme-li synchronizovat obsah souboru apod., musíme tohoto démona vyvolat sami. K tomu slouží funkce sync.
#include <unistd.h> |
void sync (void); int fsync (int filedes); |
Vrací: 0 když OK, -1 při chybě |
Ladislav Dobias