# Notes and Tutorials for Pipe OS Project
### Using Dirent API
The easiest way to iterate over the contents of a directory is through the `dirent` api. Here's an example of printing out every file in a directory using `dirent.h`:
```cpp=
#include <dirent.h>
#include <iostream>
using namespace std;
int main() {
struct dirent *entry = nullptr;
DIR *dir_ptr = nullptr;
dir_ptr = opendir("./my_dir");
if (dir_ptr != nullptr) {
// Remember that . and .. are valid file names
// that appear as part of this process. You are
// responsible for filtering them out.
while ((entry = readdir(dir_ptr)))
cout << entry->d_name << endl;
}
}
```
### Using `fork`
`fork()` in `unistd.h` allows a parent process to spawn a child process before continuing to run. The spawned child process runs the same exact code as the parent process, but the return value of the call to `fork()` will differ depending on which process is currently running the code. This allows us to differentiate and determine which process is running, and conditionally execute code based on that. As an example, here we'll create two sibling processes under a single parent, and execute code based on which process is running.
```cpp=
#include <unistd.h>
#include <iostream>
using namespace std;
int main() {
pid_t child_a_pid, child_b_pid;
if ((child_a_pid = fork()) == -1) {
// Something has gone wrong, forking failed
perror("fork");
exit(1);
}
// This is how we know that child A is running
if (child_a_pid == 0) {
cout << "Hello from child_a" << endl;
} else {
// If this branch runs, the parent process or child B is running
if ((child_b_pid = fork()) == -1) {
perror("fork");
exit(1);
}
// This is how we check if child B is running
if (child_b_pid == 0) {
cout << "Hello from child_b" << endl;
}
}
}
```
### Interprocess Communication with Pipes
Pipes allow processes to communicate with each other. A pipe is represented as a simple array of 2 file descriptors (integers), one for reading and one for writing. These file descriptors can be written to and read from using standard unix API functions, meaning there aren't any special functions for reading or writing from/to pipes. In this example, we'll create two processes and have them communicate with each other.
```cpp=
#include <unistd.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
// Declaring this enum helps us
// not have to remember that
// the first element of the pipe
// array is for reading, and the
// second is for writing
enum pipe_mode_t {
READ = 0,
WRITE = 1
};
int main() {
// Create two 2-element int arrays for containing
// the pipe file descriptors, then fill them with
// the pipe function. Remember to read its manpage
int pipe_a[2];
int pipe_b[2];
pipe(pipe_a);
pipe(pipe_b);
pid_t child_a_pid, child_b_pid;
if ((child_a_pid = fork()) == -1) {
// Something has gone wrong, forking failed
perror("fork");
exit(1);
}
// This is how we know that child A is running
if (child_a_pid == 0) {
// Child A has no reason to write to B's pipe
// so we close the WRITE end of its pipe.
close(pipe_b[WRITE]);
// It's important that the message ends in a newline,
// we'll see why!
string message = "Hello from child_a\n";
// We write to the pipe using the write function. Again,
// read its manpage! This function expects a C style
// string (a char pointer) so use the .c_str() method
// to get one to pass to this function.
write(pipe_a[WRITE], message.c_str(), message.size());
// We close the pipe once we're done writing to it.
// We won't be able to reopen it, though!
close(pipe_a[WRITE]);
// Now we read from child B's read pipe
// Super important and helpful: use the fdopen function
// to open the READ pipe like a file. This gives us access
// to utility functions for reading.
FILE *read_descriptor = fdopen(pipe_b[READ], "r");
// Use getline to read in exactly one line from pipe_b
// without having to worry about buffer allocation. Remember
// to free your char* though!
size_t len;
char *line = nullptr;
getline(&line, &len, read_descriptor);
cout << "Child B says: " << line << endl;
free(line);
} else {
// If this branch runs, the parent process or child B is running
if ((child_b_pid = fork()) == -1) {
perror("fork");
exit(1);
}
// This is how we check if child B is running
if (child_b_pid == 0) {
close(pipe_a[WRITE]);
string message = "Hello from child_b\n";
write(pipe_b[WRITE], message.c_str(), message.size());
close(pipe_b[WRITE]);
FILE *read_descriptor = fdopen(pipe_a[READ], "r");
size_t len;
char *line = nullptr;
getline(&line, &len, read_descriptor);
cout << "Child A says: " << line << endl;
free(line);
}
}
}
```
### C++ File API
Part of this project is reading from a filing and writing its contents to a pipe. Another part of the project is reading file contents from a pipe and writing them to a newly created file. We'll cover how to read and write files using C++ APIs in this example.
```cpp=
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
string contents_for_a = "the quick\nbrown fox\njumps over\nthe lazy dog";
// Creating file_a and writing to it
ofstream file_a_out;
file_a_out.open("file_a.txt");
file_a_out << contents_for_a;
file_a_out.close();
// Reading from file_a and writing its contents to stdout
ifstream file_a_in("file_a.txt");
string line;
// This getline is different than the getline in the last example,
// which is for the C file API! This getline function is part of
// the C++ standard library, and operates on ifstreams and std::strings.
// Keep in mind that getline discards the newline at the end of the line,
// so the contents of the line variable will not include a newline at the end!
while (getline(file_a_in, line)) {
cout << line << endl;
}
}
```