ein Kapitel zurück                                           ein Kapitel weiter

Wer den Artikel zu fcntl aus dem Buch Linux/Unix-Systemprogrammierung kennt, dürfte ein wenig davon entschäuscht gewesen sein. Dieser Abschnitt wurde in dem (Top)Buch etwas allzu Theoretisch und Kurz gehalten. Dieses Kapitel hier soll Abhilfe schaffen.

Kommen wir nun zu einem häufig eingesetzte Funktion unter Linux/Unix....

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

int fcntl(int fd, int kommando,.../*int arg*/);

Mit dieser Funktionen können wir verschiedene Operationen auf einem Filedeskriptor ausführen. Folgende Operationen (diese werden mit einer Konstante für kommando verwendet) stehen Ihnen dabei zur Verfügung........

F_DUPFD

Damit könne sie den Filedeskriptor fd duplizieren. In diesem Fall gibt fcntl den duplizierten Filedeskriptor zurück....

int fd,fd2;
.....
fd2=fcntl(fd,F_DUPFD,0);

Die neue Datei fd2 benutzt den selben Tabelleneintrag in der Dateitabelle (File Status Flag, Pos. Lese/Schreibzeiger) und auch die selben v-node/i-node Informationen. Der Unterschied ist das F_DUPFD seine eigenen fdflags im Prozeßeintrag, in denen das close-on-exec-Bit(FD_CLOEXEC) gelöscht ist, besitzt. Wenn diese Bit nicht gesetzt ist so bleibt der Filedeskriptor bei einem exec-Aufruf bestehen. Um Ihnen das close-on-exec-Bit zu erklären fehlt es Ihnen eigentlich noch an etwas an Hintergrundwissen zu den exec-Funktionen. Aber ein einfaches Beispiel. Zuerst Programm Nr1........

/*Download:fcntl1.c*/
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> int main() { int FH1, FH2; char puffer[10]; FH1 = open("./text.txt",O_RDONLY); if(FH1 == -1) { /*Fehler*/ exit(0); } system("./xt"); return 0; }

Mit der Funktion system() rufen wir folgendes Programm auf......

/*Download:xt.c*/
#include <unistd.h> #include <stdio.h> int main() { char puffer[20]; int ok; if( (ok=read(4, &puffer,10)) != -1) printf("%s\n",puffer); return 0; }

Das Programm xt soll nun lesen was in der Datei text.txt an den ersten 10 Bytes steht. Übersetzen sie beide Programme und rufen dann das erste auf. Logischerweise wird unser Programm xt irgendwelchen murks ausgeben. Denn POSIX schreibt vor das die FD-Flags von den anderen Prozessen nicht verwendet werden kann. Das kommt daher das bei einem exec (ein system-Aufruf verwendet unter anderem auch die exec-Funktion) das close-on-exec-Flag gesetzt wird. Nun wollen wir unseren Filedeskriptor mit fcntl duplizieren.....

/*Download:fcntl2.c*/
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> int main() { int FH1, FH2; FH1 = open("./auf2.c",O_RDONLY); if(FH1 == -1) { /*Fehler*/ exit(0); } FH2 = fcntl(FH1, F_DUPFD, 0); if(FH2 == -1) { /*Fehler*/ exit(0); } system("./xt"); return 0; }

Durch.....

FH2 = fcntl(FH1, F_DUPFD, 0);

...wird das close-on-exec-Flag gelöscht, so das der Filedeskriptor nun auch für unser neu Aufgerufenes Programm zur Verfügung steht. Ich habe im Programm den Filedeskriptor mit der Nummer 4 verwendet. Testen sie bitte Ihre Deskriptornummer im Programm mit......

printf("%d\n",FH2);

...und ergänzen sie dies dann entsprechend in dem Programm xt. Hier geht es nur um die Erklärung des close-on-exec-Flag. Besser wäre es gewesen den neuen Diskiptor an das neue aufzurufende Programm zu vererben. Aber dazu später mehr. Führen sie das Programm nun erneut aus, und nun wird auch das Ausgegeben was sie erwarten.

Daraus ergibt sich, wird ein Filedeskriptor mit fcntl oder dup (dup2) dupliziert wird das close-on-exec (Schließen bei Programmaufruf) Flag gelöscht, so das die Filedeskriptoren nicht geschlossen werden.

F_GETFD

Mit F_GETFD bekommen sie als Rückgabewert, den aktuellen Flagstatus des Filedeskriptors. Zur Zeit gibt es aber nur das close-on-exec - Flag welches sie eben kennen gelernt haben. Ist der Rückgabewert 0, so ist das Flag gelöscht, ist er 1 so ist das Flag gesetzt.......

if((fcntl(fd,F_GETFD,FD_CLOEXEC)) == 1)
 {
  /*close-on-exec-Flag ist gesetzt*/
 }
else
 {
  /*close-on-exec-Flag ist nicht gesetzt*/
 }

F_SETFD

Mit F_SETFD können sie die fd - Flags ändern. Es gibt ja nur das close-on-exec Flag. Sie müssen also nicht erst einen Filedeskriptor kopieren, um das close-on-exec Flag zu löschen. Um das close-on-exec Flag zu setzen, verwenden wir die Konstante FD_CLOEXEC. Nehmen wir also auch hier wieder unser bekanntes Beispiel von oben...

/*Download:fcntl3.c*/
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> int main() { int FH1; char puffer[10]; FH1 = open("./auf2.c",O_RDONLY); if(FH1 == -1) { /*Fehler*/ exit(0); } /*Ist das close-on-exec Flag bereits gelöscht?*/ if( (fcntl(FH1, F_GETFD, FD_CLOEXEC) == 0) ) { printf("Das close-on-exec Flag ist bereits gelöscht!!\n"); system("./xt"); /*Programmaufruf*/ } else if( (fcntl(FH1, F_SETFD, FD_CLOEXEC) > 0 ) ) { printf("close-on-exec Flag wurde gerade gesetzt\n"); system("./xt"); /*Programmaufruf*/ } else printf("Konnte das close-on-exec Flag nicht setzen\n"); return 0; }

Verwenden sie bei dem Programm xt von oben bitte als Filedeskriptor die Nummer 3. 0,1 und 2 sind ja schon Standardmäßig vergeben. Daher besitzt der nächste Deskriptor die 3.

Jetzt haben sie gesehen wie sie die fd Flags der Prozesstabelle beeinflussen können. Nun wissen sie vom Kapitel open, das in jeder Prozesstabelle ein weitere Zeiger auf die Dateitabelle verweist. Und in in dieser Tabelle befindet sich das File Status Flag. Mit der Konstante....

F_GETFL

....sie diese Flag nach dem aktuellen Zustand Abfragen. Folgende Statusmöglichkeiten können sie hier Abfragen.......

Modus Bedeutung
O_RDONLY nur lesen
O_WRONLY nur schreiben
O_RDWR lesen und schreiben
O_APPEND zum schreiben ans Dateiende öffnen
O_NONBLOCK kein Blockieren bei FIFO´s und Gerätedateien
O_SYNC nach jedem Schreiben auf die Beendigung des physik. Schreibvorgangs warten
O_ASYNC asynchrone I/O (nur bei BSD)


ACHTUNG: Wenn den Rückgabewert von F_GETFL haben wollen müssen sie es durch O_ACCMODE filtern...

 flag=fcntl(fd,F_GETFL,0);
modus=flag & O_ACCMODE;
if(modus == O_RDWR)
   { /*Auf diesem Filedeskriptor ist Lesen und Schreiben möglich*/ }
else if(modus == O_RDONLY)
   { /*Auf diesem Filedeskriptor ist nur Lesen möglich*/ }
else if(modus == O_WRONLY)
   { /*Auf diesem Filedeskriptor können sie nur schreiben*/ }

Um nun die einzelnen File Status Flags zu ändern, haben wir die Konstante...

F_SETFL

Um diese einzelnen Flags nun zum setzen benötigen wir nun anstatt, wie bei F_GETFL, den Bitweisen UND-Operator, den Bitweisen ODER-Operator. Es können aber nur folgenden Flags verändert werden....

O_APPEND; O_NONBLOCK; O_SYNC; O_ASYNC

Wir gehen hier wie folgt vor.....

modus|= O_APPEND;
if((fcntl(fd,F_SETFL,modus)) > 0)
  { /*File Status Flag O_APPEND gesetzt*/ }
else
  { /*File Status Flag O_APPEND konnte nicht gesetzt werden */ }

Weiter 2 Konstanten zum Setzen und Abfragen der Eigentumsrechte bei asyncroner E/A wären.....

F_GETOWN

fcntl liefert PID oder GID des Prozesses, der gerade die Signale SIGIO und SIGURG empfängt.

F_SETOWN

Damit legen sie die PID oder GID fest, der die Signale SIGIO und SIGURG empfängt. Ein positiver Wert legt die PID ein negativer die GID fest.

Hierzu nun ein kleines Programmbeispiel wie sie fcntl benützen können.....

/*Download:fcntl4.c*/
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> int main(int argc, char **argv) { int fd,fdup; int flags,modus; if(argc<2) { fprintf(stderr,"Benutzung : %s datei \n",argv[0]); exit (0); } if(( fd = open(argv[1],O_RDONLY)) == -1) { fprintf(stderr,"Konnte %s nicht oeffnen!!!\n",argv[1]); exit (1); } /*Wir Duplizieren den Filediskriptor mit dem Kommando F_DUPFD*/ if((fdup=fcntl(fd,F_DUPFD,0)) != -1) fprintf(stdout,"Fildeskriptor erfolgreich dupliziert!!!\n"); /*Wir überprüfen die fdflags von fd und setzten falls nicht gesetzt FD_CLOEXEC*/ if((fcntl(fd,F_GETFD,FD_CLOEXEC)) == 1) fprintf(stdout,"close-on-exec-Bit ist gesetzt!!!\n"); else if((fcntl(fd,F_SETFD,FD_CLOEXEC)) <0) fprintf(stderr,"Konnte close-on-exec-Bit nicht setzen!!!\n"); else fprintf(stdout,"close-on-exec-Bit wurde gesetzt!!!\n"); /*Wir überprüfen die file status flags von fd*/ flags=fcntl(fd,F_GETFL,0); modus=flags & O_ACCMODE; if(modus==O_RDWR) fprintf(stdout,"Datei zum lesen und schreiben geöffnet!!\n"); else if(modus==O_RDONLY) fprintf(stdout,"Datei nur zum lesen geöffnet!!\n"); else if(modus==O_WRONLY) fprintf(stdout,"Datei nur zum schreiben geöffnet!!\n"); /*Wir überprüfen weitere statur flags von fd */ /* und ändern falls nötig die flags*/ if(modus==O_APPEND) fprintf(stdout,"...wir können am Dateiende schreiben...\n"); modus|=O_APPEND; if((fcntl(fd,F_SETFL,modus)) <0) fprintf(stdout,"...Konnte Attribut O_APPEND nicht setzen...\n"); if(modus==O_APPEND) fprintf(stdout,"...O_APPEND gesetzt...\n"); flags=fcntl(fd,F_GETFD,0); modus=flags & O_ACCMODE; if(modus==O_SYNC) fprintf(stdout,"...O_SYNC gesetzt...\n"); else { modus|=O_SYNC; fcntl(fd,F_SETFL,modus); if(modus==O_SYNC) fprintf(stdout,"...O_SYNC gesetzt...\n"); else fprintf(stdout,"...konnte O_SYNC nicht setzten...\n"); } return 0; }

Wer jetzt dachte das war's mit fcntl der sieht sich getäuscht. Diese Funktion wird auch eingesetzt zum Sperren von Dateien (record locking). Hierzu vorerst nur ein kurzer Überblick, da wir uns diesem Thema ein extra Kapitel zuwenden werden...........

int fcntl(int fd, int kommando,.../*struct flock *flockzeiger*/);

Sehen wir uns zuerst die Struktur flock und deren Inhalt an....

struct flock {
  short l_type;   /*3 Möglichkeiten F_RDLCK (Lesesperre)
                                    F_WRLCK (Schreibsperre)
                                    F_UNLCK (Sperre aufheben)*/
  off_t l_start;  /*relatives offset in Byte, abhängig von l_whence*/
  short l_whence; /*SEEK_SET;SEEK_CUR;SEEK_END*/
  off_t l_len;    /*Grösse der Speicherung in Bytes,
                    0=Sperren bis Dateiende*/
  pid_t l_pid;           /*wird bei F_GETLK zurückgegeben*/
 }; 

Wenn sie sollten (wird meistens der Fall sein) eine ganze Datei zum lesen bzw. schreiben sperren wollen müssen sie l_start und l_whence an dem Dateianfang setzen. Also...

flockzeiger.l_start=0;
flockzeiger.l_whence=SEEK_SET;

Falls sie eine Datei immer wieder vom aktuellen Dateiende sichern wollen übergeben sie an l_len den Wert 0...

flockzeiger.l_len=0;

...damit sind sie immer sicher das die Datei bis zum Dateiende gesperrt ist. Egal ob sie jetzt neuen Inhalt ans Ende anfügen. Folgende Angaben können bei Kommando machen...

F_GETLK

Hiermit können wir abfragen ob und welche Sperre für eine Datei bereits vorhanden ist....

struct flock sperre;

fd=open(datei, O_CREAT|O_WRONLY);

fcntl(fd, F_GETLK, &sperre);
if(sperre.l_type==F_UNLCK)
   {/*Datei nicht gesperrt*/}
else if(sperre.l_type==F_RDLCK)
   {/*Lesesperre*/}
else if(sperre.l_type==F_WRLCK)
   {/*Schreibsperre*/}

F_SETLK

Hiermit können sie ein Sperre einrichten. Ist es nicht möglich eine Sperre einzurichten beendet die Funktion fcntl sich und setzt die Variable errno auf EACCES oder EAGAIN. Mit F_SETLK wird auch die gesetzte Sperre wieder entfernt (F_UNLCK)........

sperre.l_start=0;
sperre.l_whence=SEEK_SET;
sperre.l_len=0;
sperre.l_type=F_RDLCK;
if((fcntl(fd, F_SETLK, &sperre)!=-1)
   {/*Lesesperre gesetzt*/}

sperre.l_start=0;
sperre.l_whence=SEEK_SET;
sperre.l_len=0;
sperre.l_type=F_WRLCK);
if((fcntl(fd, F_SETLK, &sperre)!=-1)
   {/*Schreibsperre gesetzt*/}

sperre.l_type=F_UNLCK;
if((fcntl(fd, F_SETLK, &sperre)!=-1)
   {/*Sperre aufgehoben*/}

F_SETLKW

Mit diesem Kommando wird unser Prozess suspendiert bzw. er wartet bis er die geforderte Sperre einrichten kann.

WICHTIG: Wenn sie eine Datei mit einer Schreibsperre (F_WRLCK) versehen wollen, muss die Datei auch zum schreiben geöffnet (O_WRONLY) werden. Und umgekehrt wenn sie einer Datei eine Lesesperre anhängen wollen muss diese auch im Lesemodus (O_RDONLY) geöffnet werden. Beispiel zum record locking......

/*Download:fcntl5.c*/
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #define FNAME "locki.lck" void status(struct flock *lock) { printf("Status: "); switch(lock->l_type) { case F_UNLCK: printf("F_UNLCK\n"); break; case F_RDLCK: printf("F_RDLCK (pid: %d)\n", lock->l_pid); break; case F_WRLCK: printf("F_WRLCK (pid: %d)\n", lock->l_pid); break; default : break; } } int main(int argc, char **argv) { struct flock lock; int fd; char puffer[100]; fd = open(FNAME, O_WRONLY|O_CREAT|O_APPEND); memset(&lock, 0, sizeof(struct flock)); fcntl(fd, F_GETLK, &lock); if(lock.l_type==F_WRLCK || lock.l_type==F_RDLCK) { status(&lock); memset(&lock,0,sizeof(struct flock)); lock.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &lock) < 0) printf("fcntl(fd, F_SETLK, F_UNLCK) failed (%s)\n",strerror(errno)); else printf("fcntl(fd, F_SETLK, F_UNLCK) succeeded\n"); } status(&lock); while((read(1,puffer,100))>1) write(fd,puffer,100); memset(&lock, 0, sizeof(struct flock)); lock.l_type = F_WRLCK; lock.l_start=0; lock.l_whence = SEEK_SET; lock.l_len=0; lock.l_pid = getpid(); if (fcntl(fd, F_SETLK, &lock) < 0) printf("fcntl(fd, F_SETLK, F_WRLCK) failed (%s)\n",strerror(errno)); else printf("fcntl(fd, F_SETLK, F_WRLCK) succeeded\n"); status(&lock); close(fd); return 0; }

Dies war nur ein kurzer Überblick zum Thema record locking. In einem späteren Kapitel werden wir etwas genauer in diese Thema eingehen.

ein Kapitel zurück          nach oben           ein Kapitel weiter


© 2001,2002 Jürgen Wolf