Pipelines with built-ins second test fail

When I am running the test for the “tail -f /tmp/bar/file-15 | head -n 5” pipeline command my tests are failing partially and I am not understanding why.
I managed to get the first part of the output right


but I am not sure how I am supposed to get the second part, because is not present in the file to begin with.

This is my code:
// Create a pipe to redirect the output of the previous command to the stdin of the next command
int inpipe[2], outpipe[2];
if (pipe(outpipe) == -1 || pipe(inpipe) == -1) {
	std::cerr << "Error creating pipe" << std::endl;
	return;
}

// Create a child process to execute the command
pid_t pid = fork();
if (pid < 0) {
	std::cerr << "Error forking process" << std::endl;
	return;
}
// Child process: execute the command
if (pid == 0) {
	close(inpipe[1]); close(outpipe[0]); // Close unused pipe ends
	dup2(inpipe[0], STDIN_FILENO);
	dup2(outpipe[1], STDOUT_FILENO);
	close(inpipe[0]); close(outpipe[1]); // Close the original pipe ends

	// Prepare the argument list for execvp
	std::vector<std::string> args; 
	std::vector<char*> argsVector;

	argsVector.push_back(const_cast<char*>(command_path.c_str())); // Add the command
	if (!commandData.args.empty()){
		// Split the arguments by spaces and add them to the argsVector
		args = split(commandData.args, ' ');
		for (const auto &arg : args) {
			argsVector.push_back(const_cast<char*>(arg.c_str())); // Add the argument and null-terminate it
		}
	}
	argsVector.push_back(nullptr); // Null-terminate the argument list
	
	execvp(command_path.c_str(), argsVector.data());
	perror("execvp failed");
	_exit(EXIT_FAILURE); // Exit if execvp fails
}

// Parent: write previous command output to stdin of the child process 1
close(inpipe[0]); close(outpipe[1]);
write(inpipe[1], commandData.stdinCmd.c_str(), commandData.stdinCmd.size());
close(inpipe[1]); // Close the write end of the pipe
	

// Read the output of the child process
	
char buffer[1024]; // Buffer to store the output
ssize_t bytesRead;

while ((bytesRead = read(outpipe[0], buffer, sizeof(buffer) - 1)) > 0) {
	buffer[bytesRead] = '\0'; // Null-terminate the string
	commandData.stdoutCmd += buffer; // Append the output to the string

	if (originalCommand == "tail"){
		kill(pid, SIGTERM); // Terminate the child process
	}
}
close(outpipe[0]); // Close the read end of the pipe
// Wait for the child process to finish
waitpid(pid, nullptr, 0); 
return;

killing the process is a temporary solution, planning to replace it with a timeout function :slight_smile:

Yeah, that echo line and the related test does look suspicious, and I am not sure how the test could pass.

But I did notice something related. Seems like you do in-complete read and writes of the pipes? For example your write command where you write the previous output is a single write call, but writes may transfer fewer bytes, so you are not guaranteed to push the entire input. Later then your loop - with your “temporary kill pid” code - you kill the process if the original command is “tail”, which means you might not read the entire output generated! And were it not for the suspicious test setup, this would at least by one possible explanation for not generating full output.

Furthermore, I believe pipes may block due to buffering, so while you may get lucky in smaller inputs, I think there is the real possibility of deadlock. Pipes need to flow, and you are partially saved because you only write once, but consider if the sub-processes are not done, and needs more input (to generate their output), but you sitting in a read-loop waiting for their output. Nothing will happen then.

1 Like

Hey @UdristeAndrei, I’m not quite sure why two pipes (inpipe and outpipe) are used.

You might want to try simplifying the logic by using just one pipe.

Closing this thread due to inactivity. If you still need assistance, feel free to reopen or start a new discussion!

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.