Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

linux command setsid

Tags:

bash

sessionid

I am trying to write a wrapper which will execute a script as a session leader. I am confused by the behaviour of the linux command setsid. Consider this script, called test.sh:

#!/bin/bash
SID=$(ps -p $$ --no-headers -o sid)
if [ $# -ge 1 -a $$ -ne $SID ] ; then
  setsid bash test.sh
  echo pid=$$ ppid=$PPID sid=$SID parent
else
  sleep 2
  echo pid=$$ ppid=$PPID sid=$SID child
  sleep 2
fi

The output differs depending on whether it is executed or sourced:

$ bash
$ SID=$(ps -p $$ --no-headers -o sid)
$ echo pid=$$ ppid=$PPID sid=$SID
pid=9213 ppid=9104 sid= 9104
$ ./test.sh 1 ; sleep 5
pid=9326 ppid=9324 sid= 9326 child
pid=9324 ppid=9213 sid= 9104 parent
$ . ./test.sh 1 ; sleep 5
pid=9213 ppid=9104 sid= 9104 parent
pid=9336 ppid=1 sid= 9336 child
$ echo $BASH_VERSION 
4.2.8(1)-release
$ exit
exit

So, it seems to me that setsid returns immediately when the script is sourced, but it waits for its child when the script is executed. Why would the presence of a controlling tty have anything to do with setsid? Thanks!

Edit: For clarification I added pid/ppid/sid reporting to all relevant commands.

like image 425
Matei David Avatar asked Mar 13 '12 01:03

Matei David


People also ask

What is OS Setsid?

setsid creates a new session id for the command you run using it, so that it does not depend on your shell session. If the shell session is closed the other command will continue to run.

What is Session ID Linux?

By convention, the session ID of a session equals the process ID of the first member of the session, called the session leader. A process finds the ID of its session using the system call getsid() . Every session may have a controlling tty, that then also is called the controlling tty of each of its member processes.

Why is it that only a process which is not a process group leader can call Setsid () system call?

setsid() creates a new session if the calling process is not a process group leader. The calling process is the leader of the new session, the process group leader of the new process group, and has no controlling tty. The process group ID and session ID of the calling process are set to the PID of the calling process.


2 Answers

The source code of the setsid utility is actually very straightforward. You'll note that it only fork()s if it sees that its process ID and process-group ID are equal (i.e., if it sees that it's a process group leader) — and that it never wait()s for its child process: if it fork()s, then the parent process just returns immediately. If it doesn't fork(), then it gives the appearance of wait()ing for a child, but really what happens is just that it is the child, and it's Bash that's wait()ing (just as it always does). (Of course, when it really does fork(), Bash can't wait() for the child it creates, because processes wait() for their children, not their grandchildren.)

So the behavior that you're seeing is a direct consequence of a different behavior:

  • when you run . ./test.sh or source ./test.sh or whatnot — or for that matter, when you just run setsid directly from the Bash prompt — Bash will launch setsid with a new process-group-ID for job control purposes, so setsid will have the same process-ID as its process-group-ID (that is, it's a process group leader), so it will fork() and won't wait().
  • when you run ./test.sh or bash test.sh or whatnot and it launches setsid, setsid will be part of the same process group as the script that's running it, so its process-ID and process-group-ID will be different, so it won't fork(), so it'll give the appearance of waiting (without actually wait()ing).
like image 182
ruakh Avatar answered Sep 17 '22 12:09

ruakh


The behavior I observe is what I expect, though different from yours. Can you use set -x to make sure you're seeing things right?

$ ./test.sh 1
child
parent
$ . test.sh 1
child
$ uname -r
3.1.10
$ echo $BASH_VERSION
4.2.20(1)-release

When running ./test.sh 1, the script's parent — the interactive shell — is the session leader, so $$ != $SID and the conditional is true.

When running . test.sh 1, the interactive shell is executing the script in-process, and is its own session leader, so $$ == $SID and the conditional is false, thus never executing the inner child script.

like image 25
ephemient Avatar answered Sep 21 '22 12:09

ephemient