Dateizugriffe |
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(char *filename, int oflag, mode_t mode); |
| 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 |
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.
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.
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); |
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); |
#include <unistd.h> int write(int filehandle, void *buffer, size_t length); |
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.
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.
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.
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 |
#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.
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? */ |
#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); |
#include <sys/stat.h> int chown(char *pathname}, uid_t user, gid_t group); int fchown(int fd}, uid_t user, gid_t group); |
#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.
/* 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");
}
}
|
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);
}
}
|
Versuche die erste Sperre: Resource temporarily unavailable |
Ganz offensichtlich blockiert one nicht, sondern bekommt eine Fehlerrückgabe durch fcntl().
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);
}
}
|
#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.
flock(int filehandle, int operation) |
Der Parameter operation kann folgende Werte annehmen.
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 |
#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); |
|
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
|