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

a2

parent 5668161e
No related branches found
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