I'm working on building a custom command line interpreter in C, and I'm running into issues with process group management and terminal control when executing commands. Below is the main code I'm using:
static void exec(list* lst)
{
int len, pid, analyze_res, i;
cmd_params params;
int gid = -1;
analyze_res = get_cmd_info(¶ms, lst);
if (analyze_res == -1)
return;
if (strcmp(lst->first->word, "cd") == 0) {
cd(lst);
return;
}
int fd[params.cnt_prog - 1][2];
for (i = 0; i < params.cnt_prog; i++) {
len = proc_len(lst);
char* buf[len + 1];
proc2buf(&lst, buf);
buf[len] = NULL;
if (i < params.cnt_prog - 1) { // No need to create pipe for the last command
pipe(fd[i]);
}
pid = fork();
if (pid == 0) {
if (gid == -1) {
setpgid(0, 0);
} else {
setpgid(0, gid);
}
close(fd[i][0]); //close current read descriptor for this process
if (i > 0) { // Set input from previous pipe if not the first command
dup2(fd[i - 1][0], 0);
close(fd[i - 1][0]);
} else {
if (!redirect_in(¶ms)) {
exit(1);
}
}
if (i < params.cnt_prog - 1) { // Set output to the next pipe if not the last command
dup2(fd[i][1], 1);
close(fd[i][1]);
} else {
if (!redirect_out(¶ms)) {
exit(1);
}
}
execvp(buf[0], buf);
perror(buf[0]);
exit(1);
}
if (gid == -1) {
gid = pid; //group id is equal to first child id
}
// Close pipe ends in the parent
if (i > 0) {
close(fd[i - 1][0]); // Close the reading end of the previous pipe
}
if (i < params.cnt_prog - 1) {
close(fd[i][1]); // Close the writing end of the current pipe
}
}
if (!params.bg) {
tcsetpgrp(STDIN_FILENO, gid);
for (i = 0; i < params.cnt_prog; i++) {
waitpid(-gid, NULL, 0); // Wait for any child in the process group
}
tcsetpgrp(STDIN_FILENO, getpgrp());
}
}
this part is executed, if command group is in foreground:
if (!params.bg) {
tcsetpgrp(STDIN_FILENO, gid);
for (i = 0; i < params.cnt_prog; i++) {
waitpid(-gid, NULL, 0); // Wait for any child in the process group
}
tcsetpgrp(STDIN_FILENO, getpgrp());
}
I'm using setpgid(0, 0) to create a new process group for the first child process and setpgid(0, gid) for the subsequent processes in the pipeline to join the same group. However, I'm encountering issues where my shell doesn't regain control of the terminal after the command execution.
Even though I'm using tcsetpgrp(STDIN_FILENO, gid) to give the terminal to the process group and later tcsetpgrp(STDIN_FILENO, getpgrp()) to regain control, my shell remains in the background.
STDIN_FILENOis not guaranteed to be the file descriptor of the session's controlling terminal.tcsetpgrp()calls failing? You don't know, because you don't check. Likewise several other library function calls in this code. Except where in fact you don't care, it is important to check for failing function calls, and to handle them gracefully (as much as is possible) in the event that they occur.buf[len] = NULL;instead ofbuf[len] = 0;?NULLbest used with pointers.