|
1 | 1 | # Stage 12: File Module
|
| 2 | + |
| 3 | +## Recap |
| 4 | + |
| 5 | +We have implemented an upstream server for incoming connections to port 8001. |
| 6 | + |
| 7 | +## Learning Objectives |
| 8 | + |
| 9 | +We would be implementing a file server for incoming connections to port 8002. |
| 10 | + |
| 11 | +## Introduction |
| 12 | + |
| 13 | +A file server stores and delivers files to clients over a network, enabling remote access. It allows users to download, upload, and modify files. In this stage, we would be implementing a basic file server, which delivers files upon client request. When there are incoming connections to port 8002, the server serves a static file to the client through the pipe mechanism discussed earlier. |
| 14 | + |
| 15 | +## Design |
| 16 | + |
| 17 | +Two new modules `xps_file` and `xps_mime` are introduced. In `xps_file`, an `xps_file_s` struct is introduced which stores the information regarding the file including file path, file size, MIME type(describes the format of file), and file structure pointer. `xps_file` has functions for opening a file and creating an instance of `xps_file_s` struct for it, for destruction of file instance, for reading data from file and writing to the pipe, and for closing the file source. Earlier the source was receiving data using `recv()` and writes it to the pipe, but in a file server the source takes data from the file attached to it and writes it to the pipe. Here, the `ptr` field in `xps_source_s` struct points to an instance of `xps_file_s` struct which has to be read, instead of pointing to the connection instance as in earlier stages. `file_souce_handler()` reads data from the file and writes it to pipe. |
| 18 | + |
| 19 | +The `xps_mime` module is used for getting the MIME type of the file. A MIME type (Multipurpose Internet Mail Extensions type) is a standard that indicates the nature and format of a file. Originally developed for email systems to specify the type of data attached in emails, MIME types are now used extensively on the internet, particularly in HTTP, to describe the content being transferred. We would be exploring more on this in later stages. |
| 20 | + |
| 21 | +## Implementation |
| 22 | + |
| 23 | +Modules added/modified in the given order |
| 24 | + |
| 25 | +- `xps_mime` |
| 26 | +- `xps_file` |
| 27 | +- `xps_listener` |
| 28 | + |
| 29 | +Create a new folder disk in src, this would be used for adding necessary modules required for creating a file server.The two modules included are xps_mime and xps_file. |
| 30 | + |
| 31 | +## `xps_mime` Module |
| 32 | + |
| 33 | +### `xps_mime.h` |
| 34 | + |
| 35 | +The code below has the contents of the header file for `xps_mime`. Have a look at it and make a copy of it in your codebase. |
| 36 | + |
| 37 | +:::details **expserver/src/disc/xps_mime.h** |
| 38 | + |
| 39 | +```c |
| 40 | +#ifndef XPS_MIME_H |
| 41 | +#define XPS_MIME_H |
| 42 | + |
| 43 | +#include "../xps.h" |
| 44 | + |
| 45 | +const char *xps_get_mime(const char *file_path); |
| 46 | + |
| 47 | +#endif |
| 48 | +``` |
| 49 | +::: |
| 50 | + |
| 51 | +### `xps_mime.c` |
| 52 | + |
| 53 | +The function `xps_get_mime()` returns the MIME type of a file based on its extension. A MIME type lookup table (`mime_types`) maps file extensions (e.g., ".html", ".jpg") to their corresponding MIME types (e.g., "text/html", "image/jpeg"). This tells the browser how to display or interact with the file. For example, an HTML file is rendered as a web page, while an image is displayed as a picture. We won’t be using this functionality in the present stage but would be looking into in later stages. |
| 54 | + |
| 55 | +:::details **expserver/src/disc/xps_mime.c** |
| 56 | + |
| 57 | +```c |
| 58 | +#include "../xps.h" |
| 59 | +//here, some extension-mime types pairs are given, you can add as required |
| 60 | +xps_keyval_t mime_types[] = { |
| 61 | + {".c", "text/x-c"}, |
| 62 | + {".cc", "text/x-c"}, |
| 63 | + {".cpp", "text/x-c"}, |
| 64 | + {".dir", "application/x-director"}, |
| 65 | + {".dxr", "application/x-director"}, |
| 66 | + {".fgd", "application/x-director"}, |
| 67 | + {".swa", "application/x-director"}, |
| 68 | + {".text", "text/plain"}, |
| 69 | + {".txt", "text/plain"}, |
| 70 | + {".png", "image/png"}, |
| 71 | + {".png", "image/x-png"}, |
| 72 | + }; |
| 73 | +int n_mimes = sizeof(mime_types) / sizeof(mime_types[0]); |
| 74 | + |
| 75 | +const char *xps_get_mime(const char *file_path) { |
| 76 | + const char *ext = get_file_ext(file_path); |
| 77 | + |
| 78 | + if (ext == NULL) |
| 79 | + return NULL; |
| 80 | + |
| 81 | + for (int i = 0; i < n_mimes; i++) { |
| 82 | + if (strcmp(mime_types[i].key, ext) == 0) |
| 83 | + return mime_types[i].val; |
| 84 | + } |
| 85 | + |
| 86 | + return NULL; |
| 87 | +} |
| 88 | +``` |
| 89 | +::: |
| 90 | + |
| 91 | +As we are mapping the MIME type based on the file extension, a function for finding the file extension is added in `xps_utility`. Add the below given function in utility.c |
| 92 | + |
| 93 | +```c |
| 94 | +const char *get_file_ext(const char *file_path) { |
| 95 | + // Find the last occurrence of dot |
| 96 | + const char *dot = strrchr(file_path, '.'); |
| 97 | + |
| 98 | + // Check if dot is present and it is not the first character |
| 99 | + return dot && dot > strrchr(file_path, '/') ? dot : NULL; |
| 100 | +} |
| 101 | +``` |
| 102 | +
|
| 103 | +Also declare the newly created function in utility.h |
| 104 | +
|
| 105 | +## `xps_file` Module |
| 106 | +
|
| 107 | +### `xps_file.h` |
| 108 | +
|
| 109 | +The code below has the contents of the header file for `xps_file`. Have a look at it and make a copy of it in your codebase. |
| 110 | +
|
| 111 | +:::details **expserver/src/disc/xps_file.h** |
| 112 | + |
| 113 | +```c |
| 114 | +#ifndef XPS_FILE_H |
| 115 | +#define XPS_FILE_H |
| 116 | +
|
| 117 | +#include "../xps.h" |
| 118 | +
|
| 119 | +struct xps_file_s { |
| 120 | + xps_core_t *core; |
| 121 | + const char *file_path; |
| 122 | + xps_pipe_source_t *source; |
| 123 | + FILE *file_struct; |
| 124 | + size_t size; |
| 125 | + const char *mime_type; |
| 126 | +}; |
| 127 | +
|
| 128 | +xps_file_t *xps_file_create(xps_core_t *core, const char *file_path, int *error); |
| 129 | +void xps_file_destroy(xps_file_t *file); |
| 130 | +
|
| 131 | +#endif |
| 132 | +``` |
| 133 | +::: |
| 134 | + |
| 135 | + |
| 136 | +A struct `xps_file_s` is introduced to store the information regarding the file. The fields in the struct are briefly described: |
| 137 | + |
| 138 | +**`xps_core_t *core`**: pointer to the core instance |
| 139 | + |
| 140 | +**`const char *file_path`**: used to locate the file on the local disk |
| 141 | + |
| 142 | +**`xps_pipe_source_t *source`**: a pointer to source, which handles reading data from the file and passing it to a pipe |
| 143 | + |
| 144 | +**`FILE *file_struct`**: a pointer representing the opened file, it is used to interact with the file, such as reading, writing, seeking(will be explained soon). |
| 145 | + |
| 146 | +**`size_t size`**: holds the size of the file in bytes |
| 147 | + |
| 148 | +**`const char *mime_type`**: represents the MIME type of the file |
| 149 | + |
| 150 | +### `xps_file.c` |
| 151 | + |
| 152 | +Several file system-related C standard library functions are used to handle file operations such as opening, reading, seeking, and closing files. Let us look in to the file system call that we would be using: |
| 153 | + |
| 154 | +- `fopen()` |
| 155 | + |
| 156 | + ```c |
| 157 | + FILE *fopen(const char *filename, const char *mode); |
| 158 | + ``` |
| 159 | + |
| 160 | + Opens a file specified by filename and returns a pointer to a FILE structure that represents the file stream. If mode given as “rb”, it opens the file in binary read mode. **Returns**: A pointer to a FILE structure if successful, or NULL if the file cannot be opened . |
| 161 | + |
| 162 | +- `fclose()` |
| 163 | + |
| 164 | + ```c |
| 165 | + int fclose(FILE *stream); |
| 166 | + ``` |
| 167 | + |
| 168 | + Closes the file associated with the given FILE stream and releases any resources related to the file. **Returns**: 0 on success, or EOF (End Of File) on error. |
| 169 | + |
| 170 | +- `fseek()` |
| 171 | + |
| 172 | + ```c |
| 173 | + int fseek(FILE *stream, long offset, int whence); |
| 174 | + ``` |
| 175 | + |
| 176 | + Moves the file pointer to a specific location in the file. |
| 177 | + |
| 178 | + - offset: The number of bytes to move the file pointer. |
| 179 | + - whence: Specifies how the offset is interpreted: |
| 180 | + - SEEK_SET: The offset is set relative to the beginning of the file. |
| 181 | + - SEEK_CUR: The offset is added to the current position. |
| 182 | + - SEEK_END: The offset is set relative to the end of the file. |
| 183 | + |
| 184 | + **Returns**: 0 on success, or -1 on error. |
| 185 | + |
| 186 | +- `ftell()` |
| 187 | + |
| 188 | + ```c |
| 189 | + long ftell(FILE *stream); |
| 190 | + ``` |
| 191 | + |
| 192 | + Returns the current position of the file pointer in the file, measured in bytes from the beginning of the file. **Returns**: The current position (in bytes) on success, or -1L on error. |
| 193 | + |
| 194 | +- `fread()` |
| 195 | + |
| 196 | + ```c |
| 197 | + size_t fread(void *ptr, size_t size, size_t count, FILE *stream); |
| 198 | + ``` |
| 199 | + |
| 200 | + Reads data from the file into a buffer. |
| 201 | + |
| 202 | + - ptr: The buffer where the data will be stored. |
| 203 | + - size: The size of each data element. |
| 204 | + - count: The number of elements to read. |
| 205 | + - stream: The file stream to read from. |
| 206 | + |
| 207 | + **Returns**: The number of elements successfully read. If an error occurs, the return value will be less than the requested number of elements. |
| 208 | + |
| 209 | +- `ferror()` |
| 210 | + |
| 211 | + ```c |
| 212 | + int ferror(FILE *stream); |
| 213 | + ``` |
| 214 | + |
| 215 | + Checks whether an error occurred while performing file I/O operations on the given file stream. **Returns**: 0 if no error has occurred, or a non-zero value if an error has occurred. |
| 216 | + |
| 217 | +- `feof()` |
| 218 | + |
| 219 | + ```c |
| 220 | + int feof(FILE *stream); |
| 221 | + ``` |
| 222 | + |
| 223 | + Checks whether the end of the file has been reached. **Returns**: A non-zero value if the end of the file has been reached, or 0 otherwise. |
| 224 | + |
| 225 | + |
| 226 | +`errno` determines the specific error when an operation fails. In case of `fopen()`, `EACCES` indicates a permission denied error and `ENOENT` indicates the specified file or directory doesn't exist. |
| 227 | + |
| 228 | +The functions in xps_file.c are given below: |
| 229 | + |
| 230 | +1. **`xps_file_create()`** |
| 231 | + |
| 232 | + Opens the file using `fopen()`, calculates the size by seeking to the end and then getting the current position, creates a `xps_file_s` struct instance and initialize it. |
| 233 | + |
| 234 | + ```c |
| 235 | + xps_file_t *xps_file_create(xps_core_t *core, const char *file_path, int *error) { |
| 236 | + /*assert*/ |
| 237 | + |
| 238 | + *error = E_FAIL; |
| 239 | + |
| 240 | + // Opening file |
| 241 | + FILE *file_struct = fopen(file_path, "rb"); |
| 242 | + /*handle EACCES,ENOENT or any other error*/ |
| 243 | + if (file_struct == NULL) { |
| 244 | + /*logs EACCES,ENOENT or any other error*/ |
| 245 | + return NULL; |
| 246 | + } |
| 247 | + |
| 248 | + // Getting size of file |
| 249 | + |
| 250 | + // Seeking to end |
| 251 | + if (/*seek end of file using fseek()*/ != 0) { |
| 252 | + /*logs error*/ |
| 253 | + /*close file_struct*/ |
| 254 | + return NULL; |
| 255 | + } |
| 256 | + |
| 257 | + // Getting curr position which is the size |
| 258 | + long temp_size = /*get current position using ftell()*/ |
| 259 | + if (temp_size < 0) { |
| 260 | + /*logs error*/ |
| 261 | + /*close file_struct*/ |
| 262 | + return NULL; |
| 263 | + } |
| 264 | + |
| 265 | + // Seek back to start |
| 266 | + if (/*seek start of file using fseek()*/ != 0) { |
| 267 | + /*logs error*/ |
| 268 | + /*close file_struct*/ |
| 269 | + return NULL; |
| 270 | + } |
| 271 | + |
| 272 | + const char *mime_type = /*get mime type*/ |
| 273 | + |
| 274 | + /*Alloc memory for instance of xps_file_t*/ |
| 275 | + xps_pipe_source_t *source = |
| 276 | + xps_pipe_source_create((void *)file, file_source_handler, file_source_close_handler); |
| 277 | + /*if source is null, close file_struct and return*/ |
| 278 | + |
| 279 | + // Init values |
| 280 | + source->ready = true; |
| 281 | + /*initialise the fields of file instance*/ |
| 282 | + |
| 283 | + *error = OK; |
| 284 | + |
| 285 | + logger(LOG_DEBUG, "xps_file_create()", "created file"); |
| 286 | + |
| 287 | + return file; |
| 288 | + } |
| 289 | + ``` |
| 290 | + |
| 291 | +2. **`xps_file_destroy()`** |
| 292 | + |
| 293 | + Closes the file, destroys the associated pipe source, and frees the memory allocated for the file structure. |
| 294 | + |
| 295 | + ```c |
| 296 | + void xps_file_destroy(xps_file_t *file) { |
| 297 | + /*assert*/ |
| 298 | + |
| 299 | + /*fill as mentioned above*/ |
| 300 | + |
| 301 | + logger(LOG_DEBUG, "xps_file_destroy()", "destroyed file"); |
| 302 | + } |
| 303 | + |
| 304 | + ``` |
| 305 | + |
| 306 | +3. **`file_source_handler()`** |
| 307 | + |
| 308 | + It reads from the file into the buffer and upon successful reading, writes it to pipe. |
| 309 | + |
| 310 | + ```c |
| 311 | + void file_source_handler(void *ptr) { |
| 312 | + /*assert*/ |
| 313 | + |
| 314 | + xps_pipe_source_t *source = ptr; |
| 315 | + /*get file from source ptr*/ |
| 316 | + |
| 317 | + /*create buffer and handle any error*/ |
| 318 | + |
| 319 | + // Read from file |
| 320 | + size_t read_n = fread(buff->data, 1, buff->size, file->file_struct); |
| 321 | + buff->len = read_n; |
| 322 | + |
| 323 | + // Checking for read errors |
| 324 | + if (ferror(file->file_struct)) { |
| 325 | + /*destroy buff, file and return*/ |
| 326 | + } |
| 327 | + |
| 328 | + // If end of file reached |
| 329 | + if (read_n == 0 && feof(file->file_struct)) { |
| 330 | + /*destroy buff, file and return*/ |
| 331 | + } |
| 332 | + |
| 333 | + /*Write to pipe form buff*/ |
| 334 | + /*destroy buff*/ |
| 335 | + } |
| 336 | + ``` |
| 337 | + |
| 338 | +4. **`file_source_close_handler()`** |
| 339 | + |
| 340 | + This function is called when the file source is closed, triggering the destruction of the file object. |
| 341 | + |
| 342 | + ```c |
| 343 | + void file_source_close_handler(void *ptr) { |
| 344 | + /*assert*/ |
| 345 | + xps_pipe_source_t *source = ptr; |
| 346 | + /*get file from source ptr*/ |
| 347 | + /*destroy file*/ |
| 348 | + } |
| 349 | + ``` |
| 350 | + |
| 351 | + |
| 352 | +Update the xps.h by adding two newly created structs xps_file_s and |
| 353 | + |
| 354 | +```c |
| 355 | +struct xps_keyval_s { |
| 356 | + char *key; |
| 357 | + char *val; |
| 358 | +}; |
| 359 | +``` |
| 360 | + |
| 361 | +The second struct is for storing the key-value pairs of mime extensions and mime types(`mime_types` lookup table). Create the type defs for the above struct. Also include the header files of both `xps_mime` and `xps_file` modules. |
| 362 | + |
| 363 | +## `xps_listener` Module - Modifications |
| 364 | + |
| 365 | +### `listener.c` |
| 366 | + |
| 367 | +As we have to serve file for the incoming connections on port 8002, the `listener_connection_handler` function has to be modified. The `xps_file_create` function is invoked for opening the file and creating a `xps_file_s` instance for it. The path of file to be opened is specified in the argument. Then pipe is created with the source that is attached to the file instead of that attached to connection. |
| 368 | + |
| 369 | +```c |
| 370 | +if (listener->port == 8001) { |
| 371 | + .... |
| 372 | + } else if (listener->port == 8002) {// [!code ++] |
| 373 | + int error;// [!code ++] |
| 374 | + xps_file_t *file = xps_file_create(listener->core, "../public/sample.txt", &error);// [!code ++] |
| 375 | + xps_pipe_create(listener->core, DEFAULT_PIPE_BUFF_THRESH, file->source, client->sink);// [!code ++] |
| 376 | + } else { |
| 377 | + ... |
| 378 | + } |
| 379 | +``` |
| 380 | +
|
| 381 | +## Milestone #1 |
| 382 | +
|
| 383 | +Update the `build.sh` to include the newly created modules. |
| 384 | +
|
| 385 | +In the expserver folder, create a new folder public. Inside this create a file sample.txt with any content. This path is given while calling the `xps_file_create` function. |
| 386 | +
|
| 387 | +Now start the server as mentioned in previous stages. Connect a client on port 8002 using `netcat localhost 8002`. Verify that the contents in file sample.txt is received by the client in terminal. Thus we have successfully implemented a basic file server which can send files in local disk to the client. |
| 388 | +
|
| 389 | + |
| 390 | +
|
| 391 | +## Conclusion |
| 392 | +
|
| 393 | +Now all the clients connected to port 8002, would be served the specified file. Thus we have implemented a basic file server. The file server is implemented along with the pipe mechanism itself with the only difference being source reading the file attached to its `ptr` field and writing it to pipe. |
0 commit comments