All your macros are making this harder to read than if you just wrote it straight. Especially when they refer to local variables. To find out what's going on with EXEC
my eyes have to jump up from where it's used to where it's defined, find out which local arrays it uses, then jump back down to see how that access fits in the flow of main
. It's a maze of macros.
And wow, cmdfinder
? Your very own $PATH
lookup, only it's hardcoded /usr/bin:/bin
? And double wow, readdir
, just to find out if a file exists whose name is already decided? Just stat
it! Or don't do anything, just exec it and handle the ENOENT
by trying the next one. Or use execlp
that's what it's there for!
On to the main point... you don't have enough pipes, and you're not closing all the unused descriptors.
last | sort | more
is a pipeline of 3 commands connected by 2 pipes. You can't do it with one pipe. The first command should write into the first pipe, the middle command should read the first pipe and write to the second pipe, and the last command should read the second pipe.
You could create both pipes first, then do all the forks, which makes things simple to follow, but requires a lot of close
s in every child process since they'll all inherit all the pipe fds. Or you can use a more sophisticated loop, creating each pipe just before forking the first process that will use it, and closing each descriptor in the parent as soon as the relevant child process has been created. I'd hate to see how many macros you'd use for that.
Every successful dup should be followed by a close of the descriptor that was copied. dup
is short for "duplicate", not "move". After it's done, you have an extra descriptor left over, so don't just dup2(fdes[1], fileno(stdout)
- also close(fdes[1])
afterward. (To be perfectly robust you should check whether fdes[1]==fileno(stdout)
already, and in that case skip the dup2
and close
.)
FOLLOWUP QUESTIONS
You can't use one pipe for 3 processes because there would be no way to distinguish which data should go to which destination. When the first process writes to the pipe, while both of the other processes are trying to read from it, one of them will get the data but you won't be able to predict which one. You need the middle process to read what the first process writes, and the last process to read what the middle process writes.
You're halfway right about file descriptors being shared after a fork. The actual pipe object is shared. That's what makes the whole system work. But the file descriptors - the endpoints designated by small integers like 1 for standard output, 0 for standard input, and so on - are not coupled the way you suggest. The same pipe object may be associated with the same file descriptor number in two processes, the associations are independent. Closing fd 1 in one process does not cause fd 1 to become closed in any other process, even if they are related.
Sharing of the fd table, so that a close in one task has an effect in another task, is part of the "pthread" feature set, not the "fork" feature set.