File System Exercises
This chapter collects exercises on file systems. They cover the basic concepts of file systems, file attributes, directory structures, allocation methods, access control, and file system types.
1. The Main Functions of a File System
Question: briefly describe the main functions of a file system, and explain what each one does.
Approach
Group the functions by the stage of a file’s life they serve: the space a file occupies, the name it is known by, the rights it carries, and the guarantee that it survives a crash.
Steps
- Ask how the file system decides where a file’s bytes go on disk.
- Ask how a user refers to the file and how the system keeps its metadata.
- Ask how the system keeps unauthorized users out.
- Ask what happens to the data when the machine dies unexpectedly.
Answer
The main functions of a file system are:
- Space management: allocate and reclaim storage space, manage the free space, and deal with storage fragmentation
- File organization and naming: provide a naming mechanism, organize files into a hierarchical structure, and maintain file metadata
- File access control: provide the read and write interfaces for files, enforce access permissions, and ensure the security of the files
- Data persistence: make sure data is stored durably, provide backup and recovery facilities, and keep the data consistent when the system crashes
2. Why File Attributes Matter
Question: explain what file attributes are for, and list at least five important file attributes.
Approach
Start from the purpose of attributes in general — they are the metadata the file system keeps about every file — and then list the attributes that the operating system itself relies on.
Steps
- Explain what the attributes are and why the file system records them.
- Pick the attributes the system uses to find, size, and protect a file.
- Add the attributes that record the history of the file.
Answer
What file attributes are for:
- They give a complete description of a file
- They support file management and access control
- They let the file system optimize its behaviour and improve performance
Important file attributes:
- File name: the identifier a user recognizes and uses to reach the file
- File size: the number of data bytes the file contains
- File type: regular file, directory, device file, and so on
- Creation time: the timestamp of when the file was first created
- Modification time: the time the contents of the file were last changed
- Access time: the time the file was last accessed
- Owner: the creator or the current owner of the file
- Permissions: the read, write, and execute rights that are set
3. Comparing Directory Structure Types
Question: compare the advantages and disadvantages of the single-level, two-level, and tree-structured directory organizations.
Approach
Compare the three structures along the same three axes: how easy it is to find a file, how easy it is to give files unique names, and how well the structure supports sharing and protection.
Steps
- Describe the single-level directory, where every file sits directly in one flat namespace.
- Describe the two-level directory, where each user has a own directory.
- Describe the tree directory, where directories can contain other directories.
- Fill in the table for each structure.
Answer
| Structure | Advantages | Disadvantages |
|---|---|---|
| Single-level directory | Simple, fast access, easy to implement | Naming collisions, hard to manage, weak security |
| Two-level directory | User isolation, safe naming, permission management | No hierarchical organization, hard to share files |
| Tree directory | Hierarchical organization, highly flexible, supports sharing | Complex to implement, path resolution overhead, problems with circular references |
4. The Path Resolution Algorithm
Question: design an algorithm that resolves an absolute path, and state its time complexity.
Approach
Split the path into components and walk the directory tree one component at a time, starting at the root. Every step checks that the current node really is a directory before looking up the next component.
Steps
- Reject the path if it does not start with
/, since only absolute paths are handled here. - Start from the root directory.
- For each component separated by
/, look up the matching child of the current directory. - Fail if the current node is not a directory, or if no child matches the component.
- The node reached after the last component is the answer.
Answer
struct directory_node* resolve_path(const char* path) {
if (path[0] != '/') return NULL; // must be an absolute path
struct directory_node* current = root_directory;
char* path_copy = strdup(path);
char* token = strtok(path_copy, "/");
while (token != NULL) {
if (current->type != DIRECTORY) {
free(path_copy);
return NULL; // the current node is not a directory
}
struct directory_node* next = find_child(current, token);
if (next == NULL) {
free(path_copy);
return NULL; // no matching component was found
}
current = next;
token = strtok(NULL, "/");
}
free(path_copy);
return current;
}Time complexity: O(n), where n is the number of components in the path.
5. Implementing an Allocation Algorithm
Question: implement the first fit algorithm to allocate a contiguous run of storage space.
Approach
Scan the blocks in address order and keep a counter of how many free blocks have been seen in a row. As soon as the run is long enough for the file, claim it and return its first block.
Steps
- Work out how many blocks the file needs, rounding the size up.
- Scan the blocks from the lowest address upwards.
- Extend the current run whenever the block is free, and reset the run whenever it is not.
- Remember the address of the first block of the run.
- Mark the blocks as used and return the starting block; return -1 if no run was long enough.
Answer
int first_fit_allocate(int file_size, int block_size) {
int blocks_needed = (file_size + block_size - 1) / block_size;
int consecutive_blocks = 0;
int start_block = -1;
for (int i = 0; i < TOTAL_BLOCKS; i++) {
if (is_block_free(i)) {
if (consecutive_blocks == 0) {
start_block = i;
}
consecutive_blocks++;
if (consecutive_blocks >= blocks_needed) {
// Found enough consecutive blocks
mark_blocks_used(start_block, blocks_needed);
return start_block;
}
} else {
consecutive_blocks = 0;
}
}
return -1; // No contiguous space large enough was found
}6. Comparing File Allocation Methods
Question: compare the advantages and disadvantages of contiguous allocation, linked allocation, and indexed allocation.
Approach
Compare the three methods along the same four axes: how fast sequential access is, how fast random access is, what happens to space utilization, and how hard the method is to implement.
Steps
- Consider what happens when the file is read from the beginning to the end.
- Consider what happens when a single byte in the middle of the file is needed.
- Consider how the blocks are handed out and how fragments appear over time.
- Consider how much extra bookkeeping the method needs.
Answer
Contiguous allocation:
- Advantages: fast sequential access, simple to implement, fast random access
- Disadvantages: external fragmentation, the preallocation problem, hard to extend
Linked allocation:
- Advantages: no external fragmentation, dynamic growth, high space utilization
- Disadvantages: slow random access, poor reliability, space overhead
Indexed allocation:
- Advantages: fast random access, no external fragmentation, supports large files
- Disadvantages: space overhead, complex implementation, index block limits
7. The DAC Permission Check
Question: implement a permission check function for DAC (discretionary access control).
Approach
Resolve the request against the three classes of user in turn: the owner, the members of the owning group, and everyone else. The first class that matches decides the answer.
Steps
- Load the DAC record of the file and the record of the requesting user; deny the request if either is missing.
- If the user is the owner, consult the owner permission bits.
- Otherwise, if the user belongs to the owning group, consult the group permission bits.
- Otherwise, consult the permission bits for other users.
- Return 0 for any operation that is not read, write, or execute.
Answer
int check_dac_permission(const char* file_path, int user_id, int operation) {
struct file_dac* dac = get_file_dac(file_path);
if (dac == NULL) return 0;
// Look up the user
struct user_info* user = get_user_info(user_id);
if (user == NULL) return 0;
// Check whether the user is the owner
if (user_id == dac->owner_id) {
switch (operation) {
case READ_OP:
return dac->permissions.owner_read;
case WRITE_OP:
return dac->permissions.owner_write;
case EXECUTE_OP:
return dac->permissions.owner_execute;
default:
return 0;
}
}
// Check whether the user belongs to the group
if (is_user_in_group(user_id, dac->group_id)) {
switch (operation) {
case READ_OP:
return dac->permissions.group_read;
case WRITE_OP:
return dac->permissions.group_write;
case EXECUTE_OP:
return dac->permissions.group_execute;
default:
return 0;
}
}
// Permissions for everyone else
switch (operation) {
case READ_OP:
return dac->permissions.other_read;
case WRITE_OP:
return dac->permissions.other_write;
case EXECUTE_OP:
return dac->permissions.other_execute;
default:
return 0;
}
}8. Implementing File Encryption
Question: implement basic file encryption.
Approach
Derive an encryption key from the password, encrypt the file contents with a random initialization vector, and store the information needed to decrypt the file again alongside it.
Steps
- Derive a key of the required length from the password.
- Generate a fresh initialization vector so that the same plaintext never encrypts to the same ciphertext.
- Read the contents of the file into memory.
- Encrypt the contents with AES-256.
- Record the algorithm, the key hash, the key length, and the initialization vector, and write the encrypted file out.
Answer
int encrypt_file(const char* file_path, const char* password) {
// Generate the key
unsigned char key[32];
generate_key_from_password(password, key, 32);
// Generate the initialization vector
unsigned char iv[16];
generate_random_iv(iv, 16);
// Read the contents of the file
char* file_content = read_file_content(file_path);
int content_length = strlen(file_content);
// Encrypt the contents of the file
char* encrypted_content = malloc(content_length + 16);
int encrypted_length = encrypt_aes256(file_content, content_length, key, iv, encrypted_content);
// Store the encryption information
struct file_encryption* enc_info = malloc(sizeof(struct file_encryption));
enc_info->algorithm = AES256;
hash_key(key, 32, enc_info->key_hash);
enc_info->key_length = 32;
memcpy(enc_info->iv, iv, 16);
// Write the encrypted file
write_encrypted_file(file_path, encrypted_content, encrypted_length, enc_info);
free(file_content);
free(encrypted_content);
free(enc_info);
return 0;
}9. Analysing the FAT File System
Question: analyse the structure of the FAT file system and explain its advantages and disadvantages.
Approach
Walk through the four regions of the volume in the order they appear on disk, then weigh the simplicity of that layout against the performance it costs.
Steps
- Identify the four regions of the volume.
- For each region, state what it is used for.
- List the advantages that follow from the simple layout.
- List the disadvantages that follow from the fixed cluster size and the flat chain structure.
Answer
The structure of the FAT file system:
- Boot sector: holds the basic information about the file system
- FAT table: stores the file allocation information
- Root directory: stores the file and directory entries
- Data area: stores the actual file data
Advantages:
- Simplicity: the structure is simple and easy to understand and implement
- Compatibility: almost every operating system supports it
- Lightweight: little system overhead, which suits embedded systems
Disadvantages:
- Performance limits: large files are accessed inefficiently
- Wasted space: the cluster size is fixed, so small files waste space
- Fragmentation: file fragmentation appears easily
- Limited features: it does not support advanced features
10. Advanced Features of NTFS
Question: list the advanced features of the NTFS file system and explain what each one does.
Approach
Take each feature in turn and split it into what it is for and how NTFS implements it, so the answer shows both the purpose and the mechanism.
Steps
- Start with the feature that keeps the file system consistent after a crash.
- Move on to the features that save space and protect data.
- Finish with the features that control who may do what.
Answer
Advanced features of NTFS:
- Journaling:
- Purpose: guarantees data consistency and enables fast recovery
- Implementation: file system operations are recorded in a log file
- Compression:
- Purpose: saves storage space
- Implementation: file data is compressed transparently
- Encryption:
- Purpose: protects the file against unauthorized reading
- Implementation: EFS (Encrypting File System)
- Permission control:
- Purpose: provides fine-grained access control
- Implementation: ACL (Access Control List)
- Disk quotas:
- Purpose: limits how much storage a user may consume
- Implementation: the usage of each user’s files is tracked
11. Designing a File System
Question: design a simple file system that supports the basic file operations.
Approach
Build the system out of three parts — a superblock that describes the volume, an inode table that describes the files, and the data blocks themselves — and then implement file creation and file reading on top of them.
Steps
- Lay out the three parts of the file system.
- To create a file, allocate an inode and then the data blocks it needs, and roll back if either allocation fails.
- Fill in the inode with the name, the size, and the list of block numbers.
- To read a file, work out which blocks the requested range of bytes falls in and copy them out.
- Remember to clamp the read to the size of the file.
Answer
// The file system structure
struct simple_fs {
struct super_block super; // the superblock
struct inode* inode_table; // the inode table
char* data_blocks; // the data blocks
int free_blocks_count; // the number of free blocks
};
// The file operation functions
int fs_create_file(const char* name, int size) {
// 1. Allocate an inode
int inode_num = allocate_inode();
if (inode_num == -1) return -1;
// 2. Allocate the data blocks
int blocks_needed = (size + BLOCK_SIZE - 1) / BLOCK_SIZE;
int* block_numbers = allocate_blocks(blocks_needed);
if (block_numbers == NULL) {
free_inode(inode_num);
return -1;
}
// 3. Initialize the inode
struct inode* inode = &fs.inode_table[inode_num];
strcpy(inode->name, name);
inode->size = size;
inode->block_count = blocks_needed;
memcpy(inode->block_numbers, block_numbers, blocks_needed * sizeof(int));
free(block_numbers);
return inode_num;
}
int fs_read_file(int inode_num, char* buffer, int offset, int size) {
struct inode* inode = &fs.inode_table[inode_num];
if (offset >= inode->size) return 0;
if (offset + size > inode->size) {
size = inode->size - offset;
}
int start_block = offset / BLOCK_SIZE;
int end_block = (offset + size - 1) / BLOCK_SIZE;
int bytes_read = 0;
for (int block = start_block; block <= end_block; block++) {
int block_number = inode->block_numbers[block];
int block_offset = (block == start_block) ? (offset % BLOCK_SIZE) : 0;
int bytes_to_read = min(BLOCK_SIZE - block_offset, size - bytes_read);
memcpy(buffer + bytes_read,
fs.data_blocks + block_number * BLOCK_SIZE + block_offset,
bytes_to_read);
bytes_read += bytes_to_read;
}
return bytes_read;
}12. Analysing Performance Optimization
Question: analyse the ways to optimize the performance of a file system, and explain the effect of each one.
Approach
Treat optimization as a trade-off: every technique buys speed or capacity at the price of something else, and the analysis should name that price.
Steps
- Start with the techniques that cut the number of disk accesses.
- Move on to the techniques that make the accesses themselves cheaper.
- Finish with the techniques that change how the data is laid out, and note what each one costs.
Answer
Ways to optimize file system performance:
- Buffering:
- Method: use an in-memory cache to cut down on disk I/O
- Effect: markedly improves read performance
- Read-ahead:
- Method: predictively read the data that follows
- Effect: improves sequential access performance
- Write buffering:
- Method: delay the writes and process them in batches
- Effect: improves write performance, but may affect consistency
- Index optimization:
- Method: use an efficient index structure such as a B-tree
- Effect: improves random access performance
- Compression:
- Method: compress file data transparently
- Effect: saves storage space, but may affect I/O performance
- RAID technology:
- Method: access several disks in parallel
- Effect: raises I/O throughput
