Tutorial 1 - Filesystem #
Low-Level File Operations #
You can also use low-level functions to read and write files-functions provided directly by the operating system rather than by the C standard library. With these functions, you can, for instance, send packets over a network, which we’ll cover in the next semester.
Instead of using FILE pointers, low-level functions work with file descriptors (fd)-integer values that identify resources within a process. In this case, these will be files, but in general, they can refer to various system resources. To use the functions below, include the headers <fcntl.h> and <unistd.h>.
int open(const char *path, int oflag, ...);
See man 3p open.
path- path to the file being opened,oflag- flags for opening the file, combined with a bitwise OR|, similar to the mode used infopenbut also specifying behavior in various edge cases. Seeman 3p openfor the list of applicable flags.
This function returns a file descriptor fd, which we’ll use with the following functions.
Low-level functions don’t use buffering. read (see man 3p read) receives characters as soon as they’re available. This means it doesn’t always read as many characters as we expect. On one hand, we get data as quickly as possible without waiting for the system to load the rest from the disk. On the other hand, we need to make sure we actually read all the data we want.
ssize_t read(int fildes, void *buf, size_t nbyte);
fildes- file descriptor obtained fromopen,buf- pointer to the buffer where data will be stored,nbyte- size of the data the function is expected to read.
This function returns the number of bytes successfully read.
The write function works similarly (see man 3p write):
ssize_t write(int fildes, const void *buf, size_t nbyte);
fildes- file descriptor obtained fromopen,buf- pointer to the buffer containing the data,nbyte- size of the data to be sent.
The return value indicates the number of bytes successfully written.
Task #
Write a simple program that copies files.
It should accept two paths as arguments and copy the file from the first path to the second.
This time, use low-level functions.
Task Solution #
What the student needs to know:
man 3p openman 3p closeman 3p readman 3p writeman 3p mknod(only constants describing permissions foropen)
Code for file l2-1.c
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define ERR(source) (perror(source), fprintf(stderr, "%s:%d\n", __FILE__, __LINE__), exit(EXIT_FAILURE))
#define FILE_BUF_LEN 256
void usage(const char *const pname)
{
fprintf(stderr, "USAGE:%s path_1 path_2\n", pname);
exit(EXIT_FAILURE);
}
int main(const int argc, const char *const *const argv)
{
if (argc != 3)
usage(argv[0]);
const char *const path_1 = argv[1];
const char *const path_2 = argv[2];
const int fd_1 = open(path_1, O_RDONLY);
if (fd_1 == -1)
ERR("open");
const int fd_2 = open(path_2, O_WRONLY | O_CREAT, 0777);
if (fd_2 == -1)
ERR("open");
char file_buf[FILE_BUF_LEN];
for (;;)
{
const ssize_t read_size = read(fd_1, file_buf, FILE_BUF_LEN);
if (read_size == -1)
ERR("read");
if (read_size == 0)
break;
if (write(fd_2, file_buf, read_size) == -1)
ERR("write");
}
if (close(fd_2) == -1)
ERR("close");
if (close(fd_1) == -1)
ERR("close");
return EXIT_SUCCESS;
}
Notes and Questions #
Could this program be implemented using the standard C library functions instead of low-level I/O (e.g.,
fopen,fprintf, etc.)?Answer:
Yes, there’s nothing in this program that prevents us from using the functions shown earlier.Can we write data to a file descriptor returned by
openusingfprintf?Answer:
No! Functions likefprintf,fgets, andfscanfaccept aFILE*variable as an argument, while a file descriptor is anintused by the operating system to identify an open file.
Vectorized File Operations #
The writev function (see man 3p writev) provides a convenient solution when the data we want to write isn’t in a single continuous memory fragment. It allows us to gather data from multiple locations and write it to a file with a single function call. It’s defined in <sys/uio.h>.
ssize_t writev(int fildes, const struct iovec *iov, int iovcnt);
fildes- descriptor to which data is written,iov- array of structures describing the buffers from which data is gathered -struct iovec(seeman 0p sys_uio.h), which has the following fields:
void *iov_base -> pointer to the memory area
size_t iov_len -> length of the memory area
iovcnt- size of theiovarray.
This function writes to fildes:iov[0].iov_len bytes starting from iov[0].iov_base,iov[1].iov_len bytes starting from iov[1].iov_base, … up toiov[iovcnt-1].iov_len bytes starting from iov[iovcnt-1].iov_base.
The readv function works similarly (see man 3p readv):
ssize_t readv(int fildes, const struct iovec *iov, int iovcnt);
fildes- descriptor from which data is read,iov- array ofstruct iovecstructures describing the buffers
In other aspects, these functions work analogously to their non-vector counterparts write and read.
Useful pages #
- man 3p writev
- man 3p readv
- man 0p sys_uio.h