Helsingin yliopisto Tietojenkäsittelytieteen laitos
 

Tietojenkäsittelytieteen laitos

Tietoa laitoksesta:

 
Helsingin yliopisto / Tietojenkäsittelytieteen laitos / Copyright © 2001 Jan Lindström. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

3. Tiedostojärjestelmä


Sisältö:


3 Tiedostojärjestelmä

Tiedostoon viitataan nimellä lähinnä vain avattaessa, muissa kutsuissa käytetään avattaessa saatua tiedostokuvaajaa. Tiedostokuvaaja voi osoittaa:

Kaikille samat read(), write() ja close(), luomisessa ja avaamisessa eroja. Valmiiksi avatut tiedostokuvaajat:

Seuraavaksi avattavat tiedostot saavat kuvaajat 3 ... OPEN_MAX. Avatut tiedostokuvaajat periytyvät lapsiprosessille. Tiedosto on avattava ennen käyttöä, jotta


3.1 Tiedoston avaus

Tiedosto avataan funktiolla int open(const char *pathname, int oflag, . . . /* , mode_t mode */); , joka palauttaa tiedostokuvaajan numeron. Käyttötapalipukkeet (oflag):

Uutta tiedostoa luotaessa pitää määritellä käyttöoikeudet (mode). Oikeudet eivät kuitenkaan jää sellaisenaan voimaan, sillä myös prosessin umask-arvo huomioidaan. Esimerkiksi, jos

      mode = S_IRWXU | S_IRWXG | S_IRWX0
      umask = 007

niin luotavalle tiedostolle tulee oikeudet

      -rwxrwx---

sys/stat.h                                                     /* MODE MASKS */

/* the following macros are for POSIX conformance */

#define S_IRWXU   00700           read, write, execute: owner
#define S_IRUSR   00400           read permission: owner
#define S_IWUSR   00200           write permission: owner
#define S_IXUSR   00100           execute permission: owner
#define S_IRWXG   00070           read, write, execute: group
#define S_IRGRP   00040           read permission: group
#define S_IWGRP   00020           write permission: group
#define S_IXGRP   00010           execute permission: group
#define S_IRWXO   00007           read, write, execute: other
#define S_IROTH   00004           read permission: other
#define S_IWOTH   00002           write permission: other
#define S_IXOTH   00001           execute permission: other

Esimerkki:


      fd = open("Unso/puppua.txt", 
                     O_CREAT | O_WRONLY, 
                     S_IRUSR | SIWUSR | S_IRGRP);

      if (fd = -1){
            perror("Eipä auennut");
            ...
      }


3.2 Tiedoston luonti

Tiedoston voi luoda funktiolla int creat(const char *pathname, mode_t mode);, joka palauttaa tiedostokuvaajan. Tämä vastaa kutsua:

fd=open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);


3.3 Tiedoston sulkeminen

Tiedosto suljetaan funktiolla int close(int fildes); Unix sulkee kaikki avoimet tiedostot, kun ohjelma päättyy. Sääntö on silti: Sulje tiedosto heti, kun et sitä enää tarvitse.


3.4 Tiedostosta lukeminen

Avatusta tiedostosta luetaan merkkejä funktiolla ssize_t read(int fildes, void *buff, size_t nbytes);, joka Palauttaa luettujen merkkien lukumäärän tai 0 kun EOF. Muista: read() ei aina palauta arvonaan samaa arvoa kuin on pyydettyjen merkkien lukumäärä (nbytes): tiedoston viimeinen lukupyyntö jää yleensä vajaaksi!


3.5 Tiedostoon kirjoittaminen

Avattuun tiedostoon kirjoitetaan funktiolla ssize_t write(int fildes, const void *buff, size_t nbytes);, joka palauttaa kirjoitettujen merkkien lukumäärän. Yleensä paluuarvo on aina sama kuin kirjoitettavaksi pyydettyjen merkkien lukumäärä (nbytes).

 
#include < unisdt.h >
  
#define     BUFFSIZE     8192
   
int main(void)
{
   int      n;
   char buf[BUFFSIZE];
  
   while ((n=read(STDIN_FILENO,buf,BUFFSIZE))>0)
            if (write(STDOUT_FILENO, buf, n) != n)
                  perror("write error");
  
   if (n < 0)
         perror("read error");
  
   exit(0);
}


3.6 Luku/ Kirjoitusposition asettaminen

Luku/ kirjoitusposition voi asettaa funktiolla off_t lseek(int fildes, off_t offset, int whence); , joka palauttaa uuden position. Uusi positio määräytyy parametrin whence arvon perusteella seuraavasti:

Nykyposition saa selville kutsulla:

      currpos = lseek(fd, 0, SEEK_CUR);
Tiedoston koon saa selville kutsulla
      size = lseek(fd, 0, SEEK_END);
Esimerkki käytöstä:

#include < unistd.h >
#include < stdio.h >
#include < fcntl.h >

void main(int argc, char *argv[])
{
     int fd;
     off_t size;
     char * buf;

     if (argc != 3) 
          perror("Usage: gulp infile outfile ");

     if ((fd = open(argv[1],O_RDONLY)) < 0)
          perror("open");

     size = lseek(fd,0,SEEK_END);
     if ((buf = (char *)malloc(size)) == NULL)
          perror("malloc");

     lseek(fd,0,SEEK_SET);
     if (read(fd,buf,size) != size)
          perror("read");
     close(fd);

     if ((fd = open(argv[2], O_WRONLY|O_CREAT,
                      S_IRUSR|S_IWUSR)) < 0)
          perror("open");

     if (write(fd,buf,size) != size)
          perror("write");
     close(fd);

     exit(0);
}


3.7 Tiedostojärjestelmän tietorakenteet

Tiedostojärjestelmän toteutukseen liittyy kolme tietorakennetta, joiden avulla voidaan järjestää mm. tiedostojen yhteiskäyttö:

Sama tiedosto voi olla auki usealla prosessilla, koska vain yksi alkio indeksisolmutaulussa per tiedosto. Erilliset alkiot tai yhteinen alkio avoimet tiedostot taulussa:

Kahdella prosessilla voi olla yhteinen alkio avoimet tiedostot taulussa vain, kun lapsi on perinyt avoimet tiedostot äidiltään fork() -kutsussa. Saman prosessin kaksi tiedostokuvaajaa voi osoittaa samaan avoimet tiedostot taulun alkioon, jos kuvaajia on duplikoitu systeemikutsulla dup() tai dup2().

read() kasvattaa lukupositiota luettujen tavujen lkm:llä. Jos positio = tiedoston koko, palauttaa arvon 0 (= eof).

write() kasvattaa positiota kirjoitettujen tavujen lkm:llä. Jos tiedoston koko kasvaa, arvo kopioidaan i- solmutauluun. Jos lipuke O_APPEND on asetettu, kopioidaan positiolle arvo ennen kirjoittamista i- solmutaulusta eli jokainen kirjoitus menee tiedoston loppuun.

lseek() muuttaa vain positiota avoimet tiedostot taulussa. Se ei aiheuta koskaan siirräntää.


3.8 Kuvaajien duplikointi ja uudelleenjärjestely

Avatun tiedostokuvaajan voi kopioida toisen tiedostokuvaajan arvoksi funktiolla int dup(int filedes); tai int dup2(int filedes1, filedes2), jotka palauttavat uuden tiedostokuvaajan. dup() kopioi parametrina annetun kuvaajan numeroltaan pienimpään vapaaseen tiedostokuvaajaan.

if ((fd = open("puppu.dat",O_WRONLY)) < 0)
      perror("can't open");

close(STDOUT_FILENO);    
dup(fd);
close(fd);

n = write(STDOUT_FILENO,buf, BUFLEN);

dup2() sulkee kuvaajan filedes2 ja kopioi sen paikalle kuvaajan filedes1. Atominen toiminto. Edelläolevat rivit close() ja dup() voi korvata rivillä:

dup2(fd,STDOUT_FILENO);


3.9 Lipukkeiden kysely ja asettaminen

Avatun tiedoston lipukkeita voi kysellä ja asettaa funktiolla int fcntl(int filedes, int cmd, . . . /* int arg */); , missä paluuarvo riippuu parametrista cmd, jonka mahdollisia arvoja ovat:

#include < stdio.h>
#include < unistd.h>
#include < sys/types.h>                                             
#include < fcntl.h>

int main(int argc, char *argv[])
{
  int accmode, val;

  if (argc != 2)
      perror("usage: a.out ");

  if ((val=fcntl(atoi(argv[1]),F_GETFL,0))< 0)
      perror("fcntl error for fd %d",atoi(argv[1]));

  accmode = val & O_ACCMODE;
  if (accmode==O_RDONLY)           printf("read only");
  else if (accmode==O_WRONLY)      printf("write only");
  else if (accmode == O_RDWR) printf("read write");
  else 
     perror("unknown access mode");

  if (val & O_APPEND)         printf(", append");

  if (val & O_NONBLOCK)       printf(", nonblocking");

  putchar('\n');
  exit(0);
}

void set_fl(int fd, int flags)                    /* file status flags to turn on */
{
      int val;

      if ((val = fcntl(fd, F_GETFL, 0)) < 0)
            perrro("fcntl F_GETFL error");

      val |= flags;                                         /* turn on flags */

      if (fcntl(fd, F_SETFL, val) < 0)
            perror("fcntl F_SETFL error");
}


3.10 Tiedostojen ominaisuudet

Tiedosto on jono tavuja. Siihen liittyy nimi, attribuutit ja datalohkot. Unix tallettaa nämä erilleen toisistaan. Hakemisto on tiedosto, jossa on peräkkäin pareja: tiedostonimi ja i-solmunumero. Hakemisto voi edelleen sisältää hakemistotiedostojen nimiä, jolloin muodostuu puurakenne. Attribuutit on talletettu indeksisolmuun. Ja indeksisolmusta käy ilmi missä tiedoston lohkot sijaitsevat. SysV:ssä tiedostonimelle on varattu 14 tavua ja i- solmunumerolle 2 tavua. BSD:ssä tiedostonimelle on varattu 255 merkkiä.

Tiedostojen ominaisuuksia voi kysellä funktioilla stat(), fstat() ja lstat(). Sekä stat() että fstat() osaavat kulkea symbolista linkkiä pitkin todelliseen kohdetiedostoon. lstat() on muuten kuin stat(), mutta jos nimi on symbolinen linkki, se antaa linkkitiedoston ominaisuudet.

#include < sys/types.h>
#include < sys/stat.h>

struct stat {
      mode_t      st_mode;    tyyppi, käyttöoikeudet
      ino_t       st_ino;     i-solmun numero
      dev_t       st_dev;     laitenumero
      dev_t       st_rdev;    laitenumero (special)
      nlink_t     st_nlink;   tulevien linkkien lkm
      uid_t       st_uid;     omistaja
      gid_t       st_gid;     ryhmä
      off_t       st_size;    koko tavuina
      time_t      st_atime;   milloin viim. käytetty
      time_t      st_mtime;   milloin viim. muutettu
      time_t      st_ctime;   milloin viim. muutettu
                              oikeuksia i-solmuun
      long     st_blksize;    suositeltu I/O:n
                              lohkokoko
      long     st_blocks;     allokoitujen lohkojen
                              lkm 
}

int stat(const char *pathname, struct stat *buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
POSIX ei vaadi kenttiä st_rdev, st_blksize, st_blocks. Koska Unix sallii kokonaisten tiedostojärjestelmien mounttaamisen yhtenäiseksi tiedostopuuksi, tarvitaan tiedoston yksilöintiin sekä i-solmunumero että laitenumero. Tiedostotyypit (kentässä st_mode):

#define S_ISFIFO(mode) (((mode)&0xF000)==0x1000)
#define S_ISCHR(mode)  (((mode)&0xF000)==0x2000)
#define S_ISDIR(mode)  (((mode)&0xF000)==0x4000)
#define S_ISBLK(mode)  (((mode)&0xF000)==0x6000)
#define S_ISREG(mode)  (((mode)&0xF000)==0x8000)
#define S_ISLNK(mode)  (((mode)&0xF000)==0xa000)
#define S_ISSOCK(mode) (((mode)&0xF000)==0xc000)
#define S_ISDOOR(mode) (((mode)&0xF000)==0xd000)
#define S_TYPEISMQ(buf)  (0)
#define S_TYPEISSEM(buf) (0)
#define S_TYPEISSHM(buf) (0)

#include  < sys/types.h>                                               
#include  < sys/stat.h>
#include  < unistd.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) {
              perror("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);
}


3.11 Tiedostojen käyttöoikeudet

Suoritettavaan prosessiin liittyy aina: real user id (uid) ja real group id (gid), effective user id (euid), effective group id (egid) ja supplementary group list. Lisäksi voi olla: saved user id ja saved group id.

Unix tarkistaa tiedoston käyttöoikeudet seuraavasti:


jos euid = root niin
      saa kaikki oikeudet
muuten 
      jos euid = st_uid niin
            tarkista kohdasta 'user'
      muuten
            jos egid = st_gid | egid IN grouplist
                  tarkista kohdasta 'group'
            muuten
                  tarkista kohdasta 'other'

Huomaa: jos käyttäjä kuuluu samaan ryhmään kuin omistaja, ei oikeuksia tarkisteta kohdasta 'other' !!! Jos sovellus haluaa tarkistaa omat oikeutensa tiedostoon todellisen uid:in ja todellisen gid:in perusteella, se voi käyttää funktiota int access(const char *pathname, int mode);, joka palauttaa 0, jos oikeus olemassa. mode:

#include  < sys/types.h>                                             
#include  < fcntl.h>
#include  < unistd.h>
  
int main(int argc, char *argv[])
{
      if (argc != 2)
            perror("usage: a.out ");
   
      if (access(argv[1], R_OK) < 0)
            perror("access error for %s", argv[1]);
      else
            printf("read access OK\n");
  
      if (open(argv[1], O_RDONLY) < 0)
            perror("open error for %s", argv[1]);
      else
            printf("open for reading OK\n");
   
      exit(0);
}

Uusi tiedosto saa omistajan ja ryhmän tiedot prosessin kuvaajasta. st_uid = euid ja st_gid = egid tai st_gid = isähakemiston st_gid. Uudelle tiedostolle on annettava käyttöoikeudet luonnin yhteydessä (eli open() / creat() kutsussa). Ne eivät tule kuitenkaan sellaisenaan voimaan, vaan prosessin umask-arvo vaikuttaa i-solmuun tallettuviin arvoihin. umask-arvo periytyy äitiprosessilta. umask-arvoa voi muuttaa funktiolla mode_t umask(mode_t cmask);, joka palauttaa edellisen umask-arvon.


#include    < sys/types.h>                                             
#include    < sys/stat.h>
#include    < fcntl.h>
#include    < unistd.h>

int main(void)
{
  umask(0);

  if (creat("foo",S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) < 0)
            perror("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)
        perror("creat error for bar");
  exit(0);
}

Prosessi voi muuttaa omistamansa tiedoston käyttöoikeuksia funktioilla int chmod(const char *pathname, mode_t mode); ja int fchmod(int filedes, mode_t mode);, jotka palauttaa 0, jos funktion suoritus onnistuu. Vain super-user voi:

#include    < sys/types.h>                                             
#include    < sys/stat.h>
#include    < unistd.h>

int main(void)
{
      struct stat statbuf;

      /* turn on set-group-ID and turn off group-execute */

      if (stat("foo", &statbuf) < 0)
            perror("stat error for foo");

      if (chmod("foo", statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
            perror("chmod error for foo");

      /* set absolute mode to "rw-r--r--" */

      if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
            perror("chmod error for bar");

      exit(0);
}
Prosessi voi muuttaa eräin edellytyksin omistamansa tiedoston omistajaa ja ryhmää funktioilla int chown(const char *pathname, uid_t owner, gid_t group);, int fchown(int filedes, uid_t owner, gid_t group); ja int lchown(const char *pathname, uid_t owner, gid_t group);, jotka palauttavat 0 jos funktion suoritus onnistuu.

Jos viitattu tiedosto on symbolinen linkki, muuttavat chown() ja fchown() todellisen kohteen omistajaa tai ryhmää, mutta lchown() linkkitiedoston.

Ryhmän muutoksessa perussääntö kuten omistajan muutoksessa. Jos sallittua, ryhmäksi voi muuttaa vain sellaisen ryhmän johon itsekin kuuluu. Jos muutoksen tekee joku muu kuin super-user, nollataan set-uid ja set-gid bitit.


3.12 Tiedoston uudelleennimeäminen ja poisto

Tiedoston nimeä voi vaihtaa funktiolla int rename(const char *oldname, const char *newname);. Toiminta ja perusvaatimukset vaihtelevat sen mukaan onko oldname ja/tai newname tiedosto vai hakemisto.

Jotta tiedoston voisi poistaa, on tiedostoon itseensä oltava w-oikeus ja hakemistoon sekä w-oikeus että x- oikeus. Tiedosto poistuu, kun viimeinenkin siihen osoittava linkki katkaistaan funktiolla int unlink(const char *pathname); tai int remove(const char *pathname);

unlink() poistaa aina hakemistoalkion ja vähentää indeksisolmussa olevaa linkkien lukumäärää yhdellä. Jos lukumääräksi tulee 0, vapauttaa se myös tiedostoon kuuluneet lohkot sekä indeksisolmun. Jos parametrina annettu tiedosto on (symbolinen) linkkitiedosto, unlink() poistaa sen, ei linkin päässä olevaa tiedostoa. unlink() poistaa hakemistoalkion heti, mutta muut vapautukset tehdään vasta ohjelman päättyessä. Aputiedostolle voi tehdä unlink() heti, kun se on luotu, jolloin aputilan siivoaminen ei pääse unohtumaan:


#include    < sys/types.h>                                             
#include    < sys/stat.h>
#include    < fcntl.h>
#include    < unistd.h>
   
int main(void)
{
      if (open("tempfile", O_RDWR) < 0)
            perror("open error");

      if (unlink("tempfile") < 0)
            perror("unlink error");
  
      printf("file unlinked\n");
      sleep(15);
      printf("done\n");
  
      exit(0);
} 


3.13 Tiedoston aikaleimat

Indeksisolmussa on kolme aikaleimaa:

Komento ls näyttää oletusarvoisesti milloin tiedostoa on viimeeksi muutettu eli kentän st_mtime. Ko. kenttää hyödynnetään esim. varmuuskopioinnissa (täydennyskopiot). Indeksisolmun tietojen kysely funktiolla stat() tms. ei muuta aikaleimoja. Omistamansa tiedoston aikaleimoja st_atime ja st_mtime voi muuttaa funktiolla utime():

#include < sys/types.h>
#include < utime.h>
   
struct utimbuf {
      time_t  actime;
      time_t  modtime;
}     
  
int utime(const char *pathname, const struct utimbuf *times); 

Jos times = NULL && w-oikeus 
  st_atime <- current time 
  st_mtime <- current time 
muuten 
  st_atime <- times.actime 
  st_mtime <- times.modtime

utime() aiheuttaa myös kentän st_ctime päivittymisen. Komentotulkin tasolla aikaleimoja voi muuttaa komennolla:

      $ touch

#include    < sys/types.h>                                             
#include    < sys/stat.h>
#include    < fcntl.h>
#include    < utime.h>
#include    < unistd.h>

int main(int argc, char *argv[])
{
    int         i;
    struct stat    statbuf;
    struct utimbuf timebuf;

    for (i = 1; i < argc; i++) {
        if (stat(argv[i], &statbuf) < 0) {
              perror("%s: stat error", argv[i]);
              continue;
        }

        if (open(argv[i], O_RDWR | O_TRUNC) < 0) {
              perror("%s: open error", argv[i]);
              continue;
        }

        timebuf.actime  = statbuf.st_atime;
        timebuf.modtime = statbuf.st_mtime;

        if (utime(argv[i], &timebuf) < 0) {
              perror("%s: utime error", argv[i]);
              continue;
        }
    }

    exit(0);
}


3.14 Hakemiston käsittely

Hakemisto luodaan funktiolla int mkdir(const char *pathname, mode_t mode); ja tyhjä hakemisto poistetaan funktiolla int rmdir(const char *pathname);

Prosessi saa työhakemistonsa nimen funktiolla char *getcwd(char *buf, size_t bufsize); ja se voi vaihtaa työhakemistoa funktioilla int chdir(const char *pathname); ja int fchdir(int fildes);.

  
int main(void)
{
      char  *ptr;
      int    size=256;
  
      if (chdir("/usr/spool/uucppublic") < 0)
            perror("chdir failed");
  
      ptr = malloc(size);
      if (getcwd(ptr, size) == NULL)
            perror("getcwd failed");

      printf("cwd = %s\n", ptr);
      exit(0);
}

Hakemistotiedoston käsittelyyn on funktiot:

#include < sys/types.h>
#include < dirent.h>
      
struct dirent {
      ino_t d_ino;
      char d_name[NAME_MAX + 1];
}
      
DIR *opendir(const char *pathname)
struct dirent *readdir(DIR *dp);
void rewinddir(DIR *dp);
int    closedir(DIR *dp);

Funktioita opendir(), readdir() ja closedir() käytetään kuten vastaavia salasanatiedoston tai ryhmätiedoston funktioita. Vain KJ voi kirjoittaa hakemistotiedostoon. DIR on järjestelmän sisäinen rakenne, jota ei tarvitse tuntea tarkemmin (vrt. FILE *).



Jan Lindström (Jan.Lindstrom@cs.Helsinki.FI)