Link to home
Start Free TrialLog in
Avatar of chsalvia
chsalvia

asked on

Using execl to execute a daemon

I'm writing a program in C on Linux which includes a module that allows a shell command to be executed on a remote machine.  The easiest way to actually execute the command would of course be to simply use the system() function, or use popen and then grab the output.  However, I chose to use a more low-level approach due to other design requirements which are not relevant to the current problem.

So basically, I set up a pipe and fork, and then call execl.  This all works perfectly, except for one annoying exception.  It doesn't work properly if the shell command to be executed is a daemon.  In that case, it just hangs.  I can't figure out why.  My understanding is that when a daemon starts, it typically forks and then kills its parent.  Since my application has an open pipe to the parent, the call to read() should fail when the daemon kills its parent.  But instead the application just hangs.

Attached below is some bare bones code that reproduces the problem.  The code works fine if you use it with a normal shell command.  But if you try to run a daemon, it just hangs instead of returning to the prompt as it should.
int main(int argc, char** argv)
{
	if (argc < 2) {
		printf("Please specify a shell command\n");
		return 0;
	}
 
	// Create a pipe and fork
	//
	int fd[2];
	int p = pipe(fd);
	pid_t pid = fork();
 
	if (pid > 0) 
	{
		// Read from the pipe and output the result
		//
		close(fd[1]);
		char buf[1024] = { 0 };
		read(fd[0], buf, sizeof(buf));
		printf("%s\n", buf);
 
		// Wait for child to terminate
		int status;
		wait(&status);
	}
	else if (pid == 0) 
	{
		// Redirect stdout and stderr to the pipe and execute the shell
		// command
		//
		dup2(fd[1], STDOUT_FILENO);
		dup2(fd[1], STDERR_FILENO);
		close(fd[0]);
		execl("/bin/sh", "sh", "-c", argv[1], 0);
	}
}

Open in new window

Avatar of mrjoltcola
mrjoltcola
Flag of United States of America image

>>  My understanding is that when a daemon starts, it typically forks and then kills its parent.  Since my application has an open pipe to the parent, the call to read() should fail when the daemon kills its parent.  But instead the application just hangs.

No, a daemon parent exits on its own. A typical daemon forks twice, once to return control to the shell/prompt, and second to disassociate from the controlling terminal. The child does not kill the parent.

But, the problem in your case is probably because a daemon closes its file descriptors. So any pipes you created before exec will be closed on the daemon end and you get EOF.
Avatar of chsalvia
chsalvia

ASKER

>> But, the problem in your case is probably because a daemon closes its file descriptors. So any pipes you created before exec will be closed on the daemon end and you get EOF.

Right, I'm counting on getting an EOF if I execute a daemon.  But the problem is that the call to read() doesn't return 0 as it should if it gets an EOF.  Instead, it just hangs waiting for data to become available on the pipe.  

Regardless, although a daemon does close standard file descriptors, doesn't it usually do that after it has forked?  Therefore, the daemons parent should still have standard file descriptors open.
>>Regardless, although a daemon does close standard file descriptors, doesn't it usually do that after it has forked?  Therefore, the daemons parent should still have standard file descriptors open.

Only for the amount of time it takes for the parent to fork + exit, which should close the descriptor. Are you sure it is exiting? Are you sure it is a proper daemon?
>>Only for the amount of time it takes for the parent to fork + exit, which should close the descriptor. Are you sure it is exiting?
ps -A shows that the daemon parent has not exited, but is still there as a zombie process.

>>Are you sure it is a proper daemon?
Well, I tried it on two different daemons.  One was httpd from apache which is a pretty widely used daemon.  
>>ps -A shows that the daemon parent has not exited, but is still there as a zombie process.

That means it did exit, but has not been reaped by your own process that forked the child that execed due to the fact that your wait() call hasn't happened since it is blocked on the read(). But the descriptors will be closed anyway. So this is odd, and I am missing something too, I will take a closer look.



Ah, it dawned on me. Even though you dup() the pipe descriptors onto STDOUT and STDERR, you still have the pipe descriptor itself open. So when the execed daemon closes STDIN, STDOUT, STDERR, it still hangs onto whatever descriptor the pipe was. Your shell also needs to close the pipe because it might be descriptor 7, and the daemon won't know to close 7, so it hangs. As long as there is one reference to a duped handle, the handle is open.

dup2(fd[1], STDOUT_FILENO);
dup2(fd[1], STDERR_FILENO);
close(fd[1]); /* not needed anymore, we have stdout/stderr */
ASKER CERTIFIED SOLUTION
Avatar of mrjoltcola
mrjoltcola
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Here is a quicky test daemon for you to use for verification. Good luck.
// test daemon
int main() {
 
  if(fork() > 0)
    exit(0);
  setgid(0);
 
  if(fork() > 0)
    exit(0);
 
  close(0);
  close(1);
  close(2);
  sleep(5);
  printf("daemon exit\n");
}

Open in new window

Reading the solution, I see my comment is misleading for another reader. For clarification, the line of code "close(fd[1]);" _is_ needed and is the part that was added. The comment // not needed anymore... refers to the pipe descriptor in fd[1] not being needed anymore, so we close it.