                         Low Level File Access

                            Matthew Probert
                           Servile  Software



Introduction:

By definition file access defines accessing a disk through an 
operating system. The disk itself is simply a magnetic (or other) 
medium divided into areas sometimes called sectors or clusters. The 
BIOS in a PC provides a means to access individual sectors, but this 
is not file access. 

With the C programming language two types of file access are 
supported; buffered and unbuffered. The first applies to file streams, 
the second, unbuffered access, refers to file handles. The difference 
in practical terms occurs when reading or writing to a file. 

When an unbuffered file operation occurs, the program issues a command 
directly to the operating system to read or write data from a file 
handle. A buffered, file stream, operation however utilises a form of 
disk cache in memory to pass file reads and writes through its own 
memory buffer (or stream) before the data is passed on to the 
operating system.

Low level file access then refers to unbuffered, handle operations 
such as open(), read() and write().

Opening:

Before a file can be accessed it must have a handle associated with 
it. This is achieved with the open() command.

open() takes three parameters: the file name, the required access 
type, and an optional third parameter of the file access mode. 

The file name may include a path, for example:

        "myfile.ext"
        "c:\\myfile.ext"

The access type is a complex numeric value built around various bits. 
For full details you should refer to your compiler library reference 
manual, but in simple terms the access type defines whether the file 
is to be opened in read or write mode.

The access mode is applicable only when creating a new file, and 
defines the read/write attributes of the file. Under DOS this defines 
whether the 'r' attribute will be set or not. 

When opening a file things can go wrong. The open() function will 
return a value of -1 if it fails. The reasons for possible failure are 
numerous but the more common are:

        1) Attempting to open a non-existent file for reading
        2) Attempting to open a read-only file for writing
        3) Running out of available file handles.

If a program needs to be able to write to a file, but that file has 
been flagged as read-only, the access mode can be changed with the 
chmod() function. Simply issue the command 

        chmod(filename,S_IWRITE);

within the program and the read-only file will have it's read-only 
attribute removed allowing it to be opened in a read-write mode.

Reading:

Reading a file through a file handle is achieved with the read() 
function. This simple function takes as its parameters the file 
handle, the address of a memory buffer to store the read data and the 
number of bytes to read. 

Read() returns either the number of bytes read, or -1 if an error 
occurs. As -1 is equivalent to the signed form of 65535 the read() 
function can only read 65534 bytes at most.

for example;

        char buffer[1000];
        int handle;

        handle = open("myfile.ext",O_RDWR);
        bytes_read = read(handle,buffer,1000);

will attempt to read 1000 bytes from the file 'myfile.ext' and store 
the data in the character string 'buffer'. After a read, the file 
pointer is moved along the file by the number of bytes read. If you 
attempt to read when the file pointer is at the end-of-file, the 
read() function will return 0 indicating that no bytes were read.

Writing:

Writing to a file is achieved with the write() function which is 
identical to the read() function except in the direction the data 
flows. When you write() to a file data is passed from memory to the 
file, when you read() it passes from the file into memory.

Reading and writing to a file both take place at the current file 
pointer position. When a file is opened the file pointer may be 
intialised to either the start or the end of the file depending upon 
the access type with which the file was opened.


File Pointer:

When a file is open, the file pointer can be moved to a new position 
by the lseek() function which takes three parameters: the file handle, 
the offset to move the file pointer to, and the relative position to 
move the file pointer to. The relative position is one of three 
numerical values; the file beginning (SEEK_SET), the current file 
pointer position (SEEK_CUR) or the end of the file (SEEK_END). The 
offset may be either positive or negative.

For example; to move the file pointer of an open file to the end of 
the file use;

        lseek(handle,0L,SEEK_END);

lseek returns the new file pointer position, or -1 if the file pointer 
can't be moved.

The current position of a file pointer can be determined with the 
tell() function which returns the position of a file pointer 
associated with a handle relative to the file's beginning.

Problems:

A file may be opened in BINARY or TEXT mode. When a file is opened in 
TEXT mode, translation takes place, read carriage returns/line feed 
pairs are counted as a single byte by the read() function and the EOF 
character (ctrl Z) is not counted at all, and when writing a carriage 
return the program will also write an addition line-feed. This leads 
to a discrepancy between the actual bytes read/written and the file 
pointer position as this example illustrates: 


---------------------------Cut Here---------------------------
/*
    Test.C
*/

#include <stdio.h>
#include <bios.h>
#include <io.h>
#include <fcntl.h>
#include <sys\stat.h>


main()
{
    int handle;
    int bytes;
    char buffer[500];
    long pos;

    handle = open("test.c",O_RDONLY|O_TEXT);
    if (handle == -1)
    {
        perror("Error:");
        exit(0);
    }


    pos = 0;

    for(;;)
    {
        bytes = read(handle,buffer,500);
        if (bytes < 1)
            break;
        pos += bytes;
        buffer[bytes] = 0;
        printf("%s",buffer);
    }
    printf("\nPos == %ld  Real == %ld",pos,tell(handle));
}
---------------------------Cut Here---------------------------

When run, this program shows that the calculated file pointer position 
(pos) is different to the actual file pointer position revealed by the 
tell() function. I personaly always use a BINARY access type for file 
handles so that I retain control over what is read and written to the 
file.
