Add Book to My BookshelfPurchase This Book Online

Chapter 4 - The Standard I/O Library

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 (unless it was opened for appending); 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.
The Standard I/O Library provides three primary functions for manipulating the read/write offset:
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
void rewind(FILE *stream);
long ftell(FILE *stream);
The fseek function 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, fseek returns zero (this is different from lseek, described in Chapter 3, which returns the new read/write offset). On failure, the constant EOF is returned. Note that the offset is a signed value, so negative seeks are permitted.
To move to the beginning of a file, you can use the following call:
    fseek(stream, 0, SEEK_SET);
You can also use this call:
    rewind(stream);
This call has the side effect of clearing any error condition (described later) on the stream. To move to the end of a file, use this call:
    fseek(stream, 0, SEEK_END);
To obtain the value of the current offset without changing it, use this call:
    long offset;
    offset = ftell(stream);
Note that, unlike lseek, you cannot use the following call for this purpose, because fseek does not return the current offset:
    offset = fseek(stream, 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 4-9 shows the Standard I/O Library version of the seeker program introduced in Chapter 3. The program 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 out.
Example 4-9:  seeker
#include <stdlib.h>
#include <stdio.h>
#define NSTRINGS    5
#define STRSIZE     3
char *strings[] = {
    "aaa", "bbb", "ccc", "ddd", "eee"
};
int
main(int argc, char **argv)
{
    int n;
    FILE *fp;
    char *fname;
    char buf[STRSIZE], template[32];
    /*
     * Create a temporary file name.
     */
    strcpy(template, "/tmp/seekerXXXXXX");
    fname = mktemp(template);
    /*
     * Open the file.
     */
    if ((fp = fopen(fname, "w+")) == NULL) {
        perror(fname);
        exit(1);
    }
    /*
     * Write strings to the file.
     */
    for (n = 0; n < NSTRINGS; n++)
        fwrite(strings[n], sizeof(char), STRSIZE, fp);
    /*
     * Until the user quits, prompt for a string and retrieve
     * it from the file.
     */
    for (;;) {
        /*
         * Prompt for a string number.
         */
        printf("Which string (0 to quit)? ");
        scanf("%d", &n);
        if (n == 0) {
            fclose(fp);
            exit(0);
        }
        if (n < 0 || n > NSTRINGS) {
            fprintf(stderr, "Out of range.\n");
            continue;
        }
        /*
         * Find the string and read it.
         */
        fseek(fp, (n-1) * STRSIZE, SEEK_SET);
        fread(buf, sizeof(char), STRSIZE, fp);
        /*
         * Print it out.
         */
        printf("String %d = %.*s\n\n", n, STRSIZE, buf);
    }
}
    % 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
Compare this version of seeker with the one in Chapter 3 and note how much less work this version has to do to print the prompts and results. This demonstrates one of the principal benefits of using the Standard I/O Library.
The ANSI C standard specifies two additional functions for manipulating the read/write offset:
    #include <stdio.h>
    int fsetpos(FILE *stream, const fpos_t *pos);
    int fgetpos(FILE *stream, fpos_t *pos);
The fgetpos function stores the current read/write offset for stream into the object pointed to by pos. The fsetpos function sets the current read/write offset to the value of the object pointed to by pos, which should be a value returned by a call to fgetpos on the same stream. If successful, both functions return zero; otherwise, they return non-zero.
These two functions allow a program to save its place in a file so it can return to it later. However, they are new to ANSI C, and are therefore not portable to non-ANSI C environments. Fortunately, their behavior is easily duplicated using ftell and fseek.

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