An Introduction to the FreeBSD File System
In addition to its actually data contents, every file in the FreeBSD file system has a unique descriptor called inode. An inode contains the information of the file which includes at least:
Data stored in an inode may be accessed either through the ls command or the stat system call, which will be discussed shortly.
Structure of a Regular File
The data in file is saved as a logical stream of bytes, although the actually physical storage may be over multiple sectors and tracks. This logical stream of bytes starts from offset zero to the last byte with the highest offset. One can read data of variable length (limited by the actual length of the file) starting from any offset, and one can write data of any length from any offset, even an offset that is beyond the last byte of the file. For example, one can write 10 bytes of data starting from offset 1000 to a file with only one byte in it. After the write is completed, The size of the file becomes 1009, with a gap between the first bytes and the last ten bytes just written by the write. When a read tries to read data from inside the gap, the kernel treats the gap as if it were filled with zeros.
File Access Permissions
The FreeBSD file system protects files according to three classes: the owner and the group owner of the file, and other users. Each classes may be given the right to read, write, and execute the file. the three access rights are specified by three binary digits in the order of read (r), write (w) and execute (x).
System calls for File Processing
FreeBSD (4.4) has six file-related system calls. The following table briefly describe the function of each.
|open||open an existing file or create a new file|
|read||Read data from a file|
|write||Write data to a file|
|lseek||Move the read/write pointer to the specified location|
|close||Close an open file|
|unlink||Delete a file|
|chmod||Change the file protection attributes|
|stat||Read file information from inodes|
Files to be included for file-related system calls.
The open system call can be used to open an existing file or to create a new file if it does not exist already. The syntax of open has two forms:
int open(const char *path, int flags); and
int open(const char *path, int flags, mode_t modes);
The first form is normally used to open an existing file, and the second form to open a file and to create a file if it does not exist already. Both forms returns an integer called the file descriptor. The file descriptor will be used for reading from and writing to the file. If the file cannot be opened or created, it returns -1. The first parameter path in both forms sPecifies the file name to be opened or created. The second parameter (flags) specifies how the file may be used. The following list some commonly used flag values.
|O_RDONLY||open for reading only|
|O_WRONLY||open for writing only|
|O_RDWR||open for reading and writing|
|O_NONBLOCK||do not block on open|
|O_APPEND||append on each write|
|O_CREAT||create file if it does not exist|
|O_TRUNC||truncate size to 0|
|O_EXCL||error if create and file exists|
|O_SHLOCK||atomically obtain a shared lock|
|O_EXLOCK||atomically obtain an exclusive lock|
|O_DIRECT||eliminate or reduce cache effects|
|O_NOFOLLOW||do not follow symlinks|
The flag (O_CREAT) may be used to create the file if it does not exist. When this flag is used, the third parameter (modes) must be used to specify the file access permissions for the new file. Commonly used modes (or access permissions) include
|Constant Name||Octal Value||Description|
|S_IRWXU||0000700||/* RWX mask for owner */|
|S_IRUSR||0000400||/* R for owner */|
|S_IWUSR||0000200||/* W for owner */|
|S_IXUSR||0000100||/* X for owner */|
|S_IRWXO||0000007||/* RWX mask for other */|
|S_IROTH||0000004||/* R for other */|
|S_IWOTH||0000002||/* W for other */|
|S_IXOTH||0000001||/* X for other */|
R: read, W: write, and X: executable
For example, to open file "tmp.txt" in the current working directory for reading and writing:
fd = open("tmp.txt", O_RDWR);
To open "sample.txt" in the current working directory for appending or create it, if it does not exist, with read, write and execute permissions for owner only:
fd = open("tmp.txt", O_WRONLY|O_APPEND|O_CREAT, S_IRWXU);
A file may be opened or created outside the current working directory. In this case, an absolute path and relative path may prefix the file name. For example, to create a file in /tmp directory:
Read from files
The system call for reading from a file is read. Its syntax is
ssize_t read(int fd, void *buf, size_t nbytes);
The first parameter fd is the file descriptor of the file you want to read from, it is normally returned from open. The second parameter buf is a pointer pointing the memory location where the input data should be stored. The last parameter nbytes specifies the maximum number of bytes you want to read. The system call returns the number of bytes it actually read, and normally this number is either smaller or equal to nbytes. The following segment of code reads up to 1024 bytes from file tmp.txt:
int actual_count = 0; int fd = open("tmp.txt", O_RDONLY); void *buf = (char*) malloc(1024); actual_count = read(fd, buf, 1024);
Each file has a pointer, normally called read/write offset, indicating where next read will start from. This pointer is incremented by the number of bytes actually read by the read call. For the above example, if the offset was zero before the read and it actually read 1024 bytes, the offset will be 1024 when the read returns. This offset may be changed by the system call lseek, which will be covered shortly.
Write to files
The system call write is to write data to a file. Its syntax is
ssize_t write(int fd, const void *buf, size_t nbytes);
It writes nbytes of data to the file referenced by file descriptor fd from the buffer pointed by buf. The write starts at the position pointed by the offset of the file. Upon returning from write, the offset is advanced by the number of bytes which were successfully written. The function returns the number of bytes that were actually written, or it returns the value -1 if failed.
Reposition the R/W offset
The lseek system call allows random access to a file by reposition the offset for next read or write. The syntax of the system call is
off_t lseek(int fd, off_t offset, int reference);
It repositions the offset of the file descriptor fd to the argument offset according to the directive reference. The reference indicate whether offset should be considered from the beginning of the file (with reference 0), from the current position of the read/write offset (with reference 1), or from the end of the file (with reference 2). The call returns the byte offset where the next read/write will start.
The close system call closes a file. Its syntax is
int close(int fd);
It returns the value 0 if successful; otherwise the value -1 is returned.
The unlink may be used to delete a file (A file may have multiple names (also called links), here we assume that a file in this context has only one name or link.). Its syntax is:
int unlink(const char *path);
path is the file name to be deleted. The unlink system call returns the value 0 if successful, otherwise it returns the value -1.
Change file access permissions
File access permissions may be set using the chmod system call (note that there is a command with the same name for setting access permissions.). It has two forms:
int chmod(const char *path, mode_t mode); or
int fchmod(int fd, mode_t mode);
Both forms set the access permission of a file to mode. In the first form the file is identified by its name, and in the second it is identified by a file descriptor returned from the open system call. For mode, you may use any of the constants defined in the Open files section of this tutorial.
The system call returns the value 0 if successful, otherwise it returns the value -1.
Accessing file information from Inodes
The stat system call can be used to access file information of a file from its inode. It can appear in two forms:
int stat(const char *path, struct stat *sb); or
int fstat(int fd, struct stat *sb);
Both forms return the information through the stat structure pointed by sb. In the first form, the file is identified by its name and in the second form, it is identified by its file descriptor returned from a call to open. The stat structure includes at least the following elements:
|st_mode||file protection mode|
|st_uid||user ID of the file owner|
|st_size||file size in bytes|
No permission is needed to stat a file. However since the second form requires a file descriptor of the file and a file descriptor may be only obtained by open, fstat can only be applied to files that has proper access permissions.
The call returns the value 0 if successful, otherwise it returns the value -1. The call fails if the specified path or the file does not exist.