TP — File system
All the sources are available in the part-3 tarball.
The C files are located in directory part-3/tp-file-system/material/.
The general specifications of the POSIX functions are available at OpenGroup website
Overview
The aim of this lab is to learn how to use Unix system calls to manage files.
We first study how to execute basic operations such as open(), read(), write(), lseek() and close(). We implement a program to output and modify a binary file.
Second, we manipulate files using Shell commands such as ls, ln, chmod, ...
Files
Here is a brief summary of the main system calls used to manipulate files.
open(), close() and unlink()
#include <fcntl.h>
int open(const char *path, int oflag, ... );
open opens a file located at path and returns an integer file descriptor to refer to it later. This descriptor allows us to perform other operations such as reading and writing without using the file name.
oflag specifies the operations allowed on the file. Values for oflag are constructed by a bitwise-inclusive OR. Applications shall specify one of the first three file access modes:
- O_RDONLY
- Open for reading only.
- O_WRONLY
- Open for writing only.
- O_RDWR
- Open for reading and writing.
Several other flags can be combined with them.
In particular, O_CREAT allows to create the file if it does not exist already. If it already exists, nothing is done and the existing file is opened.
If oflags is O_CREAT | O_WRONLY, the file is created if it does not already exist and the user can only write in it.
If the file is created, the third argument of open() represents the file mode of the file. The following code will create a file (if it does not exist) with permission mode 0644 (in octal), i.e., read and write modes for user, read mode for group and others.
fd = open("my_new_file.txt", O_CREAT | O_RDWR, 0644);
If the returned value of open() is -1, an error occurred and the file has not been opened.
Once all read and write operations have been performed on the file, the user can close the file. It will be closed anyway when the process ends.
To delete a file, use unlink on its path.
read() and write()
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbyte);
ssize_t write(int fd, const void *buf, size_t nbyte);
The read() function reads nbyte bytes from the file associated with file descriptor, fd, into the buffer pointed to by buf.
The write() function writes nbyte bytes from the buffer pointed to by buf to the file associated with the file descriptor, fd.
Upon successful completion, read() or write() return the number of bytes actually read or written. Otherwise, they return -1 and set the global variable errno
to indicate the error.
Typically, nbyte is set to the buffer (max) size when we read from a file, and if the operation is successful, the returned value indicates the number of bytes actually read from the file.
lseek()
Associated to each open file the operating systems maintains an offset---an integer between 0 (beginning of the file) and file length---indicating where in the file the next read/write operation (among others) will start operating.
Each read/write operation implicitly advances the file offset by the amount of bytes read or written.
off_t lseek(int fd, off_t offset, int whence);
The lseek() system call explicitly changes the position of the file offset for the open file fd, as follows:
-
If whence is SEEK_SET, the file offset shall be set to offset bytes (absolute positioning from the beginning of the file).
-
If whence is SEEK_CUR, the file offset shall be set to its current location plus offset (relative positioning w.r.t. the current file offset).
-
If whence is SEEK_END, the file offset shall be set to the size of the file plus offset (absolute position from the end of the file).
Upon successful completion, the returned value corresponds to the resulting offset, as measured in bytes from the beginning of the file. Note that using lseek() with offset set to 0 and whence to SEEK_END allow to get the size of the file as returned value.
File management in C
Output a binary file
- Complete the file fs-edit-file.c. Compile and run it to make it output a file. In this part, we want to output a binary file, each element representing a byte or a 8-bits unsigned integer. We want to output 4 bytes per line. At the beginning of each line, we output the offset (index) of the file. If any bytes are missing to complete the last line, they are ignored. Edit the fs-edit-file.c. This file will be enriched to implement several binary operations, including displaying the content of the file. To check the result, you can use the Unix command od. It should look that way:
> echo "hello world" > test.dat
> ./fs-edit-file test.dat output
00000000 : 68 65 6c 6c
00000004 : 6f 20 77 6f
00000008 : 72 6c 64 0a
> od -t c test.dat
0000000 h e l l o w o r l d \n
0000014
> od -t x1 test.dat
0000000 68 65 6c 6c 6f 20 77 6f 72 6c 64 0a
0000014
- Complete the file fs-edit-file.c. Compile and run it to make it output a file.
Modify a byte in a binary file
Next, we want to modify a byte in a binary file. This operation takes two parameters, the address to be modified and its new byte value. To do so, we move the file index to address. Then we change the current byte to value.
To check the result, you can use the Unix command od. It should look that way:
> echo "hello world" > test.dat
> ./fs-edit-file test.dat output
00000000 : 68 65 6c 6c
00000004 : 6f 20 77 6f
00000008 : 72 6c 64 0a
> ./fs-edit-file test.dat modify 0x6 0x57
> ./fs-edit-file test.dat output
00000000 : 68 65 6c 6c
00000004 : 6f 20 57 6f
00000008 : 72 6c 64 0a
> od -t c test.dat
0000000 h e l l o W o r l d \n
0000014
- Complete the fs-edit-file.c file. Compile it and run it to modify a binary file as described above.
Insert a byte in a binary file
Now we want to insert a byte into a binary file. This operation takes two parameters, the address to insert and the value of the byte to insert. Note that unlike the modify operation, we need to push forward the bytes from address before inserting the new byte.
To check the result, you can use the Unix command od. It should look that way:
> echo "hello world" > test.dat
> ./fs-edit-file test.dat output
00000000 : 68 65 6c 6c
00000004 : 6f 20 77 6f
00000008 : 72 6c 64 0a
> ./fs-edit-file test.dat insert 0x6 0x20
> ./fs-edit-file test.dat output
00000000 : 68 65 6c 6c
00000004 : 6f 20 20 77
00000008 : 6f 72 6c 64
0000000c : 0a
> od -t c test.dat
0000000 h e l l o w o r l d \n
0000015
- Complete the fs-edit-file.c file. Compile it and run it to modify a binary file as described above.
Append a byte to a binary file
Here we want to append a byte to a binary file. This operation takes only one parameters, the value of the byte to append.
To check the result, you can use the Unix command od. It should look that way:
> echo "hello world" > test.dat
> ./fs-edit-file test.dat output
00000000 : 68 65 6c 6c
00000004 : 6f 20 77 6f
00000008 : 72 6c 64 0a
> ./fs-edit-file test.dat append 0xa
> ./fs-edit-file test.dat output
00000000 : 68 65 6c 6c
00000004 : 6f 20 20 77
00000008 : 6f 72 6c 64
0000000c : 0a 0a
> od -t c test.dat
0000000 h e l l o w o r l d \n \n
0000015
- Complete the fs-edit-file.c file. Compile it and run it to modify a binary file as described above.
Truncate a binary file from a given address
This time, we want to truncate the file at a given address. To do so, we have to copy the truncated file to a temporary file, delete the original file and rename the temporary file to the original file name. To achieve this, we describe two new functions:
int unlink(char *path);
int rename(const char *old, const char *new);
- The unlink() function removes a link to a file. If there is no more links, the file is removed.
- The rename() function changes the name of a file.
To check the result, you can try the following scenario:
> echo "hello world" > test.dat
> ./fs-edit-file test.dat output
00000000 : 68 65 6c 6c
00000004 : 6f 20 77 6f
00000008 : 72 6c 64 0a
> ./fs-edit-file test.dat remove 0x5
> ./fs-edit-file test.dat output
00000000 : 68 65 6c 6c
00000004 : 6f
> od -t c test.dat
0000000 h e l l o
0000005
- Complete the fs-edit-file.c file. Compile it and run it to truncate a binary file as described above.
File management with Shell
Access modes
- Create a directory private and change its access modes so that the user can read, write and execute, the group and others can only execute it.
- In private, create a directory with a name that is hard to guess (e.g., 000_ooo_000).
- Move to the initial directory and execute ls private.
- Change access modes of private such that the user can only execute it and execute ls private.
- Create a directory hierarchy so that only your friends will be able to see your vacation photos, thanks to a secret you share with them.
Inodes
mkdir tmp
ls -il .
cd tmp
touch my_orig_file
Symbolic links
- Create a new directory tmp and move to it.
- Create a file my_orig_file in this directory.
- Create a symbolic link of my_orig_file names my_symb_link
- Execute ls -il. What does -i change in ls execution ?
- Create a hard link of my_orig_file names my_hard_link
- Execute ls -il. What is the difference ?
- Move my_orig_file into my_new_file.
- Execute ls -il. What happens to my_symb_link ?
- Remove my_new_file.
- Execute ls -il. What happens to my_hard_link ?