Operating System 10 | File I/O Experiment
Operating System 10 | File I/O Experiment

- Write Files
(1) Open A File by fopen
We can also open this file in an advanced way by the file pointer. C language uses the file pointer for file operations. We can define a file pointer fp
by,
FILE *fp;
Then, to open a given file, we should use the fopen
function. Note that we should specify the file mode at the moment we open this file. The file can be load as only read, only write, only append, or combinations between these modes. We can refer to the following common modes,
+-----------+---------------------------+
| Mode | Meaning |
+-----------+---------------------------+
| r | Only read |
+-----------+---------------------------+
| w | Only write |
+-----------+---------------------------+
| a | Only append |
+-----------+---------------------------+
| r+ | Read + write |
+-----------+---------------------------+
| w+ | Write + read |
+-----------+---------------------------+
| a+ | Append + read |
+-----------+---------------------------+
Note that we need to add b
in the mode for the I/O of binary files.
For example, if we want to open and write to a file named test.txt
(create one if doesn’t exist), we can use,
fp = fopen("test.txt", "w+");
(2) Open A File by open
In the previous section, we have discussed how we can open a file by the file pointer. We can also use modes signs like w
, r
, a
, etc. However, this is a more advanced way to open a file. Let’s see how we can open a file by the file descriptor, which is a fundamental way of file I/O. Commonly, it is good to use fopen
instead of open
. You can find some reason from here.
The synopsis of the open
system call is,
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *path, int access, int mode);
where,
path
is the path of the file we would like to openaccess
is the access mode for this file. Usually, we have three macros to specify for this argument,O_RDONLY
(read-only access mode),O_WRONLY
(write-only access mode), orO_RDWR
(read-write access mode). We can also use or operator|
to specify more features. For example,O_CREATE
means to create a file if the file is doesn’t exist. What’s more,O_TRUNC
means that we will clear the file before we write to it. So here are some corresponding rules,
r == O_RDONLY
r+ == O_RDWR | O_TRUNC
w == O_WRONLY | O_CREATE | O_TRUNC
w+ == O_RDWR | O_CREATE | O_TRUNC
a == O_WRONLY | O_CREATE
a+ == O_RDWR | O_CREATE
- The
mode
argument can only be used whenaccess=O_CREAT
and it is used to specify the future accesses of the newly created file. The commonest macros for this argument areS_IRUSR
(means user has read permission),S_IWUSR
(means user has write permission), andS_IXUSR
(means user has execute permission). We can also use or operator|
to specify more than one value.
For example, if we want to open and write to a file named test.txt
by open
(create one if doesn’t exist), we can use,
open("test.txt", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
This function returns a file descriptor (datatype int) for us to use. This file descriptor can be useful for the following write and read operations.
int fd = open("test.txt", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
(3) Write/Read the File with File Pointer
If we want to write to the file, this can be easy for a file pointer. However, it would be quite hard for a file descriptor. Suppose we have a file pointer and a file descriptor for the same file now,
FILE *fp;
int fd;
If we want to write the string Hello World!\n
to the file, we can use the function fputs
,
fputs("Hello World!\n
", fp);
Or we can use the function fprintf
,
fprintf(fp, "Hello World!\n
");
The fputc
function can be used to write a single character to the file (i.e. 65 means A by ASCII),
fputc(65, fp);
Now, let’s see an example code,
The content of the output file test.txt
should be,
Hello World!
Hello World!
A
To read from this created file test.txt
, we can use fgetc
to get a character or the fgets
function for a string. In order to use read a string, we should also create a buffer to store the string we have to read (we use a buffer that can store up to 255 characters).
printf("%c\n", fgetc(fp));
char buff[255];
fgets(buff, 255, fp);
printf("%s", buff);
Now, let’s see an example code,
The output should be,
H
ello World!
Hello World!
A
Remember that we have to free the pointer fp
by fclose
function before we end the program.
(4) Write/Read the File with File Descriptor
To write a file with the file descriptor, we can use the function write
. For example, if we want to write Hello World!
to the file,
write(fd, "Hello World!", strlen("Hello World!"));
Note that the third argument must be exactly the string length we would like to write. Usually, we can use a buffer to store the message, so it would be better if we use,
#define BUF_SIZE 20
char buffer[BUF_SIZE];
char *message = "Hello World!";
strcpy(buffer, message);
write(fd, buffer, strlen(buffer));
So in general, we can write to the file with the following code.
The content of the output file test.txt
should be,
Hello World!
Now that we have the test.txt
, if we want to read from this file, we can use the function read
. In order to store the read information, we have to create a buffer buffer
.
#define BUF_SIZE 20
char buffer[BUF_SIZE];
read(hfile, buffer, BUF_SIZE);
To get the information we have read, we can directly print the value in the buffer,
printf("%s\n", buffer);
The overall code should be,
The output should be,
Hello World!
(5) Buffer Looping Write
Suppose we have a buffer that is smaller than the message we want to send, which can be common to us, what could we do if we want to write this message? Let’s say we have a buffer that can store only 2 chars but we would like to send the message Hello World!
, a direct idea is that we can cut this string into pieces and then write 2 characters at a time. We can continue this until all the characters are successfully written to the file.
To implement this idea, for each loop, we copy the string starting from the character pointer message
with a length of BUF_SIZE
to the buffer by memcpy
,
memcpy(buffer, message, BUF_SIZE);
After this procedure, the buffer is now the 2-character message we would like to write properly. Thus, we can then use write
to write the data in the buffer to the file. We should -1
to the strlen(buffer)
because we don’t want to add a \0
character for each write.
write(fd, buffer, strlen(buffer)-1);
Note that the message pointer message
has to move rightward by BUF_SIZE*sizeof(char)
because we have already written these 2 characters. Also, the length of this string should be recalculated by the strlen
function,
message += BUF_SIZE;
message_len = strlen(message);
So finally, we have the following example code to implement this idea,
The content of the output file test.txt
should be,
Hello World!
(6) Buffer Looping Read
The same problem happens when we want to read from a file. Suppose we have a file that contains a string much longer than the read buffer we have, what can we do? Well, this is much simple than the writing case because the position we read is accumulated and we can use the return value of the read
function to show where to stop. The example code is,
The output of this program should be,
Hello World!
2. send
And recv
(1) read
And write
Now, we are quite familiar with the read
and write
function and we have known that they can work for all the file descriptors. However, for the socket, use read
and write
to send our message is not safe. When we use read
or write
for sending and receiving messages, some messages can be lost and we don’t know this happens. So for socket programming, the more frequently used functions for sending messages are send
(corresponding to write
) and recv
(corresponding to read
).
(2) send
Function
When we use the write
function, we should use the file descriptor, the buffer pointer, and the length of the buffer as arguments,
write(fd, buffer, length);
It’s good to know that the send
function actually has a similar structure. When we call send
, we should have,
send(fd, buffer, length, 0);
Note that we should specify the flag
variable to 0 in our cases. So the interesting thing is that the send
function returns a value of the real bytes we send. So,
bytes_sent = send(fd, buffer, length, 0);
Because of the networking issues, we can always expect that,
bytes_sent <= length
Thus, we can maintain the real bytes we send for each loop until we successfully send the whole message.
(3) recv
Function
Similarly, the recv
function can be called by,
recv_bytes = recv(fd, buffer, BUF_SIZE, 0);
We should loop the recv
function until the returned value recv_bytes = 0
(this means that the server has no more data to send).
(4) File Transformation
Let’s finally discuss how we can implement a file transformation instance. Suppose the server has a file and each client connected to this server will receive a copy of this file. In this example, we have to do the following steps,
- Step 1. Server reads file. Because this file is stored in the server, the server should first use the function
read
to read from this file. - Step 2. Server sends file. Because we have to maintain the file sending process, we have to use
send
to send the data to the server. - Step 3. Client receives file. Because we have to maintain the file receiving process, we have to use
recv
to send the data to the server. - Step 4. Client writes file. In the end, we have to write the data we have received in the buffer to a local file by the
write
function.