Skip to content

Commit

Permalink
Create pidfile on activate
Browse files Browse the repository at this point in the history
  • Loading branch information
johnramsden committed Nov 6, 2018
1 parent 82d65d0 commit cb5b768
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 14 deletions.
2 changes: 1 addition & 1 deletion packaging/arch/PKGBUILD-git
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Maintainer: John Ramsden <johnramsden [at] riseup [dot] net>

pkgname=zedenv-git
pkgver=r103.0f21e17
pkgver=r148.d7d00f2
pkgrel=1
pkgdesc="Utility to manage Boot Environments using ZFS"
arch=('any')
Expand Down
43 changes: 30 additions & 13 deletions zedenv/cli/activate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""List boot environments cli"""

import sys
import errno

import click

Expand Down Expand Up @@ -346,19 +347,35 @@ def cli(boot_environment: str,
except RuntimeError as err:
ZELogger.log({"level": "EXCEPTION", "message": err}, exit_on_error=True)

boot_environment_root = zedenv.lib.be.root()
try:
with zedenv.lib.check.Pidfile() as pf:

boot_environment_root = zedenv.lib.be.root()

bootloader_set = zedenv.lib.be.get_property(
f"{boot_environment_root}/{boot_environment}", "org.zedenv:bootloader")
if not bootloader and bootloader_set:
bootloader = bootloader_set if bootloader_set != '-' else None
bootloader_set = zedenv.lib.be.get_property(
f"{boot_environment_root}/{boot_environment}", "org.zedenv:bootloader")
if not bootloader and bootloader_set:
bootloader = bootloader_set if bootloader_set != '-' else None

if noconfirm and not bootloader:
sys.exit("The '--noconfirm/-y' flag requires the bootloader option '--bootloader/-b'.")
if noconfirm and not bootloader:
sys.exit(
"The '--noconfirm/-y' flag requires the bootloader option '--bootloader/-b'.")

zedenv_activate(boot_environment,
boot_environment_root,
verbose,
bootloader,
noconfirm,
noop)
zedenv_activate(boot_environment,
boot_environment_root,
verbose,
bootloader,
noconfirm,
noop)

except IOError as e:
if e[0] == errno.EPERM:
ZELogger.log({
"level": "EXCEPTION", "message":
"You need root permissions to activate"
}, exit_on_error=True)
except zedenv.lib.check.ProcessRunningException as pr:
ZELogger.log({
"level": "EXCEPTION", "message":
f"Already running activate.\n {pr}"
}, exit_on_error=True)
90 changes: 90 additions & 0 deletions zedenv/lib/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import pyzfscmds.system.agnostic

import zedenv.lib.be
from zedenv.lib.logger import ZELogger

import os
import sys
import errno


def startup_check():
Expand All @@ -21,3 +26,88 @@ def startup_check():
pyzfscmds.system.agnostic.mountpoint_dataset("/").split("/")[0])
except RuntimeError as err:
raise RuntimeError(f"Couldn't get bootfs property of pool.\n{err}\n")


class Pidfile:

"""
Reference:
https://stackoverflow.com/a/23837022
"""

def __init__(self, name="zedenv.pid", directory="/var/run"):
self.pidfile = os.path.join(directory, name)

def __enter__(self):
if os.path.exists(self.pidfile):

pid = None
with open(self.pidfile) as f:
pid = self._check()
if pid:
self.pidfd = None
raise ProcessRunningException(
f'process already running in {self.pidfile} as {pid}')
else:
os.remove(self.pidfile)

if pid:
ProcessRunningException(
f'process already running in {self.pidfile} as {pid}')

try:
with open(self.pidfile, 'w+') as f:
f.write(str(os.getpid()))
except OSError as e:
ZELogger.log({
"level": "EXCEPTION", "message": f"Cannot write to pidfile {self.pidfile}"
}, exit_on_error=True)

return self

def __exit__(self, t, e, tb):
# return false to raise, true to pass
if t is None:
# normal condition, no exception
self._remove()
return True
elif isinstance(t, ProcessRunningException):
# do not remove the other process lockfile
return False
else:
# other exception
if self.pidfd:
# this was our lockfile, removing
self._remove()
return False

def _remove(self):
# removed pidfile
os.remove(self.pidfile)

def _check(self):
"""check if a process is still running
the process id is expected to be in pidfile, which should exist.
if it is still running, returns the pid, if not, return False."""

try:
with open(self.pidfile, 'r') as f:
try:
pidstr = f.read()
pid = int(pidstr)
except ValueError:
# not an integer
return False
try:
os.kill(pid, 0)
except OSError:
# can't deliver signal to pid
return False
else:
return pid
except FileNotFoundError:
return False


class ProcessRunningException(BaseException):
pass

0 comments on commit cb5b768

Please sign in to comment.