/* * ================================================================================== * File I/O Header - File Management and Transfer Operations * * ISA Project * Author: Roman Necas (xnecasr00) * Date: 13. October 2025 * * PURPOSE: * This header defines the file management layer for the ICMP covert channel * file transfer application. It provides abstractions for chunk-based file * reading/writing, path validation, and transfer statistics tracking. * * RESPONSIBILITIES: * - File opening and validation (read/write modes) * - Chunk-based file I/O for network transmission * - Fragment tracking for multi-packet transfers * - Path normalization and security validation * - File metadata preservation (permissions, timestamps) * - Transfer statistics and progress tracking * * SECURITY FEATURES: * - Path traversal prevention (../.. attacks) * - Permission validation before file access * - Atomic file operations (temp file + rename) * - Secure file deletion with overwrite * - Basename extraction to prevent path disclosure * * CHUNK-BASED I/O: * Files are read and written in chunks to match network payload size * (MAX_PAYLOAD_SIZE = 1400 bytes). This allows efficient streaming of * large files without loading entire file into memory. * * FRAGMENTATION SUPPORT: * Large files are automatically divided into fragments, each containing * one chunk of data. The file_manager_t structure tracks fragment numbers * for proper reassembly on the receiving end. * ================================================================================== */ #ifndef FILE_IO_H #define FILE_IO_H #include "common.h" /* * ================================================================================== * FILE MANAGER STRUCTURE * ================================================================================== */ /* * file_manager_t - File I/O state management * * This structure encapsulates all state required for chunk-based file operations. * It supports both reading (client mode - sending file) and writing (server mode - * receiving file) with automatic fragment tracking. * * LIFECYCLE: * 1. Allocated by caller (typically on stack or in session structure) * 2. Initialized by file_manager_open_read() or file_manager_open_write() * 3. Used for file_manager_read_chunk() or file_manager_write_chunk() calls * 4. Cleaned up by file_manager_cleanup() (closes file, frees memory) * * USAGE PATTERNS: * * Client mode (reading file to send): * file_manager_t fm; * file_manager_open_read(&fm, "/path/to/file.txt"); * while (!file_manager_is_complete(&fm)) { * uint8_t chunk[MAX_PAYLOAD_SIZE]; * size_t bytes_read; * file_manager_read_chunk(&fm, chunk, MAX_PAYLOAD_SIZE, &bytes_read); * send_data_packet(chunk, bytes_read); * } * file_manager_cleanup(&fm); * * Server mode (writing received file): * file_manager_t fm; * file_manager_open_write(&fm, "received_file.txt"); * file_manager_set_file_size(&fm, expected_size); // From metadata * for each received chunk: * file_manager_write_chunk(&fm, chunk_data, chunk_size); * file_manager_cleanup(&fm); * * THREAD SAFETY: * This structure is NOT thread-safe. Each file transfer should have its own * file_manager_t instance. * * MEMORY MANAGEMENT: * The filename field is dynamically allocated by file_manager_open_*() and * freed by file_manager_cleanup(). Always call cleanup to prevent leaks. */ typedef struct { /* * file_handle - Standard C FILE pointer for I/O operations * * This is the underlying file stream used for all read/write operations. * Opened by fopen() in file_manager_open_read() or file_manager_open_write(). * * Modes: * - Read mode: fopen(filename, "rb") - binary read * - Write mode: fopen(filename, "wb") - binary write (overwrites existing) * * Values: * - Non-NULL: Valid open file * - NULL: No file open or error occurred * * Closed by file_manager_cleanup() using SAFE_CLOSE macro (fclose). */ FILE *file_handle; /* * filename - Full path to the file * * Dynamically allocated string containing the complete path to the file * being read or written. This is used for error messages and logging. * * Allocation: * - Allocated by strdup() in file_manager_open_read() or file_manager_open_write() * - Freed by file_manager_cleanup() using SAFE_FREE macro * * Content: * - Client mode: Path specified by user (can include directories) * - Server mode: Basename only (extracted from metadata, no path components) * * Security note: * Server mode ALWAYS strips directory components to prevent path traversal * attacks (e.g., "../../../etc/passwd" becomes "passwd"). */ char *filename; /* * file_size - Total size of the file in bytes * * Client mode: Determined by stat() or fseek(SEEK_END) during file_manager_open_read() * Server mode: Set by file_manager_set_file_size() from START packet metadata * * Used to: * - Calculate total_fragments (file_size / chunk_size + 1) * - Verify transfer completion (bytes_processed == file_size) * - Display progress percentage * - Pre-allocate space on server (optional) * * Range: 0 to UINT64_MAX (theoretical max ~16 exabytes) * Practical limit: Limited by filesystem and available disk space */ uint64_t file_size; /* * bytes_processed - Number of bytes read or written so far * * Incremented by file_manager_read_chunk() or file_manager_write_chunk() * after each successful operation. * * Used to: * - Track transfer progress: (bytes_processed / file_size) * 100 * - Detect completion: (bytes_processed >= file_size) * - Calculate current file position for seeking * - Verify expected vs actual bytes received (server mode) * * Invariant: bytes_processed <= file_size (violation indicates corruption) */ uint64_t bytes_processed; /* * total_fragments - Total number of fragments for transfer * * Calculated during file_manager_open_read() as: * total_fragments = (file_size + chunk_size - 1) / chunk_size * * This is the ceiling division, ensuring all bytes are covered. * * Examples (chunk_size = 1400): * - 500 byte file: 1 fragment * - 1400 byte file: 1 fragment * - 1401 byte file: 2 fragments * - 1 MB file: 750 fragments * * Used to: * - Populate fragment_total field in IFTP header * - Display progress: "Fragment 500/750" * - Server validation: Verify received fragment numbers are valid */ uint32_t total_fragments; /* * current_fragment - Current fragment being processed (0-based) * * Client mode: Incremented after each file_manager_read_chunk() call * Server mode: Set based on fragment_current field in received DATA packet * * Range: 0 to (total_fragments - 1) * * Used to: * - Populate fragment_current field in IFTP header * - Track read position in file * - Detect duplicate or out-of-order fragments (server mode) * * Invariant: current_fragment < total_fragments (unless complete) */ uint32_t current_fragment; /* * read_mode - File operation mode flag * * Values: * - 1: Read mode (client sending file) * - 0: Write mode (server receiving file) * * Determines: * - Which operations are valid (read vs write) * - How file_size is determined (stat vs metadata) * - Whether fragments are generated (read) or validated (write) * - Error message context * * Set by file_manager_open_read() or file_manager_open_write(). */ int read_mode; /* * file_permissions - File permissions for preservation * * POSIX file mode bits (mode_t from sys/stat.h): * - Owner: read (0400), write (0200), execute (0100) * - Group: read (0040), write (0020), execute (0010) * - Other: read (0004), write (0002), execute (0001) * - Special: setuid (04000), setgid (02000), sticky (01000) * * Client mode: * - Read from source file using stat() in file_manager_open_read() * - Transmitted in START packet metadata (future enhancement) * * Server mode: * - Received from START packet metadata (future enhancement) * - Applied to written file using chmod() in file_manager_cleanup() * * Default: 0644 (rw-r--r--) if not specified * * Security note: Permissions are sanitized to prevent setuid/setgid * on received files. */ mode_t file_permissions; /* * last_modified - Last modification time (Unix timestamp) * * Seconds since Unix epoch (1970-01-01 00:00:00 UTC). * * Client mode: Read from source file using stat() * Server mode: Can be set on received file using utime() (future enhancement) * * Used to preserve file metadata across transfer. */ time_t last_modified; } file_manager_t; /* * ================================================================================== * FILE TRANSFER STATISTICS STRUCTURE * ================================================================================== */ /* * file_transfer_stats_t - File transfer performance metrics * * This structure tracks timing and throughput statistics for a file transfer * session. It complements network_stats_t which tracks packet-level metrics. * * LIFECYCLE: * 1. Initialized by file_transfer_stats_init() (zeros fields, sets start_time) * 2. Updated by file_transfer_stats_update() after each chunk transfer * 3. Finalized by file_transfer_stats_finalize() (sets end_time, calculates rate) * 4. Displayed by file_transfer_stats_print() * * USAGE: * file_transfer_stats_t stats; * file_transfer_stats_init(&stats); * stats.total_bytes = file_size; * // ... transfer chunks ... * file_transfer_stats_update(&stats, chunk_size); * file_transfer_stats_finalize(&stats); * file_transfer_stats_print(&stats); */ typedef struct { /* * total_bytes - Total bytes to transfer * * Set from file_manager_t.file_size at start of transfer. * Used to calculate progress percentage. */ uint64_t total_bytes; /* * bytes_transferred - Bytes successfully transferred * * Incremented by file_transfer_stats_update() after each chunk. * Should equal total_bytes when transfer completes. */ uint64_t bytes_transferred; /* * fragments_sent - Number of fragments sent (client mode) * * Incremented after each DATA packet transmission. * Used to track progress: "Sent fragment 500/750" */ uint32_t fragments_sent; /* * fragments_received - Number of fragments received (server mode) * * Incremented after each DATA packet reception and write. * May be less than expected if transfer is interrupted. */ uint32_t fragments_received; /* * start_time - Transfer start timestamp (Unix time) * * Set by file_transfer_stats_init() using time(NULL). * Used to calculate total transfer duration. */ time_t start_time; /* * end_time - Transfer end timestamp (Unix time) * * Set by file_transfer_stats_finalize() using time(NULL). * Used to calculate total transfer duration. */ time_t end_time; /* * transfer_rate - Transfer rate in bytes per second * * Calculated by file_transfer_stats_finalize() as: * transfer_rate = bytes_transferred / (end_time - start_time) * * Useful for performance analysis and comparison. * Typical values: 10 KB/s to 10 MB/s depending on network conditions. */ double transfer_rate; } file_transfer_stats_t; /* * ================================================================================== * CORE FILE MANAGER FUNCTIONS * ================================================================================== */ /* * file_manager_open_read - Open file for reading in client mode * * Opens the specified file for binary reading and initializes the file_manager_t * structure. This function: * 1. Validates file exists and is readable * 2. Opens file with fopen(filename, "rb") * 3. Determines file size using fseek(SEEK_END) + ftell() * 4. Reads file permissions and modification time using stat() * 5. Calculates total_fragments based on file_size and chunk_size * 6. Initializes tracking fields (bytes_processed=0, current_fragment=0) * * VALIDATION: * - File must exist (checked with access(F_OK)) * - File must be readable (checked with access(R_OK)) * - File must be a regular file (not directory, device, symlink) * - File size must be > 0 (empty files rejected) * * ERROR HANDLING: * - Returns -1 if file doesn't exist, is not readable, or is empty * - Returns -1 if fopen() fails (permissions, too many open files, etc.) * - Sets error context with detailed message * * @param fm: File manager structure to initialize (output parameter) * @param filename: Path to file to read (can be relative or absolute) * * @return: * 0: Success - file opened and ready for reading * -1: Failure - error details in error context * * EXAMPLE: * file_manager_t fm; * if (file_manager_open_read(&fm, "/path/to/file.txt") < 0) { * error_print_last(); * return -1; * } * printf("File size: %lu bytes, %u fragments\n", fm.file_size, fm.total_fragments); */ int file_manager_open_read(file_manager_t *fm, const char *filename); /* * file_manager_open_write - Open file for writing in server mode * * Opens the specified file for binary writing and initializes the file_manager_t * structure. This function: * 1. Extracts basename from filename (security: prevent path traversal) * 2. Validates output path is safe (no ../.. components) * 3. Opens file with fopen(basename, "wb") in current directory * 4. Initializes write mode tracking fields * * SECURITY FEATURES: * - ALWAYS strips directory components from filename * * "../../../etc/passwd" → "passwd" * * "/tmp/evil" → "evil" * * "subdir/file.txt" → "file.txt" * - Validates filename doesn't contain path traversal sequences * - Creates file in current working directory only * - Uses safe permissions (0644) for new files * * OVERWRITE BEHAVIOR: * - Existing files are OVERWRITTEN without warning * - Consider using file_exists() check before calling if needed * * FILE SIZE: * - file_size is initially 0 - must be set by file_manager_set_file_size() * after receiving START packet metadata * * @param fm: File manager structure to initialize (output parameter) * @param filename: Basename of file to write (path components stripped) * * @return: * 0: Success - file opened and ready for writing * -1: Failure - error details in error context * * EXAMPLE (Server): * file_manager_t fm; * // metadata->filename might be "../../../evil" * if (file_manager_open_write(&fm, metadata->filename) < 0) { * error_print_last(); * return -1; * } * // File is safely created as "./evil" in current directory * file_manager_set_file_size(&fm, metadata->file_size); */ int file_manager_open_write(file_manager_t *fm, const char *filename); /* * file_manager_read_chunk - Read next chunk of data from file * * Reads up to chunk_size bytes from the current file position and advances * the internal state (bytes_processed, current_fragment). * * BEHAVIOR: * - Reads from current file position (maintained by FILE* stream) * - Returns actual bytes read (may be less than chunk_size at end of file) * - Increments bytes_processed by bytes read * - Increments current_fragment after each read * - Safe to call repeatedly until file_manager_is_complete() returns true * * END OF FILE: * - Last chunk may be smaller than chunk_size (e.g., 500 bytes instead of 1400) * - bytes_read parameter contains actual bytes read (0 indicates EOF) * - Caller should check bytes_read to determine chunk size * * ERROR HANDLING: * - Returns -1 if read_mode is not set (file opened for write) * - Returns -1 if fread() fails (disk error, file removed, etc.) * - Returns -1 if file_handle is NULL (file not open) * * @param fm: Initialized file manager (must be in read mode) * @param buffer: Buffer to receive chunk data (min MAX_PAYLOAD_SIZE bytes) * @param chunk_size: Maximum bytes to read (typically MAX_PAYLOAD_SIZE = 1400) * @param bytes_read: Output parameter for actual bytes read * * @return: * 0: Success - chunk read successfully * -1: Failure - error details in error context * * EXAMPLE: * uint8_t chunk[MAX_PAYLOAD_SIZE]; * size_t bytes_read; * while (file_manager_read_chunk(&fm, chunk, MAX_PAYLOAD_SIZE, &bytes_read) == 0) { * if (bytes_read == 0) break; // EOF * send_data_packet(chunk, bytes_read); * } */ int file_manager_read_chunk(file_manager_t *fm, void *buffer, size_t chunk_size, size_t *bytes_read); /* * file_manager_write_chunk - Write chunk of data to file * * Writes bytes_to_write bytes from buffer to the current file position and * advances the internal state (bytes_processed, current_fragment). * * BEHAVIOR: * - Writes to current file position (maintained by FILE* stream) * - Writes exactly bytes_to_write bytes (partial writes considered errors) * - Increments bytes_processed by bytes written * - Can be called in any order (out-of-order chunks handled if seeking enabled) * * FILE SIZE VALIDATION: * - Checks that bytes_processed + bytes_to_write <= file_size (if file_size set) * - Prevents writing more data than expected from metadata * - Returns error if write would exceed expected file size * * ERROR HANDLING: * - Returns -1 if read_mode is set (file opened for read) * - Returns -1 if fwrite() fails (disk full, permissions, etc.) * - Returns -1 if file_handle is NULL (file not open) * - Returns -1 if write would exceed expected file_size * * @param fm: Initialized file manager (must be in write mode) * @param buffer: Chunk data to write * @param bytes_to_write: Number of bytes to write * * @return: * 0: Success - chunk written successfully * -1: Failure - error details in error context * * EXAMPLE: * // After receiving DATA packet with chunk * if (file_manager_write_chunk(&fm, chunk_data, chunk_size) < 0) { * error_print_last(); * send_nack_packet(ERR_FILE_ERROR); * return -1; * } */ int file_manager_write_chunk(file_manager_t *fm, const void *buffer, size_t bytes_to_write); /* * file_manager_seek_to_position - Seek to specific byte offset in file * * Moves the file position to the specified byte offset using fseek(). * Used for random-access writes when packets arrive out of order. * * USAGE: * Server mode can use this to handle out-of-order fragment reception: * fragment_offset = fragment_number * chunk_size; * file_manager_seek_to_position(&fm, fragment_offset); * file_manager_write_chunk(&fm, chunk_data, chunk_size); * * LIMITATIONS: * - position must be <= file_size * - Seeking beyond EOF is prevented * * @param fm: Initialized file manager * @param position: Byte offset from start of file (0-based) * * @return: * 0: Success * -1: Failure - invalid position or fseek() error */ int file_manager_seek_to_position(file_manager_t *fm, uint64_t position); /* * file_manager_get_fragment_info - Get current and total fragment numbers * * Returns fragment tracking information for display or packet header population. * * @param fm: File manager instance * @param current: Output parameter for current_fragment (can be NULL) * @param total: Output parameter for total_fragments (can be NULL) * * @return: * 0: Success * -1: Failure - fm is NULL */ int file_manager_get_fragment_info(const file_manager_t *fm, uint32_t *current, uint32_t *total); /* * file_manager_get_file_size - Get total file size * * @param fm: File manager instance * @return: File size in bytes, or 0 if fm is NULL */ uint64_t file_manager_get_file_size(const file_manager_t *fm); /* * file_manager_get_bytes_processed - Get bytes processed so far * * @param fm: File manager instance * @return: Bytes read or written, or 0 if fm is NULL */ uint64_t file_manager_get_bytes_processed(const file_manager_t *fm); /* * file_manager_get_filename - Get filename * * @param fm: File manager instance * @return: Pointer to filename string, or NULL if fm is NULL */ const char *file_manager_get_filename(const file_manager_t *fm); /* * file_manager_is_complete - Check if transfer is complete * * Returns true (1) if bytes_processed >= file_size, false (0) otherwise. * * @param fm: File manager instance * @return: 1 if complete, 0 if not complete or fm is NULL */ int file_manager_is_complete(const file_manager_t *fm); /* * file_manager_set_file_size - Set expected file size (server mode) * * Called after receiving START packet metadata to set the expected file size. * Also calculates total_fragments based on file_size. * * @param fm: File manager instance (must be in write mode) * @param file_size: Expected file size from metadata */ void file_manager_set_file_size(file_manager_t *fm, uint64_t file_size); /* * file_manager_get_basename - Extract basename from path * * Extracts the filename component from a path, removing directory components. * Used to sanitize filenames received from network. * * Examples: * - "/path/to/file.txt" → "file.txt" * - "../../../etc/passwd" → "passwd" * - "file.txt" → "file.txt" * * @param path: Input path (can contain directory components) * @param basename: Output buffer for basename * @param basename_len: Size of output buffer * * @return: * 0: Success * -1: Failure - path is NULL, empty, or basename_len too small */ int file_manager_get_basename(const char *path, char *basename, size_t basename_len); /* * file_manager_cleanup - Close file and free resources * * Releases all resources associated with the file manager: * 1. Closes file using fclose() (SAFE_CLOSE macro) * 2. Frees filename string (SAFE_FREE macro) * 3. Zeros structure fields * * SAFE TO CALL MULTIPLE TIMES: * Uses SAFE_FREE/SAFE_CLOSE which set pointers to NULL after cleanup. * * MUST BE CALLED: * Failure to call this function leaks a FILE* and memory for filename. * * @param fm: File manager to clean up */ void file_manager_cleanup(file_manager_t *fm); /* * ================================================================================== * FILE UTILITY FUNCTIONS * ================================================================================== */ /* * file_exists - Check if file exists * @return: 1 if exists, 0 if not exists or error */ int file_exists(const char *filename); /* * file_is_readable - Check if file is readable * @return: 1 if readable, 0 if not readable or error */ int file_is_readable(const char *filename); /* * file_is_writable - Check if file is writable * @return: 1 if writable, 0 if not writable or error */ int file_is_writable(const char *filename); /* * file_get_size - Get file size using stat() * @return: File size in bytes, or 0 on error */ uint64_t file_get_size(const char *filename); /* * file_get_permissions - Get file permissions * @return: 0 on success, -1 on error */ int file_get_permissions(const char *filename, mode_t *permissions); /* * file_set_permissions - Set file permissions * @return: 0 on success, -1 on error */ int file_set_permissions(const char *filename, mode_t permissions); /* * file_get_modification_time - Get last modification time * @return: Unix timestamp, or 0 on error */ time_t file_get_modification_time(const char *filename); /* * ================================================================================== * SAFE FILE OPERATIONS * ================================================================================== */ /* * file_create_temp - Create temporary file with unique name * * Creates a temporary file using mkstemp() with the provided template. * Template must end with "XXXXXX" which will be replaced with unique suffix. * * @param template_path: Template path (e.g., "file_XXXXXX"), modified in place * @param template_size: Size of template_path buffer * @return: File descriptor of created temp file, or -1 on error */ int file_create_temp(char *template_path, size_t template_size); /* * file_atomic_rename - Atomically rename temporary file to final name * * Uses rename() which is atomic on POSIX systems. If final file exists, * it is atomically replaced. * * @param temp_filename: Temporary file path * @param final_filename: Final file path * @return: 0 on success, -1 on error */ int file_atomic_rename(const char *temp_filename, const char *final_filename); /* * file_secure_delete - Securely delete file with overwrite * * Overwrites file contents with zeros before unlinking to prevent recovery. * Note: May not be effective on SSD/flash storage due to wear leveling. * * @param filename: File to securely delete * @return: 0 on success, -1 on error */ int file_secure_delete(const char *filename); /* * file_create_backup - Create backup copy of file * * Copies filename to filename.bak or filename.bak.N if backup exists. * * @param filename: File to backup * @param backup_filename: Output buffer for backup filename * @param backup_size: Size of backup_filename buffer * @return: 0 on success, -1 on error */ int file_create_backup(const char *filename, char *backup_filename, size_t backup_size); /* * ================================================================================== * PATH MANIPULATION FUNCTIONS * ================================================================================== */ /* * path_normalize - Normalize path by resolving . and .. components * @return: 0 on success, -1 on error */ int path_normalize(const char *input_path, char *output_path, size_t output_size); /* * path_join - Join directory and filename components * @return: 0 on success, -1 on error */ int path_join(const char *dir, const char *filename, char *output_path, size_t output_size); /* * path_get_directory - Extract directory component from filepath * @return: 0 on success, -1 on error */ int path_get_directory(const char *filepath, char *directory, size_t directory_size); /* * path_is_absolute - Check if path is absolute (starts with /) * @return: 1 if absolute, 0 if relative */ int path_is_absolute(const char *path); /* * path_is_safe - Check if path is safe (no .. traversal) * * Validates path doesn't contain: * - ".." components (parent directory traversal) * - Null bytes * - Excessively long components * * @return: 1 if safe, 0 if unsafe */ int path_is_safe(const char *path); /* * ================================================================================== * TRANSFER STATISTICS FUNCTIONS * ================================================================================== */ /* * file_transfer_stats_init - Initialize transfer statistics * Sets start_time to current time, zeros other fields. */ void file_transfer_stats_init(file_transfer_stats_t *stats); /* * file_transfer_stats_update - Update bytes transferred * Increments bytes_transferred and appropriate fragment counter. */ void file_transfer_stats_update(file_transfer_stats_t *stats, uint64_t bytes_transferred); /* * file_transfer_stats_finalize - Finalize statistics * Sets end_time and calculates transfer_rate. */ void file_transfer_stats_finalize(file_transfer_stats_t *stats); /* * file_transfer_stats_print - Print statistics summary * Displays total bytes, duration, transfer rate, etc. */ void file_transfer_stats_print(const file_transfer_stats_t *stats); /* * file_transfer_stats_get_rate - Get transfer rate * @return: Transfer rate in bytes/second */ double file_transfer_stats_get_rate(const file_transfer_stats_t *stats); /* * file_transfer_stats_get_progress - Get progress percentage * @return: Progress as percentage (0.0 to 100.0) */ double file_transfer_stats_get_progress(const file_transfer_stats_t *stats); #endif /* FILE_IO_H */