Skip to content
Snippets Groups Projects
Commit 1a4b71e8 authored by luishernandez's avatar luishernandez
Browse files

a2

parent 5668161e
Branches
No related tags found
No related merge requests found
sdbsc.c 0 → 100644
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h> // for open()
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdbool.h>
#include <errno.h>
#define MIN_STD_ID 1
#define MAX_STD_ID 100000
#define MIN_STD_GPA 0
#define MAX_STD_GPA 400
/* Filenames for the active database and temporary (compressed) db */
#define DB_FILE "student.db"
#define TMP_DB_FILE ".tmp_student.db"
/* Our student record is exactly 64 bytes.
We store an integer id (4 bytes), an integer gpa (4 bytes) and two fixed-length strings.
Using 28 bytes each for first and last names gives:
4 + 4 + 28 + 28 = 64 bytes.
*/
typedef struct{
int id; // 4 bytes
int gpa; // 4 bytes; store gpa as an integer (e.g. 345 means 3.45)
char fname[28]; // 28 bytes
char lname[28]; // 28 bytes
} student_t;
#define RECORD_SIZE (sizeof(student_t))
#define DB_SIZE (100000 * RECORD_SIZE)
/* Exit codes: note that all error cases exit with status 1 */
#define EXIT_OK 0
#define EXIT_FAIL_ARGS 1
#define EXIT_FAIL_DB 1
/* Function return codes */
#define NO_ERROR 0
#define ERR_DB_FILE -1
#define ERR_DB_OP -2
#define SRCH_NOT_FOUND -3
#define NOT_IMPLEMENTED_YET -4
/* Messages (exact text as expected by the tests) */
#define M_ERR_DB_OPEN "Error opening database file.\n"
#define M_ERR_DB_READ "Error reading from database file.\n"
#define M_ERR_DB_WRITE "Error writing to database file.\n"
#define M_ERR_DB_ADD_DUP "Cant add student with ID=%d, already exists in db.\n"
#define M_STD_ADDED "Student %d added to database.\n"
#define M_STD_DEL_MSG "Student %d was deleted from database.\n"
#define M_STD_NOT_FND_MSG "Student %d was not found in database.\n"
#define M_ERR_STD_RNG "Error: Student ID or GPA out of range.\n"
#define M_ERR_STD_PRINT "Error: Cannot print invalid student.\n"
#define M_DB_EMPTY "Database contains no student records.\n"
#define M_DB_RECORD_CNT "Database contains %d student record(s).\n"
#define M_DB_ZERO_OK "Database file zeroed.\n"
#define M_DB_COMPRESSED_OK "Database successfully compressed!\n"
/* Strings used for printing a student record.
Note: the header and record formatting must match the test expectations. */
#define STUDENT_PRINT_HDR_STRING "ID FIRST_NAME LAST_NAME GPA\n"
#define STUDENT_PRINT_FMT_STRING "%d %s %s %.2f\n"
int open_db(char *dbFile, bool should_truncate);
int get_student(int fd, int id, student_t *s);
int add_student(int fd, int id, char *fname, char *lname, int gpa);
int del_student(int fd, int id);
int count_db_records(int fd);
int print_db(int fd);
void print_student(student_t *s);
int compress_db(int fd);
int validate_range(int id, int gpa);
void usage(char *exename);
/*
* open_db
* Opens (or creates) the database file with read/write access.
* If should_truncate is true the file is re-created (zeroed) and then pre-allocated to DB_SIZE bytes.
* Otherwise, if the file is new (or not already DB_SIZE bytes long) it is extended (with zeros) to DB_SIZE bytes.
*
* Returns the file descriptor on success, or ERR_DB_FILE on failure.
* On error prints M_ERR_DB_OPEN.
*/
int open_db(char *dbFile, bool should_truncate)
{
// Set permissions: rw-rw----
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
// Open for read/write and create if necessary.
int flags = O_RDWR | O_CREAT;
if (should_truncate)
flags |= O_TRUNC;
int fd = open(dbFile, flags, mode);
if (fd == -1)
{
printf(M_ERR_DB_OPEN);
return ERR_DB_FILE;
}
// If the file was just created or we are truncating, preallocate to DB_SIZE bytes.
struct stat st;
if (fstat(fd, &st) == -1)
{
printf(M_ERR_DB_READ);
close(fd);
return ERR_DB_FILE;
}
if (st.st_size != DB_SIZE)
{
if (ftruncate(fd, DB_SIZE) == -1)
{
printf(M_ERR_DB_WRITE);
close(fd);
return ERR_DB_FILE;
}
}
return fd;
}
/*
* get_student
* Reads the student record for the given id.
* If the record is empty (all zeros) returns SRCH_NOT_FOUND.
*
* Returns NO_ERROR on success, ERR_DB_FILE on I/O error, or SRCH_NOT_FOUND if not found.
*/
int get_student(int fd, int id, student_t *s)
{
off_t offset = (id - MIN_STD_ID) * RECORD_SIZE;
if (lseek(fd, offset, SEEK_SET) == -1)
return ERR_DB_FILE;
ssize_t bytesRead = read(fd, s, RECORD_SIZE);
if (bytesRead != RECORD_SIZE)
return ERR_DB_FILE; // unexpected read error
student_t empty;
memset(&empty, 0, RECORD_SIZE);
if (memcmp(s, &empty, RECORD_SIZE) == 0)
return SRCH_NOT_FOUND;
return NO_ERROR;
}
/*
* add_student
* Adds a student to the database at the slot corresponding to its id.
* If the slot is already occupied, prints an error and returns ERR_DB_OP.
*
* Returns NO_ERROR on success, ERR_DB_FILE on I/O error, or ERR_DB_OP if student exists.
*/
int add_student(int fd, int id, char *fname, char *lname, int gpa)
{
off_t offset = (id - MIN_STD_ID) * RECORD_SIZE;
student_t existing;
/* Seek to the proper offset in the file */
if (lseek(fd, offset, SEEK_SET) == -1) {
printf(M_ERR_DB_READ);
return ERR_DB_FILE;
}
/* Read the existing record */
if (read(fd, &existing, RECORD_SIZE) != RECORD_SIZE) {
printf(M_ERR_DB_READ);
return ERR_DB_FILE;
}
/* Create an empty record to compare against */
student_t empty;
memset(&empty, 0, RECORD_SIZE);
/* If the record is not empty, a student already exists */
if (memcmp(&existing, &empty, RECORD_SIZE) != 0) {
printf(M_ERR_DB_ADD_DUP, id);
return ERR_DB_OP;
}
/* Prepare the new student record */
student_t new_student;
memset(&new_student, 0, RECORD_SIZE);
new_student.id = id;
new_student.gpa = gpa;
/* Copy the names safely into the record */
strncpy(new_student.fname, fname, sizeof(new_student.fname) - 1);
new_student.fname[sizeof(new_student.fname) - 1] = '\0';
strncpy(new_student.lname, lname, sizeof(new_student.lname) - 1);
new_student.lname[sizeof(new_student.lname) - 1] = '\0';
/* Seek back to the record location and write the new record */
if (lseek(fd, offset, SEEK_SET) == -1) {
printf(M_ERR_DB_WRITE);
return ERR_DB_FILE;
}
if (write(fd, &new_student, RECORD_SIZE) != RECORD_SIZE) {
printf(M_ERR_DB_WRITE);
return ERR_DB_FILE;
}
/* Success */
printf(M_STD_ADDED, id);
return NO_ERROR;
}
/*
* del_student
* Deletes the student with the given id by writing an empty record (all zeros) in its place.
* If the student does not exist, prints an error.
*
* Returns NO_ERROR on success, ERR_DB_FILE on I/O error, or ERR_DB_OP if student not found.
*/
int del_student(int fd, int id)
{
student_t student;
int rc = get_student(fd, id, &student);
if (rc == SRCH_NOT_FOUND)
{
printf(M_STD_NOT_FND_MSG, id);
return ERR_DB_OP;
}
else if (rc != NO_ERROR)
{
printf(M_ERR_DB_READ);
return ERR_DB_FILE;
}
student_t empty;
memset(&empty, 0, RECORD_SIZE);
off_t offset = (id - MIN_STD_ID) * RECORD_SIZE;
if (lseek(fd, offset, SEEK_SET) == -1)
{
printf(M_ERR_DB_WRITE);
return ERR_DB_FILE;
}
if (write(fd, &empty, RECORD_SIZE) != RECORD_SIZE)
{
printf(M_ERR_DB_WRITE);
return ERR_DB_FILE;
}
printf(M_STD_DEL_MSG, id);
return NO_ERROR;
}
/*
* count_db_records
* Scans through the entire database file (all 100000 records) and counts the number of valid (non‑empty) records.
*
* If count is zero, prints M_DB_EMPTY; otherwise prints M_DB_RECORD_CNT with the count.
*
* Returns the count on success or ERR_DB_FILE on error.
*/
int count_db_records(int fd)
{
if (lseek(fd, 0, SEEK_SET) == -1)
{
printf(M_ERR_DB_READ);
return ERR_DB_FILE;
}
int count = 0;
student_t student;
student_t empty;
memset(&empty, 0, RECORD_SIZE);
ssize_t bytesRead;
for (int i = 0; i < 100000; i++) {
bytesRead = read(fd, &student, RECORD_SIZE);
if (bytesRead != RECORD_SIZE)
{
printf(M_ERR_DB_READ);
return ERR_DB_FILE;
}
if (memcmp(&student, &empty, RECORD_SIZE) != 0)
count++;
}
if (count == 0)
printf(M_DB_EMPTY);
else
printf(M_DB_RECORD_CNT, count);
return count;
}
/*
* print_db
* Scans the entire database file and prints all valid student records.
* For the first valid record found, prints the header first.
*
* Returns NO_ERROR on success, or ERR_DB_FILE on error.
*/
int print_db(int fd)
{
if (lseek(fd, 0, SEEK_SET) == -1)
{
printf(M_ERR_DB_READ);
return ERR_DB_FILE;
}
student_t student;
student_t empty;
memset(&empty, 0, RECORD_SIZE);
bool headerPrinted = false;
bool anyRecord = false;
ssize_t bytesRead;
for (int i = 0; i < 100000; i++) {
bytesRead = read(fd, &student, RECORD_SIZE);
if (bytesRead != RECORD_SIZE)
{
printf(M_ERR_DB_READ);
return ERR_DB_FILE;
}
if (memcmp(&student, &empty, RECORD_SIZE) != 0)
{
if (!headerPrinted)
{
printf(STUDENT_PRINT_HDR_STRING);
headerPrinted = true;
}
float realGpa = student.gpa / 100.0;
printf(STUDENT_PRINT_FMT_STRING, student.id, student.fname, student.lname, realGpa);
anyRecord = true;
}
}
if (!anyRecord)
printf(M_DB_EMPTY);
return NO_ERROR;
}
/*
* print_student
* If the provided student pointer is valid (non-NULL and id nonzero), prints the header then the record.
* Otherwise prints M_ERR_STD_PRINT.
*/
void print_student(student_t *s)
{
if (s == NULL || s->id == 0)
{
printf(M_ERR_STD_PRINT);
return;
}
printf(STUDENT_PRINT_HDR_STRING);
float realGpa = s->gpa / 100.0;
printf(STUDENT_PRINT_FMT_STRING, s->id, s->fname, s->lname, realGpa);
}
/*
* compress_db
* (Extra credit) Compresses the database file by copying only valid records into a temporary file,
* then renames that file to be the active database.
*
* Returns the file descriptor of the new database on success, or ERR_DB_FILE on failure.
*/
int compress_db(int fd)
{
// Open temporary file.
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
int tmp_fd = open(TMP_DB_FILE, O_RDWR | O_CREAT | O_TRUNC, mode);
if (tmp_fd == -1)
{
printf(M_ERR_DB_OPEN);
return ERR_DB_FILE;
}
if (lseek(fd, 0, SEEK_SET) == -1)
{
printf(M_ERR_DB_READ);
close(tmp_fd);
return ERR_DB_FILE;
}
student_t student;
student_t empty;
memset(&empty, 0, RECORD_SIZE);
ssize_t bytesRead;
// Copy only valid records.
for (int i = 0; i < 100000; i++) {
bytesRead = read(fd, &student, RECORD_SIZE);
if (bytesRead != RECORD_SIZE)
{
printf(M_ERR_DB_READ);
close(tmp_fd);
return ERR_DB_FILE;
}
if (memcmp(&student, &empty, RECORD_SIZE) != 0)
{
if (write(tmp_fd, &student, RECORD_SIZE) != RECORD_SIZE)
{
printf(M_ERR_DB_WRITE);
close(tmp_fd);
return ERR_DB_FILE;
}
}
}
close(fd);
close(tmp_fd);
// Rename the temporary file to the active database file.
if (rename(TMP_DB_FILE, DB_FILE) == -1)
{
printf(M_ERR_DB_WRITE);
return ERR_DB_FILE;
}
int new_fd = open_db(DB_FILE, false);
if (new_fd < 0)
{
printf(M_ERR_DB_OPEN);
return ERR_DB_FILE;
}
printf(M_DB_COMPRESSED_OK);
return new_fd;
}
/*
* validate_range
* Validates that the provided id and gpa are within the allowed ranges.
*
* Returns NO_ERROR if valid, or EXIT_FAIL_ARGS otherwise.
*/
int validate_range(int id, int gpa)
{
if ((id < MIN_STD_ID) || (id > MAX_STD_ID))
return EXIT_FAIL_ARGS;
if ((gpa < MIN_STD_GPA) || (gpa > MAX_STD_GPA))
return EXIT_FAIL_ARGS;
return NO_ERROR;
}
/*
* usage
* Prints the proper usage for this program.
*/
void usage(char *exename)
{
printf("usage: %s -[h|a|c|d|f|p|x|z] options. Where:\n", exename);
printf("\t-h: prints help\n");
printf("\t-a id first_name last_name gpa(as 3 digit int): adds a student\n");
printf("\t-c: counts the records in the database\n");
printf("\t-d id: deletes a student\n");
printf("\t-f id: finds and prints a student in the database\n");
printf("\t-p: prints all records in the student database\n");
printf("\t-x: compress the database file [EXTRA CREDIT]\n");
printf("\t-z: zero db file (remove all records)\n");
}
/*============================================================*/
/* MAIN */
/*============================================================*/
int main(int argc, char *argv[])
{
char opt;
int fd;
int rc;
int exit_code = EXIT_OK;
int id, gpa;
student_t student = {0};
if (argc < 2 || argv[1][0] != '-')
{
usage(argv[0]);
exit(EXIT_FAIL_ARGS);
}
opt = argv[1][1];
if (opt == 'h')
{
usage(argv[0]);
exit(EXIT_OK);
}
/* For most options open the db without truncation.
(For -z we will reopen with truncation.) */
fd = open_db(DB_FILE, false);
if (fd < 0)
exit(EXIT_FAIL_DB);
switch(opt)
{
case 'a':
// Expect: prog -a id first_name last_name gpa
if (argc != 6)
{
usage(argv[0]);
exit_code = EXIT_FAIL_ARGS;
break;
}
id = atoi(argv[2]);
gpa = atoi(argv[5]);
if (validate_range(id, gpa) != NO_ERROR)
{
printf(M_ERR_STD_RNG);
exit_code = EXIT_FAIL_ARGS;
break;
}
rc = add_student(fd, id, argv[3], argv[4], gpa);
if (rc < 0)
exit_code = EXIT_FAIL_DB;
break;
case 'c':
rc = count_db_records(fd);
if (rc < 0)
exit_code = EXIT_FAIL_DB;
break;
case 'd':
if (argc != 3)
{
usage(argv[0]);
exit_code = EXIT_FAIL_ARGS;
break;
}
id = atoi(argv[2]);
rc = del_student(fd, id);
if (rc < 0)
exit_code = EXIT_FAIL_DB;
break;
case 'f':
if (argc != 3)
{
usage(argv[0]);
exit_code = EXIT_FAIL_ARGS;
break;
}
id = atoi(argv[2]);
rc = get_student(fd, id, &student);
if (rc == NO_ERROR)
print_student(&student);
else if (rc == SRCH_NOT_FOUND)
{
printf(M_STD_NOT_FND_MSG, id);
exit_code = EXIT_FAIL_DB;
}
else
{
printf(M_ERR_DB_READ);
exit_code = EXIT_FAIL_DB;
}
break;
case 'p':
rc = print_db(fd);
if (rc < 0)
exit_code = EXIT_FAIL_DB;
break;
case 'x':
// compress the database file (extra credit)
fd = compress_db(fd);
if (fd < 0)
exit_code = EXIT_FAIL_DB;
break;
case 'z':
// Zero the db: close and reopen with truncation.
close(fd);
fd = open_db(DB_FILE, true);
if (fd < 0)
{
exit_code = EXIT_FAIL_DB;
break;
}
printf(M_DB_ZERO_OK);
exit_code = EXIT_OK;
break;
default:
usage(argv[0]);
exit_code = EXIT_FAIL_ARGS;
}
close(fd);
exit(exit_code);
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment