Changelog:
- 25 Sep 2025: make headings below consistent with instructions at the top about return types of these functions; fix formatting error in parallelwriteoutput description; remove remark about stripping newlines in parallelwriteoutput description: remove getdelim references that were about a version of this assignment that required reading from file descirptors
- 27 Sep 2025: remove mention of \0 and things implying that writeoutput’s return value would be a string
- 28 Sep 2025: explicit specify that outputs means stdouts for parallelwriteoutput
Write and submit a single file
fork_run.cthat defines the following two functions, specified below:void writeoutput(const char *command, const char *out_path, const char *err_path)void parallelwriteoutput(int count, const char **argv_base, const char *out_path)
You should not submit any other files (i.e., no
.hfiles,Makefile, etc). You may include helper functions in the file, but it should not containmain.
1
void writeoutput(const char *command, char *out_path, char *err_path)
This should behave something like system, except that
instead of letting the child print to stdout, it should write what the
child prints to stdout to the file out_path and to stderr
to the file err_path.
You must do this by forking, execing, and dup2’ing yourself. You may
not use any standard library function that does some of those operations
for you, including but not limited to popen.
We will supply an example implementation of my_system
from the prior fork lab as a potential starting point sometime after the
late deadline for that lab on Canvas under the Files
tab.
The following main function
int main() {
printf("Hi!\n");
writeoutput("echo 1 2 3; sleep 2; echo 5 5", "out.txt", "err.txt");
printf("Bye!\n");
}Should print
Hi!
then wait for 2 seconds before printing
Bye!
And should leave
1 2 3
5 5
In out.txt.
(And an empty file in err.txt.)
You may assume that the command exists and executes normally; no need to add any error-handling logic.
2
void parallelwriteoutput(int count, const char **argv_base, const char *out_file)
Run count child processes simulatenously,
arranging for them to write all their outputs (stdouts) to
out_file, returning only after all the child processes have
finished. Each of the child processes should run a command specified by
the NULL-pointer-terminated array argv_base as follows:
- the executable run shall be
argv_base[0] - the arguments (
argvvalue) for the program run shall be the elements ofargv_basefollowed by the 0-based index of the child process converted to a string
(Running a command this way easiest if you do not run a
shell (like /bin/sh), so your call to an exec function will
likely be different than it was for writeoutput.)
The output collected may interleave the outputs of the child processes (and an implementation that does this is simpler than one that does not).
You may assume the executable argv_base[0] exists and
executes normally and that we supply the full path of the
executable.
Before returning, parallelwriteoutput must
waitpid for each child process.
For example, a main() like:
int main() {
const char *argv_base[] = {
"/bin/echo", "running", NULL
};
parallelwriteoutput(2, argv_base, "out.txt");
}
would start two child processes. One of them would run something equivalent to:
const char *argv[] = {"/bin/echo", "running", "0", NULL};
execv("/bin/echo", argv);
And another would run something equivalent to:
const char *argv[] = {"/bin/echo", "running", "1", NULL};
execv("/bin/echo", argv);
Then, it would wait for both child processes to finish and collect
their combined output into a single file output.txt. On a system with a
/bin/echo program like exists in portal, the out.txt would
probably be either:
running 0
running 1
or:
running 1
running 0
(but on some systems, maybe other interleaved outputs would be possible like:
running running 1
0
)
You can test more directly that your parallelwriteoutput passes the correct arguments by writing a program that checks the arguments it gets. For example, the following python program:
#!/usr/bin/python3
import sys
print("args =",sys.argv)
prints out its arguments. (You could also write and compile a similar
C program.1) If you save this a file called
args.py
and make that file executable, then a main() like:
int main() {
const char *argv_base[] = { "./args.py", "first", "second", "third", NULL };
parallelwriteoutput(3, argv_base, "out.txt");
}
Should result in an out.txt like:
args = ['./args.py', 'first', 'second', 'third', '0']
args = ['./args.py', 'first', 'second', 'third', '1']
args = ['./args.py', 'first', 'second', 'third', '2']
but possibly with the lines in a different order and/or interleaved.
3 Hints
3.1 Catching memory errors reliably
Memory errors such as using uninitialized memory unintentionally are a common problem on this assignment. To help avoid these problems, I recommend testing by compiling and linking using the flags:
-fsanitize=address -ftrivial-auto-var-init=pattern -Og -g-fsanitize=addresswill enable AddressSanitizer, which checks for accessing out-of-bounds mmeory;-ftrivial-auto-var-init=patternwill initialize local variables with a pattern that should more consistently trigger errors (rather than oftenworking
by accident due to leftover values on the stack). If you are using GCC, note that that this requires GCC version at least 12 (available on portal/NX viagcc-12or viamodule load gcc). (It’s been supported in Clang for much longer.)-Ogenables compiler optimizations that should work well with debugging-genables debugging information. (In addition to being useful when using the debugger, this should allow AddressSanitizer to give better information about errors it finds).
3.2 fork lab code
You may find it useful to consult your code for the fork lab. If you did not complete it, an example solution will be available on Canvas, under the files tab.
3.3 Testing that you
waitpid
If your writeoutput and parallelwriteoutput
call waitpid properly, then after they return running
waitpid(-1, NULL, 0)
should return -1 and set errno to ECHILD
(indicating that there are no child processes to wait for).
3.4 Some corner cases to test
I would recommend testing:
parallelwriteoutputwith 0 arguments after the program name or with a bunch of arguments after the program nameparallelwriteoutputwith programs that wait a few seconds — verify that it waits until the program finishes and runs them all at once. (For example, if each of 10 programs it runs wait 5 seconds, it should take around 5 seconds, not 50.)
Like:
↩︎#include <stdio.h> int main(int argc, char **argv) { printf("args = ["); for (int i = 0; i < argc; i += 1) printf("'%s'%s", argv[i], i == argc - 1 ? "" : ", "); printf("]\n"); }