Ε/Ε Αρχείων στο Unix

Μια σύντομη εισαγωγή

Οι βασικές κλήσεις συστήματος για Ε/Ε με αρχεία στο Unix είναι οι open(), read(), write(), close().

Άνοιγμα αρχείου

Αρχικά η διεργασία ανοίγει το αρχείο, χρησιμοποιώντας την κλήση συστήματος open(). Η open() επιστρέφει είτε -1 σε περίπτωση αποτυχίας, είτε έναν μη αρνητικό ακέραιο. Ο ακέραιος αυτός ονομάζεται περιγραφητής αρχείου, και από εδώ και στο εξής χρησιμοποιείται σε κάθε άλλη κλήση συστήματος για να αναφέρεται στο ανοιχτό αρχείο. Παράδειγμα:

int fd;

fd = open("/home/user/myfile", O_RDONLY);
if (fd < 0) {
      perror("open");
      exit(1);
}

Ο κώδικας ανοίγει το αρχείο μόνο για ανάγνωση (O_RDONLY flag) και κράταει τον περιγραφητή στη μεταβλητή fd. Αν αποτύχει (π.χ. δεν υπάρχει το συγκεκριμένο αρχείο) τυπώνει κατάλληλο διαγνωστικό και τερματίζει.

Ανάγνωση από αρχείο

Κάθε περιγραφητής σχετίζεται με συγκεκριμένη θέση μέσα στο ανοιχτό αρχείο, απ'όπου γίνονται όλες οι αναγνώσεις/εγγραφές. Κάθε ανάγνωση/εγγραφή προχωράει τη θέση ανάγνωσης/εγγραφής μέσα στο ανοιχτό αρχείο. Η κλήση read() διαβάζει έναν αριθμό από bytes, όσα ζητήσουμε, από την τρέχουσα θέση ανάγνωσης/εγγραφής και τα αποθηκεύει στη μνήμη.

Από το manual page φαίνεται τι χρειάζεται να γίνει #include και πώς καλείται:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

Οπότε, με μια κλήση όπως αυτή:

char buf[1000];
ssize_t ret;

ret = read(fd, buf, 1000);

Ζητάμε να διαβαστούν 1000 bytes και να αποθηκευτούν όπου δείχνει ο δείκτης buf, δηλαδή στον πίνακα buf[] ξεκινώντας από το buf[0]. Φροντίζουμε να υπάρχει αρκετός χώρος για τα δεδομένα, αλλιώς το πρόγραμμα εκτελεί μη επιτρεπόμενη πρόσβαση στη μνήμη και 99.99% θα τερματίσει με "Segmentation Fault".

Η read() επιστρέφει ένα από τα παρακάτω:

  • Αρνητική τιμή (-1): συνέβη σφάλμα Ε/Ε, ο τύπος του σφάλματος είναι στη μεταβλητή errno. Κάνουμε perror("read") και τερματίζουμε το πρόγραμμα.
  • 0: End-of-file: Η θέση ανάγνωσης/εγγραφής ήταν στο τέλος του αρχείου, δεν υπάρχουν άλλα δεδομένα.
  • Οποιονδήποτε αριθμό από 1 έως count: Επιστρέφει πόσα bytes διάβασε πραγματικά. Είναι πολύ σημαντικό ότι η read() δεν διαβάζει υποχρεωτικά όσα της ζητήσαμε, για διάφορους λόγους. Έχει το δικαίωμα να διαβάσει όσα θέλει, από 1 έως count και να επιστρέψει τον αριθμό τους. Πχ, αν είμαστε 40 bytes πριν από το τέλος του αρχείου και ζητήσουμε 1000, μάλλον θα πάρουμε 40 bytes στον buffer και στην επόμενη κλήση θα επιστραφεί 0 (end of file).

Οπότε:

if (ret < 0) {
      perror("read");
      exit(1);
} else if (ret == 0) {
      printf("I am at end-of-file\n");
} else {
      printf("I read %d bytes\n", ret);
}

Εγγραφή σε αρχείο

Αντίστοιχα ισχύουν για την κλήση write():

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

Η write() γράφει στην τρέχουσα θέση ανάγνωσης εγγραφής το πολύ count bytes, τα οποία προέρχονται από την θέση μνήμης buf. Παράδειγμα:

char buf[] = "kalhmera";
write(fd, buf, strlen(buf));

οπότε 8 bytes θα γραφτούν στο αρχείο. Η τιμή επιστροφής είναι:

  • Αρνητική τιμη (-1): συνέβη σφάλμα E/E, ο τύπος του είναι στη μεταβλητή errno, κάνουμε perror("write") και τερματίζουμε το πρόγραμμα.
  • 0: δεν υπάρχει σφάλμα, αλλά για κάποιο λόγο δεν μπόρεσε να γράψει τα δεδομένα. Πρακτικά δεν συμβαίνει ποτέ.
  • έναν αριθμό από 1 έως count που δείχνει πόσα bytes έγραψε.

Οπότε:

if (ret < 0) {
      perror("write");
      exit(1);
} else if (ret == 0) {
      printf("write returned zero?!\n");
} else {
      printf("I wrote %d bytes\n", ret);
}

Κλείσιμο αρχείου

Τέλος, κλείνουμε το ανοιχτό αρχείο με close στον περιγραφητή:

if (close(fd) < 0) {
      perror("close");
      exit(1);
}

Είναι πολύ σημαντικό μετά από κάθε κλήση συστήματος να γίνεται έλεγχος επιτυχίας. Αν κάτι πάει στραβά πρέπει να δίνεται έγκαιρα κάποιο μήνυμα λάθους και να μην συνεχίζει η εκτέλεση του προγράμματος, ώστε να μπορείτε να εντοπίζετε ευκολότερα προγραμματιστικά σας λάθη.

Περισσότερες πληροφορίες για όλα τα παραπάνω μπορείτε να βρείτε στα Manual pages: man 2 open, close, read, write.