Blame src/python/daemonize.py

Packit bbb0ff
#!/usr/bin/env python
Packit bbb0ff
Packit bbb0ff
# Authors:  Jiri Jaburek  <jjaburek@redhat.com>
Packit bbb0ff
#
Packit bbb0ff
# Description: Daemonization backend for rlDaemonize
Packit bbb0ff
#
Packit bbb0ff
# Copyright (c) 2012 Red Hat, Inc. All rights reserved. This copyrighted
Packit bbb0ff
# material is made available to anyone wishing to use, modify, copy, or
Packit bbb0ff
# redistribute it subject to the terms and conditions of the GNU General
Packit bbb0ff
# Public License v.2.
Packit bbb0ff
#
Packit bbb0ff
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit bbb0ff
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
Packit bbb0ff
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
Packit bbb0ff
# for more details.
Packit bbb0ff
#
Packit bbb0ff
# You should have received a copy of the GNU General Public License
Packit bbb0ff
# along with this program; if not, write to the Free Software
Packit bbb0ff
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Packit bbb0ff
Packit bbb0ff
import os, sys
Packit bbb0ff
Packit bbb0ff
from pwd import getpwnam
Packit bbb0ff
from grp import getgrnam
Packit bbb0ff
Packit bbb0ff
from optparse import OptionParser
Packit bbb0ff
from shlex import shlex
Packit bbb0ff
Packit bbb0ff
def file_write(filename, content):
Packit bbb0ff
    fd = open(filename, 'w')
Packit bbb0ff
    fd.write(content + '\n')
Packit bbb0ff
    fd.close()
Packit bbb0ff
Packit bbb0ff
def close_all_fds():
Packit bbb0ff
    try:
Packit bbb0ff
        maxfd = os.sysconf('SC_OPEN_MAX')  # same as _SC_OPEN_MAX in C
Packit bbb0ff
    except:
Packit bbb0ff
        maxfd = 1024
Packit bbb0ff
Packit bbb0ff
    for fd in range(0, maxfd):
Packit bbb0ff
        try:
Packit bbb0ff
            os.close(fd)
Packit bbb0ff
        except OSError:
Packit bbb0ff
            pass
Packit bbb0ff
Packit bbb0ff
# daemonize `command' (list) with arguments,
Packit bbb0ff
# optionally change argv[0] to `alias',
Packit bbb0ff
#            write child pid to file named `pidfile',
Packit bbb0ff
#            fully daemonize or just background (fork) - `true_daemon',
Packit bbb0ff
#            change effective+real user/group to `su' (list, user and group)
Packit bbb0ff
#            (when true_daemon=True) redirect stdin/out/err to filenames
Packit bbb0ff
#              specified in ioredir vector (list),
Packit bbb0ff
def daemonize(command, alias=None, pidfile=None, true_daemon=True, su=None, ioredir=None):
Packit bbb0ff
    if not alias:
Packit bbb0ff
        alias = command[0]
Packit bbb0ff
Packit bbb0ff
    if not true_daemon:
Packit bbb0ff
        pid = os.fork()
Packit bbb0ff
        if (pid != 0):
Packit bbb0ff
            # parent, write pidfile and _exit,
Packit bbb0ff
            # avoiding possible double cleanups/flushes
Packit bbb0ff
            if pidfile:
Packit bbb0ff
                file_write(pidfile, str(pid))
Packit bbb0ff
            os._exit(0)
Packit bbb0ff
        else:
Packit bbb0ff
           # child, simply execute
Packit bbb0ff
           os.execvp(command[0],[alias]+command[1:])
Packit bbb0ff
Packit bbb0ff
    else:
Packit bbb0ff
        pid = os.fork()
Packit bbb0ff
        if (pid != 0):
Packit bbb0ff
            # parent, just exit, since final pid depends on the second fork
Packit bbb0ff
            os._exit(0)
Packit bbb0ff
        else:
Packit bbb0ff
            os.setsid()
Packit bbb0ff
            pid = os.fork()
Packit bbb0ff
            if (pid != 0):
Packit bbb0ff
                # parent of the second child
Packit bbb0ff
                if pidfile:
Packit bbb0ff
                    file_write(pidfile, str(pid))
Packit bbb0ff
                os._exit(0)
Packit bbb0ff
            else:
Packit bbb0ff
                # second child, real daemon!
Packit bbb0ff
Packit bbb0ff
                os.chdir('/')
Packit bbb0ff
Packit bbb0ff
                # change real and effective uid/gid of a process
Packit bbb0ff
                if su:
Packit bbb0ff
                    uid = getpwnam(su[0]).pw_uid
Packit bbb0ff
                    gid = getgrnam(su[1]).gr_gid
Packit bbb0ff
                    os.setgroups([])
Packit bbb0ff
                    os.setregid(gid, gid)
Packit bbb0ff
                    os.setreuid(uid, uid)
Packit bbb0ff
Packit bbb0ff
                # pre-create possible in/out files with default umask,
Packit bbb0ff
                # with original stderr (in case of errors), but with new uid/gid
Packit bbb0ff
                if ioredir:
Packit bbb0ff
                    os.open(ioredir[0], os.O_RDWR)
Packit bbb0ff
                    os.open(ioredir[1], os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0666)
Packit bbb0ff
                    os.open(ioredir[2], os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0666)
Packit bbb0ff
Packit bbb0ff
                os.umask(0)
Packit bbb0ff
Packit bbb0ff
                close_all_fds()
Packit bbb0ff
                if ioredir:
Packit bbb0ff
                    for ioport in ioredir:
Packit bbb0ff
                        os.open(ioport, os.O_RDWR)
Packit bbb0ff
                else:
Packit bbb0ff
                    os.open(os.devnull, os.O_RDWR)
Packit bbb0ff
                    os.dup2(0,1)
Packit bbb0ff
                    os.dup2(0,2)
Packit bbb0ff
Packit bbb0ff
                # execute
Packit bbb0ff
                os.execvp(command[0],[alias]+command[1:])
Packit bbb0ff
Packit bbb0ff
Packit bbb0ff
# argument parsing
Packit bbb0ff
def error(msg):
Packit bbb0ff
    print >> sys.stderr, "error: " + str(msg)
Packit bbb0ff
    sys.exit(1)
Packit bbb0ff
Packit bbb0ff
parser = OptionParser(usage='%prog [options] COMMAND')
Packit bbb0ff
parser.add_option('--alias', action='store', type='string', metavar='NAME',
Packit bbb0ff
                  dest='alias', help='specify custom argv[0]')
Packit bbb0ff
parser.add_option('--background', action='store_true',
Packit bbb0ff
                  dest='background', help='background (fork) only, nothing else')
Packit bbb0ff
parser.add_option('--su', action='store', type='string', metavar='USER:GROUP',
Packit bbb0ff
                  dest='su', help='run daemon under another user')
Packit bbb0ff
parser.add_option('--ioredir', action='store', type='string', metavar='IN,OUT,ERR',
Packit bbb0ff
                  dest='ioredir', help='redirect std{in,out,err} of the daemon to files')
Packit bbb0ff
parser.add_option('--pidfile', action='store', type='string', metavar='FILE',
Packit bbb0ff
                  dest='pidfile', help='write daemon pid to a file')
Packit bbb0ff
Packit bbb0ff
(opts, args) = parser.parse_args()
Packit bbb0ff
Packit bbb0ff
# additional parsing
Packit bbb0ff
if opts.su:
Packit bbb0ff
    su = opts.su.split(':')
Packit bbb0ff
else:
Packit bbb0ff
    su = None
Packit bbb0ff
if opts.ioredir:
Packit bbb0ff
    ioredir = opts.ioredir.split(',')
Packit bbb0ff
else:
Packit bbb0ff
    ioredir = None
Packit bbb0ff
Packit bbb0ff
# sanity checks
Packit bbb0ff
if not args:
Packit bbb0ff
    error("no COMMAND specified")
Packit bbb0ff
if len(args) > 1:
Packit bbb0ff
    error("COMMAND can be only one argument, quote it")
Packit bbb0ff
if opts.su:
Packit bbb0ff
    if len(su) != 2:
Packit bbb0ff
        error("wrong --su argument specification")
Packit bbb0ff
    for i in su:
Packit bbb0ff
        if not i:
Packit bbb0ff
            error("wrong --su argument specification")
Packit bbb0ff
if opts.ioredir:
Packit bbb0ff
    if len(ioredir) != 3:
Packit bbb0ff
        error("wrong --ioredir argument specification")
Packit bbb0ff
    for i in ioredir:
Packit bbb0ff
        if not i:
Packit bbb0ff
            error("wrong --ioredir argument specification")
Packit bbb0ff
Packit bbb0ff
# shell-expand the COMMAND into list
Packit bbb0ff
lex = shlex(args[0])
Packit bbb0ff
lex.whitespace_split = True
Packit bbb0ff
args = list(lex)
Packit bbb0ff
Packit bbb0ff
# input parsing finished
Packit bbb0ff
daemonize(args, alias=opts.alias, pidfile=opts.pidfile,
Packit bbb0ff
          true_daemon=(not opts.background), su=su, ioredir=ioredir)