1.1 Background
Consider the scenario of reading from a file and transferring the data to another program over the network. This scenario describes the behaviour of many server applications, including Web applications serving static content, FTP servers, mail servers, etc. The core of the operation is in the following two calls:
    read(file, user_buffer, len);
    write(socket, user_buffer, len);
Figure 1 shows how data is moved from the file to the socket.
Behind these two calls, the data has been copied at least four times, and almost as many user/kernel context switches have been performed. Figure 2 shows the process involved. The top side shows context switches, and the bottom side shows copy operations.
1. The read system call causes a context switch from user mode to kernel mode. The first copy is performed by the DMA (Direct Memory Access) engine, which reads file contents from the disk and stores them into a kernel address space buffer.
2. Data is copied from the kernel buffer into the user buffer ,and the read system call returns. The return from the call causes a context switch from kernel back to user mode. Now the data is stored in the user address space buffer, and it can begin its way down again.
3. The write system call causes a context switch from user mode to kernel mode. A third copy is per- formed to put the data into a kernel address space buffer again. This time, though, the data is put into a different buffer, a buffer that is associated with sockets specifically.
4. The write system call returns, creating our fourth context switch. Return from write call does not guarantee the start of the transmission. It simply means the Ethernet driver had free descriptors in its queue and has accepted our data for transmission. Independently and asynchronously, a fourth copy happens as the DMA engine passes the data from the kernel buffer to the protocol engine. (The forked DMA copy in Figure 2 illustrates the fact that the last copy can be delayed).
As you can see, a lot of data duplication happens in this process. Some of the duplication could be eliminated to decrease overhead and increase performance. To eliminate overhead, we could start by eliminating some of the copying between the kernel and user buffers.

1.2 Overview and Technical Details
Your task in this lab is to implement zero-copy read and write operations that would eliminate the copying between the kernel and user buffers. You will develop a new library with a set of library calls that allow a user to:
• Open a file
• Read from the file without using a user buffer 
• Write to the file without using a user buffer
• Reposition within the file
• Close the file
The user directly uses the kernel buffer provided by the library calls to read and write data.
Your implementation should NOT call read and write system calls or other library calls that wrap around read and write system calls. Calling read and write would involve some type of duplication of buffers. You should use the mmap system call in your implementation.

2 Exercises in Lab 4
The goal of this lab assignment is to produce a zero-copy IO library. All function and data structures names are prefixed by zc_. The library uses a data structure called zc_file (defined in zc_io.c) to maintain the information about the opened files and help in the reading and writing operations. You are required to use it and add any information needed to maintain the information about the opened files into this data structure.
For ex1 to ex3, operations on the same file will not be issued concurrently (i.e. you do not need to be concerned about synchronization). We will change this assumption in ex4 and bonus exercise. For all exercises, you may assume that there is no concurrent opening of the same file (the file is opened at most once at the same time, and the file is not modified outside the runner).
The provided runner implements a few testcases on reading and writing a file using the zc_io library. It is not exhaustive but will catch some common errors. If your implementation is correct, the runner will run successfully. Otherwise, it may segmentation fault, or print a “FAIL” message with the reason of the failure. You are also encouraged to implement your own program to test the library.

2.1 Exercise 1A: Zero-copy Read [1% + 1% demo or 2% submission]
You are required to implement four library calls to open/close and perform zero copy read from a file.

- zc_file *zc_open(const char *path)
Opens file specified by path and returns a zc_file pointer on success, or NULL otherwise. Open the file using the O_CREAT and O_RDWR flags.
You can use fstat() to obtain information (if needed) regarding the opened file.
-int zc_close(zc_file *file)
Flushes the information to the file and closes the underlying file descriptor associated with the file. If successful, the function returns 0, otherwise it returns -1. Free any memory that you allocated for the zc_file structure. You can use msync() flush copy of file in virtual memory into file.
-const char *zc_read_start(zc_file *file, size_t *size)
The function returns the pointer to a chunk of *size bytes of data from the file. If the file contains less than *size bytes remaining, then the number of bytes available should be written to *size. The purpose of zc_read_start is to provide the kernel buffer that already contains the data to be read. This avoids the need to copy these data to another buffer as in the case of read system call. Instead, the user can simply use the data from the returned pointer.
Your zc_file structure should help you keep track of a offset in the file. Once size bytes have been requested for reading (or writing), the offset should advance by size and the next time when zc_read_start or zc_write_start is called, the next bytes after offset should be offered.
Note that reading and writing is done using the same offset.
-void zc_read_end(zc_file *file)
This function is called when a reading operation on file has ended.
It is always guaranteed that the function is paired with a previous call to zc_read_start.

Fill the following template:
#include "zc_io.h"

// The zc_file struct is analogous to the FILE struct that you get from fopen.
struct zc_file {
    // Insert the fields you need here.


    /* Some suggested fields :
        - pointer to the virtual memory space
        - offset from the start of the virtual memory
        - total size of the file
        - file descriptor to the opened file
        - mutex for access to the memory space and number of readers
    */
};

/**************
 * Exercise 1 *
 **************/

zc_file* zc_open(const char* path) {
    // To implement
    return NULL;
}

int zc_close(zc_file* file) {
    // To implement
    return -1;
}

const char* zc_read_start(zc_file* file, size_t* size) {
    // To implement
    return NULL;
}

void zc_read_end(zc_file* file) {
    // To implement
}

char* zc_write_start(zc_file* file, size_t size) {
    // To implement
    return NULL;
}

void zc_write_end(zc_file* file) {
    // To implement
}

/**************
 * Exercise 2 *
 **************/

off_t zc_lseek(zc_file* file, long offset, int whence) {
    // To implement
    return -1;
}

/**************
 * Exercise 3 *
 **************/

int zc_copyfile(const char* source, const char* dest) {
    // To implement
    return -1;
}

/**************
 * Bonus Exercise *
 **************/

const char* zc_read_offset(zc_file* file, size_t* size, long offset) {
    // To implement
    return NULL;
}

char* zc_write_offset(zc_file* file, size_t size, long offset) {
    // To implement
    return NULL;
}