Είσοδος-Έξοδος αρχείων
Είσοδος-Έξοδος αρχείων Οι περισσότερες λειτουργίες στα αρχεία μπορούν να πραγματοποιηθούν με πέντε συναρτήσεις open, read, write, lseek, close Οι συναρτήσεις αυτές καλούνται συλλογικά ως «ε/ε χωρίς χρήση ενδιάμεσης μνήμης» (unbuffered I/O) σε αντιδιαστολή με τις αντίστοιχες συναρτήσεις της βιβλιοθήκης της C Κάθε κλήση των open, read, write, lseek, close οδηγεί στην εκτέλεση μιας κλήσης συστήματος Οι συναρτήσεις αντιμετωπίζουν ζητήματα συγχρονισμού, τι συμβαίνει όταν πολλαπλές διεργασίες προσπελάζουν το ίδιο αρχείο Ειδικές συναρτήσεις (fcntl, ioctl) αντιμετωπίζουν εξειδικευμένα ζητήματα, π.χ. χαρακτηριστικά ειδικών αρχείων, ενδείξεις
Περιγραφείς αρχείων Ο πυρήνας γνωρίζει τα ανοικτά αρχεία μέσω του σχετικού περιγραφέα αρχείου Όταν δημιουργούμε ή ανοίγουμε ένα αρχείο, ο πυρήνας επιστρέφει έναν περιγραφέα αρχείου Όταν θέλουμε να γράψουμε, να διαβάσουμε ή να μετακινηθούμε σε ένα αρχείο, παρέχουμε στον πυρήνα τον σχετικό περιγραφέα ως όρισμα της συνάρτησης Κατά σύμβαση, οι φλοιοί του Unix αντιστοιχίζουν στους περιγραφείς 0, 1 και 2 την κανονική είσοδο, κανονική έξοδο και κανονική έξοδο σφαλμάτων αντίστοιχα Το POSIX ορίζει τις συμβολικές σταθερές STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO που είναι καλό να χρησιμοποιούνται αντί των 0, 1 και 2 (<unistd.h>) Οι περιγραφείς λαμβάνουν τιμές από 0 έως OPEN_MAX - 1
Η συνάρτηση open Άνοιγμα και δημιουργία αρχείων #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int oflag, ... /* mode_t mode */); Επιστρέφει περιγραφέα αρχείου αν επιτύχει, αλλιώς –1 Το πρώτο όρισμα υποδεικνύει τη διαδρομή του αρχείου που θέλουμε να ανοιχθεί Το δεύτερο όρισμα υποδεικνύει τον τρόπο ανοίγματος του αρχείου ορίζεται ως διάζευξη επιπέδου bit συμβολικών σταθερών (επόμενη διαφάνεια) Το τρίτο όρισμα καθορίζει τα δικαιώματα πρόσβασης στο αρχείο, εφ’ όσον αυτό θα δημιουργηθεί
Το 2ο όρισμα της συνάρτησης open Διάζευξη επιπέδου bit συμβολικών σταθερών ΑΚΡΙΒΩΣ ΜΙΑ από τις κάτωθι: Ο_RDONLY Ανάγνωση μόνο O_WRONLY Εγγραφή μόνο O_RDWR Ανάγνωση και εγγραφή Οποιονδήποτε αριθμό (συμπεριλαμβανομένου 0) από τις κάτωθι: Ο_APPEND Οι εγγραφές πάνε στο τέλος του αρχείου O_CREAT Το αρχείο δημιουργείται αν δεν υπάρχει. Απαιτεί να προσδιορίζεται η 3η παραμέτρος O_EXCL Μαζί με την O_CREAT, αν το αρχείο υπάρχει επιστρέφεται σφάλμα και το αρχείο δεν ανοίγεται O_TRUNC Αν το αρχείο υπάρχει και ανοίγεται για εγγραφή ή για ανάγνωση/εγγραφή, περικόπτεται σε μήκος 0 O_NOCTTY Για άνοιγμα τερματικών καθορίζεται ότι δεν είναι τερματικό ελέγχου της διεργασίας O_NONBLOCK Μη ανασταλτική είσοδος/έξοδος (σε επόμενο κεφάλαιο) O_SYNC Οι κλήσεις write περιμένουν να ολοκληρωθεί η φυσική εγγραφή
Το 2ο όρισμα της συνάρτησης open - Παραδείγματα open("/tmp/myfile.txt", O_RDONLY); open("/tmp/myfile.txt", O_RDONLY, S_IRWXU); open("thefile.bin", O_WRONLY); open("thefile.bin", O_WRONLY | O_TRUNC); open("thefile.bin", O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP); open("thefile.bin", O_WRONLY | O_CREAT | O_EXCL, S_IRWXU); open("thefile.bin", O_RDWR | O_SYNC); open("thefile.bin", O_RDWR | O_APPEND);
Περικοπή ονομάτων Η συμπεριφορά της open όταν δημιουργεί νέο αρχείο και το καθορισθέν όνομα είναι μεγαλύτερο από το NAME_MAX του συγκεκριμένου συστήματος αρχείων μπορεί να είναι: η σιωπηλή περικοπή του ονόματος του αρχείου στα όρια του NAME_MAX π.χ. NAME_MAX = 14 open("a_long_filename", O_CREAT, S_IRWXU) Δημιουργείται το αρχείο a_long_filenam Η αποτυχία της open με επιστροφή της τιμής –1 και θέση του errno στην τιμή ENAMETOOLONG Το τι ακριβώς συμβαίνει εξαρτάται από την τιμή του _POSIX_NO_TRUNC για το συγκεκριμένο σύστημα αρχείων Εκτός από την open, τα ίδια ισχύουν και για άλλες συναρτήσεις που δέχονται διαδρομές αρχείων π.χ. stat Αντίστοιχο σφάλμα μπορεί να συμβεί αν το συνολικό μήκος διαδρομής ξεπεράσει το PATH_MAX
Παράδειγμα [long_name.c] #define MAX_NAME_GUESS 256 int make_long_filename(char *path) { char *theFileName; int fd, theSize, ctr, pathLen = strlen(path); if ((theSize = pathconf(path, _PC_NAME_MAX)) == -1) { if (errno == 0) theSize = MAX_NAME_GUESS; else { perror("_PC_NAME_MAX: "); exit(1); } } if ((theFileName = malloc(theSize + pathLen + 4)) == NULL) { perror("malloc"); exit(1); } strcpy(theFileName, path); theFileName[pathLen] = '/'; for (ctr = 1; ctr <= theSize + 1; ctr++) theFileName[pathLen + ctr] = 'a'; theFileName[pathLen + ctr] = '\0'; if ((fd = open(theFileName, O_WRONLY | O_CREAT, S_IRWXU)) == -1) { perror(theFileName); return -1;} printf("Created %s\n", theFileName); close(fd); return 0;
Δημιουργία αρχείων – Η συνάρτηση creat int creat(const char *pathname, mode_t mode); Επιστρέφει περιγραφέα αρχείου αν επιτύχει, αλλιώς –1 Ισοδύναμη με open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode); Ήταν χρήσιμη σε παλαιότερα συστήματα UNIX, όπου η open δεν υποστήριζε τη δυνατότητα O_CREAT Η open είναι πιο γενική από την creat και γι’ αυτό πρέπει να προτιμάται π.χ. μπορεί να δημιουργήσει αρχείο και να το ανοίξει για ανάγνωση/εγγραφή
Συνάρτηση close Τα ανοικτά αρχεία κλείνουν με τη συνάρτηση close int close(int fd); Επιστρέφει 0 για επιτυχή εκτέλεση, -1 για σφάλμα (π.χ. κλείσιμο μη ανοικτού αρχείου) Το κλείσιμο των αρχείων αυτόματα αίρει όλα τα κλειδώματα που έχει θέσει η διεργασία στο αρχείο Όταν τερματίζει μία διεργασία ο πυρήνας αυτόματα κλείνει όλα τα αρχεία που αυτή έχει ανοικτά είναι ωστόσο καλή πρακτική να κλείνουμε ρητώς τα αρχεία στη διεργασία
Η συνάρτηση read Η ανάγνωση δεδομένων από ένα αρχείο πραγματοποιείται με τη συνάρτηση read ssize_t read(int filedes, void *buff, size_t nbytes); Σε επιτυχή εκτέλεση επιστρέφει το πλήθος των bytes που διαβάστηκαν. Αν φθάσαμε στο τέλος του αρχείου επιστρέφεται 0, αν υπήρξε σφάλμα επιστρέφεται -1 Το πλήθος των bytes που πραγματικά διαβάστηκαν μπορεί να είναι μικρότερο από αυτό που ζητήθηκε όταν: σε κανονικό αρχείο, φθάσαμε στο τέλος του αρχείου πριν διαβαστούν τα Bytes που ζητήθηκαν (π.χ. σε αρχείο μεγέθους 300 bytes ζητάμε να διαβάσουμε 1000) Όταν διαβάζουμε από τερματικό, συνήθως επιστρέφεται μόνο μία γραμμή (είναι δυνατόν να το αλλάξουμε) Όταν διαβάζουμε από το δίκτυο, λόγω χρήσης ενδιάμεσων μνημών είναι πιθανόν να επιστραφούν λιγότερα bytes Όταν διαβάζουμε από συσκευές προσανατολισμένες σε εγγραφές (π.χ. ταινίες) μπορεί να έχουμε τη δυνατότητα να διαβάσουμε μέχρι 1 εγγραφή τη φορά
Η συνάρτηση read (συνέχεια) Η τρέχουσα μετατόπιση αρχικά είναι 0 Όταν διαβάζουμε/γράφουμε επιτυχώς, η τρέχουσα μετατόπιση προχωράει τόσες θέσεις, όσο και το πλήθος των δεδομένων που διαβάστηκαν/γράφτηκαν Η συνάρτηση lseek μετακινεί την τρέχουσα μετατόπιση σε οποιοδήποτε σημείο
Συνάρτηση read – Παράδειγμα [fsize.c] Υπολογισμός μεγέθους αρχείου (εξαιρετικά αναποτελεσματικός) #include <unistd.h> #include <fcntl.h> #include <errno.h> #define BYTES_TO_READ 1000 size_t fileSize(char *path) { int fd; size_t result = 0, nbytes; char buff[BYTES_TO_READ]; if ((fd = open(path, O_RDONLY)) == -1) { perror(path); return -1; } while ((nbytes = read(fd, buff, BYTES_TO_READ)) > 0) result += nbytes; if (nbytes == -1) perror(path); close(fd); return result; }
Συνάρτηση write Για εγγραφή δεδομένων σε ανοικτό αρχείο ssize_t write(int filedes, void *buff, size_t nbytes); Η τιμή που επιστρέφεται κανονικά είναι ίση με την Τρίτη παράμετρο, αλλιώς έχει προκύψει σφάλμα (π.χ. δεν υπάρχει χώρος στον δίσκο, ο χρήστης έχει ξεπεράσει το όριο χρήσης του δίσκου, η διεργασία έχει ξεπεράσει το όριο μεγέθους αρχείου κ.λπ.) Ως προς το σημείο εγγραφής των δεδομένων: Αν στο άνοιγμα το αρχείου δεν έχει ορισθεί η ένδειξη O_APPEND, τα δεδομένα γράφονται στην τρέχουσα μετατόπιση του αρχείου Αν στο άνοιγμα το αρχείου έχει ορισθεί η ένδειξη O_APPEND, η τρέχουσα μετατόπιση του αρχείου τίθεται στο τέλος του αρχείου πριν την εγγραφή και έτσι τα δεδομένα γράφονται στο τέλος του αρχείου Η τρέχουσα μετατόπιση του αρχείου αυξάνεται κατά το πλήθος των bytes που γράφηκαν
Συνάρτηση write – Παράδειγμα [write0.c] #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main(void) { int fd, nbytes; char *str1 = "ABCD", *str2 = "EFGHIJKL"; fd = open("text.dat", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); if (fd == -1) { perror("opening text.dat"); exit(1); } if (write(fd, str1, strlen(str1) /* + 1 */) < strlen(str1)) { perror("writing text.dat"); close(fd); exit(2); } if (write(fd, str2, strlen(str2) /* + 1 */) < strlen(str2)) { close(fd); return 0; }
Συνάρτηση write – Παράδειγμα [write.c] #include <math.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <errno.h> int main(void) { int fd, nbytes; double theNumber, theSine; fd = open("sines.dat", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU) if (fd == -1) { perror("opening sines.dat"); exit(1);} for (theNumber = -M_PI; theNumber <= M_PI; theNumber += M_PI / 100) { theSine = sin(theNumber); if (write(fd, &theSine, sizeof(double)) < sizeof(double)) { perror("writing sines.dat"); close(fd); exit(2); } close(fd); return 0;
Συνάρτηση lseek Για κάθε ανοικτό αρχείο υπάρχει η τρέχουσα μετατόπιση του αρχείου μη αρνητικός ακέραιος που μετρά το πλήθος των bytes από την αρχή του αρχείου (στο «μη αρνητικός» υπάρχουν λίγες εξαιρέσεις) Αμέσως μετά το άνοιγμα του αρχείου έχει τιμή 0 Η τρέχουσα μετατόπιση αλλάζει αυτόματα στις αναγνώσεις και εγγραφές Μπορεί να αλλάξει ρητώς με τη συνάρτηση lseek off_t lseek(int filedes, off_t offset, int whence); Επιστρέφει τη νέα τιμή της τρέχουσα μετατόπισης ή την τιμή (off_t)-1 αν υπάρχει σφάλμα Η δεύτερη παράμετρος ορίζει τη νέα μετατόπιση Αν η τρίτη παράμετρος έχει την τιμή: SEEK_SET, η νέα μετατόπιση μετράται από την αρχή του αρχείου SEEK_CUR, η νέα μετατόπιση μετράται από την τρέχουσα θέση του αρχείου SEEK_END, η νέα μετατόπιση μετράται από το τέλος του αρχείου Η συνάρτηση lseek επιτυγχάνει ακόμη και αν ορίσουμε μετατόπιση μετά το τέλος του αρχείου
Συνάρτηση lseek – Παράδειγμα [lseek.c] Ανάγνωση των bytes 6-8 από ένα αρχείο #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> #include <sys/types.h> int main(int argc, char *argv[]) { int fd, nbytes, cnt; char buf[3]; if (argc == 1) return 0; if ((fd = open(argv[1], O_RDONLY)) == -1) {perror(argv[1]); exit(1); } if (lseek(fd, (off_t)5, SEEK_SET) == -1) { perror("cannot seek"); close(fd); exit(1); } if ((nbytes = read(fd, buf, 3)) == -1) { perror("Cannot read"); close(fd); exit(1); } printf("read %d bytes\n", nbytes); for (cnt = 0; cnt < nbytes; cnt++) putchar(buf[cnt]); putchar('\n'); close(fd); return 0; }
Συνάρτηση lseek – Παράδειγμα [lseek1.c] Εγγραφή των bytes ABC στις θέσεις 100-102 σε ένα αρχείο #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> #include <sys/types.h> int main(int argc, char *argv[]) { int fd, nbytes, cnt; char buf[3] = {'Α', 'Β', 'C'}; if (argc == 1) return 0; if ((fd = open(argv[1], O_WRONLY)) == -1) {perror(argv[1]); exit(1); } if (lseek(fd, (off_t)99, SEEK_SET) == -1) { perror("cannot seek"); close(fd); exit(1); } if ((nbytes = write(fd, buf, 3)) != 3) { perror("Cannot write"); close(fd); exit(1); } close(fd); return 0; }
Αποτελεσματικότητα εισόδου/εξόδου (1) Έστω το πρόγραμμα [buffersize.c] #include <fcntl.h> #include <sys/types.h> #include <errno.h> #include <unistd.h> #include <stdio.h> #define BUFFERSIZE 8192 int main(void) { int fd; ssize_t num_bytes; long loop_counter = 0; char buff[BUFFERSIZE]; while ((num_bytes = read(STDIN_FILENO, buff, BUFFERSIZE)) > 0) { if (write(STDOUT_FILENO, buff, num_bytes) < num_bytes) { perror("writing file"); break; } loop_counter++; } if (num_bytes < 0) perror("reading file"); fprintf(stderr, "%ld loops done\n", loop_counter); return 0; Πως επηρεάζεται από την τιμή του BUFFERSIZE;
Αποτελεσματικότητα εισόδου/εξόδου (2) Αρχείο εισόδου μεγέθους 6ΜΒ, έξοδος στο /dev/null BUFFERSIZE User CPU System CPU Clock time #loops 1 10.014 12.588 23.643 6093480 4 2.233 3.404 5.629 1523370 16 0.620 0.851 1.456 380843 64 0.150 0.240 0.401 95211 256 0.070 0.090 0.142 23803 1024 0.040 0.060 0.081 5951 4096 0.030 0.063 1488 16384 0.056 372 65536 93 Ο πυρήνας διαβάζει/γράφει δεδομένα σε/από ενδιάμεσες μνήμες μεγέθους BUFSIZ
Συνάρτηση ftruncate Περικοπή του μεγέθους αρχείου #include <unistd.h> int ftruncate(int fd, off_t length); int truncate(const char *path, off_t length); Το μέγεθος του αρχείου περικόπτεται στο μέγεθος που προσδιορίζεται Επιστρέφει –1 σε περίπτωση αποτυχίας Η ftruncate λειτουργεί σε ανοικτά αρχεία, η truncate σε κλειστά
Συνάρτηση ftruncate – παράδειγμα [trunc.c] #include <fcntl.h> #include <stdio.h> #include <errno.h> #include <unistd.h> typedef struct {char isbn[20]; int yearPub; char title[32]; float price; } book; int main(void) { int fd; ssize_t nbytes; off_t new_len; book theBook; if ((fd = open("books.dat", O_RDWR)) == -1) { perror("Cannot open books.dat"); exit(1); } if ((new_len = lseek(fd, -(off_t)sizeof(book), SEEK_END)) == -1) { perror("cannot seek"); close(fd); exit(1);} if ((nbytes = read(fd, &theBook, sizeof(book))) != sizeof(book)) { perror("cannot read"); close(fd); exit(1); } if (lseek(fd, sizeof(book), SEEK_SET) == -1) { perror("cannot seek"); close(fd); exit(1); } if ((nbytes = write(fd, &theBook, sizeof(book))) != sizeof(book)) { perror("cannot write"); close(fd); exit(1); } if (ftruncate(fd, new_len) == -1) { perror("error in truncation"); close(fd); exit(1); } close(fd); return 0; }
Διαμοιρασμός αρχείων Το Unix υποστηρίζει τον διαμοιρασμό αρχείων μεταξύ διεργασιών Δομές πυρήνα: κάθε διεργασία έχει μία καταχώρηση στον πίνακα διεργασιών. Η καταχώρηση της διεργασίας έχει έναν πίνακα περιγραφέων ανοικτών αρχείων, με κάθε στοιχείο του να έχει (α) ενδείξεις για το αρχείο και (β) έναν δείκτη στον πίνακα αρχείων του πυρήνα Ο πίνακας αρχείων του πυρήνα έχει μία καταχώρηση για κάθε ανοικτό αρχείο στο σύστημα. Κάθε καταχώρηση περιλαμβάνει: τις ενδείξεις για το αρχείο (ανάγνωση, εγγραφή, προσθήκη, κλπ) την τρέχουσα μετατόπιση του αρχείου Έναν δείκτη στη δομή κόμβου-ι (v-node) του αρχείου Η δομή κόμβου-ι τυπικά διαβάζεται από τον δίσκο κατά το άνοιγμα του αρχείου (τα στοιχεία του κόμβου-δ [I-node]) και περιλαμβάνει: το μέγεθος του αρχείου τον ιδιοκτήτη του αρχείου δείκτες προς τα μπλοκ δεδομένων του αρχείου τη συσκευή στην οποία βρίσκεται φυσικά το αρχείο κ.ο.κ.
Διαμοιρασμός αρχείων Πίνακας περιγραφέων ανοικτών αρχείων Ενδείξεις αρχείου τρέχουσα μετατόπιση Δείκτης κόμβου-ι Πληροφορίες κόμβου-ι Πληροφορίες κόμβου-δ Τρέχον μέγεθος αρχείου Πίνακας περιγραφέων ανοικτών αρχείων flags ptr fd0 fd1 fd2 ... Ενδείξεις αρχείου τρέχουσα μετατόπιση Δείκτης κόμβου-ι Πληροφορίες κόμβου-ι Πληροφορίες κόμβου-δ Τρέχον μέγεθος αρχείου Καταχώρηση πίνακα διεργασιών Πίνακας αρχείων Πίνακας κόμβων-ι
Λειτουργίες εισόδου-εξόδου Βάσει των δομών διαχείρισης αρχείων: Όταν ολοκληρώνεται μία λειτουργία εγγραφής, η τρέχουσα θέση στον πίνακα αρχείων αυξάνεται. Αν η τρέχουσα θέση υπερβαίνει το τρέχον μέγεθος αρχείου, το τρέχον μέγεθος αρχείου αυξάνεται κατάλληλα Αν ένα αρχείο έχει ανοιχθεί με την ένδειξη O_APPEND, τίθεται η σχετική ένδειξη στον πίνακα αρχείων. Όταν έχουμε κλήση συστήματος write, πρώτα αντιγράφεται το τρέχον μέγεθος αρχείου στην τρέχουσα μετατόπιση, και έτσι η εγγραφή γίνεται πάντα στο τέλος του αρχείου Η συνάρτηση lseek αλλάζει μόνο την τρέχουσα μετατόπιση του αρχείου, χωρίς να πραγματοποιείται είσοδος/έξοδος Αν ένα αρχείο τοποθετηθεί στο τέλος του μέσω της lseek, αντιγράφεται το τρέχον μέγεθος αρχείου στην τρέχουσα μετατόπιση
Διαμοιρασμός αρχείων Δύο ανεξάρτητες διεργασίες ανοίγουν το ίδιο αρχείο κάθε διεργασία έχει τη δική της τρέχουσα μετατόπιση Πιν. περιγρ. αρχείων Ενδείξεις αρχείου τρέχουσα μετατόπιση Δείκτης κόμβου-ι flags ptr fd0 fd1 ... δ1 Ενδείξεις αρχείου τρέχουσα μετατόπιση Δείκτης κόμβου-ι Πληροφορίες κόμβου-ι Πληροφορίες κόμβου-δ Τρέχον μέγεθος αρχείου Πιν. περιγρ. αρχείων flags ptr fd0 fd1 ... Πίνακας αρχείων Πίνακας κόμβων-ι δ2 Πίνακας διεργασιών
Διαμοιρασμός αρχείων [sharefile.c] int main(void) { int infd, outfd, i, count; char buf[128], buf1[128]; printf("Enter str:"); gets(buf); if ((infd = open("/etc/passwd", O_RDONLY)) == -1) { perror("/etc/passwd"); exit(1); } if ((outfd = open("testfile.txt", O_WRONLY | O_CREAT, 0644)) == -1) { perror("testfile.txt"); exit(1); } gets(buf1); count = read(infd, buf1, 100); buf1[count] = '\0'; puts(buf1); for (i = 0; i < 5; i++) {gets(buf1); write(outfd, buf, strlen(buf));} close(infd); close(outfd); return 0; }
Διαμοιρασμός αρχείων Το ανωτέρω σχήμα λειτουργεί καλά για πολλαπλούς αναγνώστες, για πολλαπλούς εγγραφείς όμως είναι δυνατόν να δημιουργηθούν προβλήματα. Απαιτούνται ατομικές λειτουργίες (στη συνέχεια) Είναι δυνατόν να υπάρχουν καταχωρήσεις πινάκων περιγραφέων ανοικτών αρχείων σε διαφορετικές διεργασίες που δείχνουν στην ίδια καταχώρηση του πίνακα αρχείων του πυρήνα (στη συνέχεια) και συνεπώς μοιράζονται την ίδια μετατόπιση και τις λοιπές ενδείξεις αρχείων που αποθηκεύονται στη δομή πυρήνα Οι ενδείξεις στον πίνακα περιγραφέων ανοικτών αρχείων επηρεάζουν τη συγκεκριμένη διεργασία, οι ενδείξεις στον πίνακα αρχείων επηρεάζουν όλες τις διεργασίες που χρησιμοποιούν τη συγκεκριμένη καταχώρηση. Είναι δυνατόν να διαβάσουμε/τροποποιήσουμε αυτές του πίνακα περιγραφέων ανοικτών αρχείων μόνο ή και τις δύο με τη συνάρτηση fcntl (στη συνέχεια)
Ατομικές λειτουργίες [atomic-1.c] Θεωρήστε το κάτωθι απόσπασμα προγράμματος: fd = open(DATAFILE, O_WRONLY); (1) lseek(fd, 0, SEEK_END); (2) write(fd, &theRecord, sizeof(theRecord)); (3) close(fd); (4) Ο κώδικας λειτουργεί σωστά αν εκτελεστεί από μία διεργασία Αν εκτελεστεί από πολλές διεργασίες, υπάρχει ο κίνδυνος η ανάγνωση του μεγέθους του αρχείου (2) να μην είναι έγκυρη όταν εκτελεστεί η εντολή (3). Οι δύο διαδικασίες εκτελούνται ατομικά θέτοντας την ένδειξη O_APPEND
Ατομικές λειτουργίες (2) Θεωρήστε το κάτωθι απόσπασμα προγράμματος: if ((fd = open(DATAFILE, O_WRONLY)) < 0) { (1) if (errno == ENOENT) { (2) if ((fd = creat(DATAFILE, MODE)) < 0) (3) perror("create error"); (4) } (5) else { (6) perror("open error"); } Το πρόγραμμα ΔΕΝ λειτουργεί σωστά αν μεταξύ της open και της creat άλλη διεργασία δημιουργήσει το αρχείο Λύση: open(DATAFILE, O_WRONLY | O_CREAT | O_EXCL, MODE); εκτελείται ως ατομική λειτουργία
Περιγραφείς που δείχνουν στην ίδια δομή πυρήνα Μία διεργασία ανοίγει ένα αρχείο και κατόπιν δημιουργεί θυγατρική διεργασία Η θυγατρική διεργασία ως αντίγραφο της γονικής, κληρονομεί και τον περιγραφέα αρχείου που δείχνει σε συγκεκριμένη δομή πίνακα αρχείων του πυρήνα
Περιγραφείς που δείχνουν στην ίδια δομή πυρήνα Γονική διεργασία Πιν. περιγρ. αρχείων Ενδείξεις αρχείου τρέχουσα μετατόπιση Δείκτης κόμβου-ι Πληροφορίες κόμβου-ι Πληροφορίες κόμβου-δ Τρέχον μέγεθος αρχείου flags ptr fd0 fd1 fd2 infd Πιν. περιγρ. αρχείων fd2 fd1 ptr flags infd fd0 Θυγατρική διεργασία Εκτελεί fork()
Περιγραφείς που δείχνουν στην ίδια δομή πυρήνα [sharefile2.c] int main(void) { int outfd, count; char *str1; pid_t chpid; if ((outfd = open("testfile.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) { perror("testfile.txt"); exit(1); } if ((chpid = fork()) == -1) { perror("creating child"); exit(1); } else if (chpid == 0) { sleep(1); for (count = 0; count < 5; count++) { printf("CHILD: file position = %ld\n", tell(outfd)); write(outfd, "CHILD PROCESS\n", 14); sleep(2); } close(outfd); return 0; } printf("PARENT: file position = %ld\n", tell(outfd)); write(outfd, "PARENT PROCESS\n", 15); sleep(2); } close(outfd); wait(NULL); return 0;
Συναρτήσεις dup και dup2 #include <unistd.h> int dup(int filedes); int dup2(int filedes, int filedes2); Στην dup επιλέγεται ο πρώτος μη ανοικτός περιγραφέας αρχείων Αν στη dup2 ο περιγραφέας αρχείων είναι ανοικτός, πρώτα κλείνεται και μετά γίνεται η αντιγραφή ως ατομική ενέργεια Σε περίπτωση αποτυχίας επιστρέφουν –1 Πιν. περιγρ. αρχείων Ενδείξεις αρχείου τρέχουσα μετατόπιση Δείκτης κόμβου-ι Πληροφορίες κόμβου-ι Πληροφορίες κόμβου-δ Τρέχον μέγεθος αρχείου flags ptr fd0 fd1 fd2
Παράδειγμα dup και dup2 Φλοιός με εντολή ls > myfile char in_redir[PATH_MAX], out_redir[PATH_MAX]; int inrfd, outrfd, childPid; if ((childPid = fork()) == 0) { if (in_redir[0] != '\0') { /* input redirection */ ... } if (out_redir[0] != '\0') { /* output redirection */ if ((outrfd = open(out_redir, O_WRONLY | O_CREAT | O_TRUNC, theMode)) == -1) { perror(out_redir); exit(1); dup2(outrfd, STDOUT_FILENO); close(outrfd); if (execvp("ls", lsargs) == -1) ... /* Parent and error handling */
Συνάρτηση fcntl Η συνάρτηση fcntl χρησιμοποιείται για αναφορά και ορισμό των ιδιοτήτων ενός ήδη ανοικτού αρχείου #include <sys/types.h> #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* int arg */); Η εντολή (2η παράμετρος) μπορεί να είναι: F_DUPFD – δημιουργία αντιγράφου ανοικτού περιγραφέα αρχείων F_GETFD, F_SETFD – αναφορά/ορισμός ενδείξεων περιγραφέα αρχείου F_GETFL, F_SETFL – αναφορά/ορισμός ενδείξεων καταχώρησης στον πίνακα αρχείων F_GETOWN, F_SETOWN – αναφορά/ορισμός ιδιοκτησίας ασύγχρονης εισόδου/εξόδου F_GETLK, F_SETLK, F_SETLNK - αναφορά/ορισμός κλειδωμάτων εγγραφών Η τρίτη παράμετρος όταν παρατίθεται είναι ακέραιος, εκτός από την περίπτωση των κλειδωμάτων εγγραφών που είναι δείκτης σε δομή
Συνάρτηση fcntl Εντολή F_DUPFD newfd = fcntl(fd, F_DUPFD, minFileDes); Δημιουργία αντιγράφου του περιγραφέα αρχείων fd. Το αντίγραφο δημιουργείται σε περιγραφέα αρχείων με τιμή μεγαλύτερη ή ίση από το minFileDes. Η ένδειξη F_CLOEXEC του νέου περιγραφέα αρχείων τίθεται σε 0 (ο περιγραφέας δεν θα κλεισθεί όταν εκτελεστεί συνάρτηση της οικογένειας exec) Παρόμοια με την dup, με την εξαίρεση του 3ου ορίσματος
Συνάρτηση fcntl Εντολές F_GETFD, F_SETFD fdflags = fcntl(fd, F_GETFD, 0); res = fcntl(fd, F_SETFD, fdflags); Σε επίπεδο περιγραφέα αρχείου ορίζεται μόνο η ένδειξη F_CLOEXEC Παράδειγμα: fdflags = fcntl(fd, F_GETFD); if (flags & F_CLOEXEC == 0) puts("File does not close on exec"); else puts("File closes on exec"); puts("Clearing F_CLOEXEC flag..."); flags &= ~F_CLOEXEC; res = fcntl(fd, F_SETFD, flags); puts("Seting F_CLOEXEC flag..."); flags |= F_CLOEXEC;
Συνάρτηση fcntl Εντολές F_GETFL, F_SETFL fdflags = fcntl(fd, F_GETFL, 0); res = fcntl(fd, F_SETFL, fdflags); Οι ενδείξεις σε επίπεδο πίνακα ανοικτών αρχείων (πυρήνας) είναι: Ενδείξεις αρχείου Περιγραφή O_RDONLY Τρόπος προσπέλασης στο αρχείο. Απομονώνεται από τις άλλες ενδείξεις με την πράξη flags & O_ACCMODE O_WRONLY O_RDWR O_APPEND Οι εγγραφές στο τέλος του αρχείου O_NONBLOCK Μη ανασταλτική είσοδος-έξοδος O_SYNC Σύγχρονη εγγραφή O_ASYNC Ασύγχρονη είσοδος/έξοδος (μόνο στο 4.3+BSD)
Συνάρτηση fcntl – Παράδειγμα GETFL [getfl.c] #include <sys/types.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int accmode, val; if (argc != 2) return 2; if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) == -1) {perror(argv[1]); exit(1); } accmode = val & O_ACCMODE; if (accmode == O_RDONLY) puts("Read only"); else if (accmode == O_WRONLY) puts("Write only"); else if (accmode == O_RDWR) puts("Read/Write"); else puts("*** Unknown access mode ***"); if (val & O_APPEND) printf("Append"); if (val & O_NONBLOCK) printf(", non-blocking"); #if defined(O_SYNC) if (val & O_SYNC) printf(", synchronous writes"); #endif #if defined(O_ASYNC) if (val & O_SYNC) printf(", asynchronous I/O"); putchar('\n'); return 0; }
Συνάρτηση fcntl – Παράδειγμα SETFL /* Get current access mode */ if ((val = fcntl(theFd, F_GETFL, 0)) == -1) { perror("cannot get current access mode"); exit(1); } /* Set append mode */ fcntl(theFd, F_SETFL, val | O_APPEND); /* write data */ write(fd, &theRec, sizeof(theRec)); /* Restore old access mode */ fcntl(theFd, F_SETFL, val);
O_SYNC και επιδόσεις Η προσθήκη του O_SYNC εξασφαλίζει ότι τα δεδομένα γράφονται στον δίσκο χειροτερεύει τις επιδόσεις του προγράμματός μας (όχι κατ’ ανάγκην του συστήματος) Ενδεικτικές επιδόσεις, αρχείο 6 MB BUFFERSIZE User CPU System CPU Clock time Σχόλια 4096 0.004 0.16 0.22 > /dev/null 0.23 0.53 > diskfile, no SYNC 0.01 0.70 1.76 > diskfile, SYNC Χρησιμοποιείται κυρίως από βάσεις δεδομένων
Η συνάρτηση ioctl Ενσωματώνει οποιαδήποτε λειτουργικότητα δεν μπορεί να μοντελοποιηθεί γενικά ειδικά χαρακτηριστικά συσκευών μπλοκ, χαρακτήρων, τερματικών, διόδων (sockets) κλπ. #include <sys/ioctl.h> #include <unistd.h> int ioctl(int fd, int request, ...); Για τις «αιτήσεις» (2η παράμετρος) χρησιμοποιούνται συμβολικές σταθερές από τα κάτωθι αρχεία ενσωμάτωσης: Κατηγορία Ονομασία σταθερών Αρχείο ενσωμάτωσης Πλήθος αιτήσεων Ετικέτες δίσκων DIOxxx <disklabel.h> 10 Ε/Ε αρχείων FIOxxx <ioctl.h> 7 Ε/Ε μαγνητικών ταινιών MTIOxxx <mtio.h> 4 Ε/Ε διόδων SIOxxx 25 Ε/Ε τερματικών TIOxxx 35
Η συνάρτηση ioctl - Παράδειγμα Πρόγραμμα ioctl-t.c #include <fcntl.h> #include <unistd.h> #include <termios.h> int main(void) { ioctl(STDOUT_FILENO, TIOCSTOP); printf("Hello there\n"); printf("HELLO... HELLO I SAID!\n"); sleep(5); ioctl(STDOUT_FILENO, TIOCSTART); printf("Ah, there you are!\n"); return 0; }
Αρχεία και κατάλογοι Το λειτουργικό σύστημα, εκτός από τις βασικές λειτουργίες στα αρχεία (άνοιγμα, ανάγνωση, εγγραφή κ.λπ.) προσφέρει μία σειρά υπηρεσιών: αναφορά ιδιοτήτων τύπος αρχείου δικαιώματα πρόσβασης ιδιοκτησία αρχείων χρόνοι δημιουργίας, πρόσβασης, τροποποίησης περικοπή μεγέθους δημιουργία συνδέσμων και συμβολικών συνδέσμων, διαγραφή, μετονομασία δημιουργία, διαγραφή και χειρισμός καταλόγων χειρισμός τρέχοντος καταλόγου διαχείριση κρυφής μνήμης
Αναφορά ιδιοτήτων Μέσω των συναρτήσεων stat, fstat, lstat γεμίζουν μία δομή τύπου struct stat Στη stat ορίζουμε τη διαδρομή, η fstat λειτουργεί σε ανοικτά αρχεία. Η lstat επιστρέφει πληροφορίες για συμβολικούς συνδέσμους struct stat { mode_t st_mode; /* Τύπος αρχείου & δικαιώματα */ inode_t st_ino; /* αριθμός κόμβου-δ */ dev_t st_dev; /* αριθμός συσκευής (συστ. αρχείων) */ dev_t st_rdev; /* αριθμός συσκευής για ειδικά αρχεία */ nlink_t st_nlink; /* αριθμός συνδέσμων */ uid_t st_uid; /* ιδιοκτήτης */ gid_t st_gid; /* ομάδα */ off_t st_size; /* μέγεθος, για κανονικά αρχεία */ time_t st_atime; /* χρόνος τελευταίας προσπέλασης */ time_t st_mtime; /* χρόνος τελευταίας τροποποίησης */ time_t st_ctime; /* χρόνος δημιουργίας */ long st_blksize; /* βέλτιστο μέγεθος μπλοκ για είσοδο/έξοδο */ long st_blocks; /* Πλήθος μπλοκ κατανεμημένων στο αρχείο */ }
Τύποι αρχείων Ένα αντικείμενο του συστήματος αρχείων μπορεί να είναι: Κανονικό αρχείο. Ο πυρήνας του Unix δεν διαχωρίζει ανάμεσα σε δυαδικά αρχεία και αρχεία κειμένου Κατάλογος. Περιέχει ονόματα αντικειμένων συστήματος αρχείων και δείκτες. Δυνατή η ανάγνωσή του, η εγγραφή γίνεται μόνο από τον πυρήνα Ειδικό αρχείο χαρακτήρων Ειδικό αρχείο μπλοκ Αρχείο κατονομασμένης σωλήνωσης (ή FIFO) για διαδιεργασιακή επικοινωνία Δίοδος (socket), για διαδιεργασιακή επικοινωνία Συμβολικός σύνδεσμος
Έλεγχος τύπου αρχείων Μέσω μακροεντολών που ορίζονται στο αρχείο ενσωμάτωσης sys/stat.h Όλες οι μακροεντολές δέχονται ως παράμετρο το πεδίο st_mode μιας δομής τύπου struct stat και επιστρέφουν αληθές ή ψευδές Μακροεντολή Τύπος αρχείου S_ISREG() κανονικό αρχείο S_ISDIR() κατάλογος S_ISCHR() ειδικό αρχείο χαρακτήρων S_ISBLK() ειδικό αρχείο μπλοκ S_ISFIFO() κατονομασμένη σωλήνωση S_ISLNK() σύνδεσμος S_ISSOCK() δίοδος Σε παλαιότερες εκδόσεις του Unix δεν υπήρχαν οι μακροεντολές. Έπρεπε να γίνει σύζευξη επιπέδου bit του st_mode με τη σταθερά S_IFMT και το αποτέλεσμα να συγκριθεί με μία σταθερά S_Ifxxx #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
Έλεγχος τύπου αρχείων – Παράδειγμα [stat-ftype.c] #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <string.h> int main(int argc, char *argv[]) { int i; struct stat buf; char *desc_str; for (i = 1; i < argc; i++) { printf("%s: ", argv[i]); if (lstat(argv[i], &buf) == -1) desc_str = strerror(errno); else if (S_ISREG(buf.st_mode)) desc_str = "regular file"; else if (S_ISDIR(buf.st_mode)) desc_str = "directory"; else if (S_ISCHR(buf.st_mode)) desc_str = "character special"; else if (S_ISBLK(buf.st_mode)) desc_str = "block special"; else if (S_ISFIFO(buf.st_mode)) desc_str = "FIFO"; else if (S_ISLNK(buf.st_mode)) desc_str = "symbolic link"; #ifdef S_ISSOCK else if (S_ISSOCK(buf.st_mode)) desc_str = "socket"; #endif else desc_str = "**Unknown mode**"; printf("%s\n", desc_str); } return 0;
Δικαιώματα πρόσβασης Το πεδίο st_mode μιας δομής τύπου struct stat κωδικοποιεί και τα δικαιώματα πρόσβασης στο αντικείμενο του συστήματος αρχείων Το st_mode περιέχει εννέα bits σε τρεις ομάδες των τριών (rwx για ιδιοκτήτη, ομάδα, λοιπούς) συν δύο bits για παραχώρηση ταυτότητας χρήστη και ομάδας Συμβολική σταθερά Σημασία S_IRUSR Ανάγνωση από τον ιδιοκτήτη S_IWUSR Εγγραφή από τον ιδιοκτήτη S_IXUSR Εκτέλεση από τον ιδιοκτήτη S_IRGRP Ανάγνωση από την ομάδα S_IWGRP Εγγραφή από την ομάδα S_IXGRP Εκτέλεση από την ομάδα S_IROTH Ανάγνωση από τους λοιπούς S_IWOTH Εγγραφή από τους λοιπούς S_IXOTH Εκτέλεση από τους λοιπούς S_ISUID Παραχώρηση ταυτότητας χρήστη S_ISGID Παραχώρηση ταυτότητας ομάδας
Bits παραχώρησης ταυτότητας Κάθε διεργασία διαθέτει τους ακόλουθους προσδιοριστές ταυτοτήτων: Πραγματική ταυτότητα χρήστη Πραγματική ταυτότητα ομάδας Τα πραγματικά στοιχεία του χρήστη που εκτελεί τη διεργασία Ενεργός ταυτότητα χρήστη Ενεργός ταυτότητα ομάδας Συμπληρωματικές ταυτότητες ομάδας Χρησιμοποιούνται στους ελέγχους πρόσβασης αρχείων Αποθηκευμένη ταυτότητα χρήστη Αποθηκευμένη ταυτότητα ομάδας Επιτρέπουν την εναλλαγή μεταξύ μεταξύ της πραγματικής ταυτότητας χρήστη/ομάδας και της παραχωρημένης ταυτότητας χρήστη/ομάδας, απλοποιώντας τη συγγραφή προγραμμάτων setuid Π.χ. creat("myfile") αρχείο ιδιόκτητο από την ενεργό ταυτότητα χρήστη Κανονικά, η πραγματική ταυτότητα χρήστη/ομάδας ισούται με την ενεργό ταυτότητα χρήστη/ομάδας, εκτός αν στα δικαιώματα του αρχείου ορίζεται το bit παραχώρησης ταυτότητας χρήστη/ομάδας
Κανόνες δικαιωμάτων πρόσβασης Για να μπορούμε να ανοίξουμε ένα αρχείο πρέπει να έχουμε δικαίωμα εκτέλεσης σε όλους τους καταλόγους της πλήρους διαδρομής του αρχείου, συν τα δικαιώματα στο αρχείο που απαιτούνται από τον τρόπο που ορίζει το άνοιγμα Για άνοιγμα με ένδειξη O_RDONLY ή O_RDWR απαιτείται δικαίωμα ανάγνωσης Για άνοιγμα με ένδειξη O_WRONLY ή O_RDWR ή O_TRUNC απαιτείται δικαίωμα εγγραφής στο αρχείο Για δημιουργία αρχείου (O_CREAT ή creat()) απαιτείται δικαίωμα εκτέλεσης και εγγραφής στον κατάλογο Για διαγραφή αρχείου απαιτείται δικαίωμα εκτέλεσης και εγγραφής στον κατάλογο, όχι στο ίδιο το αρχείο (κανονική σημασιολογία) Για εκτέλεση του αρχείου μέσω της συνάρτησης exec απαιτείται δικαίωμα εκτέλεσης του αρχείου να πρόκειται για απλό αρχείο
Διαδικασία ελέγχου δικαιωμάτων Αν η ενεργός ταυτότητα χρήστη είναι 0 η πρόσβαση επιτρέπεται Αν η ενεργός ταυτότητα χρήστη είναι ίδια με την ταυτότητα ιδιοκτήτη του αρχείου, τότε: Αν τα σχετικά bit δικαιωμάτων ιδιοκτήτη είναι ενεργά, τότε η πρόσβαση επιτρέπεται Αν κάποιο από τα σχετικά bit δικαιωμάτων ιδιοκτήτη είναι ανενεργό, τότε η πρόσβαση απαγορεύεται Αλλιώς, αν η ενεργός ταυτότητα ομάδας είναι ίδια με την ομάδα του αρχείου, τότε: Αν τα σχετικά bit δικαιωμάτων ομάδας είναι ενεργά, τότε η πρόσβαση επιτρέπεται Αν κάποιο από τα σχετικά bit δικαιωμάτων ομάδας είναι ανενεργό, τότε η πρόσβαση απαγορεύεται Αλλιώς, ελέγχονται τα bits δικαιωμάτων λοιπών Αν τα σχετικά bit δικαιωμάτων είναι ενεργά, τότε η πρόσβαση επιτρέπεται Αν κάποιο από τα bit δικαιωμάτων ομάδας είναι ανενεργό, τότε η πρόσβαση απαγορεύεται
Ιδιοκτησία νέων αρχείων και καταλόγων Ο ιδιοκτήτης ενός νέου αρχείου είναι καθορίζεται από την ενεργό ταυτότητα χρήστη της διεργασίας Η ομάδα ενός αρχείου μπορεί να τίθεται με έναν από τους ακόλουθους τρόπους: Η ενεργός ταυτότητα ομάδας καθορίζει την ομάδα του αρχείου Το αρχείο κληρονομεί την ομάδα του καταλόγου εντός του οποίου δημιουργείται Στο πρότυπο SVR4 η επιλογή εξαρτάται από το bit παραχώρησης ταυτότητας ομάδας του καταλόγου Στο πρότυπο BSD 4.3+ πάντα χρησιμοποιείται η ομάδα του καταλόγου Τα ίδια ισχύουν και για τους καταλόγους Οι ομάδες καταλόγων μπορούν να διαδίδονται σε όλη την υποιεραρχία καταλόγων/αρχείων
Η συνάρτηση access Η συνάρτηση access διενεργεί ελέγχους για ύπαρξη δικαιωμάτων πρόσβασης βάσει των πραγματικών ταυτοτήτων χρήστη και ομάδας Χρήσιμο όταν γράφουμε ένα πρόγραμμα setuid/setgid που θέλει να ελέγξει αν υπάρχουν τα σχετικά δικαιώματα στον χρήστη που εκτελεί το πρόγραμμα #include <unistd.h> int access(const char *fname, int mode); Επιστρέφει 0 = ΟΚ, -1 = σφάλμα Mode Περιγραφή R_OK Ανάγνωση W_OK Εγγραφή X_OK Εκτέλεση F_OK Ύπαρξη Ή οποιοσδήποτε συνδυασμός με διάζευξη επιπέδου bit (π.χ. R_OK | W_OK)
Η συνάρτηση umask Η συνάρτηση umask καθορίζει ποια bits δικαιωμάτων ΔΕΝ θα είναι ενεργά στα νέα αρχεία/καταλόγους που δημιουργούνται Κάθε bit που είναι ενεργό (1) στο umask είναι ανενεργό (0) στο νέο αρχείο/κατάλογο Ποτέ δεν επιστρέφει λάθος Αν δεν έχουμε χρησιμοποιήσει τη συνάρτηση umask, λαμβάνεται η τιμή της ρύθμισης umask από το περιβάλλον Παράδειγμα [umask.c]: #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { umask(0); close(creat("file1", S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH)); umask(S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH); close(creat("file2", S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | return 0; } Αποτέλεσμα: -rwxrwxrwx 1 costas staff 0 Nov 10 15:18 file1 -rwxr-x--- 1 costas staff 0 Nov 10 15:18 file2
chmod και fchmod Αλλαγή των δικαιωμάτων πρόσβασης σε ένα αρχείο #include <sys/types.h> #include <sys/stat.h> int chmod(const char *fname, mode_t mode); int fchmod(int filedes, mode_t mode); Επιτρέπεται μόνο στον ιδιοκτήτη και τον υπερ-χρήστη (βάσει ενεργού ταυτότητας χρήστη) Τα νέα δικαιώματα πρόσβασης είναι συνδυασμός με τελεστή διάζευξης επιπέδου OR των: Σταθερών που ορίζονται για τα δικαιώματα στη συνάρτηση stat S_IRWXU == S_IRUSR | S_IWUSR | S_IXUSR S_IRWXG == S_IRGRP | S_IWGRP | S_IXGRP S_IRWXO == S_IROTH | S_IWOTH | S_IXOTH S_ISUID S_ISGID S_ISVTX - bit διατήρησης κώδικα
chmod και fchmod Δεν επηρεάζεται από το umask Bits μπορούν να καθαρίζονται αυτόματα στις εξής περιπτώσεις: Αν τίθεται το bit διατήρησης κώδικα και ο χρήστης δεν είναι ο υπερχρήστης Αν τίθεται το bit παραχώρησης ταυτότητας ομάδας και η ομάδα του αρχείου δεν είναι μία από τις ομάδες στις οποίες ανήκει ο χρήστης Τα συστήματα 4.3+BSD καθαρίζουν αυτόματα τα bits παραχώρησης ταυτότητας χρήστη και ομάδας, αν γραφεί το αρχείο από χρήστη διαφορετικό από τον ιδιοκτήτη ή χρήστη που δεν ανήκει στην ομάδα αντίστοιχα
chmod – Παράδειγμα [chmod.c] #include <sys/types.h> #include <sys/stat.h> int main(void) { struct stat buf; mode_t new_mode; /* Set absolute mode to rx-r--r-- ; chmod 644 file1*/ if (chmod("file1", S_IRUSR | S_IXUSR | S_IRGRP | S_IROTH) == -1) perror("Changing mode of file1"); /* Read perms of file2 for changing; chmod g+s,o-w file2 */ if (stat("file2", &buf) == 0) { new_mode = buf.st_mode & S_IAMB; new_mode |= S_ISGID; new_mode &= ~S_IWOTH; if (chmod("file2", new_mode) == -1) perror("Changing mode of file2"); } else perror("cannot stat file2"); return 0;
chown, fchown και lchown Αλλαγή ιδιοκτήτη/ομάδας αρχείου/συνδέσμου #include <sys/types.h> #include <unistd.h> int chown(const char *fname, uid_t owner, gid_t group); int fchown(int filedes, uid_t owner, gid_t group); int lchown(const char *fname, uid_t owner, gid_t group); Αν η τιμή του owner ή group είναι –1, το σχετικό στοιχείο δεν αλλάζει Σε μερικά συστήματα μόνο ο υπερχρήστης μπορεί να εκτελέσει την chown Χρήστες έδιναν την ιδιοκτησία δικών τους αρχείων για να παρακάμπτουν τα όρια χρήσης δίσκων Μπορούμε να διαγνώσουμε αν ισχύει ή όχι βάσει της τιμής της παραμέτρου _POSIX_CHOWN_RESTRICTED για το συγκεκριμένο αρχείο (σύστημα αρχείων) Η ομάδα μπορεί να αλλάξει από τον ιδιοκτήτη σε οποιαδήποτε ομάδα μετέχει ο χρήστης (από τον υπερχρήστη, σε οποιαδήποτε ομάδα) Αλλαγή ιδιοκτήτη ή ομάδας συνεπάγεται τον καθαρισμό του bit παραχώρησης ταυτότητας χρήστη/ομάδας αντίστοιχα
link, unlink, remove, rename #include <unistd.h> int link(const char *existing, const char *new); int unlink(const char *existing); int remove(const char *existing); int rename(const char *existing, const char *new); Η unlink μπορεί να χρησιμοποιηθεί και σε ανοικτά αρχεία Το αρχείο δεν κλείνει και διαγράφεται από τον δίσκο όταν ρητώς το κλείσει η διεργασία (ή επανεκκινηθεί το μηχάνημα) Μπορεί να χρησιμοποιηθεί από διεργασίες για να εξασφαλίσουν ότι προσωρινά αρχεία θα διαγραφούν Η remove είναι η τυποποίηση της ANSI C για τη διαγραφή αρχείων Η rename αλλάζει όνομα στο αρχείο/κατάλογο ή/και το μετακινεί σε άλλο κατάλογο Μπορεί να αποτύχει αν μετακινηθεί κατάλογος σε χαμηλότερο στην ιεραρχία π.χ. rename("/dir1", "/dir1/dir2"); Μπορεί να αποτύχει για διαφορετικά συστήματα αρχείων
Σύστημα αρχείων UNIX Πίνακας διαμερίσεων Δίσκος Διαμέριση 1 Διαμέριση 2 Διαμέριση 3 Μπλοκ εκκίνησης Υπερ- μπλοκ Σύστημα αρχείων Κόμβοι-δ Μπλοκ δεδομένων & καταλόγων Κόμβος-δ …
Σύστημα αρχείων UNIX Μπλοκ αρχείων και καταλόγων Κόμβοι-δ κδ1 κδ2 κδ3 κδ4 #κδ . .. file1 dir2
Συμβολικοί σύνδεσμοι Έμμεσοι δείκτες προς αρχεία – δείχνουν στο όνομα και όχι στον κόμβο-δ, όπως οι άμεσοι σύνδεσμοι #include <unistd.h> int symlink(const char *existing, const char *new); int readlink(const char *existing, char *buf, int bufsiz); Λειτουργούν και μεταξύ συστημάτων αρχείων Κλήσεις συστήματος μπορεί να διασχίζουν τους συμβολικούς συνδέσμους ή όχι Κλήσεις που διασχίζουν τους συμβολικούς συνδέσμους: access, chdir, chmod, chown1, creat, exec, link, mkdir, mkfifo, mknod, open, opendir, pathconf, stat, truncate Κλήσεις που ΔΕΝ διασχίζουν τους συμβολικούς συνδέσμους: chown1, lchown, lstat, readlink, remove, rename, unlink 1 chown: εξαρτάται από την υλοποίηση
Συμβολικοί σύνδεσμοι [symlink.c] int main(void) { struct stat link_stat, file_stat; char buf[512]; int result; if (symlink("/etc/passwd", "mylink") == -1) { perror("symlink mylink to /etc/passwd"); exit(1);} if (stat("mylink", &file_stat) == -1) { perror("cannot stat mylink"); exit(1);} if (lstat("mylink", &link_stat) == -1) { printf("stat size: %ld, lstat size: %ld\n", (long)(file_stat.st_size), (long)(link_stat.st_size)); if ((result = readlink("mylink", buf, 511)) == -1) { perror("reading mylink link contents"); exit(1);} buf[result] = '\0'; printf("mylink contents: %s\n", buf); if (unlink("mylink") == -1) { perror("unlinking mylink"); exit(1);} return 0; }
Χρονόσημα αρχείων Σε κάθε αντικείμενο του συστήματος αρχείων αντιστοιχίζονται τρία χρονόσημα: Πεδίο Περιγραφή Παράδειγμα κ.σ. Ένδειξη ls st_atime Πρόσβαση σε δεδομένα read -u st_mtime Αλλαγή δεδομένων write Εξ ορισμού st_ctime Αλλαγή στοιχείων κόμβου-δ chown, chmod -c Η πρόσβαση σε στοιχεία του κόμβου-δ (π.χ. stat, access) δεν καταγράφεται σε χρονόσημο Οι κλήσεις συστήματος μπορεί να επηρεάζουν τα χρονόσημα: του αντικειμένου (read, write, truncate) του καταλόγου που περιέχει το αντικείμενο (rename, rmdir) και τα δύο (creat, unlink)
Συνάρτηση utime Αλλαγή των χρονοσήμων πρόσβασης και τροποποίησης #include <sys/types.h> #include <utime.h> int utime(const char *pathname, const struct utimebuf *times); Χρησιμοποιείται κυρίως από προγράμματα αρχειοθέτησης/συμπίεσης struct utimebuf { time_t actime; time_t modtime; } Αν η δεύτερη παράμετρος είναι ο δείκτης NULL, και τα δύο χρονόσημα τίθενται στο τρέχον time_t struct tm μέσω των localtime, mktime
mkdir, rmdir Δημιουργία καταλόγων Διαγραφή καταλόγων #include <sys/types.h> #include <sys/stat.h> int mkdir(const char *pathname, mode_t mode); οι καταχωρήσεις . και .. δημιουργούνται αυτόματα ως ατομική λειτουργία Η μάσκα umask επηρεάζει τα δικαιώματα Στη γενική περίπτωση, θέλουμε να δίνουμε δικαίωμα εκτέλεσης στους καταλόγους Διαγραφή καταλόγων #include <unistd.h> int rmdir(const char *pathname); Μόνο για κενούς καταλόγους
Διαβάζοντας καταλόγους Μόνον ο πυρήνας γράφει άμεσα σε καταλόγους. Η ανάγνωση γίνεται με τυποποιημένες συναρτήσεις βιβλιοθήκης #include <sys/types.h> #include <dirent.h> DIR *opendir(const char *pathname); struct dirent *readdir(DIR *dp); void rewinddir(DIR *dp); int closedir(DIR *dp); Η δομή dirent περιέχει τουλάχιστον τα επόμενα μέλη: struct dirent { inode_t d_ino; char d_name[NAME_MAX + 1]; /* Null terminated */ } H συνάρτηση readdir δεν έχει απαραίτητα δυνατότητα επανεισόδου
Δυνατότητα επανεισόδου Έστω το κάτωθι πρόγραμμα εκτύπωσης κατά στήλες αρχείων και ημερομηνιών τελευταίας τροποποίησης αρχείων [readdir1.c]: int main(int argc, char *argv[]) { struct dirent **d_ents; char **dent_dates; int ncols, ctr1, ctr2; DIR *d1; struct stat iteminfo; ncols = (argc > 1) ? (atoi(argv[1])) : 1; if ((ncols < 1) || (ncols > 100)) { fprintf(stderr, "Invalid columns value - defaulting to 1\n"); ncols = 1; } d_ents = malloc(sizeof(struct dirent *) * ncols); dent_dates = malloc(sizeof(char *) * ncols); d1 = opendir("."); do { for (ctr1 = 0; ctr1 < ncols; ctr1++) { if ((d_ents[ctr1] = readdir(d1)) == NULL) break; lstat(d_ents[ctr1]->d_name, &iteminfo); dent_dates[ctr1] = asctime(localtime(&(iteminfo.st_mtime))); } for (ctr2 = 0; ctr2 < ctr1; ctr2++) printf("%s-->%.24s ", d_ents[ctr2]->d_name, dent_dates[ctr2]); putchar('\n'); } while (ctr1 > 0); closedir(d1); return 0; }
Δυνατότητα επανεισόδου Για 1 στήλη: .-->Thu Nov 9 10:42:38 2006 ..-->Thu Nov 2 16:16:34 2006 alarm-sigaction.c-->Thu Oct 13 10:38:07 2005 alarm.c-->Thu Oct 13 17:42:17 2005 Για 2 στήλες: .-->Thu Nov 2 16:16:34 2006 ..-->Thu Nov 2 16:16:34 2006 alarm-sigaction.c-->Thu Oct 13 17:42:17 2005 alarm.c-->Thu Oct 13 17:42:17 2005 Οι ημερομηνίες για τα πρώτα αρχεία είναι λάθος! Το ίδιο θα συμβεί και με όλα εκτός από τα τελευταία αρχεία για πάνω από μία στήλες Αιτία: Η συνάρτηση asctime και ο τρόπος που επιστρέφει τα αποτελέσματα Αντίστοιχο πρόβλημα υπάρχει και στη readdir αλλά εμφανίζεται αν χρησιμοποιήσουμε πάνω από ένα όριο στηλών
Δυνατότητα επανεισόδου Για τις κλήσεις συστήματος/συναρτήσεις με ενδεχόμενο πρόβλημα επανεισόδου, οι σύγχρονες υλοποιήσεις παρέχουν συζυγείς κ.σ/συναρτήσεις με το επίθεμα _r, όπου η καλούσα συνάρτηση ορίζει τον χώρο που θα τοποθετηθεί το αποτέλεσμα struct readdir_r(DIR *dirp, struct dirent *entry); char *asctime_r(constr struct tm *, char *buf /*, int len */); Το προηγούμενο πρόγραμμα τροποποιείται ως εξής [readdir1_r.c]: int main(int argc, char *argv[]) { struct dirent d_ent; char (*dent_dates)[26], (*fnames)[MAXNAMLEN +1]; ... do { for (ctr1 = 0; ctr1 < ncols; ctr1++) { if (readdir_r(d1, &(d_ent)) == NULL) break; strcpy(fnames[ctr1], d_ent.d_name); lstat(d_ent.d_name, &iteminfo); asctime_r(localtime(&(iteminfo.st_mtime)), dent_dates[ctr1][0], 26); } for (ctr2 = 0; ctr2 < ctr1; ctr2++) printf("%s-->%.24s ", fnames[ctr2], dent_dates[ctr2][0]); putchar('\n');
Ανάγνωση καταλόγων [read_a_dir.c] int main(int argc, char *argv[]) { DIR *dp; struct dirent *dirp; char thePath[PATH_MAX]; if (argc != 2) { fprintf(stderr, "provide the directory name\n"); exit(1);} if ((dp = opendir(argv[1])) == NULL) perror(argv[1]); exit(1);} while ((dirp = readdir(dp)) != NULL) { printf("%lu\t%-30s\t", (unsigned long)(dirp->d_ino), dirp->d_name); sprintf(thePath, "%s/%s", argv[1], dirp->d_name); putchar(access(thePath, R_OK) == 0 ? 'r' : '-'); putchar(access(thePath, W_OK) == 0 ? 'w' : '-'); putchar(access(thePath, X_OK) == 0 ? 'x' : '-'); putchar('\n'); } closedir(dp); return 0;
Διαχείριση τρέχοντος καταλόγου Κλήσεις συστήματος για ορισμό και αναφορά τρέχοντος καταλόγου #include <unistd.h> int chdir(const char *pathname); int fchdir(int filedes); int getcwd(char *buf, size_t size); Η αλλαγή τρέχοντος καταλόγου ισχύει για τη διεργασία που αλλάζει κατάλογο και όχι για τη γονική της if (chdir(argv[1]) == -1) err_die("cannot chdir"); if ((dirp = opendir(".")) == NULL) while ((dirp = readdir(dp)) != NULL) { printf("%lu\t%-30s\t", (unsigned long)(dirp->d_ino), dirp->d_name); putchar(access(dirp->d_name, R_OK) == 0 ? 'r' : '-'); putchar(access(dirp->d_name, W_OK) == 0 ? 'w' : '-'); putchar(access(dirp->d_name, X_OK) == 0 ? 'x' : '-'); putchar('\n'); }
Αντιστοίχιση με ε/ε που χρησιμοποιεί ενδιάμεση μνήμη Περιγραφέας αρχείου με FILE * FILE *fdopen(int filedes, const char *mode); int fileno(FILE *fp); read/write/seek size_t fread(void *ptr, size_t size, size_t nitems, FILE *fp); size_t fwrite(void *ptr, size_t size, size_t nitems, FILE *fp); off_t fseek(FILE *fp, long newpos, int whence); Προσωρινά αρχεία char *tmpnam(char *s); Αν s == NULL, το αποτέλεσμα αποθηκεύεται σε εσωτερικό πίνακα που επικαλύπτεται σε επόμενη κλήση. Το s έχει μέγεθος τουλάχιστον L_tmpnam + 1 bytes (stdio.h) char *tmpnam_r(char *s); Αν s == NULL, επιστρέφει NULL char *tempnam(const char *dir, const char *prefix); FILE *tmpfile(void); Όταν δεν ορίζεται κατάλογος, τα προσωρινά αρχεία δημιουργούνται στον κατάλογο που ορίζει η σταθερά P_tmpdir (stdio.h) Τα ονόματα έχουν μήκος το πολύ L_tmpnam bytes (stdio.h)
Απόδοση ε/ε ενδιάμεσης μνήμης και ε/ε/ χωρίς ενδιάμεση μνήμη Μέθοδος User CPU System CPU Clock time Βέλτιστος χρόνος χωρίς χρήση ε/ε 0.0 0.3 fgets, fputs 2.2 2.6 getc, putc 4.3 4.8 fgetc, fputc 4.6 5.0 Χωρίς χρήση ε/ε, byte προς byte 23.8 397.9 423.4