diff --git a/Animal Processing.html b/Animal Processing.html new file mode 100644 index 0000000..a9e5644 --- /dev/null +++ b/Animal Processing.html @@ -0,0 +1,2797 @@ + + +Animal Processing

Animal Processing 1.0.2

fork, wait, exec, pipe, dup2


If there is a conflict between the subject and the skeleton or moulinette, the subject is always right.
However, please report any inconsistencies to your assistant.

Submissions

Repository structure
At the end, your git repo must follow this architecture:
epita-prepa-computer-science-prog-104-p-04-2030-firstname.lastname
+├── AnimalProcessing
+│   ├── Fundamentals
+│   │   ├── exec.c
+│   │   ├── exec_file.c
+│   │   ├── forky.c
+│   │   ├── pipe.c
+│   │   └── wait.c
+│   └── Proficiencies
+│       ├── double_pipe.c
+│       ├── dup.c
+│       └── project.c
+├── .gitignore
+└── README
+

Do not forget to check the following requirements before submitting your work:
  • You shall obviously replace firstname.lastname with your login.
  • The .gitignore file is mandatory.
  • Remove all personal tests from your code, except those from the Tests folder.
  • The given prototypes must be strictly respected.
  • The code MUST compile! Otherwise you will not receive a grade.

.gitignore example
Here is an example of a .gitignore file:
*.a
+*.lib
+*.o
+*.obj
+*.out
+
+.idea/
+*~
+*.DotSettings.user
+

This needs to be setup before the first submission!

Introduction


Warning(s)
For this practical, the only libraries allowed are the following.
They import basic functions that you will need later in this practical.
It is your responsibility to add them.

Code example(s)
#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <err.h>
+

Warning(s)
For this practical, all the child processes must quit with the exit function.

This first section will cover the basics of multiprocessing. It will allow you to better understand how to use and operate processes.



forky.c


Lore
Tom Nook realized he couldn't manage the island's expansion all by himself. To speed up the construction of bridges and the sale of items, he needs to multiply his workforce. This is where Timmy and Tommy come in! With a simple stamp of a magic tool called fork(), one employee becomes two.

Task: Implement the forky() function, which will display "I am the parent!\n" in the parent and "I am the child!\n" in the child. The function will return 0 and -1 in case of error.
If you've forgotten how the fork function works, you can use man 3 fork or refer to the course material.

For this function specifically, there's no need to wait for the child process.

Warning(s)
We will verify that the fork is properly implemented.

Warning(s)
When your code is executed, the parent and child may be reversed, this is perfectly normal and depends on the device.

Prototype(s)
int forky();
+

Code example(s)
int res = forky();
+printf("%d\n", res);
+

Output
I am the parent!
+I am the child!
+0
+



wait.c


Lore
Blathers is fascinated by the Fibonacci sequence he observes on the snail shells brought to the museum. He asks one of his young assistants (the child process) to do the tedious calculations while he waits patiently so as not to fall asleep on the job.

For this exercise, you now need to communicate with the child function. There will be two steps in your function:
- The child process will calculate the Fibonacci number of the parameter given to the function, x, and return it (if x is negative, the child must exit with -1).
- The parent process will wait for the child process and retrieve the result (which is the Fibonacci number). It will then display "My child knows how to count the Fibonacci sequence. He found <number> for <x>!\n".

If an error occurs when using fork() or waitpid(), the function should return -1 otherwise, it should return 0.

Warning(s)
For this exercise, you must use waitpid(2) to wait for the child process.

Information
When a parent process catches a child with wait() or waitpid(), it receives a raw status integer. You must use macros to decode it:
  • WIFEXITED(status): Returns true if the child terminated normally (by calling exit() or returning from main()), rather than being killed by a signal.
  • WEXITSTATUS(status): Extracts the actual return code (e.g., the N in exit(N)). Warning: You should only use this if WIFEXITED returned true!

In short: WIFEXITED asks "Did it exit cleanly?", and WEXITSTATUS asks "What was its exit code?".

You can use man 2 wait for more information.

Prototype(s)
int wait_child(int x);
+

Code example(s)
int res = wait_child(-1);
+printf("%d\n",  res);                       // -1
+
+res = wait_child(7);                        // 13
+printf("%d\n", res);                        // 0
+



exec.c


Lore
Isabelle spends her days at Resident Services executing town ordinances. When she receives an order that is too complex, she delegates the task completely: she replaces the current work program with a specialist using the execvp() command on her NookPhone.

Hint(s)
For this exercise, execvp might be very usefull...

You have a powerful tool at your disposal for executing system commands: execvp(3). This function replaces the current process with a new one based on the command provided.

Because execvp completely takes over the process, you must use fork(2) to create a child process. This prevents your main program from being terminated.
Your function should:

  • Fork a child process.
  • Call execvp inside that child.
  • In the parent, wait for the child to finish and return its exit status.
  • Return -1 only if an error occurs during fork, execvp, or wait.

Hint(s)
You will maybe encounter a problem during this exercise with the return code of your function.
A fact about fork(2) is that it returns a code between 0 and 255 (because the return code is stored in a single byte which limits the possible values to the range 0-255).
So because your child process is returning -1 in case of an error, your program will return 255 and not -1.

Prototype(s)
int execute_me(char *cmd, char **argv);
+

Code example(s)
char *argv[] = {"ls", "-l", NULL};
+int r1 = execute_me("ls", argv);
+printf("%d\n", r1);
+
+char *argv[] = {"ls", "-l", NULL};
+int r2 = execute_me("wrong", argv);
+printf("%d\n", r2);
+

Output
# should be the same as doing "ls -l" in the shell
+0
+255
+



exec_file.c


Lore
It's Saturday night and K.K. Slider is in the plaza. Instead of picking songs at random, a villager asks him to play a very specific track from his list. K.K. therefore uses execlp() to directly launch the file of the requested sheet music.

For this exercise, it is almost the same thing as execvp but in a different way. We let you take a look at the man page of exec(3).
Your goal here is to do the same thing as the previous exercise but with execlp.
For this exercise, we will only test execlp with 2 arguments in the array args.

Prototype(s)
int execute_me2(char *file, char **args);
+

Code example(s)
char *argv[] = {"ls", "-l", NULL};
+execute_me2("ls", argv);                 // should be the same as doing "ls -l" in the shell
+
+char *argv[] = {"ls", "-l", NULL};
+execute_me2("wrong", argv);              // should return 255
+



pipe.c


Lore
The Dodo brothers from Dodo Airlines (DAL) have developed a pneumatic transport system under the island to send express letters. The sender slips their message into the communication tube, and the recipient listens at the other end to collect the mail without leaving their house!

There is one more essential thing used to really communicate between the child and parent processes. The pipe(2) is a function that allows interprocess communication. You can have a better explanation using man pipe.
In this exercise, your program needs to create a string "Hello from your child !\n" and gives it to the parent process. Then the parent will display "My child sends me : <the message of the child>" on the stdout.
If an error occured during the program, just print "an error occured\n" in the stdout and stop everything.

Information
Until now, you have handled files using FILE*.
Imagine FILE* as interacting at the post office counter with Pelly or Phyllis: it's high-level, the text is nicely formatted, and it's easy to read.
But behind the counter, Tom Nook's operating system doesn't care about this politeness; it manages everything with raw cubbyhole numbers called File Descriptors (fd).
An fd is a simple integer that identifies a resource opened by your process (by default: 0 for stdin, 1 for stdout, 2 for stderr).
It's the low-level, unfiltered version of a file.
Good news: if you have a FILE* but a function requires an fd (like read), you can easily ask for the cubbyhole number using the fileno(my_file) function.

Now that you know about these numbered cubbyholes, let's talk about Pipes.
A pipe is an invisible, one-way communication channel created by the operating system.
When you call the pipe(int fd[2]) function, the system "plumbs" a tube and gives you two ends (two File Descriptors): fd[1] is used exclusively for writing (the tube's input) and fd[0] is used exclusively for reading (the tube's output).
Think of the Dodo Airlines (DAL) pneumatic tube mail system: one process slips its letter into the fd[1] slot, and the other process waits at the other end at the fd[0] opening to catch the message.
Warning: because it is strictly one-way, if both processes want to talk back and forth, you will need to build two separate pipes!

Once you have your cubbyhole number (fd), how do you use it?
Unlike Pelly's neatly formatted letters, everything here is manual!
To send a message, you use write(fd, message, size).
This is the physical act of shoving your packet of data into the tube.

Conversely, read(fd, buffer, size) is the act of reaching into the other end to grab the message and stuff it into your pockets (the buffer).

But beware of Nook Inc.'s golden rule: always return your keys! When you are done, you absolutely must call close(fd).
This is crucial with pipes: if you don't seal the input with close(fd[1]), the villager at the output will wait forever with read, thinking another letter is on the way.
Closing the fd is the only way to signal: "That's it, I'm done writing, you can go home!".

Feel free to check the respective man pages for these functions to learn more about how they work and their various return codes.

Prototype(s)
void piped();
+

Code example(s)
piped();
+

Output
My child sends me : Hello from your child !
+


Now that you know the basics of multiprocessing, we can enter in the good vibes of it !



dup.c


Lore
The post office is overwhelmed by Tom Nook's loan reminders. Instead of announcing everyone's debts in the public town square, Nook demands that all invoices be silently and directly redirected to the personal mailbox of the concerned villager.

The goal of this function is to execute a command and redirect its standard output to a file.
In a child process created by fork(2), you must open the file specified by output with fopen(3) in write mode.
You then perform the redirection so that the file replaces STDOUT_FILENO before calling execvp(3) to run the command.

The parent process is responsible for waiting for the child to finish and retrieving its exit status.
In terms of error handling, if one of the function call fails, you must print "Something went wrong in duper.\n" and return 1.
Upon a successful execution, the parent must print "My child send me <return code>.\n" and return 0.

Information
Remember our cubbyhole numbers (fd)?
By default, when a program uses printf or execvp, it always drops its output into cubbyhole number 1 (the public plaza, meaning stdout).
But what if Tom Nook demands that this output goes secretly into a specific file or pipe?
That's where the dup2(oldfd, newfd) function comes in.
It's a bit like swapping the name tags on mailboxes: it forces the newfd to become an exact clone of the oldfd.
In practice, if you use dup2(my_file_fd, STDOUT_FILENO), you are replacing the public cubbyhole (stdout) with your own file.
From then on, every time the program thinks it's shouting in the public plaza (stdout), its message will actually be diverted and quietly tucked away inside your file!

Hint(s)
To bridge the gap between FILE* and the int file descriptor required by dup2, you might find the fileno(3) function very useful.

Prototype(s)
int duper(char **argv, char *output)
+

Code example(s)
int main(int argc, char **argv)
+{
+    duper(argv + 2, argv[1]);
+}
+

Output
$ ./duper test.txt echo toto
+My child send me 0.
+$ cat test.txt
+toto
+



project.c


Lore
Welcome to the Nook Stop! The famous multimedia terminal at Resident Services needs an operating system overhaul. You are tasked with coding the machine's new main loop (REPL) so that villagers can execute their requests, check past errors, and cleanly exit the interface.

The objective of this exercise is to simulate the basic functionality of a terminal. To do this, you must read user input from stdin, process it, and continue reading in a loop.

Input Cases

There are three specific cases to handle for the input:

  • exit [n] : Quits the program with a return code n. If n is not specified, the default is 0.
  • run [Command] : Executes the given command and its arguments using execvp.
  • ? : Prints the exit code of the last executed command, or 0 if no command has been run yet.

The goal is to tokenize the user input into individual words using spaces, newlines (\n), and tabs (\t) as delimiters.

Returns a NULL-terminated array of strings.

All memory must be allocated on the heap.

The memory allocated for the array of strings and the strings themselves will be freed by the caller function.

Prototype(s)
char** split(char* line);
+

Code example(s)
char input[] = "run ls -l  -h   ";
+char** res = split(input);
+// res[0] = "run"
+// res[1] = "ls"
+// res[2] = "-l"
+// res[3] = "-h"
+// res[4] = NULL
+

The exec_cmd function is responsible for launching external programs. To prevent the main program from closing when a command is executed, you must isolate the execution within a dedicated process. This follows the standard Unix process lifecycle.
The function should return the exit code of the executed command, or -1 if an error occurs during execution.

Prototype(s)
int exec_cmd(char** args);
+

Code example(s)
char* cmd1[] = {"ls", "-l", NULL};
+int res = exec_cmd(cmd1);
+// res = 0
+// execute ls -l and return the exit code
+

The main function serves as the program's engine, orchestrating a continuous Read-Eval-Print Loop (REPL).
In each iteration, it uses getline to capture user input, which is then passed to split for tokenization into a heap-allocated array.
The loop must evaluate the first token to dispatch the correct action:
  • If the command is "exit", it should quit the program with a return code n. If n is not specified, the default is 0
  • If the command is "run", it should invoke exec_cmd with the subsequent tokens as arguments.
  • If the command is "?", it should print the exit status of the last executed command or 0 if no command has been run yet.
  • Else, is should print "Unknown command\n".
Crucially, you must update a persistent variable with the exit status of each executed process and ensure that all dynamically allocated memory is freed before the next cycle to prevent leaks.

Code example(s)
int execution_loop();
+

Code example(s)
int main() {
+  return execution_loop();
+}
+

Output
$ ./project
+run ls -l
+# (output of ls -l)
+run echo Hello World
+Hello World
+?
+0
+exit 42
+$ echo $?
+42
+

Information
If you wish, you may use the following function to detect whether the program is running in an interactive terminal (user) or not (automated script/moulinette).
This allows you to print a prompt (like > or $ ) to stdout before reading input only when a real user is interacting with the program.

Code example(s)
int is_terminal()
+{
+    return isatty(STDIN_FILENO) && isatty(STDOUT_FILENO);
+}
+

Output
$ ./project
+21sh$ run ls -l
+# (output of ls -l)
+21sh$ run echo Hello World
+Hello World
+21sh$ ?
+0
+21sh$ exit 42
+$ echo $?
+42
+



double_pipe.c


Lore
Katrina the fortune teller has set up her tent in the island plaza. To know your financial future, a mystical dialogue is established: you slip your question into a slit in the canvas (Pipe A), Katrina reads the stars, and her occult response comes out through another opening (Pipe B).

The goal of this exercise is to implement a continuous data exchange between two processes using a double-pipe setup.
Instead of just executing a command, the parent process will delegate a specific mathematical task to its child and wait for the result.

The implementation is not strictly defined; only the final execution of the main function will be tested.

Execution Flow


The Parent:

  • Reads an integer n from stdin.
  • Sends n through Pipe A.
  • Waits (blocks) to read the response from Pipe B.
  • Displays the result: "The Oracle says the square is: [result]".
  • If the user enters -1, the parent closes the pipes and terminates.

The Child:

  • Enters an infinite loop, waiting to read from Pipe A.
  • Upon receiving n, computes the square: n2n^2.
  • Sends the result back through Pipe B.
  • If the read end of Pipe A is closed (EOF), the child exits cleanly.

The only constraint is that fork() must be called only once.
If the user input is not a valid number, simply restart the reading process.

[ Keyboard / stdin ]
|
| 1. Inputs the number 'n'
v
+-----------------------+ +-----------------------+
| | 2. Sends 'n' (write) | |
| PARENT Process | ---------------------------> | CHILD Process |
| (The Villager) | PIPE A (pipe1) | (The Oracle) |
| | | |
| - pipe1[1] : Write | | - pipe1[0] : Read |
| - pipe2[0] : Read | | - pipe2[1] : Write |
| | | |
| | | 3. Computes: n * n |
| | 4. Returns 'n²' (write) | |
| | <--------------------------- | |
+-----------------------+ PIPE B (pipe2) +-----------------------+
|
| 5. Displays "The Oracle says..."
v
[ Screen / stdout ]

Output
$ ./oracle
+5
+The Oracle says the square is: 25
+10
+The Oracle says the square is: 100
+-1
+$ echo $?
+0
+


[ 1.0.2 ] 2026-04-14 16:15:00
Fundamentals
  • forky.c > forky: remove space before ! in exemple

[ 1.0.1 ] 2026-04-13 18:00:00
Proficiencies
  • project.c > split: clarify memory management


This page and all subpages are for internal use at EPITA only.
The use of this document must abide by the following rules:
Copyright © 2026-2027 - EPITA
\ No newline at end of file