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