diff mbox

[ovs-dev,V9,12/17] python tests: Prepare porting Python daemon on Windows

Message ID 1469534548-4592-1-git-send-email-pboca@cloudbasesolutions.com
State Superseded
Delegated to: Guru Shetty
Headers show

Commit Message

Paul Boca July 26, 2016, 12:02 p.m. UTC
Renamed daemon.py to daemon_unix.py and implemented a wrapper over it.

Signed-off-by: Paul-Daniel Boca <pboca@cloudbasesolutions.com>
---
V8: Initial commit.
V9: No changes
---
 python/automake.mk        |   1 +
 python/ovs/daemon.py      | 489 ++----------------------------------------
 python/ovs/daemon_unix.py | 530 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 550 insertions(+), 470 deletions(-)
 create mode 100644 python/ovs/daemon_unix.py

Comments

Alin Serdean July 26, 2016, 12:28 p.m. UTC | #1
LGTM.
Acked-by: Alin Gabriel Serdean <aserdean@cloudbasesolutions.com>


> -----Mesaj original-----

> De la: dev [mailto:dev-bounces@openvswitch.org] În numele Paul Boca

> Trimis: Tuesday, July 26, 2016 3:03 PM

> Către: dev@openvswitch.org

> Subiect: [ovs-dev] [PATCH V9 12/17] python tests: Prepare porting Python

> daemon on Windows

> 

> Renamed daemon.py to daemon_unix.py and implemented a wrapper over

> it.

> 

> Signed-off-by: Paul-Daniel Boca <pboca@cloudbasesolutions.com>

> ---

> V8: Initial commit.

> V9: No changes

> ---

>  python/automake.mk        |   1 +

>  python/ovs/daemon.py      | 489 ++----------------------------------------

>  python/ovs/daemon_unix.py | 530

> ++++++++++++++++++++++++++++++++++++++++++++++

>  3 files changed, 550 insertions(+), 470 deletions(-)  create mode 100644

> python/ovs/daemon_unix.py

> 

> diff --git a/python/automake.mk b/python/automake.mk index

> 1c8fa38..4d3fcb6 100644

> --- a/python/automake.mk

> +++ b/python/automake.mk

> @@ -11,6 +11,7 @@ ovstest_pyfiles = \

>  ovs_pyfiles = \

>  	python/ovs/__init__.py \

>  	python/ovs/daemon.py \

> +	python/ovs/daemon_unix.py \

>  	python/ovs/fcntl_win.py \

>  	python/ovs/db/__init__.py \

>  	python/ovs/db/data.py \

> diff --git a/python/ovs/daemon.py b/python/ovs/daemon.py index

> bd06195..f45f757 100644

> --- a/python/ovs/daemon.py

> +++ b/python/ovs/daemon.py

> @@ -1,4 +1,4 @@

> -# Copyright (c) 2010, 2011, 2012 Nicira, Inc.

> +# Copyright (c) 2016 Cloudbase Solutions Srl

>  #

>  # Licensed under the Apache License, Version 2.0 (the "License");  # you may

> not use this file except in compliance with the License.

> @@ -12,515 +12,64 @@

>  # See the License for the specific language governing permissions and  #

> limitations under the License.

> 

> -import errno

> -import fcntl

> -import os

> -import resource

> -import signal

>  import sys

> -import time

> 

> -import ovs.dirs

> -import ovs.fatal_signal

> -import ovs.process

> -import ovs.socket_util

> -import ovs.timeval

> -import ovs.util

> -import ovs.vlog

> -

> -vlog = ovs.vlog.Vlog("daemon")

> -

> -# --detach: Should we run in the background?

> -_detach = False

> -

> -# --pidfile: Name of pidfile (null if none).

> -_pidfile = None

> -

> -# Our pidfile's inode and device, if we have created one.

> -_pidfile_dev = None

> -_pidfile_ino = None

> -

> -# --overwrite-pidfile: Create pidfile even if one already exists and is locked?

> -_overwrite_pidfile = False

> -

> -# --no-chdir: Should we chdir to "/"?

> -_chdir = True

> -

> -# --monitor: Should a supervisory process monitor the daemon and restart it

> if -# it dies due to an error signal?

> -_monitor = False

> -

> -# File descriptor used by daemonize_start() and daemonize_complete().

> -_daemonize_fd = None

> -

> -RESTART_EXIT_CODE = 5

> +# This is only a wrapper over Linux implementations if sys.platform !=

> +"win32":

> +    import ovs.daemon_unix as daemon_util

> 

> 

>  def make_pidfile_name(name):

> -    """Returns the file name that would be used for a pidfile if 'name' were

> -    provided to set_pidfile()."""

> -    if name is None or name == "":

> -        return "%s/%s.pid" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME)

> -    else:

> -        return ovs.util.abs_file_name(ovs.dirs.RUNDIR, name)

> +    return daemon_util.make_pidfile_name(name)

> 

> 

>  def set_pidfile(name):

> -    """Sets up a following call to daemonize() to create a pidfile named

> -    'name'.  If 'name' begins with '/', then it is treated as an absolute path.

> -    Otherwise, it is taken relative to ovs.util.RUNDIR, which is

> -    $(prefix)/var/run by default.

> -

> -    If 'name' is null, then ovs.util.PROGRAM_NAME followed by ".pid" is

> -    used."""

> -    global _pidfile

> -    _pidfile = make_pidfile_name(name)

> +    daemon_util.set_pidfile(name)

> 

> 

>  def set_no_chdir():

> -    """Sets that we do not chdir to "/"."""

> -    global _chdir

> -    _chdir = False

> +    daemon_util.set_no_chdir()

> 

> 

>  def ignore_existing_pidfile():

> -    """Normally, daemonize() or daemonize_start() will terminate the

> program

> -    with a message if a locked pidfile already exists.  If this function is

> -    called, an existing pidfile will be replaced, with a warning."""

> -    global _overwrite_pidfile

> -    _overwrite_pidfile = True

> +    daemon_util.ignore_existing_pidfile()

> 

> 

>  def set_detach():

> -    """Sets up a following call to daemonize() to detach from the foreground

> -    session, running this process in the background."""

> -    global _detach

> -    _detach = True

> +    daemon_util.set_detach()

> 

> 

>  def get_detach():

> -    """Will daemonize() really detach?"""

> -    return _detach

> +    return daemon_util.get_detach()

> 

> 

>  def set_monitor():

> -    """Sets up a following call to daemonize() to fork a supervisory process to

> -    monitor the daemon and restart it if it dies due to an error signal."""

> -    global _monitor

> -    _monitor = True

> -

> -

> -def _fatal(msg):

> -    vlog.err(msg)

> -    sys.stderr.write("%s\n" % msg)

> -    sys.exit(1)

> -

> -

> -def _make_pidfile():

> -    """If a pidfile has been configured, creates it and stores the running

> -    process's pid in it.  Ensures that the pidfile will be deleted when the

> -    process exits."""

> -    pid = os.getpid()

> -

> -    # Create a temporary pidfile.

> -    tmpfile = "%s.tmp%d" % (_pidfile, pid)

> -    ovs.fatal_signal.add_file_to_unlink(tmpfile)

> -    try:

> -        # This is global to keep Python from garbage-collecting and

> -        # therefore closing our file after this function exits.  That would

> -        # unlock the lock for us, and we don't want that.

> -        global file_handle

> -

> -        file_handle = open(tmpfile, "w")

> -    except IOError as e:

> -        _fatal("%s: create failed (%s)" % (tmpfile, e.strerror))

> -

> -    try:

> -        s = os.fstat(file_handle.fileno())

> -    except IOError as e:

> -        _fatal("%s: fstat failed (%s)" % (tmpfile, e.strerror))

> -

> -    try:

> -        file_handle.write("%s\n" % pid)

> -        file_handle.flush()

> -    except OSError as e:

> -        _fatal("%s: write failed: %s" % (tmpfile, e.strerror))

> -

> -    try:

> -        fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)

> -    except IOError as e:

> -        _fatal("%s: fcntl failed: %s" % (tmpfile, e.strerror))

> -

> -    # Rename or link it to the correct name.

> -    if _overwrite_pidfile:

> -        try:

> -            os.rename(tmpfile, _pidfile)

> -        except OSError as e:

> -            _fatal("failed to rename \"%s\" to \"%s\" (%s)"

> -                   % (tmpfile, _pidfile, e.strerror))

> -    else:

> -        while True:

> -            try:

> -                os.link(tmpfile, _pidfile)

> -                error = 0

> -            except OSError as e:

> -                error = e.errno

> -            if error == errno.EEXIST:

> -                _check_already_running()

> -            elif error != errno.EINTR:

> -                break

> -        if error:

> -            _fatal("failed to link \"%s\" as \"%s\" (%s)"

> -                   % (tmpfile, _pidfile, os.strerror(error)))

> -

> -    # Ensure that the pidfile will get deleted on exit.

> -    ovs.fatal_signal.add_file_to_unlink(_pidfile)

> -

> -    # Delete the temporary pidfile if it still exists.

> -    if not _overwrite_pidfile:

> -        error = ovs.fatal_signal.unlink_file_now(tmpfile)

> -        if error:

> -            _fatal("%s: unlink failed (%s)" % (tmpfile, os.strerror(error)))

> -

> -    global _pidfile_dev

> -    global _pidfile_ino

> -    _pidfile_dev = s.st_dev

> -    _pidfile_ino = s.st_ino

> +    daemon_util.set_monitor()

> 

> 

>  def daemonize():

> -    """If configured with set_pidfile() or set_detach(), creates the pid file

> -    and detaches from the foreground session."""

> -    daemonize_start()

> -    daemonize_complete()

> -

> -

> -def _waitpid(pid, options):

> -    while True:

> -        try:

> -            return os.waitpid(pid, options)

> -        except OSError as e:

> -            if e.errno == errno.EINTR:

> -                pass

> -            return -e.errno, 0

> -

> -

> -def _fork_and_wait_for_startup():

> -    try:

> -        rfd, wfd = os.pipe()

> -    except OSError as e:

> -        sys.stderr.write("pipe failed: %s\n" % os.strerror(e.errno))

> -        sys.exit(1)

> -

> -    try:

> -        pid = os.fork()

> -    except OSError as e:

> -        sys.stderr.write("could not fork: %s\n" % os.strerror(e.errno))

> -        sys.exit(1)

> -

> -    if pid > 0:

> -        # Running in parent process.

> -        os.close(wfd)

> -        ovs.fatal_signal.fork()

> -        while True:

> -            try:

> -                s = os.read(rfd, 1)

> -                error = 0

> -            except OSError as e:

> -                s = ""

> -                error = e.errno

> -            if error != errno.EINTR:

> -                break

> -        if len(s) != 1:

> -            retval, status = _waitpid(pid, 0)

> -            if retval == pid:

> -                if os.WIFEXITED(status) and os.WEXITSTATUS(status):

> -                    # Child exited with an error.  Convey the same error to

> -                    # our parent process as a courtesy.

> -                    sys.exit(os.WEXITSTATUS(status))

> -                else:

> -                    sys.stderr.write("fork child failed to signal "

> -                                     "startup (%s)\n"

> -                                     % ovs.process.status_msg(status))

> -            else:

> -                assert retval < 0

> -                sys.stderr.write("waitpid failed (%s)\n"

> -                                 % os.strerror(-retval))

> -                sys.exit(1)

> -

> -        os.close(rfd)

> -    else:

> -        # Running in parent process.

> -        os.close(rfd)

> -        ovs.timeval.postfork()

> -

> -        global _daemonize_fd

> -        _daemonize_fd = wfd

> -    return pid

> -

> -

> -def _fork_notify_startup(fd):

> -    if fd is not None:

> -        error, bytes_written = ovs.socket_util.write_fully(fd, "0")

> -        if error:

> -            sys.stderr.write("could not write to pipe\n")

> -            sys.exit(1)

> -        os.close(fd)

> -

> -

> -def _should_restart(status):

> -    global RESTART_EXIT_CODE

> -

> -    if os.WIFEXITED(status) and os.WEXITSTATUS(status) ==

> RESTART_EXIT_CODE:

> -        return True

> -

> -    if os.WIFSIGNALED(status):

> -        for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE", "SIGILL",

> -                        "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):

> -            if os.WTERMSIG(status) == getattr(signal, signame, None):

> -                return True

> -    return False

> -

> -

> -def _monitor_daemon(daemon_pid):

> -    # XXX should log daemon's stderr output at startup time

> -    # XXX should use setproctitle module if available

> -    last_restart = None

> -    while True:

> -        retval, status = _waitpid(daemon_pid, 0)

> -        if retval < 0:

> -            sys.stderr.write("waitpid failed\n")

> -            sys.exit(1)

> -        elif retval == daemon_pid:

> -            status_msg = ("pid %d died, %s"

> -                          % (daemon_pid, ovs.process.status_msg(status)))

> -

> -            if _should_restart(status):

> -                if os.WCOREDUMP(status):

> -                    # Disable further core dumps to save disk space.

> -                    try:

> -                        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))

> -                    except resource.error:

> -                        vlog.warn("failed to disable core dumps")

> -

> -                # Throttle restarts to no more than once every 10 seconds.

> -                if (last_restart is not None and

> -                    ovs.timeval.msec() < last_restart + 10000):

> -                    vlog.warn("%s, waiting until 10 seconds since last "

> -                              "restart" % status_msg)

> -                    while True:

> -                        now = ovs.timeval.msec()

> -                        wakeup = last_restart + 10000

> -                        if now > wakeup:

> -                            break

> -                        sys.stdout.write("sleep %f\n" % (

> -                            (wakeup - now) / 1000.0))

> -                        time.sleep((wakeup - now) / 1000.0)

> -                last_restart = ovs.timeval.msec()

> -

> -                vlog.err("%s, restarting" % status_msg)

> -                daemon_pid = _fork_and_wait_for_startup()

> -                if not daemon_pid:

> -                    break

> -            else:

> -                vlog.info("%s, exiting" % status_msg)

> -                sys.exit(0)

> -

> -    # Running in new daemon process.

> -

> -

> -def _close_standard_fds():

> -    """Close stdin, stdout, stderr.  If we're started from e.g. an SSH session,

> -    then this keeps us from holding that session open artificially."""

> -    null_fd = ovs.socket_util.get_null_fd()

> -    if null_fd >= 0:

> -        os.dup2(null_fd, 0)

> -        os.dup2(null_fd, 1)

> -        os.dup2(null_fd, 2)

> +    daemon_util.daemonize()

> 

> 

>  def daemonize_start():

> -    """If daemonization is configured, then starts daemonization, by forking

> -    and returning in the child process.  The parent process hangs around until

> -    the child lets it know either that it completed startup successfully (by

> -    calling daemon_complete()) or that it failed to start up (by exiting with a

> -    nonzero exit code)."""

> -

> -    if _detach:

> -        if _fork_and_wait_for_startup() > 0:

> -            # Running in parent process.

> -            sys.exit(0)

> -

> -        # Running in daemon or monitor process.

> -        os.setsid()

> -

> -    if _monitor:

> -        saved_daemonize_fd = _daemonize_fd

> -        daemon_pid = _fork_and_wait_for_startup()

> -        if daemon_pid > 0:

> -            # Running in monitor process.

> -            _fork_notify_startup(saved_daemonize_fd)

> -            _close_standard_fds()

> -            _monitor_daemon(daemon_pid)

> -        # Running in daemon process

> -

> -    if _pidfile:

> -        _make_pidfile()

> +    daemon_util.daemonize_start()

> 

> 

>  def daemonize_complete():

> -    """If daemonization is configured, then this function notifies the parent

> -    process that the child process has completed startup successfully."""

> -    _fork_notify_startup(_daemonize_fd)

> -

> -    if _detach:

> -        if _chdir:

> -            os.chdir("/")

> -        _close_standard_fds()

> +    daemon_util.daemonize_complete()

> 

> 

>  def usage():

> -    sys.stdout.write("""

> -Daemon options:

> -   --detach                run in background as daemon

> -   --no-chdir              do not chdir to '/'

> -   --pidfile[=FILE]        create pidfile (default: %s/%s.pid)

> -   --overwrite-pidfile     with --pidfile, start even if already running

> -""" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))

> -

> +    daemon_util.usage()

> 

> -def __read_pidfile(pidfile, delete_if_stale):

> -    if _pidfile_dev is not None:

> -        try:

> -            s = os.stat(pidfile)

> -            if s.st_ino == _pidfile_ino and s.st_dev == _pidfile_dev:

> -                # It's our own pidfile.  We can't afford to open it,

> -                # because closing *any* fd for a file that a process

> -                # has locked also releases all the locks on that file.

> -                #

> -                # Fortunately, we know the associated pid anyhow.

> -                return os.getpid()

> -        except OSError:

> -            pass

> 

> -    try:

> -        file_handle = open(pidfile, "r+")

> -    except IOError as e:

> -        if e.errno == errno.ENOENT and delete_if_stale:

> -            return 0

> -        vlog.warn("%s: open: %s" % (pidfile, e.strerror))

> -        return -e.errno

> -

> -    # Python fcntl doesn't directly support F_GETLK so we have to just try

> -    # to lock it.

> -    try:

> -        fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)

> -

> -        # pidfile exists but wasn't locked by anyone.  Now we have the lock.

> -        if not delete_if_stale:

> -            file_handle.close()

> -            vlog.warn("%s: pid file is stale" % pidfile)

> -            return -errno.ESRCH

> -

> -        # Is the file we have locked still named 'pidfile'?

> -        try:

> -            raced = False

> -            s = os.stat(pidfile)

> -            s2 = os.fstat(file_handle.fileno())

> -            if s.st_ino != s2.st_ino or s.st_dev != s2.st_dev:

> -                raced = True

> -        except IOError:

> -            raced = True

> -        if raced:

> -            vlog.warn("%s: lost race to delete pidfile" % pidfile)

> -            return -errno.EALREADY

> -

> -        # We won the right to delete the stale pidfile.

> -        try:

> -            os.unlink(pidfile)

> -        except IOError as e:

> -            vlog.warn("%s: failed to delete stale pidfile (%s)"

> -                            % (pidfile, e.strerror))

> -            return -e.errno

> -        else:

> -            vlog.dbg("%s: deleted stale pidfile" % pidfile)

> -            file_handle.close()

> -            return 0

> -    except IOError as e:

> -        if e.errno not in [errno.EACCES, errno.EAGAIN]:

> -            vlog.warn("%s: fcntl: %s" % (pidfile, e.strerror))

> -            return -e.errno

> -

> -    # Someone else has the pidfile locked.

> -    try:

> -        try:

> -            error = int(file_handle.readline())

> -        except IOError as e:

> -            vlog.warn("%s: read: %s" % (pidfile, e.strerror))

> -            error = -e.errno

> -        except ValueError:

> -            vlog.warn("%s does not contain a pid" % pidfile)

> -            error = -errno.EINVAL

> -

> -        return error

> -    finally:

> -        try:

> -            file_handle.close()

> -        except IOError:

> -            pass

> -

> -

> -def read_pidfile(pidfile):

> -    """Opens and reads a PID from 'pidfile'.  Returns the positive PID if

> -    successful, otherwise a negative errno value."""

> -    return __read_pidfile(pidfile, False)

> -

> -

> -def _check_already_running():

> -    pid = __read_pidfile(_pidfile, True)

> -    if pid > 0:

> -        _fatal("%s: already running as pid %d, aborting" % (_pidfile, pid))

> -    elif pid < 0:

> -        _fatal("%s: pidfile check failed (%s), aborting"

> -               % (_pidfile, os.strerror(pid)))

> +def read_pidfile(pidfile_name):

> +    return daemon_util.read_pidfile(pidfile_name)

> 

> 

>  def add_args(parser):

> -    """Populates 'parser', an ArgumentParser allocated using the argparse

> -    module, with the command line arguments required by the daemon

> module."""

> -

> -    pidfile = make_pidfile_name(None)

> -

> -    group = parser.add_argument_group(title="Daemon Options")

> -    group.add_argument("--detach", action="store_true",

> -            help="Run in background as a daemon.")

> -    group.add_argument("--no-chdir", action="store_true",

> -            help="Do not chdir to '/'.")

> -    group.add_argument("--monitor", action="store_true",

> -            help="Monitor %s process." % ovs.util.PROGRAM_NAME)

> -    group.add_argument("--pidfile", nargs="?", const=pidfile,

> -            help="Create pidfile (default %s)." % pidfile)

> -    group.add_argument("--overwrite-pidfile", action="store_true",

> -            help="With --pidfile, start even if already running.")

> +    daemon_util.add_args(parser)

> 

> 

>  def handle_args(args):

> -    """Handles daemon module settings in 'args'.  'args' is an object

> -    containing values parsed by the parse_args() method of ArgumentParser.

> The

> -    parent ArgumentParser should have been prepared by add_args() before

> -    calling parse_args()."""

> -

> -    if args.detach:

> -        set_detach()

> -

> -    if args.no_chdir:

> -        set_no_chdir()

> -

> -    if args.pidfile:

> -        set_pidfile(args.pidfile)

> -

> -    if args.overwrite_pidfile:

> -        ignore_existing_pidfile()

> -

> -    if args.monitor:

> -        set_monitor()

> +    daemon_util.handle_args(args)

> diff --git a/python/ovs/daemon_unix.py b/python/ovs/daemon_unix.py

> new file mode 100644 index 0000000..3bd56af

> --- /dev/null

> +++ b/python/ovs/daemon_unix.py

> @@ -0,0 +1,530 @@

> +# Copyright (c) 2010, 2011, 2012, 2016 Nicira, Inc.

> +#

> +# Licensed under the Apache License, Version 2.0 (the "License"); # you

> +may not use this file except in compliance with the License.

> +# You may obtain a copy of the License at:

> +#

> +#     http://www.apache.org/licenses/LICENSE-2.0

> +#

> +# Unless required by applicable law or agreed to in writing, software #

> +distributed under the License is distributed on an "AS IS" BASIS, #

> +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

> implied.

> +# See the License for the specific language governing permissions and #

> +limitations under the License.

> +

> +import errno

> +import os

> +import signal

> +import sys

> +import time

> +

> +import ovs.dirs

> +import ovs.fatal_signal

> +import ovs.process

> +import ovs.socket_util

> +import ovs.timeval

> +import ovs.util

> +import ovs.vlog

> +

> +import fcntl

> +import resource

> +

> +vlog = ovs.vlog.Vlog("daemon")

> +

> +# --detach: Should we run in the background?

> +_detach = False

> +

> +# --pidfile: Name of pidfile (null if none).

> +_pidfile = None

> +

> +# Our pidfile's inode and device, if we have created one.

> +_pidfile_dev = None

> +_pidfile_ino = None

> +

> +# --overwrite-pidfile: Create pidfile even if one already exists and is locked?

> +_overwrite_pidfile = False

> +

> +# --no-chdir: Should we chdir to "/"?

> +_chdir = True

> +

> +# --monitor: Should a supervisory process monitor the daemon and

> +restart it if # it dies due to an error signal?

> +_monitor = False

> +

> +# File descriptor used by daemonize_start() and daemonize_complete().

> +_daemonize_fd = None

> +

> +

> +RESTART_EXIT_CODE = 5

> +

> +

> +def make_pidfile_name(name):

> +    """Returns the file name that would be used for a pidfile if 'name' were

> +    provided to set_pidfile()."""

> +    if name is None or name == "":

> +        return "%s/%s.pid" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME)

> +    else:

> +        return ovs.util.abs_file_name(ovs.dirs.RUNDIR, name)

> +

> +

> +def set_pidfile(name):

> +    """Sets up a following call to daemonize() to create a pidfile named

> +    'name'.  If 'name' begins with '/', then it is treated as an absolute path.

> +    Otherwise, it is taken relative to ovs.util.RUNDIR, which is

> +    $(prefix)/var/run by default.

> +

> +    If 'name' is null, then ovs.util.PROGRAM_NAME followed by ".pid" is

> +    used."""

> +    global _pidfile

> +    _pidfile = make_pidfile_name(name)

> +

> +

> +def set_no_chdir():

> +    """Sets that we do not chdir to "/"."""

> +    global _chdir

> +    _chdir = False

> +

> +

> +def ignore_existing_pidfile():

> +    """Normally, daemonize() or daemonize_start() will terminate the

> program

> +    with a message if a locked pidfile already exists.  If this function is

> +    called, an existing pidfile will be replaced, with a warning."""

> +    global _overwrite_pidfile

> +    _overwrite_pidfile = True

> +

> +

> +def set_detach():

> +    """Sets up a following call to daemonize() to detach from the foreground

> +    session, running this process in the background."""

> +    global _detach

> +    _detach = True

> +

> +

> +def get_detach():

> +    """Will daemonize() really detach?"""

> +    return _detach

> +

> +

> +def set_monitor():

> +    """Sets up a following call to daemonize() to fork a supervisory process to

> +    monitor the daemon and restart it if it dies due to an error signal."""

> +    global _monitor

> +    _monitor = True

> +

> +

> +def _fatal(msg):

> +    vlog.err(msg)

> +    sys.stderr.write("%s\n" % msg)

> +    sys.exit(1)

> +

> +

> +def _make_pidfile():

> +    """If a pidfile has been configured, creates it and stores the running

> +    process's pid in it.  Ensures that the pidfile will be deleted when the

> +    process exits."""

> +    pid = os.getpid()

> +

> +    # Create a temporary pidfile.

> +    tmpfile = "%s.tmp%d" % (_pidfile, pid)

> +    ovs.fatal_signal.add_file_to_unlink(tmpfile)

> +

> +    try:

> +        # This is global to keep Python from garbage-collecting and

> +        # therefore closing our file after this function exits.  That would

> +        # unlock the lock for us, and we don't want that.

> +        global file_handle

> +

> +        file_handle = open(tmpfile, "w")

> +    except IOError as e:

> +        _fatal("%s: create failed (%s)" % (tmpfile, e.strerror))

> +

> +    try:

> +        s = os.fstat(file_handle.fileno())

> +    except IOError as e:

> +        _fatal("%s: fstat failed (%s)" % (tmpfile, e.strerror))

> +

> +    try:

> +        file_handle.write("%s\n" % pid)

> +        file_handle.flush()

> +    except OSError as e:

> +        _fatal("%s: write failed: %s" % (tmpfile, e.strerror))

> +

> +    try:

> +        fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)

> +    except IOError as e:

> +        _fatal("%s: fcntl failed: %s" % (tmpfile, e.strerror))

> +

> +    # Rename or link it to the correct name.

> +    if _overwrite_pidfile:

> +        try:

> +            os.rename(tmpfile, _pidfile)

> +        except OSError as e:

> +            _fatal("failed to rename \"%s\" to \"%s\" (%s)"

> +                   % (tmpfile, _pidfile, e.strerror))

> +    else:

> +        while True:

> +            try:

> +                os.link(tmpfile, _pidfile)

> +                error = 0

> +            except OSError as e:

> +                error = e.errno

> +            if error == errno.EEXIST:

> +                _check_already_running()

> +            elif error != errno.EINTR:

> +                break

> +        if error:

> +            _fatal("failed to link \"%s\" as \"%s\" (%s)"

> +                   % (tmpfile, _pidfile, os.strerror(error)))

> +

> +    # Ensure that the pidfile will get deleted on exit.

> +    ovs.fatal_signal.add_file_to_unlink(_pidfile)

> +

> +    # Delete the temporary pidfile if it still exists.

> +    if not _overwrite_pidfile:

> +        error = ovs.fatal_signal.unlink_file_now(tmpfile)

> +        if error:

> +            _fatal("%s: unlink failed (%s)"

> +                   % (tmpfile, os.strerror(error)))

> +

> +    global _pidfile_dev

> +    global _pidfile_ino

> +    _pidfile_dev = s.st_dev

> +    _pidfile_ino = s.st_ino

> +

> +

> +def daemonize():

> +    """If configured with set_pidfile() or set_detach(), creates the pid file

> +    and detaches from the foreground session."""

> +    daemonize_start()

> +    daemonize_complete()

> +

> +

> +def _waitpid(pid, options):

> +    while True:

> +        try:

> +            return os.waitpid(pid, options)

> +        except OSError as e:

> +            if e.errno == errno.EINTR:

> +                pass

> +            return -e.errno, 0

> +

> +

> +def _fork_and_wait_for_startup():

> +    try:

> +        rfd, wfd = os.pipe()

> +    except OSError as e:

> +        sys.stderr.write("pipe failed: %s\n" % os.strerror(e.errno))

> +        sys.exit(1)

> +

> +    try:

> +        pid = os.fork()

> +    except OSError as e:

> +        sys.stderr.write("could not fork: %s\n" % os.strerror(e.errno))

> +        sys.exit(1)

> +

> +    if pid > 0:

> +        # Running in parent process.

> +        os.close(wfd)

> +        ovs.fatal_signal.fork()

> +        while True:

> +            try:

> +                s = os.read(rfd, 1)

> +                error = 0

> +            except OSError as e:

> +                s = ""

> +                error = e.errno

> +            if error != errno.EINTR:

> +                break

> +        if len(s) != 1:

> +            retval, status = _waitpid(pid, 0)

> +            if retval == pid:

> +                if os.WIFEXITED(status) and os.WEXITSTATUS(status):

> +                    # Child exited with an error.  Convey the same error to

> +                    # our parent process as a courtesy.

> +                    sys.exit(os.WEXITSTATUS(status))

> +                else:

> +                    sys.stderr.write("fork child failed to signal "

> +                                     "startup (%s)\n"

> +                                     % ovs.process.status_msg(status))

> +            else:

> +                assert retval < 0

> +                sys.stderr.write("waitpid failed (%s)\n"

> +                                 % os.strerror(-retval))

> +                sys.exit(1)

> +

> +        os.close(rfd)

> +    else:

> +        # Running in child process.

> +        os.close(rfd)

> +        ovs.timeval.postfork()

> +

> +        global _daemonize_fd

> +        _daemonize_fd = wfd

> +    return pid

> +

> +

> +def _fork_notify_startup(fd):

> +    if fd is not None:

> +        error, bytes_written = ovs.socket_util.write_fully(fd, "0")

> +        if error:

> +            sys.stderr.write("could not write to pipe\n")

> +            sys.exit(1)

> +        os.close(fd)

> +

> +

> +def _should_restart(status):

> +    global RESTART_EXIT_CODE

> +

> +    if os.WIFEXITED(status) and os.WEXITSTATUS(status) ==

> RESTART_EXIT_CODE:

> +        return True

> +

> +    if os.WIFSIGNALED(status):

> +        for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE", "SIGILL",

> +                        "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):

> +            if os.WTERMSIG(status) == getattr(signal, signame, None):

> +                return True

> +    return False

> +

> +

> +def _monitor_daemon(daemon_pid):

> +    # XXX should log daemon's stderr output at startup time

> +    # XXX should use setproctitle module if available

> +    last_restart = None

> +    while True:

> +        retval, status = _waitpid(daemon_pid, 0)

> +        if retval < 0:

> +            sys.stderr.write("waitpid failed\n")

> +            sys.exit(1)

> +        elif retval == daemon_pid:

> +            status_msg = ("pid %d died, %s"

> +                          % (daemon_pid,

> +ovs.process.status_msg(status)))

> +

> +            if _should_restart(status):

> +                if os.WCOREDUMP(status):

> +                    # Disable further core dumps to save disk space.

> +                    try:

> +                        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))

> +                    except resource.error:

> +                        vlog.warn("failed to disable core dumps")

> +

> +                # Throttle restarts to no more than once every 10 seconds.

> +                if (last_restart is not None and

> +                    ovs.timeval.msec() < last_restart + 10000):

> +                    vlog.warn("%s, waiting until 10 seconds since last "

> +                              "restart" % status_msg)

> +                    while True:

> +                        now = ovs.timeval.msec()

> +                        wakeup = last_restart + 10000

> +                        if now > wakeup:

> +                            break

> +                        sys.stdout.write("sleep %f\n" % (

> +                            (wakeup - now) / 1000.0))

> +                        time.sleep((wakeup - now) / 1000.0)

> +                last_restart = ovs.timeval.msec()

> +

> +                vlog.err("%s, restarting" % status_msg)

> +                daemon_pid = _fork_and_wait_for_startup()

> +                if not daemon_pid:

> +                    break

> +            else:

> +                vlog.info("%s, exiting" % status_msg)

> +                sys.exit(0)

> +

> +    # Running in new daemon process.

> +

> +

> +def _close_standard_fds():

> +    """Close stdin, stdout, stderr.  If we're started from e.g. an SSH session,

> +    then this keeps us from holding that session open artificially."""

> +    null_fd = ovs.socket_util.get_null_fd()

> +    if null_fd >= 0:

> +        os.dup2(null_fd, 0)

> +        os.dup2(null_fd, 1)

> +        os.dup2(null_fd, 2)

> +

> +

> +def daemonize_start():

> +    """If daemonization is configured, then starts daemonization, by forking

> +    and returning in the child process.  The parent process hangs around until

> +    the child lets it know either that it completed startup successfully (by

> +    calling daemon_complete()) or that it failed to start up (by exiting with a

> +    nonzero exit code)."""

> +

> +    if _detach:

> +        if _fork_and_wait_for_startup() > 0:

> +            # Running in parent process.

> +            sys.exit(0)

> +

> +        # Running in daemon or monitor process.

> +        os.setsid()

> +

> +    if _monitor:

> +        saved_daemonize_fd = _daemonize_fd

> +        daemon_pid = _fork_and_wait_for_startup()

> +        if daemon_pid > 0:

> +            # Running in monitor process.

> +            _fork_notify_startup(saved_daemonize_fd)

> +            _close_standard_fds()

> +            _monitor_daemon(daemon_pid)

> +        # Running in daemon process

> +

> +    if _pidfile:

> +        _make_pidfile()

> +

> +

> +def daemonize_complete():

> +    """If daemonization is configured, then this function notifies the parent

> +    process that the child process has completed startup successfully."""

> +    _fork_notify_startup(_daemonize_fd)

> +

> +    if _detach:

> +        if _chdir:

> +            os.chdir("/")

> +        _close_standard_fds()

> +

> +

> +def usage():

> +    sys.stdout.write("""

> +Daemon options:

> +   --detach                run in background as daemon

> +   --no-chdir              do not chdir to '/'

> +   --pidfile[=FILE]        create pidfile (default: %s/%s.pid)

> +   --overwrite-pidfile     with --pidfile, start even if already running

> +""" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))

> +

> +

> +def __read_pidfile(pidfile, delete_if_stale):

> +    if _pidfile_dev is not None:

> +        try:

> +            s = os.stat(pidfile)

> +            if s.st_ino == _pidfile_ino and s.st_dev == _pidfile_dev:

> +                # It's our own pidfile.  We can't afford to open it,

> +                # because closing *any* fd for a file that a process

> +                # has locked also releases all the locks on that file.

> +                #

> +                # Fortunately, we know the associated pid anyhow.

> +                return os.getpid()

> +        except OSError:

> +            pass

> +

> +    try:

> +        file_handle = open(pidfile, "r+")

> +    except IOError as e:

> +        if e.errno == errno.ENOENT and delete_if_stale:

> +            return 0

> +        vlog.warn("%s: open: %s" % (pidfile, e.strerror))

> +        return -e.errno

> +

> +    # Python fcntl doesn't directly support F_GETLK so we have to just try

> +    # to lock it.

> +    try:

> +        fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)

> +

> +        # pidfile exists but wasn't locked by anyone.  Now we have the lock.

> +        if not delete_if_stale:

> +            file_handle.close()

> +            vlog.warn("%s: pid file is stale" % pidfile)

> +            return -errno.ESRCH

> +

> +        # Is the file we have locked still named 'pidfile'?

> +        try:

> +            raced = False

> +            s = os.stat(pidfile)

> +            s2 = os.fstat(file_handle.fileno())

> +            if s.st_ino != s2.st_ino or s.st_dev != s2.st_dev:

> +                raced = True

> +        except IOError:

> +            raced = True

> +        if raced:

> +            vlog.warn("%s: lost race to delete pidfile" % pidfile)

> +            return -errno.EALREADY

> +

> +        # We won the right to delete the stale pidfile.

> +        try:

> +            os.unlink(pidfile)

> +        except IOError as e:

> +            vlog.warn("%s: failed to delete stale pidfile (%s)"

> +                            % (pidfile, e.strerror))

> +            return -e.errno

> +        else:

> +            vlog.dbg("%s: deleted stale pidfile" % pidfile)

> +            file_handle.close()

> +            return 0

> +    except IOError as e:

> +        if e.errno not in [errno.EACCES, errno.EAGAIN]:

> +            vlog.warn("%s: fcntl: %s" % (pidfile, e.strerror))

> +            return -e.errno

> +

> +    # Someone else has the pidfile locked.

> +    try:

> +        try:

> +            error = int(file_handle.readline())

> +        except IOError as e:

> +            vlog.warn("%s: read: %s" % (pidfile, e.strerror))

> +            error = -e.errno

> +        except ValueError:

> +            vlog.warn("%s does not contain a pid" % pidfile)

> +            error = -errno.EINVAL

> +

> +        return error

> +    finally:

> +        try:

> +            file_handle.close()

> +        except IOError:

> +            pass

> +

> +

> +def read_pidfile(pidfile):

> +    """Opens and reads a PID from 'pidfile'.  Returns the positive PID if

> +    successful, otherwise a negative errno value."""

> +    return __read_pidfile(pidfile, False)

> +

> +

> +def _check_already_running():

> +    pid = __read_pidfile(_pidfile, True)

> +    if pid > 0:

> +        _fatal("%s: already running as pid %d, aborting" % (_pidfile, pid))

> +    elif pid < 0:

> +        _fatal("%s: pidfile check failed (%s), aborting"

> +               % (_pidfile, os.strerror(pid)))

> +

> +

> +def add_args(parser):

> +    """Populates 'parser', an ArgumentParser allocated using the argparse

> +    module, with the command line arguments required by the daemon

> module."""

> +

> +    pidfile = make_pidfile_name(None)

> +

> +    group = parser.add_argument_group(title="Daemon Options")

> +    group.add_argument("--detach", action="store_true",

> +            help="Run in background as a daemon.")

> +    group.add_argument("--no-chdir", action="store_true",

> +            help="Do not chdir to '/'.")

> +    group.add_argument("--monitor", action="store_true",

> +            help="Monitor %s process." % ovs.util.PROGRAM_NAME)

> +    group.add_argument("--pidfile", nargs="?", const=pidfile,

> +            help="Create pidfile (default %s)." % pidfile)

> +    group.add_argument("--overwrite-pidfile", action="store_true",

> +            help="With --pidfile, start even if already running.")

> +

> +

> +def handle_args(args):

> +    """Handles daemon module settings in 'args'.  'args' is an object

> +    containing values parsed by the parse_args() method of ArgumentParser.

> The

> +    parent ArgumentParser should have been prepared by add_args() before

> +    calling parse_args()."""

> +

> +    if args.detach:

> +        set_detach()

> +

> +    if args.no_chdir:

> +        set_no_chdir()

> +

> +    if args.pidfile:

> +        set_pidfile(args.pidfile)

> +

> +    if args.overwrite_pidfile:

> +        ignore_existing_pidfile()

> +

> +    if args.monitor:

> +        set_monitor()

> --

> 2.7.2.windows.1

> _______________________________________________

> dev mailing list

> dev@openvswitch.org

> http://openvswitch.org/mailman/listinfo/dev
diff mbox

Patch

diff --git a/python/automake.mk b/python/automake.mk
index 1c8fa38..4d3fcb6 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -11,6 +11,7 @@  ovstest_pyfiles = \
 ovs_pyfiles = \
 	python/ovs/__init__.py \
 	python/ovs/daemon.py \
+	python/ovs/daemon_unix.py \
 	python/ovs/fcntl_win.py \
 	python/ovs/db/__init__.py \
 	python/ovs/db/data.py \
diff --git a/python/ovs/daemon.py b/python/ovs/daemon.py
index bd06195..f45f757 100644
--- a/python/ovs/daemon.py
+++ b/python/ovs/daemon.py
@@ -1,4 +1,4 @@ 
-# Copyright (c) 2010, 2011, 2012 Nicira, Inc.
+# Copyright (c) 2016 Cloudbase Solutions Srl
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,515 +12,64 @@ 
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import errno
-import fcntl
-import os
-import resource
-import signal
 import sys
-import time
 
-import ovs.dirs
-import ovs.fatal_signal
-import ovs.process
-import ovs.socket_util
-import ovs.timeval
-import ovs.util
-import ovs.vlog
-
-vlog = ovs.vlog.Vlog("daemon")
-
-# --detach: Should we run in the background?
-_detach = False
-
-# --pidfile: Name of pidfile (null if none).
-_pidfile = None
-
-# Our pidfile's inode and device, if we have created one.
-_pidfile_dev = None
-_pidfile_ino = None
-
-# --overwrite-pidfile: Create pidfile even if one already exists and is locked?
-_overwrite_pidfile = False
-
-# --no-chdir: Should we chdir to "/"?
-_chdir = True
-
-# --monitor: Should a supervisory process monitor the daemon and restart it if
-# it dies due to an error signal?
-_monitor = False
-
-# File descriptor used by daemonize_start() and daemonize_complete().
-_daemonize_fd = None
-
-RESTART_EXIT_CODE = 5
+# This is only a wrapper over Linux implementations
+if sys.platform != "win32":
+    import ovs.daemon_unix as daemon_util
 
 
 def make_pidfile_name(name):
-    """Returns the file name that would be used for a pidfile if 'name' were
-    provided to set_pidfile()."""
-    if name is None or name == "":
-        return "%s/%s.pid" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME)
-    else:
-        return ovs.util.abs_file_name(ovs.dirs.RUNDIR, name)
+    return daemon_util.make_pidfile_name(name)
 
 
 def set_pidfile(name):
-    """Sets up a following call to daemonize() to create a pidfile named
-    'name'.  If 'name' begins with '/', then it is treated as an absolute path.
-    Otherwise, it is taken relative to ovs.util.RUNDIR, which is
-    $(prefix)/var/run by default.
-
-    If 'name' is null, then ovs.util.PROGRAM_NAME followed by ".pid" is
-    used."""
-    global _pidfile
-    _pidfile = make_pidfile_name(name)
+    daemon_util.set_pidfile(name)
 
 
 def set_no_chdir():
-    """Sets that we do not chdir to "/"."""
-    global _chdir
-    _chdir = False
+    daemon_util.set_no_chdir()
 
 
 def ignore_existing_pidfile():
-    """Normally, daemonize() or daemonize_start() will terminate the program
-    with a message if a locked pidfile already exists.  If this function is
-    called, an existing pidfile will be replaced, with a warning."""
-    global _overwrite_pidfile
-    _overwrite_pidfile = True
+    daemon_util.ignore_existing_pidfile()
 
 
 def set_detach():
-    """Sets up a following call to daemonize() to detach from the foreground
-    session, running this process in the background."""
-    global _detach
-    _detach = True
+    daemon_util.set_detach()
 
 
 def get_detach():
-    """Will daemonize() really detach?"""
-    return _detach
+    return daemon_util.get_detach()
 
 
 def set_monitor():
-    """Sets up a following call to daemonize() to fork a supervisory process to
-    monitor the daemon and restart it if it dies due to an error signal."""
-    global _monitor
-    _monitor = True
-
-
-def _fatal(msg):
-    vlog.err(msg)
-    sys.stderr.write("%s\n" % msg)
-    sys.exit(1)
-
-
-def _make_pidfile():
-    """If a pidfile has been configured, creates it and stores the running
-    process's pid in it.  Ensures that the pidfile will be deleted when the
-    process exits."""
-    pid = os.getpid()
-
-    # Create a temporary pidfile.
-    tmpfile = "%s.tmp%d" % (_pidfile, pid)
-    ovs.fatal_signal.add_file_to_unlink(tmpfile)
-    try:
-        # This is global to keep Python from garbage-collecting and
-        # therefore closing our file after this function exits.  That would
-        # unlock the lock for us, and we don't want that.
-        global file_handle
-
-        file_handle = open(tmpfile, "w")
-    except IOError as e:
-        _fatal("%s: create failed (%s)" % (tmpfile, e.strerror))
-
-    try:
-        s = os.fstat(file_handle.fileno())
-    except IOError as e:
-        _fatal("%s: fstat failed (%s)" % (tmpfile, e.strerror))
-
-    try:
-        file_handle.write("%s\n" % pid)
-        file_handle.flush()
-    except OSError as e:
-        _fatal("%s: write failed: %s" % (tmpfile, e.strerror))
-
-    try:
-        fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
-    except IOError as e:
-        _fatal("%s: fcntl failed: %s" % (tmpfile, e.strerror))
-
-    # Rename or link it to the correct name.
-    if _overwrite_pidfile:
-        try:
-            os.rename(tmpfile, _pidfile)
-        except OSError as e:
-            _fatal("failed to rename \"%s\" to \"%s\" (%s)"
-                   % (tmpfile, _pidfile, e.strerror))
-    else:
-        while True:
-            try:
-                os.link(tmpfile, _pidfile)
-                error = 0
-            except OSError as e:
-                error = e.errno
-            if error == errno.EEXIST:
-                _check_already_running()
-            elif error != errno.EINTR:
-                break
-        if error:
-            _fatal("failed to link \"%s\" as \"%s\" (%s)"
-                   % (tmpfile, _pidfile, os.strerror(error)))
-
-    # Ensure that the pidfile will get deleted on exit.
-    ovs.fatal_signal.add_file_to_unlink(_pidfile)
-
-    # Delete the temporary pidfile if it still exists.
-    if not _overwrite_pidfile:
-        error = ovs.fatal_signal.unlink_file_now(tmpfile)
-        if error:
-            _fatal("%s: unlink failed (%s)" % (tmpfile, os.strerror(error)))
-
-    global _pidfile_dev
-    global _pidfile_ino
-    _pidfile_dev = s.st_dev
-    _pidfile_ino = s.st_ino
+    daemon_util.set_monitor()
 
 
 def daemonize():
-    """If configured with set_pidfile() or set_detach(), creates the pid file
-    and detaches from the foreground session."""
-    daemonize_start()
-    daemonize_complete()
-
-
-def _waitpid(pid, options):
-    while True:
-        try:
-            return os.waitpid(pid, options)
-        except OSError as e:
-            if e.errno == errno.EINTR:
-                pass
-            return -e.errno, 0
-
-
-def _fork_and_wait_for_startup():
-    try:
-        rfd, wfd = os.pipe()
-    except OSError as e:
-        sys.stderr.write("pipe failed: %s\n" % os.strerror(e.errno))
-        sys.exit(1)
-
-    try:
-        pid = os.fork()
-    except OSError as e:
-        sys.stderr.write("could not fork: %s\n" % os.strerror(e.errno))
-        sys.exit(1)
-
-    if pid > 0:
-        # Running in parent process.
-        os.close(wfd)
-        ovs.fatal_signal.fork()
-        while True:
-            try:
-                s = os.read(rfd, 1)
-                error = 0
-            except OSError as e:
-                s = ""
-                error = e.errno
-            if error != errno.EINTR:
-                break
-        if len(s) != 1:
-            retval, status = _waitpid(pid, 0)
-            if retval == pid:
-                if os.WIFEXITED(status) and os.WEXITSTATUS(status):
-                    # Child exited with an error.  Convey the same error to
-                    # our parent process as a courtesy.
-                    sys.exit(os.WEXITSTATUS(status))
-                else:
-                    sys.stderr.write("fork child failed to signal "
-                                     "startup (%s)\n"
-                                     % ovs.process.status_msg(status))
-            else:
-                assert retval < 0
-                sys.stderr.write("waitpid failed (%s)\n"
-                                 % os.strerror(-retval))
-                sys.exit(1)
-
-        os.close(rfd)
-    else:
-        # Running in parent process.
-        os.close(rfd)
-        ovs.timeval.postfork()
-
-        global _daemonize_fd
-        _daemonize_fd = wfd
-    return pid
-
-
-def _fork_notify_startup(fd):
-    if fd is not None:
-        error, bytes_written = ovs.socket_util.write_fully(fd, "0")
-        if error:
-            sys.stderr.write("could not write to pipe\n")
-            sys.exit(1)
-        os.close(fd)
-
-
-def _should_restart(status):
-    global RESTART_EXIT_CODE
-
-    if os.WIFEXITED(status) and os.WEXITSTATUS(status) == RESTART_EXIT_CODE:
-        return True
-
-    if os.WIFSIGNALED(status):
-        for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE", "SIGILL",
-                        "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):
-            if os.WTERMSIG(status) == getattr(signal, signame, None):
-                return True
-    return False
-
-
-def _monitor_daemon(daemon_pid):
-    # XXX should log daemon's stderr output at startup time
-    # XXX should use setproctitle module if available
-    last_restart = None
-    while True:
-        retval, status = _waitpid(daemon_pid, 0)
-        if retval < 0:
-            sys.stderr.write("waitpid failed\n")
-            sys.exit(1)
-        elif retval == daemon_pid:
-            status_msg = ("pid %d died, %s"
-                          % (daemon_pid, ovs.process.status_msg(status)))
-
-            if _should_restart(status):
-                if os.WCOREDUMP(status):
-                    # Disable further core dumps to save disk space.
-                    try:
-                        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
-                    except resource.error:
-                        vlog.warn("failed to disable core dumps")
-
-                # Throttle restarts to no more than once every 10 seconds.
-                if (last_restart is not None and
-                    ovs.timeval.msec() < last_restart + 10000):
-                    vlog.warn("%s, waiting until 10 seconds since last "
-                              "restart" % status_msg)
-                    while True:
-                        now = ovs.timeval.msec()
-                        wakeup = last_restart + 10000
-                        if now > wakeup:
-                            break
-                        sys.stdout.write("sleep %f\n" % (
-                            (wakeup - now) / 1000.0))
-                        time.sleep((wakeup - now) / 1000.0)
-                last_restart = ovs.timeval.msec()
-
-                vlog.err("%s, restarting" % status_msg)
-                daemon_pid = _fork_and_wait_for_startup()
-                if not daemon_pid:
-                    break
-            else:
-                vlog.info("%s, exiting" % status_msg)
-                sys.exit(0)
-
-    # Running in new daemon process.
-
-
-def _close_standard_fds():
-    """Close stdin, stdout, stderr.  If we're started from e.g. an SSH session,
-    then this keeps us from holding that session open artificially."""
-    null_fd = ovs.socket_util.get_null_fd()
-    if null_fd >= 0:
-        os.dup2(null_fd, 0)
-        os.dup2(null_fd, 1)
-        os.dup2(null_fd, 2)
+    daemon_util.daemonize()
 
 
 def daemonize_start():
-    """If daemonization is configured, then starts daemonization, by forking
-    and returning in the child process.  The parent process hangs around until
-    the child lets it know either that it completed startup successfully (by
-    calling daemon_complete()) or that it failed to start up (by exiting with a
-    nonzero exit code)."""
-
-    if _detach:
-        if _fork_and_wait_for_startup() > 0:
-            # Running in parent process.
-            sys.exit(0)
-
-        # Running in daemon or monitor process.
-        os.setsid()
-
-    if _monitor:
-        saved_daemonize_fd = _daemonize_fd
-        daemon_pid = _fork_and_wait_for_startup()
-        if daemon_pid > 0:
-            # Running in monitor process.
-            _fork_notify_startup(saved_daemonize_fd)
-            _close_standard_fds()
-            _monitor_daemon(daemon_pid)
-        # Running in daemon process
-
-    if _pidfile:
-        _make_pidfile()
+    daemon_util.daemonize_start()
 
 
 def daemonize_complete():
-    """If daemonization is configured, then this function notifies the parent
-    process that the child process has completed startup successfully."""
-    _fork_notify_startup(_daemonize_fd)
-
-    if _detach:
-        if _chdir:
-            os.chdir("/")
-        _close_standard_fds()
+    daemon_util.daemonize_complete()
 
 
 def usage():
-    sys.stdout.write("""
-Daemon options:
-   --detach                run in background as daemon
-   --no-chdir              do not chdir to '/'
-   --pidfile[=FILE]        create pidfile (default: %s/%s.pid)
-   --overwrite-pidfile     with --pidfile, start even if already running
-""" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))
-
+    daemon_util.usage()
 
-def __read_pidfile(pidfile, delete_if_stale):
-    if _pidfile_dev is not None:
-        try:
-            s = os.stat(pidfile)
-            if s.st_ino == _pidfile_ino and s.st_dev == _pidfile_dev:
-                # It's our own pidfile.  We can't afford to open it,
-                # because closing *any* fd for a file that a process
-                # has locked also releases all the locks on that file.
-                #
-                # Fortunately, we know the associated pid anyhow.
-                return os.getpid()
-        except OSError:
-            pass
 
-    try:
-        file_handle = open(pidfile, "r+")
-    except IOError as e:
-        if e.errno == errno.ENOENT and delete_if_stale:
-            return 0
-        vlog.warn("%s: open: %s" % (pidfile, e.strerror))
-        return -e.errno
-
-    # Python fcntl doesn't directly support F_GETLK so we have to just try
-    # to lock it.
-    try:
-        fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
-
-        # pidfile exists but wasn't locked by anyone.  Now we have the lock.
-        if not delete_if_stale:
-            file_handle.close()
-            vlog.warn("%s: pid file is stale" % pidfile)
-            return -errno.ESRCH
-
-        # Is the file we have locked still named 'pidfile'?
-        try:
-            raced = False
-            s = os.stat(pidfile)
-            s2 = os.fstat(file_handle.fileno())
-            if s.st_ino != s2.st_ino or s.st_dev != s2.st_dev:
-                raced = True
-        except IOError:
-            raced = True
-        if raced:
-            vlog.warn("%s: lost race to delete pidfile" % pidfile)
-            return -errno.EALREADY
-
-        # We won the right to delete the stale pidfile.
-        try:
-            os.unlink(pidfile)
-        except IOError as e:
-            vlog.warn("%s: failed to delete stale pidfile (%s)"
-                            % (pidfile, e.strerror))
-            return -e.errno
-        else:
-            vlog.dbg("%s: deleted stale pidfile" % pidfile)
-            file_handle.close()
-            return 0
-    except IOError as e:
-        if e.errno not in [errno.EACCES, errno.EAGAIN]:
-            vlog.warn("%s: fcntl: %s" % (pidfile, e.strerror))
-            return -e.errno
-
-    # Someone else has the pidfile locked.
-    try:
-        try:
-            error = int(file_handle.readline())
-        except IOError as e:
-            vlog.warn("%s: read: %s" % (pidfile, e.strerror))
-            error = -e.errno
-        except ValueError:
-            vlog.warn("%s does not contain a pid" % pidfile)
-            error = -errno.EINVAL
-
-        return error
-    finally:
-        try:
-            file_handle.close()
-        except IOError:
-            pass
-
-
-def read_pidfile(pidfile):
-    """Opens and reads a PID from 'pidfile'.  Returns the positive PID if
-    successful, otherwise a negative errno value."""
-    return __read_pidfile(pidfile, False)
-
-
-def _check_already_running():
-    pid = __read_pidfile(_pidfile, True)
-    if pid > 0:
-        _fatal("%s: already running as pid %d, aborting" % (_pidfile, pid))
-    elif pid < 0:
-        _fatal("%s: pidfile check failed (%s), aborting"
-               % (_pidfile, os.strerror(pid)))
+def read_pidfile(pidfile_name):
+    return daemon_util.read_pidfile(pidfile_name)
 
 
 def add_args(parser):
-    """Populates 'parser', an ArgumentParser allocated using the argparse
-    module, with the command line arguments required by the daemon module."""
-
-    pidfile = make_pidfile_name(None)
-
-    group = parser.add_argument_group(title="Daemon Options")
-    group.add_argument("--detach", action="store_true",
-            help="Run in background as a daemon.")
-    group.add_argument("--no-chdir", action="store_true",
-            help="Do not chdir to '/'.")
-    group.add_argument("--monitor", action="store_true",
-            help="Monitor %s process." % ovs.util.PROGRAM_NAME)
-    group.add_argument("--pidfile", nargs="?", const=pidfile,
-            help="Create pidfile (default %s)." % pidfile)
-    group.add_argument("--overwrite-pidfile", action="store_true",
-            help="With --pidfile, start even if already running.")
+    daemon_util.add_args(parser)
 
 
 def handle_args(args):
-    """Handles daemon module settings in 'args'.  'args' is an object
-    containing values parsed by the parse_args() method of ArgumentParser.  The
-    parent ArgumentParser should have been prepared by add_args() before
-    calling parse_args()."""
-
-    if args.detach:
-        set_detach()
-
-    if args.no_chdir:
-        set_no_chdir()
-
-    if args.pidfile:
-        set_pidfile(args.pidfile)
-
-    if args.overwrite_pidfile:
-        ignore_existing_pidfile()
-
-    if args.monitor:
-        set_monitor()
+    daemon_util.handle_args(args)
diff --git a/python/ovs/daemon_unix.py b/python/ovs/daemon_unix.py
new file mode 100644
index 0000000..3bd56af
--- /dev/null
+++ b/python/ovs/daemon_unix.py
@@ -0,0 +1,530 @@ 
+# Copyright (c) 2010, 2011, 2012, 2016 Nicira, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import errno
+import os
+import signal
+import sys
+import time
+
+import ovs.dirs
+import ovs.fatal_signal
+import ovs.process
+import ovs.socket_util
+import ovs.timeval
+import ovs.util
+import ovs.vlog
+
+import fcntl
+import resource
+
+vlog = ovs.vlog.Vlog("daemon")
+
+# --detach: Should we run in the background?
+_detach = False
+
+# --pidfile: Name of pidfile (null if none).
+_pidfile = None
+
+# Our pidfile's inode and device, if we have created one.
+_pidfile_dev = None
+_pidfile_ino = None
+
+# --overwrite-pidfile: Create pidfile even if one already exists and is locked?
+_overwrite_pidfile = False
+
+# --no-chdir: Should we chdir to "/"?
+_chdir = True
+
+# --monitor: Should a supervisory process monitor the daemon and restart it if
+# it dies due to an error signal?
+_monitor = False
+
+# File descriptor used by daemonize_start() and daemonize_complete().
+_daemonize_fd = None
+
+
+RESTART_EXIT_CODE = 5
+
+
+def make_pidfile_name(name):
+    """Returns the file name that would be used for a pidfile if 'name' were
+    provided to set_pidfile()."""
+    if name is None or name == "":
+        return "%s/%s.pid" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME)
+    else:
+        return ovs.util.abs_file_name(ovs.dirs.RUNDIR, name)
+
+
+def set_pidfile(name):
+    """Sets up a following call to daemonize() to create a pidfile named
+    'name'.  If 'name' begins with '/', then it is treated as an absolute path.
+    Otherwise, it is taken relative to ovs.util.RUNDIR, which is
+    $(prefix)/var/run by default.
+
+    If 'name' is null, then ovs.util.PROGRAM_NAME followed by ".pid" is
+    used."""
+    global _pidfile
+    _pidfile = make_pidfile_name(name)
+
+
+def set_no_chdir():
+    """Sets that we do not chdir to "/"."""
+    global _chdir
+    _chdir = False
+
+
+def ignore_existing_pidfile():
+    """Normally, daemonize() or daemonize_start() will terminate the program
+    with a message if a locked pidfile already exists.  If this function is
+    called, an existing pidfile will be replaced, with a warning."""
+    global _overwrite_pidfile
+    _overwrite_pidfile = True
+
+
+def set_detach():
+    """Sets up a following call to daemonize() to detach from the foreground
+    session, running this process in the background."""
+    global _detach
+    _detach = True
+
+
+def get_detach():
+    """Will daemonize() really detach?"""
+    return _detach
+
+
+def set_monitor():
+    """Sets up a following call to daemonize() to fork a supervisory process to
+    monitor the daemon and restart it if it dies due to an error signal."""
+    global _monitor
+    _monitor = True
+
+
+def _fatal(msg):
+    vlog.err(msg)
+    sys.stderr.write("%s\n" % msg)
+    sys.exit(1)
+
+
+def _make_pidfile():
+    """If a pidfile has been configured, creates it and stores the running
+    process's pid in it.  Ensures that the pidfile will be deleted when the
+    process exits."""
+    pid = os.getpid()
+
+    # Create a temporary pidfile.
+    tmpfile = "%s.tmp%d" % (_pidfile, pid)
+    ovs.fatal_signal.add_file_to_unlink(tmpfile)
+
+    try:
+        # This is global to keep Python from garbage-collecting and
+        # therefore closing our file after this function exits.  That would
+        # unlock the lock for us, and we don't want that.
+        global file_handle
+
+        file_handle = open(tmpfile, "w")
+    except IOError as e:
+        _fatal("%s: create failed (%s)" % (tmpfile, e.strerror))
+
+    try:
+        s = os.fstat(file_handle.fileno())
+    except IOError as e:
+        _fatal("%s: fstat failed (%s)" % (tmpfile, e.strerror))
+
+    try:
+        file_handle.write("%s\n" % pid)
+        file_handle.flush()
+    except OSError as e:
+        _fatal("%s: write failed: %s" % (tmpfile, e.strerror))
+
+    try:
+        fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
+    except IOError as e:
+        _fatal("%s: fcntl failed: %s" % (tmpfile, e.strerror))
+
+    # Rename or link it to the correct name.
+    if _overwrite_pidfile:
+        try:
+            os.rename(tmpfile, _pidfile)
+        except OSError as e:
+            _fatal("failed to rename \"%s\" to \"%s\" (%s)"
+                   % (tmpfile, _pidfile, e.strerror))
+    else:
+        while True:
+            try:
+                os.link(tmpfile, _pidfile)
+                error = 0
+            except OSError as e:
+                error = e.errno
+            if error == errno.EEXIST:
+                _check_already_running()
+            elif error != errno.EINTR:
+                break
+        if error:
+            _fatal("failed to link \"%s\" as \"%s\" (%s)"
+                   % (tmpfile, _pidfile, os.strerror(error)))
+
+    # Ensure that the pidfile will get deleted on exit.
+    ovs.fatal_signal.add_file_to_unlink(_pidfile)
+
+    # Delete the temporary pidfile if it still exists.
+    if not _overwrite_pidfile:
+        error = ovs.fatal_signal.unlink_file_now(tmpfile)
+        if error:
+            _fatal("%s: unlink failed (%s)"
+                   % (tmpfile, os.strerror(error)))
+
+    global _pidfile_dev
+    global _pidfile_ino
+    _pidfile_dev = s.st_dev
+    _pidfile_ino = s.st_ino
+
+
+def daemonize():
+    """If configured with set_pidfile() or set_detach(), creates the pid file
+    and detaches from the foreground session."""
+    daemonize_start()
+    daemonize_complete()
+
+
+def _waitpid(pid, options):
+    while True:
+        try:
+            return os.waitpid(pid, options)
+        except OSError as e:
+            if e.errno == errno.EINTR:
+                pass
+            return -e.errno, 0
+
+
+def _fork_and_wait_for_startup():
+    try:
+        rfd, wfd = os.pipe()
+    except OSError as e:
+        sys.stderr.write("pipe failed: %s\n" % os.strerror(e.errno))
+        sys.exit(1)
+
+    try:
+        pid = os.fork()
+    except OSError as e:
+        sys.stderr.write("could not fork: %s\n" % os.strerror(e.errno))
+        sys.exit(1)
+
+    if pid > 0:
+        # Running in parent process.
+        os.close(wfd)
+        ovs.fatal_signal.fork()
+        while True:
+            try:
+                s = os.read(rfd, 1)
+                error = 0
+            except OSError as e:
+                s = ""
+                error = e.errno
+            if error != errno.EINTR:
+                break
+        if len(s) != 1:
+            retval, status = _waitpid(pid, 0)
+            if retval == pid:
+                if os.WIFEXITED(status) and os.WEXITSTATUS(status):
+                    # Child exited with an error.  Convey the same error to
+                    # our parent process as a courtesy.
+                    sys.exit(os.WEXITSTATUS(status))
+                else:
+                    sys.stderr.write("fork child failed to signal "
+                                     "startup (%s)\n"
+                                     % ovs.process.status_msg(status))
+            else:
+                assert retval < 0
+                sys.stderr.write("waitpid failed (%s)\n"
+                                 % os.strerror(-retval))
+                sys.exit(1)
+
+        os.close(rfd)
+    else:
+        # Running in child process.
+        os.close(rfd)
+        ovs.timeval.postfork()
+
+        global _daemonize_fd
+        _daemonize_fd = wfd
+    return pid
+
+
+def _fork_notify_startup(fd):
+    if fd is not None:
+        error, bytes_written = ovs.socket_util.write_fully(fd, "0")
+        if error:
+            sys.stderr.write("could not write to pipe\n")
+            sys.exit(1)
+        os.close(fd)
+
+
+def _should_restart(status):
+    global RESTART_EXIT_CODE
+
+    if os.WIFEXITED(status) and os.WEXITSTATUS(status) == RESTART_EXIT_CODE:
+        return True
+
+    if os.WIFSIGNALED(status):
+        for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE", "SIGILL",
+                        "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):
+            if os.WTERMSIG(status) == getattr(signal, signame, None):
+                return True
+    return False
+
+
+def _monitor_daemon(daemon_pid):
+    # XXX should log daemon's stderr output at startup time
+    # XXX should use setproctitle module if available
+    last_restart = None
+    while True:
+        retval, status = _waitpid(daemon_pid, 0)
+        if retval < 0:
+            sys.stderr.write("waitpid failed\n")
+            sys.exit(1)
+        elif retval == daemon_pid:
+            status_msg = ("pid %d died, %s"
+                          % (daemon_pid, ovs.process.status_msg(status)))
+
+            if _should_restart(status):
+                if os.WCOREDUMP(status):
+                    # Disable further core dumps to save disk space.
+                    try:
+                        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
+                    except resource.error:
+                        vlog.warn("failed to disable core dumps")
+
+                # Throttle restarts to no more than once every 10 seconds.
+                if (last_restart is not None and
+                    ovs.timeval.msec() < last_restart + 10000):
+                    vlog.warn("%s, waiting until 10 seconds since last "
+                              "restart" % status_msg)
+                    while True:
+                        now = ovs.timeval.msec()
+                        wakeup = last_restart + 10000
+                        if now > wakeup:
+                            break
+                        sys.stdout.write("sleep %f\n" % (
+                            (wakeup - now) / 1000.0))
+                        time.sleep((wakeup - now) / 1000.0)
+                last_restart = ovs.timeval.msec()
+
+                vlog.err("%s, restarting" % status_msg)
+                daemon_pid = _fork_and_wait_for_startup()
+                if not daemon_pid:
+                    break
+            else:
+                vlog.info("%s, exiting" % status_msg)
+                sys.exit(0)
+
+    # Running in new daemon process.
+
+
+def _close_standard_fds():
+    """Close stdin, stdout, stderr.  If we're started from e.g. an SSH session,
+    then this keeps us from holding that session open artificially."""
+    null_fd = ovs.socket_util.get_null_fd()
+    if null_fd >= 0:
+        os.dup2(null_fd, 0)
+        os.dup2(null_fd, 1)
+        os.dup2(null_fd, 2)
+
+
+def daemonize_start():
+    """If daemonization is configured, then starts daemonization, by forking
+    and returning in the child process.  The parent process hangs around until
+    the child lets it know either that it completed startup successfully (by
+    calling daemon_complete()) or that it failed to start up (by exiting with a
+    nonzero exit code)."""
+
+    if _detach:
+        if _fork_and_wait_for_startup() > 0:
+            # Running in parent process.
+            sys.exit(0)
+
+        # Running in daemon or monitor process.
+        os.setsid()
+
+    if _monitor:
+        saved_daemonize_fd = _daemonize_fd
+        daemon_pid = _fork_and_wait_for_startup()
+        if daemon_pid > 0:
+            # Running in monitor process.
+            _fork_notify_startup(saved_daemonize_fd)
+            _close_standard_fds()
+            _monitor_daemon(daemon_pid)
+        # Running in daemon process
+
+    if _pidfile:
+        _make_pidfile()
+
+
+def daemonize_complete():
+    """If daemonization is configured, then this function notifies the parent
+    process that the child process has completed startup successfully."""
+    _fork_notify_startup(_daemonize_fd)
+
+    if _detach:
+        if _chdir:
+            os.chdir("/")
+        _close_standard_fds()
+
+
+def usage():
+    sys.stdout.write("""
+Daemon options:
+   --detach                run in background as daemon
+   --no-chdir              do not chdir to '/'
+   --pidfile[=FILE]        create pidfile (default: %s/%s.pid)
+   --overwrite-pidfile     with --pidfile, start even if already running
+""" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))
+
+
+def __read_pidfile(pidfile, delete_if_stale):
+    if _pidfile_dev is not None:
+        try:
+            s = os.stat(pidfile)
+            if s.st_ino == _pidfile_ino and s.st_dev == _pidfile_dev:
+                # It's our own pidfile.  We can't afford to open it,
+                # because closing *any* fd for a file that a process
+                # has locked also releases all the locks on that file.
+                #
+                # Fortunately, we know the associated pid anyhow.
+                return os.getpid()
+        except OSError:
+            pass
+
+    try:
+        file_handle = open(pidfile, "r+")
+    except IOError as e:
+        if e.errno == errno.ENOENT and delete_if_stale:
+            return 0
+        vlog.warn("%s: open: %s" % (pidfile, e.strerror))
+        return -e.errno
+
+    # Python fcntl doesn't directly support F_GETLK so we have to just try
+    # to lock it.
+    try:
+        fcntl.lockf(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
+
+        # pidfile exists but wasn't locked by anyone.  Now we have the lock.
+        if not delete_if_stale:
+            file_handle.close()
+            vlog.warn("%s: pid file is stale" % pidfile)
+            return -errno.ESRCH
+
+        # Is the file we have locked still named 'pidfile'?
+        try:
+            raced = False
+            s = os.stat(pidfile)
+            s2 = os.fstat(file_handle.fileno())
+            if s.st_ino != s2.st_ino or s.st_dev != s2.st_dev:
+                raced = True
+        except IOError:
+            raced = True
+        if raced:
+            vlog.warn("%s: lost race to delete pidfile" % pidfile)
+            return -errno.EALREADY
+
+        # We won the right to delete the stale pidfile.
+        try:
+            os.unlink(pidfile)
+        except IOError as e:
+            vlog.warn("%s: failed to delete stale pidfile (%s)"
+                            % (pidfile, e.strerror))
+            return -e.errno
+        else:
+            vlog.dbg("%s: deleted stale pidfile" % pidfile)
+            file_handle.close()
+            return 0
+    except IOError as e:
+        if e.errno not in [errno.EACCES, errno.EAGAIN]:
+            vlog.warn("%s: fcntl: %s" % (pidfile, e.strerror))
+            return -e.errno
+
+    # Someone else has the pidfile locked.
+    try:
+        try:
+            error = int(file_handle.readline())
+        except IOError as e:
+            vlog.warn("%s: read: %s" % (pidfile, e.strerror))
+            error = -e.errno
+        except ValueError:
+            vlog.warn("%s does not contain a pid" % pidfile)
+            error = -errno.EINVAL
+
+        return error
+    finally:
+        try:
+            file_handle.close()
+        except IOError:
+            pass
+
+
+def read_pidfile(pidfile):
+    """Opens and reads a PID from 'pidfile'.  Returns the positive PID if
+    successful, otherwise a negative errno value."""
+    return __read_pidfile(pidfile, False)
+
+
+def _check_already_running():
+    pid = __read_pidfile(_pidfile, True)
+    if pid > 0:
+        _fatal("%s: already running as pid %d, aborting" % (_pidfile, pid))
+    elif pid < 0:
+        _fatal("%s: pidfile check failed (%s), aborting"
+               % (_pidfile, os.strerror(pid)))
+
+
+def add_args(parser):
+    """Populates 'parser', an ArgumentParser allocated using the argparse
+    module, with the command line arguments required by the daemon module."""
+
+    pidfile = make_pidfile_name(None)
+
+    group = parser.add_argument_group(title="Daemon Options")
+    group.add_argument("--detach", action="store_true",
+            help="Run in background as a daemon.")
+    group.add_argument("--no-chdir", action="store_true",
+            help="Do not chdir to '/'.")
+    group.add_argument("--monitor", action="store_true",
+            help="Monitor %s process." % ovs.util.PROGRAM_NAME)
+    group.add_argument("--pidfile", nargs="?", const=pidfile,
+            help="Create pidfile (default %s)." % pidfile)
+    group.add_argument("--overwrite-pidfile", action="store_true",
+            help="With --pidfile, start even if already running.")
+
+
+def handle_args(args):
+    """Handles daemon module settings in 'args'.  'args' is an object
+    containing values parsed by the parse_args() method of ArgumentParser.  The
+    parent ArgumentParser should have been prepared by add_args() before
+    calling parse_args()."""
+
+    if args.detach:
+        set_detach()
+
+    if args.no_chdir:
+        set_no_chdir()
+
+    if args.pidfile:
+        set_pidfile(args.pidfile)
+
+    if args.overwrite_pidfile:
+        ignore_existing_pidfile()
+
+    if args.monitor:
+        set_monitor()