/*
* Copyright ©2026 Naomi Alterman. All rights reserved. Permission is
* hereby granted to students registered for University of Washington
* CSE 333 for use solely during Spring Quarter 2026 for purposes of
* the course. No other use, copying, distribution, or modification
* is permitted without prior written consent. Copyrights for
* third-party components of this work must be honored. Instructors
* interested in reusing these course materials should contact the
* author.
*/
#include <dirent.h> // for directory ops
#include <stdio.h> // for printf, snprintf
#include <stdlib.h> // for EXIT_SUCCESS, malloc, free
#include <string.h> // for strlen, strcmp, strrchr
#include <stdbool.h> // for bool
#include "ro_file.h"
#define BUFFER_SIZE 256
/*** HELPER FUNCTION DECLARATIONS ******************************************/
// Returns whether or not a filename ends in ".txt".
bool IsTxtFile(char* filename);
// Concatenate the directory and file names into a full path. The caller is
// responsible for freeing the allocated string. Exits if an error occurs.
char* Concatenate(char* dirname, char* filename);
// Print the content of the file to stdout. Returns false if an error occurs.
bool ReadAndPrintFile(char* fullpath);
/*
* This program:
* - Accepts a directory name as a command-line argument.
* - Scans through the directory looking for all files whose names end with
* the four characters ".txt".
* - For every such file found, read the contents of those files using the
* ro_file module and then write them without adding any additional
* characters or formatting to stdout using cstdlib.
*/
int main(int argc, char** argv) {
// Make sure that the user passed us a directory that we can open.
if (argc != 2) {
fprintf(stderr, "Usage: ./ex4 directory_name\n");
return EXIT_FAILURE;
}
DIR* dir_p = opendir(argv[1]);
if (dir_p == NULL) {
perror("Open directory failed");
return EXIT_FAILURE;
}
struct dirent* next_entry;
// Iterate through, reading filenames.
for (next_entry = readdir(dir_p);
next_entry != NULL;
next_entry = readdir(dir_p)) {
char* entry_name = next_entry->d_name;
// Only read if the filename ends in ".txt".
if (IsTxtFile(entry_name)) {
char* fullpath = Concatenate(argv[1], entry_name);
if (!ReadAndPrintFile(fullpath)) {
free(fullpath);
closedir(dir_p);
return EXIT_FAILURE;
}
free(fullpath);
}
}
// Clean up and exit.
closedir(dir_p);
return EXIT_SUCCESS;
}
/*** HELPER FUNCTION DEFINITIONS *******************************************/
bool IsTxtFile(char* filename) {
char* dot = strrchr(filename, '.');
return dot && !strcmp(dot, ".txt");
}
char* Concatenate(char* dirname, char* filename) {
bool has_trailing_slash = dirname[strlen(dirname) - 1] == '/';
// Concatenate directory and file name.
size_t dlen = strlen(dirname);
size_t flen = strlen(filename);
// Malloc space for full path name:
// dlen + strlen("/") + flen + strlen('\0') = dlen + flen + 2
int size_to_malloc = has_trailing_slash ? dlen + flen + 1 : dlen + flen + 2;
char* fullpath = (char*) malloc(sizeof(char) * (size_to_malloc));
if (fullpath == NULL) {
fprintf(stderr, "Error on malloc.\n");
exit(EXIT_FAILURE);
}
if (has_trailing_slash) {
snprintf(fullpath, size_to_malloc, "%s%s", dirname, filename);
} else {
snprintf(fullpath, size_to_malloc, "%s/%s", dirname, filename);
}
return fullpath;
}
bool ReadAndPrintFile(char* fullpath) {
char buf[BUFFER_SIZE];
ssize_t res;
// Open up the file.
RO_FILE* file = ro_open(fullpath);
if (file == NULL) {
perror("ro_open failed");
return false;
}
// Loop through, reading the file BUFFER_SIZE byte chunks at a time.
for (res = ro_read(buf, BUFFER_SIZE, file);
res > 0; // no error and not EOF
res = ro_read(buf, BUFFER_SIZE, file)) {
// Print out what we read.
if (fwrite(buf, 1, res, stdout) != res) {
perror("fwrite failed");
ro_close(file);
return false;
}
}
// Test to see if we encountered an error while reading.
if (res < 0) {
perror("ro_read failed");
ro_close(file);
return false;
}
// Close file.
if (ro_close(file) != 0) {
perror("ro_closed failed");
return false;
}
return true;
ro_file.c
/*
* Copyright ©2023 Justin Hsia. All rights reserved. Permission is
* hereby granted to students registered for University of Washington
* CSE 333 for use solely during Winter Quarter 2023 for purposes of
* the course. No other use, copying, distribution, or modification
* is permitted without prior written consent. Copyrights for
* third-party components of this work must be honored. Instructors
* interested in reusing these course materials should contact the
* author.
*/
#include "ro_file.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
/*** INTERNAL DATA TYPES AND CONSTANTS **************************************/
static const int RO_FILE_BUF_LEN = 512; // do not modify
struct ro_file_st {
int fd; // The file descriptor we are currently managing.
char* buf; // Pointer to our internal buffer for this file.
off_t buf_pos; // The position in the file that the beginning of our
// internal buffer currently corresponds to.
int buf_index; // The index in our internal buffer that corresponds to the
// user's current position in the file.
int buf_end; // How many bytes currently in our internal buffer are
// actually from the file.
// To understand why this is important to track, consider
// the case when the file length is < RO_FILE_BUF_LEN.
};
/*** STATIC HELPER FUNCTION DECLARATIONS ************************************/
// Copy up to 'amount' bytes from the RO_FILE's internal buffer to 'out'.
// Returns the number of bytes copied.
static size_t flush_buffer(RO_FILE* file, char* out, int amount);
// Fill in the RO_FILE's internal buffer with as many bytes as can be read
// from the file.
// Returns the number of bytes copied into the buffer, or -1 on any error.
static ssize_t fill_buffer(RO_FILE* file);
/*** FUNCTION DEFINITIONS ***************************************************/
RO_FILE* ro_open(char* filename) {
// Allocate a new RO_FILE
RO_FILE* result = (RO_FILE*) malloc(sizeof(RO_FILE));
if (result == NULL) {
return NULL;
}
// Get the file descriptor for the file
int fd = open(filename, O_RDONLY);
if (fd == -1) {
free(result);
return NULL;
}
result->fd = fd;
// Allocate the internal buffer
result->buf = (char*) malloc(sizeof(char) * RO_FILE_BUF_LEN);
if (result->buf == NULL) {
free(result->buf);
free(result);
close(fd);
return NULL;
}
// Initialize the other fields (no reading done yet)
result->buf_pos = 0;
result->buf_index = 0;
result->buf_end = 0;
return result;
}
ssize_t ro_read(char* ptr, size_t len, RO_FILE* file) {
// 1. If we have bytes in our internal buffer, flush as many as we can to
// 'ptr'.
size_t num_copied_out = flush_buffer(file, ptr, len);
while (num_copied_out != len) {
// 2. If we haven't provided the bytes requested, repopulate our buffer
// with bytes from the file.
ssize_t num_filled = fill_buffer(file);
if (num_filled == 0) {
// reached EOF; no more bytes to copy
break;
} else if (num_filled < 0) {
// pass error back to client
return num_filled;
}
// 3. Copy filled bytes into 'ptr'.
num_copied_out += flush_buffer(file, ptr+num_copied_out,
len-num_copied_out);
// 4. Repeat steps 2-3 until request is fulfilled.
}
return num_copied_out;
}
off_t ro_tell(RO_FILE* file) {
if (file == NULL) {
return -1;
}
return file->buf_pos + file->buf_index;
}
int ro_seek(RO_FILE* file, off_t offset, int whence) {
if (file == NULL) {
return 1;
}
// Seek to specified offset from specified whence
off_t res = lseek(file->fd, offset, whence);
if (res == -1) {
return 1;
}
// Update the fields
// Note: this is a naive implementation as we stated that you didn't have to
// determine if the resulting offset is a position in our buffer.
file->buf_pos = res;
file->buf_index = 0;
file->buf_end = 0;
return 0;
}
int ro_close(RO_FILE* file) {
if (file == NULL) {
return -1;
}
// Clean up resources, returns non-zero on error
int res = close(file->fd);
free(file->buf);
free(file);
return res;
}
/*** STATIC HELPER FUNCTION DEFINITIONS *************************************/
size_t flush_buffer(RO_FILE* file, char* out, int amount) {
// Flush bytes from buffer to "out" one byte at a time with
// bytes to flush = min(amount, bytes available in buffer),
// accumulating total number of bytes flushed
int bytes_flushed;
for (bytes_flushed = 0;
bytes_flushed < (file->buf_end - file->buf_index)
&& bytes_flushed < amount;
bytes_flushed++) {
out[bytes_flushed] = file->buf[file->buf_index + bytes_flushed];
}
// Advance index into the buffer by number of bytes flushed
file->buf_index += bytes_flushed;
return bytes_flushed;
}
ssize_t fill_buffer(RO_FILE* file) {
// Point into the file where the buffer left off
file->buf_pos += file->buf_index;
// Reset buffer index and len
file->buf_index = 0;
file->buf_end = 0;
// Accumulate number of bytes buffer
int bytes_buffered = 0;
// POSIX read loop that attempts to buffer RO_FILE_BUF_LEN bytes
while (bytes_buffered < RO_FILE_BUF_LEN) {
ssize_t res = read(file->fd, (file->buf) + bytes_buffered,
RO_FILE_BUF_LEN - bytes_buffered);
// Reached EOF
if (res == 0) {
break;
// Reached error, check for error code
} else if (res < 0) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
// Return -1 on POSIX error
return res;
}
bytes_buffered += res;
}
// Update buffer status
file->buf_end = bytes_buffered;
return bytes_buffered;
}