Dateizugriffe

Willemers Informatik-Ecke


Weitere Informationen zur UNIX- und Linux-Programmierung finden Sie im umfassenden Handbuch UNIX.


Universeller Dateibegriff unter UNIX

UNIX hat eine Reihe von Standarddateizugriffe definiert, die mit C auch auf die anderen Plattformen übertragen wurden. Nirgends ist aber der Dateibegriff so universell wie unter UNIX. Mit den gleichen Funktionen können Dateien, Pipes und sogar Netzverbindungen zugegriffen werden.

Anwendungsprogramme verwenden fopen

Neben diesen Dateizugriffen, die durch die Aufrufe open(), read(), write() und close() geprägt sind und die einen Integer als Dateihandle zurückgeben, gibt es noch die Dateizugriffe mit fopen(), fread(), fwrite() und fclose(), die als Dateihandle einen Zeiger auf die Struktur FILE zurückgeben. Diese in der Anwendungsprogrammierung weiter verbreitete Variante basiert auf den zuerst genannten Basiszugriffen und erweitert sie um ein Pufferkonzept. Sie gehören auch nicht zu den Systemaufrufen, die vom Betriebssystem behandelt werden, sondern sind Bibliotheksfunktionen. Nähere Informationen dazu finden sich in den Dokumentationen der Compiler oder natürlich unter man 3 fopen.

Grundoperationen

Um eine Datei lesen oder schreiben zu können, muss sie zunächst durch den Aufruf von open() geöffnet werden. Vor Ende des Programmes wird sie mit close() wieder geschlossen.

Öffnen: open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename, int oflag, mode_t mode);

filename
Der Pfad und Dateiname der zu öffnenden Datei.
oflag
Dieser Parameter bestimmt, wie die Datei geöffnet wird. Es können mehrere Attribute verwendet werden, indem sie mit einander durch den senkrechten Strich geodert werden.
Konstante Bedeutung
O_RDONLY Datei nur zum Lesen öffnen
O_WRONLY Datei nur zum Schreiben öffnen
O_RDWR Datei zum Lesen und Schreiben öffnen
O_APPEND Es wird ans Ende der Datei angehängt
O_CREAT Erzeuge die Datei, wenn sie nicht existiert
O_EXCL Exklusiver Zugriff

mode
Hier werden Schreib- und Leserechte angegeben, wie sie vom chmod bekannt sind.

Bei einem Fehler gibt open -1 zurück. Der Fehlercode befindet sich in der globalen Variablen errno. Im Erfolgsfall wird ein Dateihandle vom Typ int zurückgegeben, der von den Dateifunktionen benötigt wird, um die entsprechende Datei weiter zu bearbeiten.

Paralleles Überschreiben vermeiden

Das Flag O_APPEND ist vor allem beim Beschreiben von Protokolldateien wichtig. Da nun ein einfaches write ausreicht, ist die Operation untrennbar. Ein paralleler Prozess, der auch schreiben will, kann also nur davor oder dahinter schreiben, aber nicht an die gleiche Stelle. Dagegen ist der scheinbar gleichwertige Ansatz mit lseek und write ohne O_APPEND im open teilbar, da es zwei Operationen sind. Dann können parallele Prozesse versehentlich in den gleichen Bereich schreiben.

Die Kombination O_CREAT | O_EXCL ist wunderbar als Semaphore verwendbar. Da dies nur ein untrennbarer Aufruf ist, kann immer nur ein Prozess die Datei im Zugriff haben. Beispielsweise wird diese Technik vom Drucksystem verwendet, um zu vermeiden, dass zwei Prozesse gleichzeitig einen Eintrag in das Spoolverzeichnis schreiben. Dazu wird vor dem eigentlichen Schreiben versucht, eine Datei namens lock mit den oben angegebenen Parameter zu eröffnen. Der Aufruf misslingt, wenn bereits eine Datei existiert. Derjenige, der also diesen Aufruf erfolgreich durchführen kann, ist also der einzige Prozess im kritischen Bereich. Nach Ende der Arbeiten löscht er einfach wieder die Datei lock und läutet damit die nächste Runde ein.

creat()

Analog zum open() mit der Option O_CREAT gibt es den Befehl creat():

int creat(const char *filename, mode_t mode);

Zum Schließen der Datei wird der Aufruf close() verwendet.

Schließen: close

int close(int fd);

Dateien schließen

Dass eine geöffnete Datei auch wieder geschlossen werden sollte, sagt bereits der Ordnungssinn. Darüber hinaus ist es einsehbar, dass das System bei übermäßig vielen geöffneten Dateien auch Verwaltungsaufwand hat. Der Hauptgrund, eine Datei möglichst bald wieder zu schließen, ist aber der, dass eine offene Datei immer ein Risiko darstellt, da der Zustand der Datei in der Schwebe ist.

Als Parameter nimmt close() das Dateihandle.

Der Rückgabewert ist 0, wenn alles in Ordnung ist und -1, wenn ein Fehler auftritt. In bestimmten Fällen kann eine gepufferte Schreiboperation erst beim Schließen der Datei auffallen. Insofern sollte man auch den Rückgabewert von close prüfen, wenn man eine sichere Information braucht, ob die Dateioperationen geklappt haben.

Lesen: read

#include <unistd.h>
int  read(int filehandle, void *buffer, size_t length);

Parameter

Damit aus einer Datei gelesen werden kann, muss sie geöffnet werden. Den Rückgabewert von open() braucht read() als ersten Parameter. Vor dem Lesen muss ein Puffer angelegt werden, in dem die zu lesenden Daten abgelegt werden. Die Adresse dieses Speichers wird als weiterer Parameter benötigt. Schließlich muss angegeben werden, wie groß der Datenblock sein soll, der gelesen wird. Dieser Parameter sollte nicht zu klein gewählt werden. Das byteweise Einlesen einer mittelgroßen Datei kann ein Programm minutenlang beschäftigen.

Rückgabewert

Der Rückgabewert gibt an, wieviele Bytes gelesen wurden und er sollte mit dem Parameter length übereinstimmen. Ist er kleiner, sind scheinbar die letzten Byte erreicht worden. Da aber beispielsweise im Netz Verzögerungen auftreten können, sollte man auch in diesem Fall noch einmal lesen, bis der Rückgabewert 0 ist. Ist der Rückgabewert -1, ist ein Fehler aufgetreten. Nähere Informationen zu der Ursache des Fehlers findet man in errno.

Schreiben: write

#include <unistd.h>
int write(int filehandle, void *buffer, size_t length);

Parameter

Auch write() braucht das Dateihandle als ersten Parameter. Der zweite Parameter gibt die Adresse des Speichers an, aus dem geschrieben werden soll. Im letzten Parameter wird angegeben, wie groß der zu schreibende Bereich ist.

Rückgabewert

Der Rückgabewert ist wie bei read() normalerweise identisch mit dem Paramter length. Im Fehlerfall ist er -1 und die Variable errno gibt über die Ursache Auskunft.

Standardein- und ausgabe

Die drei Konstanten stdin, stdout und stderr brauchen nicht geöffnet oder geschlossen zu werden, verhalten sich aber kompatibel zu Dateien, die durch open() geöffnet wurden.

Filter

Mit Hilfe dieser Konstanten lassen sich Filter programmieren, die in Pipes gehängt werden können. Das entsprechende Programmgerüst sieht so aus:

int zeichen;
while (EOF != (zeichen=getc(stdin))) {
    // verändere die Eingabe in gewünschter Form

    putc(zeichen, stdout);
}

Dass die Variable zeichen ein int und kein char ist, ist kein Versehen! Eine Variable vom Typ char hat nur Platz für genau ein Zeichen. Der Typ int hat dagegen einen größeren Wertebereich und kann damit zusätzliche Zustände, also auch Fehlerzustände, aufnehmen. Nur mit einem int ist das EOF (End Of File) zu erkennen.

#include <stdio.h>

   int getc(int DateiHandle);
   int getchar(void);
   int putc(int c, int DateiHandle);
   int putchar(int c);
   char * gets(char *s);
   int puts(char *s);

Die Funktion getchar entspricht der Funktion getc, bezieht sich aber immer auf stdin. Im Beispiel hätte man also auch getchar verwenden können. Gleiches gilt für putchar. Mit gets wird eine Zeile von der Standardeingabe gelesen und puts gibt eine Zeichenkette auf der Standardausgabe aus. Dabei hängt puts noch ein Zeilenvorschubzeichen an die Ausgabe an.

Die Funktion gets liest einen String aus der Standardeingabe bis ein Zeilenendetrenner erscheint. Da gets keinen Parameter hat, der die Länge der Eingabe beschränkt, wird die Funktion auch über das Ende des Speichers hinwegschreiben, wenn genügend Eingabedaten vorliegen. Dieser Designfehler der Funktion wird gern für Angriffe ausgenutzt. In kritischer Umgebung ist diese Funktion also unbedingt zu vermeiden.

Positionieren: lseek

Ein Lesezugriff liest einen Block aus einer bereits geöffneten Datei. Er fängt normalerweise vorn an und setzt an der zuletzt erreichten Stelle wieder auf, um einen weiteren Lesezugriff zu bedienen. Analoges geschieht beim Schreibzugriff. Es gibt also einen Dateipositionszeiger, der immer an die Stelle der letzten Dateioperation zeigt.

Will man innerhalb der Datei an einer bestimmten Stelle anfangen zu lesen, kann man mit dem Aufruf lseek() an eine anzugebende Stelle in der Datei springen.

int lseek(int filehandle, off_t offset, int whence);

Der Parameter offset bestimmt die Position, ab der die nächste Operation beginnt. whence legt fest, von wo der Offset berechnet wird.

Konstante} Bedeutung
SEEK_SET ab Dateianfang
SEEK_CUR ab der aktuellen Position
SEEK_END ab Dateiende

Dateien mit Löchern

Mit lseek ist es möglich, Löcher in Dateien entstehen zu lassen. Wird beispielsweise bei einer Datei mit einem Byte Länge an die Position 1000 gesprungen und geschrieben, gibt es ein Loch, das nicht auf der Platte angelegt wird. Für das Programm bleibt das transparent. Liest man später einen Bereich in diesem Loch, erhält man einen mit Nullen aufgefüllten Speicherbereich. Problematischer ist es, wenn man später sukzessive das Loch beschreibt, da die Speicherbereiche auf der Platte dann nicht mehr linear vorliegen und mit Einbußen bei der Performance zu rechnen ist.

Datei duplizieren: dup

#include <unistd.h>

int dup(int oldfd);

Diese Funktion gibt ein Dateihandle zurück, der auf die gleiche Datei zeigt. Auf diese Weise ist es beispielsweise möglich an zwei verschiedenen Stellen gleichzeitig in einer Datei zu arbeiten, da jedes Handle seinen eigenen Positionszeiger besitzt. Ist es notwendig, dass das Handle eine bestimmte Nummer hat, benötigt man dup2.

#include <unistd.h>

int dup2(int oldfd}, int dupfd);

Dateieigenschaften ermitteln

Man kann aus dem Programm heraus ermitteln, wie die Zugriffsrechte auf eine Datei sind. Man erhält Informationen über Zeiten des letzten Schreibens und Lesens, kann den Besitzer und den Typ der Datei feststellen.

stat() und fstat()

Mit der Funktion stat() und fstat() kann man Informationen über eine Datei ermitteln. Die Ergebnisse werden in einer Variablen vom Typ stat abgelegt. Diese Variable muss der Programmierer anlegen und dessen Adresse der jeweiligen Funktion übergeben. Der Unterschied zwischen beiden Funktionen liegt darin, dass stat() den Dateinamen und fstat() das Dateihandle zur Identifikation der Datei verwendet.

#include <sys/types.h>
#include <sys/stat.h>
int  stat(char *path,     struct stat *buffer);
int fstat(int filehandle, struct stat *buffer); 

Die Ergebnisse finden sich in der Variablen von Typ stat, auf die der Parameter buffer zeigt. Die Definition der Struktur stat lautet:

struct stat {
  dev_t  st_dev     /* (P) Device, auf dem die Datei liegt */
  ushort st_ino     /* (P) Inode-Nummer */
  ushort st_mode    /* (P) Dateityp  */
  short  st_nlink   /* (P) Anzahl der Links der Datei  */
  ushort st_uid     /* (P) Eigner-ID (uid)  */
  ushort st_gid     /* (P) Gruppen-ID (gid)  */
  dev_t  st_rdev    /* Major- und Minornumber, falls Device */
  off_t  st_size    /* (P) Größe in Byte  */
  time_t st_atime   /* (P) Zeitpunkt letzter Zugriffs  */
  time_t st_mtime   /* (P) Zeitpunkt letzte Änderung  */
  time_t st_ctime   /* (P) Zeitpunkt letzte Statusänderung */
};

Die Bestandteile dieser Struktur können sich je nach System unterscheiden. Die mit (P) gekennzeichneten Elemente sind aber zwingend von POSIX vorgeschrieben.

st_dev und st_ino

st_dev und st_ino beschreiben eindeutig den Ort einer Datei. st_dev ist das Device, bei Festplatten also die Partition. st_ino bezeichnet den Inode, also die Katalognummer, in der auf die Datei verwiesen wird.

st_mode

Die rechten 12 Bit beschreiben die Rechtezuordnung der Datei, wie sie von
chmod bekannt ist. Zu berücksichtigen ist, dass die Werte oktal sind, Der Modus 755 muss also als 0755 in einem C-Programm dargestellt werden. In den nächsten vier Bit wird kodiert, welchen Typs die Datei ist. Um beides zu trennen, gibt es die Konstante S_IFMT. Mit ihr kann man eine Maske über diese Bits setzen.

Normalerweise wird man aber eher direkt die Werte ausmaskieren, die man sucht. Dabei gibt es Konstanten, mit denen man arbeiten sollte.

Konstante} Dateityp
S_IFSOCK Sockets
S_IFLNK Symbolische Links
S_IFREG reguläre Dateien
S_IFBLK Block-Devices
S_IFDIR Verzeichnisse
S_IFCHR Char-Devices
S_IFIFO FIFOs

Um einen bestimmten Typ abzufragen maskiert man mit einem einfachen & den Inhalt von st_mode aus. Ist der Wert ungleich Null, ist das entsprechende Bit gesetzt.

struct stat s;

  fstat(filehandle, &s);
  if (s.st_mode & S_IFREG) /* regulaere Datei? */

st_nlink

In st_nlink steht, wieviele harte Links auf die Datei zeigen.

st_uid und st_gid

Mit st_uid und st_gid werden der Besitzer und die Besitzergruppe ermittelt. Der Wert ist eine Zahl, nämlich die, welche in /etc/passwd bzw. in /etc/group festgelegt wird.

st_rdev

In st_rdev ist die Major- und Minornummer kodiert, sofern es sich bei der Datei um ein Device handelt.

st_size

Sofern es sich bei der Datei um eine reguläre Datei handelt, findet sich in st_size die Größe in Bytes.

st_atime, st_mtime und st_ctime

Jeder lesende oder schreibende Zugriff auf die Datei aktualisiert den Wert st_atime. Jede Veränderung des Dateiinhalts wird in in st_mtime notiert. Der Zeitpunkt der Änderungen bezüglich der Benutzer, Rechte, Linkzahl oder ähnlichem, also allem, was nicht den Inhalt betrifft, wird in st_ctime festgehalten.

Zugriffsrecht ermitteln

access()

Will ein Programm ermitteln, ob es beispielsweise Schreibrecht auf eine Datei hat, könnte es mit stat alle Informationen über die Datei auswerten. Schneller geht es mit der Funktion access(), die nur ermittelt, ob das gewünschte Recht verfügbar ist.

#include <unistd.h>

int access(const char *pathname}, int mode);

An den Parameter mode können folgende Konstanten übergeben werden:

Konstante} Bedeutung
F_OK Existenz der Datei
R_OK Prozess darf lesen
W_OK Prozess darf schreiben
X_OK Prozess darf die Datei ausführen

Der Rückgabewert ist 0, wenn der Zugriff erlaubt ist oder EACCESS, wenn nicht.

Dateieigenschaften ändern

Die Aufrufe chmod, chown und chgrp basieren natürlich auf Systemaufrufen, die sich auch aus einem Programm heraus aufrufen lassen.

Zugriffsrechte ändern

Der Aufruf chmod (siehe S. \gpSeitenverweis{chmod}) hat sein direktes Gegenstück in der Funktion chmod() bzw. fchmod(). Beide Funktionen unterscheiden sich darin, ob die Datei durch ein Dateihandle oder durch ihren Dateinamen bestimmt wird.

#include <sys/stat.h>

int chmod(const char *pathname}, mode_t mode);
int fchmod(int fd}, mode_t mode);

chmod()

Die Variable mode erhält die Kodierung der Rechte, wie man sie vom Kommanodo chmod kennt. Dem bekannten Parameter des Kommandozeilenwerkzeug wird eine Null vorangesetzt, damit der Wert oktal übergeben wird. Würde man auf der Kommmandozeile 644 verwenden, würde im Programm chmod(fh, 0644) angegeben.

Besitzer und Gruppe ändern

Auch für die Aufrufe chown und chgrp gibt es Gegenstücke in den Systemaufrufen. Allerdings werden beide Funktionalitäten durch die gleiche Funktion chown() bzw. fchown() behandelt. Eine Funktion chgrp() gibt es demzufolge nicht.

#include <sys/stat.h>

int chown(char *pathname}, uid_t user, gid_t group);
int fchown(int fd}, uid_t user, gid_t group);

chown()

Mit diesen Funktionen kann Benutzer und Gruppe einer Datei geändert werden. Will man nur den Wert des Benutzers oder nur der Gruppe ändern, wird für den jeweils anderen Parameter -1 übergeben.

Maske für die Rechte neuer Dateien: umask

Diese Maske legt fest, welche Berechtigungen vom Programm bei der Erzeugung einer Datei oder Verzeichnisses nicht vergeben wird. Die Berechtigungen sind wie bei chmod aufgebaut, allerdings negiert.

#include <sys/stat.h>

  int umask(int mask);

Der Rückgabewert ist die vormals geltende umask.

Sperren

Wenn mehrere Prozesse in ein und derselben Datei Änderungen vornehmen wollen, wird es notwendig, Bereiche dieser Datei sperren zu müssen. Diese Problematik ist bei den ersten Versionen von UNIX nicht berücksichtigt worden. Erst später beim Einsatz im kommerziellen Umfeld wurde die Notwendigkeit erkannt und in den verschiedenen UNIX-Dialekten unterschiedlich nachgereicht. So gibt es mehrere API-Aufrufe, um einen Dateibereich zu sperren. Relevant ist der Standard unter POSIX, den man bei Wahlmöglichkeit auch einsetzen sollte. Es kann aber durchaus passieren, dass man auf einen der anderen Sperrmechanismen in älteren Programmen stößt, die aus Kompatibilitätsgründen oft noch funktionieren.

Dateisystem muss Sperren erlauben

Nicht jedes Dateisystem unterstützt Sperren, insbesondere wenn es sich um ein Netzdateisystem handelt. Aus diesem Grund sollte das Funktionieren der Sperren vor dem Einsatz in der Produktionsumgebung getestet werden.

Sperren nach POSIX

Als erstes wird die POSIX-Variante beschrieben. Dort erfolgt das Sperren von Dateiausschnitten über die Funktion fcntl().

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

fcntl(int filehandle, int command, struct flock *sperre);

Der Parameter command gibt die Funktion an und kann folgende Werte annehmen:

Konstante Bedeutung
F_GETLK Ermittle, ob eine Sperre gesetzt ist
F_SETLK Eine Sperre setzen oder Fehler zurückgeben
F_SETLKW Eine Sperre setzen oder warten

Die Elemente der Struktur flock geben die näheren Informationen über die Sperre an.

Element} Wert Bedeutung
l_type F_WRLCK exklusiv
F_RDLCK geteilt, verhindere Schreibzugriff
F_UNLCK Sperre aufheben
l_whence SEEK_SET vom Dateianfang
SEEK_CUR ab aktueller Dateiposition
SEEK_END vom Dateiende
l_start Offset des Sperrbereichs
l_len Länge des Bereichs, bei 0 bis Dateiende

Das flock-Element l_pid liefert bei F_GETLK den Prozess, der die Sperre gesetzt hat.

Beispielprogramm one

Als Beispiele dienen zwei Programme, die von zwei Terminalsitzungen aus gestartet werden. Das eine heißt one.c} und das andere two.c. Zentraler Bestandteil der Programme ist die Funktion Sperren().

/* Sperren() aus one.c */
#include <unistd.h>
#include <stdio.h>

int Sperren(char *name)
{
long data;
int fh;
struct flock sperre;

    fh = open(name, O_RDWR, 0644);
    if (0<fh) {
        sperre.l_type   = F_WRLCK;
        sperre.l_whence = SEEK_SET;
        sperre.l_start  = 5*sizeof(data);
        sperre.l_len    = sizeof(data);
        if (0> fcntl(fh, F_SETLK, &sperre)) {
            perror("Versuche die erste Sperre");
        }
        getchar(); /* nun ist der Satz gesperrt */
        sperre.l_type   = F_UNLCK;
        if (0> fcntl(fh, F_SETLK, &sperre)) {
            perror("Löse die erste Sperre");
        }
        close(fh);
    } else {
        perror("Kann Datei nicht öffnen");
    }
}

Beispielprogramm two

Das Programm one wird gestartet und bleibt in der Funktion Sperren() an der Stelle stehen, wo es mit getchar() auf die Returntaste wartet. In dieser Zeit wird auf der zweiten Konsole das Programm two gestartet. Dessen Funktion Sperren() sieht nur geringfügig anders aus:

int Sperren(char *name)
{
long data;
int fh;
struct flock sperre;

    fh = open(name, O_RDWR, 0644);
    if (0<fh) {
        puts("two: vor lock");
        sperre.l_type   = F_WRLCK;
        sperre.l_whence = SEEK_SET;
        sperre.l_start  = 5*sizeof(data);
        sperre.l_len    = sizeof(data);
        if (0> fcntl(fh, F_SETLKW, &sperre)) {
            perror("Versuche die zweite Sperre");
        }
        puts("two: nach lock");
        getchar();
        sperre.l_type   = F_UNLCK;
        if (0> fcntl(fh, F_SETLKW, &sperre)) {
            perror("Löse die zweite Sperre");
        }
        close(fh);
    }
}

Verhaltenstest

Das Programm two wird noch >>two: vor lock<< melden und dann stehen. Es blockiert also an der von one gesetzten Sperre. Wird das Programm one durch Enter weitergeführt, gibt es die Sperre frei und two meldet sich mit >>two: nach lock<<. Nun hält Programm two die Sperre. Startet man jetzt das Programm one, erhält man die folgende Meldung:

Versuche die erste Sperre: Resource temporarily unavailable

Ganz offensichtlich blockiert one nicht, sondern bekommt eine Fehlerrückgabe durch fcntl().

Wer blockiert hier?

Zuletzt soll bei einer bestehenden Sperre ermittelt werden, welcher Prozess die Sperre setzt. Dazu verwenden wir die Funktion Sniff():

int Sniff(char *name)
{
long data;
int fh;
struct flock sperre;

    fh = open(name, O_RDWR, 0644);
    if (0<fh) {
        sperre.l_type   = F_WRLCK;
        sperre.l_whence = SEEK_SET;
        sperre.l_start  = 5*sizeof(data);
        sperre.l_len    = sizeof(data);
        if (0> fcntl(fh, F_GETLK, &sperre)) {
            perror("Problem bei fcntl");
        }
        printf("Prozess: %d\n", sperre.l_pid);
        close(fh);
    }
}

lockf

Die Funktion lockf wurde durch System V eingeführt. Sie wird von manchen Systemen, beispielsweise Linux, als Schnittstelle zum POSIX-Sperren verwendet.

#include <sys/file.h>

    int lockf(int fd, int cmd, off_t len);

Der Parameter cmd wird mit einer Konstanten besetzt, die die Funktion angibt.

F_LOCK
Setzt eine exklusive Sperre. Ist der Bereich bereits gesperrt, blockiert der Prozess. So ist gewährleistet, dass nur ein Prozess eine Sperre setzen kann.
F_TLOCK
wie F_LOCK wir ein Bereich exklusiv gesperrt. Bei einer bereits vorliegenden Sperre blockiert der Prozess aber nicht. Statt dessen gibt die Funktion einen Fehler zurück.
F_ULOCK
hebt die Sperre auf
F_TEST
Prüft, ob eine Sperre auf diesem Bereich liegt. Die Funktion gibt -1, wenn ein anderer Prozess die Sperre gesetzt hat. Der Rückgabewert 0 bedeutet, dass keine Sperre vorliegt oder die Sperre vom eigenen Prozess stammt.
Mit dem Parameter len kann die Länge des zu sperrenden Bereichs festgelegt werden. Die Position innerhalb der Datei wird durch einen vorherigen lseek() bestimmt.

flock

Diese Funktion zum Sperren von ganzen Datei wurde durch BSD 4.2 eingeführt. flock sperrt nicht einen Ausschnitt, sondern die komplette Datei.

flock(int filehandle, int operation)

Der Parameter operation kann folgende Werte annehmen.

LOCK_SH
Shared lock. Mehrere Prozesse können parallel sperren. Dies ist eine eher ungewöhnliche Operation und macht nur im Zusammenhang mit mandantory locking (siehe unten) Sinn.
LOCK_EX
Exclusive lock. Nur ein Prozess kann sperren.
LOCK_UN
Unlock. Die Sperre wird aufgehoben.
LOCK_NB
Nonblocking . Die Operation blockiert nicht, sondern gibt eine Fehlermeldung zurück. Dies wird durch Odern einem anderen Kommando hinzugefügt.

Die Funktion gibt im Erfolgsfall 0 ansonsten -1 zurück. Nähere Informationen finden sich dann in der Variablen errno.

locking

Die Funktion locking wurde unter XENIX 31 verwendet.

int locking(int fd, int flags, long nbytes);

Der Parameter flag kann folgende Werte annehmen.

Konstante} Bedeutung
LK_LOCK Eine Sperre setzen
LK_UNLOCK Eine Sperre aufheben

Die Sperre wird ab der aktuellen Dateiposition gesetzt. Als eigenen Parameter kennt locking nur die Länge des zu sperrenden Bereichs. Um die Dateiposition zu ändern, muss man zuvor lseek() aufrufen.

advisory und mandatory

Es gibt zwei Arten des Sperrens: advisory (übersetzt etwa empfohlen) und mandatory (übersetzt zwingend). Beim advisory wird der gesperrte Bereich nur dadurch geschützt, dass alle Programme, die auf die Datei zugreifen, nur über die Sperrmechanismen zugreifen. Greift ein anderer Prozess zu oder hält sich jemand nicht an diese Abmachung, hat die Sperre keinen Wert.

mandatory wird dateiweise per chmod geschaltet

Beim Aktivieren des mandatory locking wird die Sperre durch das System überwacht. Alle Dateizugriffe werden geprüft, um die Sperre zu schützen. Mandatory locking wird mit denselben Aufrufen realisiert wie das advisory locking. Der Umstieg erfolgt durch das Setzen bestimmter Dateiattribute. Mit dem
chmod wird das Set group id Bit gesetzt und die Ausführbarkeit für die Gruppe gelöscht. Da zu sperrende Dateien Daten enthalten und keine ausführbaren Dateien sind, wäre eine solche Rechtevergabe ansonsten auch unsinnig. Der Befehl, die Datei datendatei auf mandantory locking umzustellen, lautet:

chmod 2644 datendatei

mandatory ist selten sinnvoll

Letztlich ist das mandatory locking nicht so wichtig, wie man vielleicht auf den ersten Blick glaubt. Eine Datendatei wird normalerweise mit Programmen zugegriffen, die >>wissen<<, wie die Daten zu behandeln sind und damit auch darauf eingestellt sind, die Sperren konkurrierender Prozesse zu beachten. Alles andere ist definitiv ein Programmierfehler. Normalerweise wird nur dann mit Fremdprogrammen auf solche Dateien zugegriffen, wenn etwas schief gelaufen ist und eine Administration notwendig ist. Und in diesen Fällen würde ein mandatrory locking die Hilfe der entsprechenden Tools aussperren. Da das mandatory locking zusätzlich die Performance herabsetzt, wird es eher selten verwendet.

Link erzeugen: link, symlink

Links können auch aus dem Programm erstellt werden. Der Systemaufruf heißt link():

#include <unistd.h>

int link(const char *orig}, const char *new);

Die Funktion link() erzeugt einen harten Link wie der Befehl link. Auch hier gelten die Einschränkungen, wie sie beim Befehl gelten. Die Funktion gibt 0 bei Erfolg und -1 bei einem Fehler zurück.

#include <unistd.h>

int symlink(const char *orig}, const char *new);

Mit der Funktion symlink werden symbolische Links erzeugt.

Löschen: unlink

Die Funktion zum Löschen einer Datei heißt unlink(), was seine Funktion auch besser beschreibt als remove. Tatsächlich entfernt das Löschen eine Datei nur dann, wenn der zu löschende Verzeichniseintrag der letzte Link auf die Datei ist.

#include <unistd.h>

int unlink(const char *pathname);

Die Funktion gibt 0 bei Erfolg und -1 bei einem Fehler zurück. Die Fehlerursache steht in der Variablen errno.

Umbenennen: rename

Die Funktion zum Umbenennen von Dateien heißt rename(), obwohl sie wie das Kommando mv wirkt. Es ist also auch möglich, damit Dateien innerhalb des gleichen Dateisystems zu verschieben.

#include <unistd.h>

int rename(const char *oldpath, const char *newpath);

Die Funktion gibt 0 bei Erfolg und -1 bei einem Fehler zurück. Die Fehlerursache steht in der Variablen errno.

Temporäre Dateien

Temporäre Dateien werden unter UNIX immer im Verzeichnis /tmp oder /usr/tmp abgelegt. Auf diese Verzeichnisse kann jeder frei zugreifen. Will man sicher sein, dass der Name der temporären Datei nicht auch von einem anderen Programm verwendet wird, lässt man ihn vom System erzeugen. Dies tut die Funktion tmpnam().

#include <stdio.h>

char *tmpnam(char *s);

Ist der Parameter s NULL, so liefert die Funktion als Rückgabewert einen Zeiger auf einen internen Namen, der beim nächsten Aufruf von tmpnam() überschrieben wird. Ist s nicht NULL, liefert die Funktion den Namen an dieser Adresse ab.

Es gibt daneben noch die Funktion tmpfile(), die einen Zeiger auf FILE zurückgibt. Hier wird intern mit tmpnam ein Name gewählt und sofort eröffnet. Eine Besonderheit ist, dass diese Datei nach dem Schließen oder bei Programmende automatisch gelöscht wird.

#include <stdio.h>

FILE *tmpfile (void);


1 vgl. Rochkind, Marc J.: UNIX Programmierung für Fortgeschrittene. Hanser, 1988. S. 256-261.

Diese Seite basiert auf Inhalten aus dem Buch Arnold Willemer: Wie werde ich UNIX-Guru
Verlagsrechte bei galileo computing


Homepage (C) Copyright 2002 Arnold Willemer