daląí předchozí obsah
Daląí: Systémové soubory Předchozí: Souborový vstup a výstup

  Soubory a adresáře





Úvod

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.



Funkce stat, fstat, lstat

#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ů       */
};



Typy souborů

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()
Tabulka 8: Zjiątění typu souboru

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);
}



Přístupová práva

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
Tabulka 9: UID a GID procesu

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í:

  1. Efektivní UID je 0 (superuľivatel)
  2. Efektivní UID je shodné s UID vlastníka souboru a je nastaven odpovídající typ masky.
  3. Efektivní GID je shodné s GID vlastníka souboru a je nastaven odpovídající bit masky.
  4. Je povolen přístup ostatním uľivatelům.


  Funkce access

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
Tabulka 10: Konstanty mode pro funkci access

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

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);
}



Funkce chmod a fchmod

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);
}

POZOR! Při nastavování S_SVTX (sticky bitu) musíte mít privilegia superuľivatele.



Sticky bit

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í.



Funkce chown, fchown a lchown

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ě



Velikosti souborů

Č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é.



Zaříznutí souboru

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ě



Systém souborů podrobněji

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.

Zde má být moc hezký obrázek 'filesyst', ąkoda, ľe ho nevidíte

Obrázek 4: Struktura systému souborů

Adresář
je speciální soubor, který obsahuje jméno souboru a číslo i-nodu daného souboru.

I-node
(moľná bychom mohli říkat informační uzel) obsahuje daląí informace o souboru, jako velikost, časy vytvoření, modifikace, přístupu, .... Také zde nalezneme jedno číslo, které ukazuje, kolik odkazů na tento soubor existuje. Těmto odkazům se říká pevné linky. Kdyľ toto číslo dosáhne nuly, je soubor pak smazán.



Funkce link, unlink, remove a rename

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.



Funkce symlink a readlink

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ě



  Funkce mkdir a rmdir

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ě



Čtení adresářů

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);
}


Funkce chdir, fchdir a getcwd

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.



Funkce sync a fsync

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ě



Cvičení

  1. Co se stane, kdyľ nastavíte masku práv vytváření souborů na 777 oktalově? Odpověď ověřte pomocí príkazu shellu umask.
  2. Ověřte, ľe zruąením práva pro čtení vlastníkem u souboru, který sami vlastníte, si sami zamezíte ve čtení tohoto souboru.
  3. V kapitole ?? naąe verze progamu ftw nikdy nemění adresář. Modifikujte tuto rutinu tak, aby prováděla chdir do kaľdého adresáře, a tedy umoľnovala volat lstat pouze se jménem souboru a nikoli s celou cestou.
    Aľ jsou vąechny poloľky v adesáři zpracovány, proveďte chdir(" "). Porovnejte čas provádění touto verzí programu a verzí v textu. Porovnejte časy při opakovaném pouľití stejného programu.


daląí předchozí obsah
Daląí: Systémové soubory Předchozí: Souborový vstup a výstup

Ladislav Dobias
Sat Nov 1 15:38:32 MET 1997