Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
C
cs283
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Luis Hernandez
cs283
Commits
1a4b71e8
Commit
1a4b71e8
authored
4 months ago
by
luishernandez
Browse files
Options
Downloads
Patches
Plain Diff
a2
parent
5668161e
Branches
Branches containing commit
No related tags found
No related merge requests found
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
sdbsc.c
+573
-0
573 additions, 0 deletions
sdbsc.c
with
573 additions
and
0 deletions
sdbsc.c
0 → 100644
+
573
−
0
View file @
1a4b71e8
#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
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment