Diese Folge erläutert eine Vielzahl von Systemcalls im Kontext der Dateiensysteme von Linux, deren Bedeutung in erster Linie in Sachen Manipulation und Erkenntnis liegen. Linux kennt davon sehr viele, ich werde mich zunächst auf die wichtigsten beschränken - andere werden an anderer Stelle besprochen, da sie besser in den dann aktuellen Bezugsrahmen passen. Diese Verweise auf später und die aktuellen Beschränkungen sind natürlich willkürlich - ich halte es für sehr schwierig, die Systemcalls wirklich systematisch in Kategorien einzuteilen.
Als zusätzliche Hintergrund-Information machen wir zuerst einen kleinen Ausflug, der die Implementation eines Systemcalls ein klein wenig aufhellt.
In der letzten Folge hatte ich erwähnt, daß ein Systemcall wie read oder write lediglich die Schnittstelle zum VFS ist. Heute will ich ein paar weitergehende Bemerkungen als Präzisierung vornehmen - ich wähle zur Betrachtung den read-Befehl des ext2 - Filesystems.
Die Bibliotheksfunktion read mündet im Aufruf des Kernelmoduls sys_read. Dies geschieht über die generierte sys_call -Funktion, die mittels Softinterrupt 0x80 in das sys_call- Kernelmodul springt, das dann das zuständige sys_ Modul indiziert aufruft (ein späterer Beitrag behandelt die Implementation neuer Systemcalls, dann wird alles klarer).
sys_read ist eine Kernel-Funktion, die nach einigen Checks zum Aufruf
file->f_op->read(inode, file, buf, count);
führt, der Zeiger file ist ein Zeiger auf struct file, deren Instanzen als ringverkettete Liste die traditionelle Filetable bilden und inode ist Zeiger auf struct inode, die wiederum mit ihren Listenelementen die klassische Inodetable realisiert. Die Initialisierung dieser Tabellen erfolgt mit dem Systemcall open, der für normale Dateien und Verzeichnisse im VFS etabliert ist, die konkreten Zeigerwerte werden beim Zugriff über den Deskriptor ermittelt - soweit in Relation zu anderen Unix-Systemen keine großen Änderungen.
Neu ist lediglich die Tatsache, daß read ein Zeiger auf eine Funktion ist. Alle grundlegenden Operationen auf Dateien sind als Zeiger auf Funktionen in der struct file_operations beschrieben, eine wesentliche Voraussetzung in Sachen Implementation des virtuellen Dateiensystems, denn das ermöglicht ein spätes Binden des wirklichen Aufrufs an einen konkreten Adresswert.
Dieser Zeiger read hat beim Aufruf die Adresse der Funktion, die von dem jeweiligen Dateiensystem zum Lesen benutzt wird , da in der struct file wiederum eine Komponente struct file_operations *f_op existiert, die als Verweis auf die wirklichen Dateioperation einen korrekten Aufruf realisiert.
Das Schreiben auf Verzeichnisse behält sich der Kernel vor, anders sieht es mit dem Lesen aus - frühere Unixvarianten ließen das direkte Lesen eines Verzeicnisses mit den Systemcall read zu, neuere bieten hierfür den Systemcall readdir mit einem entsprechenden open und close. Diese Systemcalls sind auch in Linux implementiert. Wir besprechen im fogenden
#include <sys/dir.h> DIR * opendir(char *); struct dirent readdir(DIR *); int closedir(DIR *);Diese Systemcalls sind realtiv einfach und wir schreiben gleich ein kleines Programm zum Lesen und Ausgeben eines Verzeichnisses.
| Listing 1: readdir.C |
#include <unistd.h>
#include <iostream.h>
main (int argc, char **argv)
{
DIR *dirp;
struct direct *d;
dirp = opendir(*++argv);
while (d = readdir(dirp))
{
cout << d->d_name << ´:´ << d_ino << '\n';
}
closedir(dirp);
}
|
opendir öffnet das Verzeichnis, der Name wird als Parameter übergeben, er versteht sich als Pfad (path). Der Zeiger dirp ist ein Zeiger auf eine Struktur, die von readdir für die weitere Arbeit benötigt wird. readdir liefert einen Zeiger auf die struct direct, die in ihren Komponenten die relevanten Informationen für uns enthält. Es existiert außerdem ein Makro
#define direct direntso daß sich die Linux typische
struct dirent {
long d_ino;
__kernel_off_t d_off;
unsigned short d_reclen;
char d_name[256];
};
ergibt. Die Komponenten d_ino und d_name enthalten die für uns wichtigen
Informationen, die auf dem Bildschirm ausgegeben werden. Die Inode-Nummer
ist die einzige Information, die uns zur Inode führt, die alle relevanten
weiteren Inforamtionen enthält. Wir betrachten nun die weiteren Systemcalls.
#include <sys/types.h> #include <sys/stat.h> int stat(char *path,struct stat *buffer); int fstat(int fd,struct stat *buffer);Beide Systemcalls leisten die gleiche Arbeit, sie liefern Informationen aus der Inode, stat erwartet einen Path, fstat hingegen einen Dateideskriptor, der auf eine geöffnetet Datei verweist. Die Informationen stehen nach Ausführung der Systemcalls in der
struct stat {
dev_t st_dev;
unsigned short __pad1;
ino_t st_ino;
umode_t st_mode;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
dev_t st_rdev;
unsigned short __pad2;
off_t st_size;
unsigned long st_blksize;
unsigned long st_blocks;
time_t st_atime;
unsigned long __unused1;
time_t st_mtime;
unsigned long __unused2;
time_t st_ctime;
unsigned long __unused3;
unsigned long __unused4;
unsigned long __unused5;
}
die hier in voller Länge wiedergegeben ist. Die Komponenten enthalten die üblichen Unix Inode-Infos, die Datentypen der Komponenten können der Datei types.h entnommen werden - traditioneller Unix-Stil.
Wir entwickeln eine kleine Funktion, die die Größe einer Datei zurückliefert.
| Listing 2: get_size.C |
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream.h>
int get_size(char *);
main(int argc,char ** argv)
{
cout << get_size(*++argv);
}
int get_size(char *name)
{
struct stat buffer;
stat(name,&buffer);
return buffer.st_size;
}
|
Die Informationen sind relativ einfach zu behandeln, problematischer ist lediglich st_mode, da diese Komponenete in den einzelnen Bits Infos über
Zugriffsrechte
suid-Bit
sticky-Bit
Dateientyp
enthält. Mit den üblichen Bitoperationen lassen sich diese Informationen jederzeit auslesen.Die Zugriffsrechte liegen in den Bits 0 - 8, mit
printf("%o",buffer.st_mode & 0777);
lassen sich diese z.B. ausgeben.
#include <unistd.h> int chown(char *path, int owner, int group); int chmod(char *path, int mode);und schreiben
| Listing 3: change_ident.C |
#include <unistd.h>
#include <iostream.h>
main(int argc, char **argv)
{
if(argc != 4)
{
cerr << "usage: change_ident uid gid file\n";
exit (1);
}
chown(argv[3],atoi(argv[1]),atoi(argv[2]));
// file uid gid
}
|
Change_ident verändert den Eigentümer und die Gruppe, die Werte werden numerisch übergeben, die effektive User-Id des ausführenden Prozesses muß 0 sein (euid kommt später), also sollte root dieses Programm ausführen. Der Call chmod verändert die Zugriffsrechte, arbeitet aber ansonsten analog zu chown, die Zugiffsrechte können vom Eigentümer und vom Superuser verändert werden.
Der Systemcall chroot verändert für einen Prozeß das root-Verzeichnis, ein Wechsel über das neue root-Verzeichnis hinaus ist dann nicht mehr möglich, der Prototyp lautet:
#include <unistd.h> int chroot(char *path);chroot ist z.B von Bedeutung für anonymous ftp, da ja bekanntlich hier ein Teilnehmer nur eine Teilsicht des Dateiensystems erhält, die Anwendung des Systemaufrufs ist trivial, da dieser lediglich den Namen des neuen root-Verzeichnisses erhält.
Das aktuelle Verzeichnis verändern wir mit dem Call chdir, der Name des neuen Verzeichnisses wird übergeben. Die Shell implementiert so ihr internes Kommando cdr. Zunächst der Prototyp
#include <unistd.h> int chdir(char * path);und anschließend implementieren wir die interne Funktion cd:
| Listing 4: cd.C |
#include <unistd.h>
int cd(char *);
char * getenv(char *);
int cd(char *path)
{
if(path == 0) {
return chdir(getenv("HOME")); //Aufruf war cd
}
return chdir(path); //Aufruf war cd path
}
|
Die Funktion getenv liefert das Heimatverzeichnis aus der Umgebungsvariablen HOME (besser wäre es, HOME aus der passwd zu holen, da die Umgebungsvariable geändert sein könnte, so what..), anschließend wechseln wir das Verzeichnis, allerdings nur, wenn dieser Wechsel möglich ist. Sicher können wir nur sein, wenn wir uns vorher überzeugt hätten, daß die Berechtigung vorliegt. Das führt uns zur Abteilung
#include <unistd.h>
int access(char *path, int mode);
if(access(argv[1],R_OK)==-1)
{
perror("access: ");
exit(1);
}
Für die Zugriffskontrolle existieren die Makros
#include <unistd.h> int unlink(char *path); int rmdir(char *path);zur Verfügung, wobei path jeweils der Name des zu löschenden Objektes ist.
mknod erzeugt inodes pipe erzeugt selbige dup dupliziert Deskriptor fcntl manipuliert Deskriptoren select selektiert Eingabekanäle flock sperrt Datei ioctl manipuliert Gerätetreiber mount montiert Dateiensysteme umount demontiert selbige sync synchronisiert Dateiensysteme statfs Infos zum Dateiensystem umask Maske für Zugriffsrechte utime Zeitstempel setzen uselib shared libs auswahleEs gibt also noch einiges zu lernen. Die nächste Folge beschäftigt sich mit Prozessen. In der Zwichenzeit viel Erfolg beim Experimentieren mit den bisher besprochenen Systembefehlen.
| Infos |
|
[1] Unix Programmierung für Fortgeschrittene, Hanser, 1985 [2] Linux-Kernel-Programmierung, Addison-Wesley, 1997 |
| Der Autor |
|
Wolfgang Hetzler arbeitet seit 1985 freiberuflich als Dozent für Informatik und als Lehrbeauftragter an der FH Frankfurt. Linux kennt er seit 0.9xx und ist ihm ein Quell ständiger Auseinandersetzung. Zu erreichen ist er unter het@het.gg.eunet.de. |
Copyright © 1998 Linux-Magazin Verlag