views:

542

answers:

2

Are there any special considerations for using Python in an 'init.d' script being run through init? (i.e. booting Ubuntu)

From what I understand through googling/testing on Ubuntu, the environment variables provided to an 'init.d' script are scarce and so using "#!/usr/bin/env python" might not work.

Anything else?

+1  A: 

I am assuming this is running some kind of daemon written in python, if not then this may not apply.

You will (probably) want to do the standard unix double fork and redirect file descriptors thing. This is the one I use (Adapted from an ActiveState code recepie whose url eludes me at the moment).

def daemonize(stdin, stdout, stderr, pidfile):
    if os.path.isfile(pidfile):
        p = open(pidfile, "r")
        oldpid = p.read().strip()
        p.close()
        if os.path.isdir("/proc/%s"%oldpid):
            log.err("Server already running with pid %s"%oldpid)
            sys.exit(1)
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError, e:
        log.err("Fork #1 failed: (%d) %s"%(e.errno, e.strerror))
        sys.exit(1)
    os.chdir("/")
    os.umask(0)
    os.setsid()
    try:
        pid = os.fork()
        if pid > 0:
            if os.getuid() == 0:
                pidfile = open(pidfile, "w+")
                pidfile.write(str(pid))
                pidfile.close()
            sys.exit(0)
    except OSError, e:
        log.err("Fork #2 failed: (%d) %s"%(e.errno, e.strerror))
        sys.exit(1)
    try:
        os.setgid(grp.getgrnam("nogroup").gr_gid)
    except KeyError, e:
        log.err("Failed to get GID: %s"%e)
        sys.exit(1)
    except OSError, e:
        log.err("Failed to set GID: (%s) %s"%(e.errno, e.strerror))
        sys.exit(1)
    try:
        os.setuid(pwd.getpwnam("oracle").pw_uid)
    except KeyError, e:
        log.err("Failed to get UID: %s"%e)
        sys.exit(1)
    except OSError, e:
        log.err("Failed to set UID: (%s) %s"%(e.errno, e.strerror))
        sys.exit(1)
    for f in sys.stdout, sys.stderr:
        f.flush()
    si = open(stdin, "r")
    so = open(stdout, "a+")
    se = open(stderr, "a+", 0)
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())

Just run this before starting up your daemon loop and it will probably do the right thing.

As a side note, I am using #!/usr/bin/env python as the shebang line in a script on ubuntu and it is working fine for me.

You will probably still want to redirect stdout/stderr to a file even if you are not running a daemon to provide debug info.

Peter Ellis
init scripts are not daemons.
PiotrLegnica
Peter - I am referring to a script in the /etc/init.d/ directory (executed at boot time); the said script is written in Python (because I can't stand bash).
jldupont
+1  A: 

That just highlights the biggest problem with python in an init.d script -- added complexity.

Python has no specification, and the env doesn't even have to point to cpython. If you upgrade and python breaks, you'll have to bite your tongue. And there is a much greater chance that python will break than sh (the safe bet for init.d scripts). Reason being, simple utility:

ecarroll@x60s:/etc/init.d$ ldd /usr/bin/python
    linux-gate.so.1 =>  (0xb7ff7000)
    libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0xb7fc9000)
    libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0xb7fc5000)
    libutil.so.1 => /lib/tls/i686/cmov/libutil.so.1 (0xb7fc0000)
    libz.so.1 => /lib/libz.so.1 (0xb7faa000)
    libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7f84000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e21000)
    /lib/ld-linux.so.2 (0xb7ff8000)
ecarroll@x60s:/etc/init.d$ ldd /bin/sh
    linux-gate.so.1 =>  (0xb803f000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7ec7000)
    /lib/ld-linux.so.2 (0xb8040000)

Python is linking into libpthread, libdl, libutil, libz, libm amongst other things that can possibly break. Python is simply doing more.

-rwxr-xr-x 1 root root  86K 2008-11-05 01:51 /bin/dash
-rwxr-xr-x 1 root root 2.2M 2009-04-18 21:53 /usr/bin/python2.6

You can read up more about what you're specifically talking about with env variables here: http://www.debian.org/doc/debian-policy/ch-opersys.html#s9.9 The main problem is that the defaults for env can be set in /etc/profile which would only run if the script is being run under a shell that supports reading it.

Evan Carroll