Add Book to My BookshelfPurchase This Book Online

Chapter 3 - Low-Level I/O Routines

UNIX Systems Programming for SVR4
David A. Curry
 Copyright © 1996 O'Reilly & Associates, Inc.

Repositioning the Read/Write Offset
One of the values the operating system associates with each file is the read/write offset, also called the file offset. The read/write offset specifies the “distance,” measured in bytes from the beginning of the file, at which the next read or write takes place. When a file is first opened or created, the file offset is zero. The first read or write starts at the beginning of the file. As reads and writes are performed, the offset is incremented by the number of bytes read or written each time. There is only one read/write offset for each file, so a read of 10 bytes followed by a write of 20 bytes leaves the read/write offset at 30.
To examine and change the value of the read/write offset, use the lseek function:
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
lseek sets the read/write offset to offset bytes from the position in the file specified by whence, which can have one of the following values:
SEEK_SET
Set the read/write offset to offset bytes from the beginning of the file.
SEEK_CUR
Set the read/write offset to offset bytes from the current offset.
SEEK_END
Set the read/write offset to offset bytes from the end of the file.
On success, lseek returns the new read/write offset. On failure, it returns -1 and stores an error code in the external variable errno. Note that the offset parameter is a signed value, so negative seeks are permitted.
To move to the beginning of a file, use the following call:
lseek(fd, 0, SEEK_SET);
To move to the end of a file, use the following call:
lseek(fd, 0, SEEK_END);
To obtain the value of the current offset without changing it, use the following call:
off_t offset;
offset = lseek(fd, 0, SEEK_CUR);
The concept of the end of a file is somewhat fluid—it is perfectly legal to seek past the end of the file and then write data. This creates a “hole” in the file that does not take up any storage space on the disk. However, when reading a file with holes in it, the holes are read as zero-valued bytes. This means that once a file with holes is created, it is impossible to copy it precisely, because all the holes are filled in when the copy takes place. (There are ways around this, but they involve reading the raw disk blocks rather than simply opening the file and reading it directly.)
Example 3-2 shows a program that writes five strings to a file and then prompts for a number between 1 and 5. It seeks to the proper location for the string of that number, reads it from the file, and prints it. Note the use of the mktemp function to create a temporary filename (see Chapter 2, Utility Routines for a description of mktemp).
Example 3-2:  seeker
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define NSTRINGS    5
#define STRSIZE     3
char *strings[] = {
    "aaa", "bbb", "ccc", "ddd", "eee"
};
int
main(int argc, char **argv)
{
    int n, fd;
    char *fname;
    char buf[STRSIZE], answer[8], template[32];
    /*
     * Create a temporary file name.
     */
    strcpy(template, "/tmp/seekerXXXXXX");
    fname = mktemp(template);
    /*
     * Create the file.
     */
    if ((fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0666)) < 0) {
        perror(fname);
        exit(1);
    }
    /*
     * Write strings to the file.
     */
    for (n = 0; n < NSTRINGS; n++)
        write(fd, strings[n], STRSIZE);
    /*
     * Until the user quits, prompt for a string and retrieve
     * it from the file.
     */
    for (;;) {
        /*
         * Prompt for the string number.
         */
        write(1, "Which string (0 to quit)? ", 26);
        n = read(0, answer, sizeof(answer));
        answer[n-1] = '\0';
        n = atoi(answer);
        if (n == 0) {
            close(fd);
            exit(0);
        }
        if (n < 0 || n > NSTRINGS) {
            write(2, "Out of range.\n", 14);
            continue;
        }
        /*
         * Find the string and read it.
         */
        lseek(fd, (n-1) * STRSIZE, SEEK_SET);
        read(fd, buf, STRSIZE);
        /*
         * Print it out.
         */
        write(1, "String ", 7);
        write(1, answer, strlen(answer));
        write(1, " = ", 3);
        write(1, buf, STRSIZE);
        write(1, "\n\n", 2);
    }
}
    % seeker
    Which string (0 to quit)? 1
    String 1 = aaa
    Which string (0 to quit)? 5
    String 5 = eee
    Which string (0 to quit)? 3
    String 3 = ccc
    Which string (0 to quit)? 4
    String 4 = ddd
    Which string (0 to quit)? 2
    String 2 = bbb
    Which string (0 to quit)? 0
Note the number of steps involved in printing the prompts in this program. This is one of the principal drawbacks to using low-level I/O; complex input and output formatting involves a lot of work. Contrast this example with the redesigned version shown in the next chapter.
Porting Notes
On most pre-POSIX systems, the constants used with lseek are called L_SET, L_INCR, and L_XTND. On even older UNIX systems, no constants are defined at all. The integers 0, 1, and 2 are used instead. In either case, you can replace them with the POSIX constants SEEK_SET, SEEK_CUR, and SEEK_END, respectively.

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback