CS 3377 Project
unix/CS 3377 Project (1).pdf
CS 3377 Project The goal: create several versions of a process that updates and saves a binary file as a new file.
The Setup This project will be done in 4 parts. To keep them separate, I implemented a factory pattern so that you (and the autograder) can test each copying method separately. It will look like this: FileModifierFactory: creates
1. Part1SimpleFileModifier: fill in during part 1 2. Part2MultiProcessModifier: fill in during part 2 3. Part3ThreadedModifier: fill in during part 3 4. Part4SocketModifier: fill in during part 4
You will be given (and not need to modify):
1. main.cpp. Launches the appropriate test based on the arguments 2. Util.cpp/h. Includes some useful attributes. 3. FileModifierFactory.cpp & .h. These build the proper PartXModifier based on
the argument. 4. PipeCopier.cpp & .h. Helps you with the pipe for part 2.
While each part will be tested separately, you are encouraged to reuse code as much of it will be useful for multiple parts.
The File The file you are to read, modify, and save is a binary file that contains a sales list. A binary file is a non-text file, meaning some things (like numbers) aren’t stored as digits but as the ints/floats you use as variables. The name of the files to read and write will be in Util.h. The file will be structured like this:
Field Size Type Purpose HEADER NumEntries 4 bytes Integer Tells you how many
entries you need to read
ENTRY (repeated NumEntries times)
Date/Time sizeof(time_t) Time Timestamp (# seconds since 1/1/1970)
Item ID Sizeof(int) Integer Item’s code Item Name 50 bytes char* Name of the item
sold
Item Quantity Sizeof(int) Integer Number sold Item Price Sizeof(float) Float Price of the
products
What You’ll Do In each part, the goal is to copy the file, adding two additional sales:
1. The Sobell book: a. Time 1612195200 (2/1/2020 4 PM GMT) b. ID 4636152 c. Name “A Programming Guide to Linux Commands, Editors, and Shell
Programming by Sobell” [warning: this is more than 49 characters, so you have to truncate it—I say 49 because you need a null terminator]
d. Quantity: 70 e. Price: 70.99
2. The Advanced Programming book a. Time 1613412000 (2/15/2020 6 PM GMT) b. ID 6530927 c. Name “Advanced Programming in the UNIX Environment by Stevens and Rago”
[warning: more than 49 characters again] d. Quantity: 68 e. Price: 89.99
Be sure to update the total number of entries to account for these new ones.
Part 1 (Due 3/29) You will read in the file (Util::inputFileName), add the two entries, and save the file (Util::outputFileName) using open(), close(), read(), and write(). You must use the low-level functions we will talk about in APUE chapter 3 (open, close, read, write). Failure to do so will result in 0 points for this part of the project. It is highly recommended that you do the file reading and writing in class(es) outside Part1SimpleFileModifier, as that code will be useful later.
Part 2 (Due 4/12) In this case you will spawn a new process using fork() and exec(), and split the responsibilities like this:
1. The original process will read the file (2nd argument=2) and then write the data over a pipe to the child process.
2. The child process will read the file from the pipe (which will be set to standard input) and write the data to the output file.
3. PipeMaker will take care of the pipe setup for you:
a. Create PipeMaker before the fork. b. In the parent process, call setUpToWrite() to ready this process for writing. You’ll
get back the file descriptor to write to. Write the file data to that file descriptor (hint: it’s just like writing to a file).
c. In the child process, before execing, call setUpToRead() to dup the pipe output to standard input. You can then exec the process (21S_CS3377_Project) with the write option (2nd argument=3), read the data from standard input (just a file descriptor, remember!), and write to the output file.
d. Either the parent or the child process can do the update (but not both, obviously).
When calling exec, use the command 21S_CS3377_Project 2 3. This will trigger main to give you the proper setup for the child process. You will be responsible for spotting the Util::IOType of WRITE (3), and read from standard input rather than the input file.
Part 3 (Due 4/26) In this part you will create a thread and pass the file data from one thread to the other. The threads will be like this:
1. Main thread: read the data, create the thread, and pass the data along 2. Created thread: receive the data and output it to the file
I did all of this inside of Part3ThreadedModifier. Create a mutex and condition for both threads to share, and pass a pointer to the Part3ThreadedModifier object to pthread_create (and read it in the other thread). Then you can use the shared mutex and condition to coordinate passing the data. The easiest way to pass the data is to use a variable inside Part3ThreadedModifier (type EntryInfo). The main thread should lock the mutex before creating the receiving thread (and the receiving thread should attempt to lock the mutex right after it starts up) to ensure the proper ordering. Then do a loop in each thread like this:
Main (sending) thread Receiving thread Wait on shared condition (for writing thread to be ready)
Signal condition to say we’re ready Wait on condition (for an entry to be
ready) Update the variable with the next entry Signal the condition Loop around and wait on the condition again
Retrieve the info, save it for later writing
Loop around and signal the condition again
Once you’ve passed all the entries (5 or 7 depending on where you want to add the new ones), unlock the mutex on both sides.
Part 4 (Due 5/10) Here you will use two processes again, this time with a socket connecting them. A port number to use (12345) is at Util::portNumber.
• Note: if you get an error that the port is already in use, it’s likely because you just ran the project and the operating system hasn’t released the port yet. You can either wait a bit (a few minutes at most) or change the port number (12346, etc.).
When I did this step, I reversed the setup from part 2: the main process here reads from the socket (writing to the output file) and the spawned process writes to the socket (reading from the input file). Again, it is up to you where you want to add the two new entries. When you spawn the 2nd process, use 21S_CS3377_Project 4 3. After the fork, the socket reading process (parent process for me) creates a socket and listens on that socket using the port number above. The socket writing process (child process for me) creates a socket and connects to the listen socket. Depending on the timing of things the listen socket may not be ready the first time; here is code to repeatedly wait for the listen socket to be available: int amountToWait = 1; while ( connect(fileDescriptor, (struct sockaddr*) &serverAddress, sizeof(serverAddress))) { if ( errno != ECONNREFUSED) { // Something unexpected happened throw FileModifyException("Error connecting"); } std::cout << "Not ready to connect yet...\n"; // Exponential backoff sleep(amountToWait); amountToWait = amountToWait * 2; } Once the connection is made (reader gets back a file descriptor from accept() and the writer gets out of the loop above) you can transfer the data. Remember that a socket is just a file descriptor, so your code to write/read from earlier parts will work here, too.
unix/Project part 1.pdf
FileReader.cpp //
#include <fcntl.h> #include <cerrno> #include <unistd.h> #include <cstring> #include "FileReader.h" #include "FileModifyException.h" #include "Util.h"
using namespace std;
char nameFirst[50] = "A Programming Guide to Linux Commands, Editors, a"; char nameSecond[50] = "Advanced Programming in the UNIX Environment by S";
string FileReader::getErrnoString() { return strerror(errno);
}
void FileReader::verifyNumBytes(ssize_t expected, ssize_t actual) { if ( expected != actual) {
throw FileModifyException("Bytes read/written (" + to_string(expected) + ") don't match actual (" + to_string(actual) + ")");
} }
void FileReader::readEntries(std::list<EntryInfo>& entryList, const char* fileName) { int fileDescriptor = open(fileName, O_RDONLY); if ( fileDescriptor < 0) {
close(fileDescriptor); throw FileModifyException("Unable to open output file: " + getErrnoString());
} readEntries(entryList, fileDescriptor); close(fileDescriptor);
}
void FileReader::readEntries(list<EntryInfo> &entryList, int fileDescriptor) { int numItems; size_t amountRead = read(fileDescriptor, &numItems, sizeof(int)); if ( amountRead <= 0) {
close(fileDescriptor); throw FileModifyException("File is empty/error reading");
}
for (int i=0; i < numItems; i++) { EntryInfo incoming; verifyNumBytes(sizeof(time_t), read(fileDescriptor, &incoming.timestamp, sizeof(time_t))); verifyNumBytes(sizeof(int), read(fileDescriptor, &incoming.itemID, sizeof(int))); char* inputName = new char[50]; verifyNumBytes(50, read(fileDescriptor, inputName, 50 * sizeof(char)));
incoming.itemName = inputName; verifyNumBytes(sizeof(int), read(fileDescriptor, &incoming.quantity, sizeof(int))); verifyNumBytes(sizeof(float), read(fileDescriptor, &incoming.price, sizeof(float))); entryList.push_back(incoming);
} }
void FileReader::addMissingEntries(list<EntryInfo> & entries) { EntryInfo additionalFirst; additionalFirst.timestamp = 1612195200; // (2/1/2020 4 PM GMT) additionalFirst.itemID = 4636152; additionalFirst.itemName = nameFirst; additionalFirst.quantity = 70; additionalFirst.price = 70.99; entries.push_back(additionalFirst);
EntryInfo additionalSecond; additionalSecond.timestamp = 1613412000; //(2/15/2020 6 PM GMT) additionalSecond.itemID = 6530927; additionalSecond.itemName = nameSecond; additionalSecond.quantity = 68; additionalSecond.price = 89.99; entries.push_back(additionalSecond);
}
FileReader.h
//
#ifndef INC_21S_CS3377_PROJECT_FILEREADER_H #define INC_21S_CS3377_PROJECT_FILEREADER_H
#include <list> #include <string> #include "Util.h"
class FileReader { public:
void readEntries(std::list<EntryInfo>& entryList, const char* fileName); void readEntries(std::list<EntryInfo>& entryList, int fileDescriptor); static void addMissingEntries(std::list<EntryInfo>& entries);
private: static std::string getErrnoString(); static void verifyNumBytes(ssize_t expected, ssize_t actual);
};
#endif //INC_21S_CS3377_PROJECT_FILEREADER_H
FileWriter.h
//
#ifndef INC_21S_CS3377_PROJECT_FILEWRITER_H #define INC_21S_CS3377_PROJECT_FILEWRITER_H
#include <list> #include <string> #include "Util.h"
class FileWriter { public:
void addEntry(const EntryInfo& entryInfo); void writeFile(const char* fileToWrite); void writeFile(int fileDescriptor);
private: std::string getErrnoString();
std::list<EntryInfo> entries; };
#endif //INC_21S_CS3377_PROJECT_FILEWRITER_H
Part1SimpleFileModifier.cpp
/////
#include "Part1SimpleFileModifier.h" #include "FileModifyException.h" #include "FileReader.h" #include "FileWriter.h"
using namespace std;
void Part1SimpleFileModifier::modifyAndCopyFile(const char *sourceFile, const char *destFile) { FileReader fileReader; list<EntryInfo> entries; fileReader.readEntries(entries, sourceFile); fileReader.addMissingEntries(entries);
FileWriter fileWriter; for ( EntryInfo info : entries) {
fileWriter.addEntry(info); }
fileWriter.writeFile(destFile);
Part1SimpleFileModifier.h
//////
#ifndef INC_21S_CS3377_PROJECT_PART1SIMPLEFILEMODIFIER_H #define INC_21S_CS3377_PROJECT_PART1SIMPLEFILEMODIFIER_H
#include "Modifier.h"
class Part1SimpleFileModifier : public Modifier { public:
void doSetup(IOType ioType) override {} void modifyAndCopyFile(const char* sourceFile,
const char* destFile) override; };
#endif //INC_21S_CS3377_PROJECT_PART1SIMPLEFILEMODIFIER_H
unix/CMakeLists.txt
cmake_minimum_required(VERSION 3.17) project(ProjectTemplate) set(CMAKE_CXX_STANDARD 14) add_executable(21S_CS3377_Project main.cpp FileModifierFactory.cpp Part1SimpleFileModifier.cpp Part2MultiProcessModifier.cpp Part3ThreadedModifier.cpp Part4SocketModifier.cpp PipeMaker.cpp Util.cpp)
unix/FileModifierFactory.cpp
unix/FileModifierFactory.cpp
//
// Created by erik on 2/2/21.
//
#include
<
string
>
#include
<
sstream
>
#include
"FileModifierFactory.h"
#include
"Part1SimpleFileModifier.h"
#include
"FileModifyException.h"
#include
"Part2MultiProcessModifier.h"
#include
"Part3ThreadedModifier.h"
#include
"Part4SocketModifier.h"
using
namespace
std
;
// Create the proper modifier based on which part of the project you're doing
Modifier
*
FileModifierFactory
::
createModifier
(
ModifierType
type
,
IOType
ioType
,
int
argc
,
char
**
argv
)
{
Modifier
*
modifier
=
nullptr
;
switch
(
type
)
{
case
ModifierType
::
SIMPLE_MODIFIER
:
if
(
ioType
!=
IOType
::
READ_AND_WRITE
)
{
throw
FileModifyException
(
"Simple file modifier must be R/W"
);
}
modifier
=
new
Part1SimpleFileModifier
();
break
;
case
ModifierType
::
MULTI_PROCESS_MODIFIER
:
if
(
ioType
==
IOType
::
READ_AND_WRITE
)
{
throw
FileModifyException
(
"Multi-process modifier must be read or write"
);
}
modifier
=
new
Part2MultiProcessModifier
();
break
;
case
ModifierType
::
THREADED_MODIFIER
:
if
(
ioType
!=
IOType
::
READ_AND_WRITE
)
{
throw
FileModifyException
(
"Threaded file modifier must be R/W"
);
}
modifier
=
new
Part3ThreadedModifier
();
break
;
case
ModifierType
::
IPC_MODIFIER
:
if
(
ioType
==
IOType
::
READ_AND_WRITE
)
{
throw
FileModifyException
(
"IPC modifier must be read or write"
);
}
modifier
=
new
Part4SocketModifier
();
break
;
default
:
throw
FileModifyException
(
"Unknown modifier "
+
to_string
(
static_cast
<
int
>
(
type
))
+
Util
::
usage
);
}
modifier
->
doSetup
(
ioType
);
return
modifier
;
}
unix/FileModifierFactory.h
// // Created by erik on 2/2/21. // #ifndef INC_21S_CS3377_PROJECT_FILEMODIFIERFACTORY_H #define INC_21S_CS3377_PROJECT_FILEMODIFIERFACTORY_H #include "Modifier.h" #include "Util.h" class FileModifierFactory { public: static Modifier* createModifier(ModifierType type, IOType ioType, int argc, char** argv); }; #endif //INC_21S_CS3377_PROJECT_FILEMODIFIERFACTORY_H
unix/FileModifyException.h
// // Created by erik on 2/3/21. // #ifndef INC_21S_CS3377_PROJECT_FILEMODIFYEXCEPTION_H #define INC_21S_CS3377_PROJECT_FILEMODIFYEXCEPTION_H #include <exception> #include <string> class FileModifyException : public std::exception { public: explicit FileModifyException(const std::string& message) noexcept : message(message) {} const char* what() const noexcept override { return message.c_str(); } private: std::string message; }; #endif //INC_21S_CS3377_PROJECT_FILEMODIFYEXCEPTION_H
unix/Modifier.h
// // Created by erik on 2/2/21. // #ifndef INC_21S_CS3377_PROJECT_MODIFIER_H #define INC_21S_CS3377_PROJECT_MODIFIER_H #include "Util.h" class Modifier { public: // Method for doing setup work virtual void doSetup(IOType ioType) = 0; virtual void modifyAndCopyFile(const char* sourceFile, const char* destFile) = 0; }; #endif //INC_21S_CS3377_PROJECT_MODIFIER_H
unix/Part1SimpleFileModifier.cpp
unix/Part1SimpleFileModifier.cpp
//
// Created by erik on 2/2/21.
//
#include
"Part1SimpleFileModifier.h"
#include
"FileModifyException.h"
#include
"FileReader.h"
#include
"FileWriter.h"
using
namespace
std
;
void
Part1SimpleFileModifier
::
modifyAndCopyFile
(
const
char
*
sourceFile
,
const
char
*
destFile
)
{
// TODO: Fill in
}
void
Part1SimpleFileModifier
::
modifyAndCopyFile
(
const
char
*
sourceFile
,
const
char
*
destFile
)
{
FileReader
fileReader
;
list
<
EntryInfo
>
entries
;
fileReader
.
readEntries
(
entries
,
sourceFile
);
fileReader
.
addMissingEntries
(
entries
);
FileWriter
fileWriter
;
for
(
EntryInfo
info
:
entries
)
{
fileWriter
.
addEntry
(
info
);
}
fileWriter
.
writeFile
(
destFile
);
}
unix/Part1SimpleFileModifier.h
// // Created by erik on 2/2/21. // #ifndef INC_21S_CS3377_PROJECT_PART1SIMPLEFILEMODIFIER_H #define INC_21S_CS3377_PROJECT_PART1SIMPLEFILEMODIFIER_H #include "Modifier.h" class Part1SimpleFileModifier : public Modifier { public: // You shouldn't need to do any setup, but fill in if needed void doSetup(IOType ioType) override {} void modifyAndCopyFile(const char* sourceFile, const char* destFile) override; }; #endif //INC_21S_CS3377_PROJECT_PART1SIMPLEFILEMODIFIER_H
unix/Part2MultiProcessModifier.cpp
unix/Part2MultiProcessModifier.cpp
//
// Created by Erik Peterson on 2/10/21.
//
#include
<
unistd
.
h
>
#include
<
iostream
>
#include
<
cstring
>
#include
<
sys
/
wait
.
h
>
#include
"Part2MultiProcessModifier.h"
#include
"PipeMaker.h"
#include
"FileModifyException.h"
using
namespace
std
;
void
Part2MultiProcessModifier
::
doSetup
(
IOType
ioType
)
{
// TODO: fill in
}
void
Part2MultiProcessModifier
::
modifyAndCopyFile
(
const
char
*
sourceFile
,
const
char
*
destFile
)
{
// TODO: fill in
}
unix/Part2MultiProcessModifier.h
// // Created by Erik Peterson on 2/10/21. // #ifndef INC_21S_CS3377_PROJECT_PART2MULTIPROCESSMODIFIER_H #define INC_21S_CS3377_PROJECT_PART2MULTIPROCESSMODIFIER_H #include "Modifier.h" class Part2MultiProcessModifier : public Modifier { public: void doSetup(IOType ioType) override; void modifyAndCopyFile(const char* sourceFile, const char* destFile) override; private: }; #endif //INC_21S_CS3377_PROJECT_PART2MULTIPROCESSMODIFIER_H
unix/Part3ThreadedModifier.cpp
unix/Part3ThreadedModifier.cpp
//
// Created by Erik Peterson on 2/10/21.
//
#include
<
iostream
>
#include
"Part3ThreadedModifier.h"
#include
"FileModifyException.h"
using
namespace
std
;
void
Part3ThreadedModifier
::
doSetup
(
IOType
ioType
)
{
// TODO: fill in
}
void
Part3ThreadedModifier
::
modifyAndCopyFile
(
const
char
*
sourceFile
,
const
char
*
destFile
)
{
// TODO: fill in
}
// Use this as the starting point for the thread you create
void
*
Part3ThreadedModifier
::
threadEntry
(
void
*
arg
)
noexcept
{
return
nullptr
;
}
Part3ThreadedModifier
::~
Part3ThreadedModifier
()
noexcept
{
// TODO: clean up your condition and mutex
}
unix/Part3ThreadedModifier.h
// // Created by Erik Peterson on 2/10/21. // #ifndef INC_21S_CS3377_PROJECT_PART3THREADEDMODIFIER_H #define INC_21S_CS3377_PROJECT_PART3THREADEDMODIFIER_H #include "Modifier.h" class Part3ThreadedModifier : public Modifier { public: void doSetup(IOType ioType) override; void modifyAndCopyFile(const char* sourceFile, const char* destFile) override; ~Part3ThreadedModifier() noexcept; private: static void* threadEntry(void* arg) noexcept; }; #endif //INC_21S_CS3377_PROJECT_PART3THREADEDMODIFIER_H
unix/Part4SocketModifier.cpp
unix/Part4SocketModifier.cpp
//
// Created by Erik Peterson on 2/10/21.
//
#include
<
iostream
>
#include
<
unistd
.
h
>
#include
<
cstring
>
#include
"Part4SocketModifier.h"
#include
"FileModifyException.h"
using
namespace
std
;
Part4SocketModifier
::~
Part4SocketModifier
()
{
// TODO: clean up anything needed
}
void
Part4SocketModifier
::
doSetup
(
IOType
ioType
)
{
// TODO: fill in
}
void
Part4SocketModifier
::
modifyAndCopyFile
(
const
char
*
sourceFile
,
const
char
*
destFile
)
{
// TODO: fill in
}
unix/Part4SocketModifier.h
// // Created by Erik Peterson on 2/10/21. // #ifndef INC_21S_CS3377_PROJECT_PART4SOCKETMODIFIER_H #define INC_21S_CS3377_PROJECT_PART4SOCKETMODIFIER_H #include "Modifier.h" class Part4SocketModifier : public Modifier { public: ~Part4SocketModifier(); void doSetup(IOType ioType) override; void modifyAndCopyFile(const char* sourceFile, const char* destFile) override; private: }; #endif //INC_21S_CS3377_PROJECT_PART4SOCKETMODIFIER_H
unix/PipeMaker.cpp
unix/PipeMaker.cpp
//
// Created by Erik Peterson on 2/11/21.
//
#include
<
unistd
.
h
>
#include
<
cerrno
>
#include
<
cstring
>
#include
<
string
>
#include
"PipeMaker.h"
#include
"FileModifyException.h"
using
namespace
std
;
PipeMaker
::
PipeMaker
()
{
int
rc
=
pipe
(
pipeFDs
);
if
(
rc
<
0
)
{
string errorString
=
"Unable to create pipe: "
;
errorString
.
append
(
strerror
(
errno
));
throw
FileModifyException
(
errorString
);
}
}
int
PipeMaker
::
setUpToWrite
()
{
// Close of fd[0] since we'll be writing, not reading
// DO NOT dup the write file descriptor as that would mess up writing other things...
close
(
pipeFDs
[
0
]);
return
pipeFDs
[
1
];
}
int
PipeMaker
::
setUpToRead
()
{
// Close of fd[1] since we'll be reading, not writing
close
(
pipeFDs
[
1
]);
int
usedFD
=
pipeFDs
[
0
];
int
rc
=
dup2
(
usedFD
,
STDIN_FILENO
);
if
(
rc
<
0
)
{
string errorString
=
"Unable to dup read pipe: "
;
errorString
.
append
(
strerror
(
errno
));
throw
FileModifyException
(
errorString
);
}
return
usedFD
;
}
unix/PipeMaker.h
// // Created by Erik Peterson on 2/11/21. // #ifndef INC_21S_CS3377_PROJECT_PIPEMAKER_H #define INC_21S_CS3377_PROJECT_PIPEMAKER_H class PipeMaker { public: PipeMaker(); // Call when you're the parent process, to write to the child int setUpToWrite(); // Call when you're the child process, to read from the parent int setUpToRead(); private: int pipeFDs[2]; }; #endif //INC_21S_CS3377_PROJECT_PIPEMAKER_H
unix/Util.cpp
unix/Util.cpp
//
// Created by erik on 2/3/21.
//
#include
"Util.h"
#include
"FileModifyException.h"
#include
<
string
>
using
namespace
std
;
const
char
*
Util
::
usage
=
"FileModifier { 1 | 2 {R|W} |3 | 4 {R|W} }\n"
;
const
char
*
Util
::
inputFilename
=
"input"
;
const
char
*
Util
::
outputFilename
=
"output"
;
ModifierType
Util
::
toModifierType
(
int
type
)
{
switch
(
type
)
{
case
1
:
return
ModifierType
::
SIMPLE_MODIFIER
;
case
2
:
return
ModifierType
::
MULTI_PROCESS_MODIFIER
;
case
3
:
return
ModifierType
::
THREADED_MODIFIER
;
case
4
:
return
ModifierType
::
IPC_MODIFIER
;
default
:
throw
FileModifyException
(
"Unknown modifier type (int) "
+
to_string
(
type
));
}
}
IOType
Util
::
toIOType
(
int
type
)
{
switch
(
type
)
{
case
1
:
return
IOType
::
READ_AND_WRITE
;
case
2
:
return
IOType
::
READ
;
case
3
:
return
IOType
::
WRITE
;
default
:
throw
FileModifyException
(
"Unknown IO type (int) "
+
to_string
(
type
));
}
}
unix/Util.h
// // Created by erik on 2/2/21. // #ifndef INC_21S_CS3377_PROJECT_UTIL_H #define INC_21S_CS3377_PROJECT_UTIL_H #include <ctime> struct EntryInfo { time_t timestamp; int itemID; char* itemName; int quantity; float price; }; enum class ModifierType { // Part 1 SIMPLE_MODIFIER = 1, // Part 2 MULTI_PROCESS_MODIFIER, // Part 3 THREADED_MODIFIER, // Part 4 IPC_MODIFIER }; /** * This enum says whether the FileModifier will read, write, or both * Parts 1 and 3 are READ_AND_WRITE (single process), whereas parts 2 and 4 * are in two parts (the initial process getting READ_AND_WRITE, and you giving * the 2nd process the proper code for its role) */ enum class IOType : unsigned int { READ_AND_WRITE = 1, READ, WRITE }; class Util { public: const static char* usage; const static char* inputFilename; const static char* outputFilename; const static int portNumber = 12345; static ModifierType toModifierType(int type); static IOType toIOType(int type); }; #endif //INC_21S_CS3377_PROJECT_UTIL_H
unix/cmake-build-debug/ProjectCheckerLinux
unix/cmake-build-debug/ProjectCheckerMac
unix/cmake-build-debug/input
unix/main.cpp
#include <iostream> #include <unistd.h> #include <cstring> #include "Util.h" #include "Modifier.h" #include "FileModifierFactory.h" #include "FileModifyException.h" using namespace std; //--------------------------------------------------------- // Arguments: // ModifierType: 1-4, corresponds to the project parts // 1. Simple modifier // 2. Multiprocess modifier // 3. Threaded modifier // 4. IPC (socket) modifier // // IOType: required for modifier types 2 and 4, because // they use 2 processes (one to read and the other // to write). Not needed for modifier types 1 & 3 // // For parts 2 & 4, call the read version (2) // from CLion. Your code should call the write // (3) version for the 2nd process. //-------------------------------------------------------- int main(int argc, char** argv) { // Delete the output file so we're starting fresh unlink(Util::outputFilename); if ( argc < 2) { cerr << "No type specified.\n" << Util::usage; exit(1); } ModifierType modifierType; // Turn argv[1] (first argument) into the proper modifier type try { modifierType = Util::toModifierType(stoi(argv[1])); } catch ( FileModifyException e) { cerr << "Unable to determine type of " << argv[1] << ": " << e.what() << "\n" << Util::usage; exit(1); } catch ( exception e) { cerr << "Is " << argv[1] << " an integer? Couldn't convert it to a ModifierType:\n" << Util::usage; exit(1); } // Get the IOType (if specified) IOType ioType = IOType::READ_AND_WRITE; if ( argc == 3) { try { ioType = Util::toIOType(stoi(argv[2])); } catch ( FileModifyException e) { cerr << "Unable to determine I/O type of " << argv[2] << ": " << e.what() << "\n" << Util::usage; } catch ( exception e) { cerr << "Is " << argv[2] << " an integer? Couldn't convert it to an IOType\n" << Util::usage; } } // Factory-build the modifier Modifier* modifier; try { modifier = FileModifierFactory::createModifier(modifierType, ioType, argc, argv); } catch(FileModifyException e) { cerr << "Error creating modifier object: " << e.what(); exit(1); } catch ( exception e) { cerr << "Error creating modifier object: " << e.what(); exit(1); } try { modifier->modifyAndCopyFile(Util::inputFilename, Util::outputFilename); } catch (FileModifyException e) { cerr << "Error modifying file: " << e.what(); exit(1); } catch ( exception e) { cerr << "Error modifying file: " << e.what(); exit(1); } // Exec ProjectChecker to do the checking for us // The #ifdef here is some trickery to launch the MacOS or Linux version // of the checker program based on your architecture int error = 0; #ifdef __linux__ error = execl("./ProjectCheckerLinux", "ProjectChecker", NULL); #elif __APPLE__ error = execl("./ProjectCheckerMac", "ProjectChecker", NULL); #else cerr << "Unknown architecture!" << endl; exit(1); #endif if ( error) { cerr << "Unable to start file checker!" << strerror(errno) << endl; exit(1); } }
unix/makefile
testPartOne: clean compile testSimple testPartTwo: clean compile testMultiProcess testPartThree: clean compile testThreaded testPartFour: clean compile testSocket clean: rm -f cmake-build-debug/21S_CS3377_Project compile: main.cpp g++ -I . -pthread -o cmake-build-debug/21S_CS3377_Project main.cpp FileModifierFactory.cpp Part1SimpleFileModifier.cpp Part2MultiProcessModifier.cpp Part3ThreadedModifier.cpp Part4SocketModifier.cpp PipeMaker.cpp Util.cpp testSimple: cd cmake-build-debug && ./21S_CS3377_Project 1 testMultiProcess: cd cmake-build-debug && ./21S_CS3377_Project 2 2 testThreaded: cd cmake-build-debug && ./21S_CS3377_Project 3 testSocket: cd cmake-build-debug && ./21S_CS3377_Project 4 2
unix/FileReader.cpp
#include <fcntl.h> #include <cerrno> #include <unistd.h> #include <cstring> #include "FileReader.h" #include "FileModifyException.h" #include "Util.h" using namespace std; char nameFirst[50] = "A Programming Guide to Linux Commands, Editors, a"; char nameSecond[50] = "Advanced Programming in the UNIX Environment by S"; string FileReader::getErrnoString() { return strerror(errno); } void FileReader::verifyNumBytes(ssize_t expected, ssize_t actual) { if ( expected != actual) { throw FileModifyException("Bytes read/written (" + to_string(expected) + ") don't match actual (" + to_string(actual) + ")"); } } void FileReader::readEntries(std::list<EntryInfo>& entryList, const char* fileName) { int fileDescriptor = open(fileName, O_RDONLY); if ( fileDescriptor < 0) { close(fileDescriptor); throw FileModifyException("Unable to open output file: " + getErrnoString()); } readEntries(entryList, fileDescriptor); close(fileDescriptor); } void FileReader::readEntries(list<EntryInfo> &entryList, int fileDescriptor) { int numItems; size_t amountRead = read(fileDescriptor, &numItems, sizeof(int)); if ( amountRead <= 0) { close(fileDescriptor); throw FileModifyException("File is empty/error reading"); } for (int i=0; i < numItems; i++) { EntryInfo incoming; verifyNumBytes(sizeof(time_t), read(fileDescriptor, &incoming.timestamp, sizeof(time_t))); verifyNumBytes(sizeof(int), read(fileDescriptor, &incoming.itemID, sizeof(int))); char* inputName = new char[50]; verifyNumBytes(50, read(fileDescriptor, inputName, 50 * sizeof(char)));incoming.itemName = inputName; verifyNumBytes(sizeof(int), read(fileDescriptor, &incoming.quantity, sizeof(int))); verifyNumBytes(sizeof(float), read(fileDescriptor, &incoming.price, sizeof(float))); entryList.push_back(incoming); } } void FileReader::addMissingEntries(list<EntryInfo> & entries) { EntryInfo additionalFirst; additionalFirst.timestamp = 1612195200; // (2/1/2020 4 PM GMT) additionalFirst.itemID = 4636152; additionalFirst.itemName = nameFirst; additionalFirst.quantity = 70; additionalFirst.price = 70.99; entries.push_back(additionalFirst); EntryInfo additionalSecond; additionalSecond.timestamp = 1613412000; //(2/15/2020 6 PM GMT) additionalSecond.itemID = 6530927; additionalSecond.itemName = nameSecond; additionalSecond.quantity = 68; additionalSecond.price = 89.99; entries.push_back(additionalSecond); }
unix/FileReader.h
// #ifndef INC_21S_CS3377_PROJECT_FILEREADER_H #define INC_21S_CS3377_PROJECT_FILEREADER_H #include <list> #include <string> #include "Util.h" class FileReader { public: void readEntries(std::list<EntryInfo>& entryList, const char* fileName); void readEntries(std::list<EntryInfo>& entryList, int fileDescriptor); static void addMissingEntries(std::list<EntryInfo>& entries); private: static std::string getErrnoString(); static void verifyNumBytes(ssize_t expected, ssize_t actual); }; #endif //INC_21S_CS3377_PROJECT_FILEREADER_H
unix/FileWriter.h
// #ifndef INC_21S_CS3377_PROJECT_FILEWRITER_H #define INC_21S_CS3377_PROJECT_FILEWRITER_H #include <list> #include <string> #include "Util.h" class FileWriter { public: void addEntry(const EntryInfo& entryInfo); void writeFile(const char* fileToWrite); void writeFile(int fileDescriptor); private: std::string getErrnoString(); std::list<EntryInfo> entries; }; #endif //INC_21S_CS3377_PROJECT_FILEWRITER_H
unix/FileWriter.cpp
#include <fcntl.h> #include <cerrno> #include <unistd.h> #include <cstring> #include "FileWriter.h" #include "FileModifyException.h" using namespace std; string FileWriter::getErrnoString() { return strerror(errno); } void FileWriter::addEntry(const EntryInfo& entryInfo) { entries.push_back(entryInfo); } void FileWriter::writeFile(const char* fileToWrite) { int fileDescriptor = open(fileToWrite, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); if ( fileDescriptor < 0) { throw FileModifyException("Unable to open output file: " + getErrnoString()); } writeFile(fileDescriptor); close(fileDescriptor); } void FileWriter::writeFile(int fileDescriptor) { int numItemsToWrite = entries.size(); write(fileDescriptor, &numItemsToWrite, sizeof(int)); for ( EntryInfo info : entries) { write(fileDescriptor, &info.timestamp, sizeof(time_t)); write(fileDescriptor, &info.itemID, sizeof(int)); write(fileDescriptor, info.itemName, 50); write(fileDescriptor, &info.quantity, sizeof(int)); write(fileDescriptor, &info.price, sizeof(float)); } }