diff --git a/.gitignore b/.gitignore index 0d20b64..e3a9d19 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.pyc +__pycache__/ +.mypy_cache/ diff --git a/85-ebsmount.rules.in b/85-ebsmount.rules.in deleted file mode 100644 index 24faa66..0000000 --- a/85-ebsmount.rules.in +++ /dev/null @@ -1,9 +0,0 @@ -# udev rules to trigger ebsmount-udev on ebs attach|detach - -# Amazon EC2 -KERNEL=="xvd[f-p]", SUBSYSTEM=="block", ATTRS{devtype}=="vbd", ACTION=="add", RUN+="@PATH_BIN@/ebsmount-udev add" -KERNEL=="xvd[f-p]", SUBSYSTEM=="block", ACTION=="remove", RUN+="@PATH_BIN@/ebsmount-udev remove" - -# Eucalyptus / OpenStack -KERNEL=="vd[a-z]*", SUBSYSTEM=="block", ACTION=="add", RUN+="@PATH_BIN@/ebsmount-udev add" -KERNEL=="vd[a-z]*", SUBSYSTEM=="block", ACTION=="remove", RUN+="@PATH_BIN@/ebsmount-udev remove" diff --git a/Makefile b/Makefile deleted file mode 100644 index ae1166c..0000000 --- a/Makefile +++ /dev/null @@ -1,79 +0,0 @@ -# standard Python project Makefile -progname = $(shell awk '/^Source/ {print $$2}' debian/control) -name= - -prefix = /usr/local -PATH_BIN = $(prefix)/bin -PATH_ETC = $(destdir)/etc -PATH_INSTALL_LIB = $(prefix)/lib/$(progname) -PATH_UDEV_RULES = $(destdir)/lib/udev/rules.d - -all: help - -debug: - $(foreach v, $V, $(warning $v = $($v))) - @true - -### Extendable targets - -# target: help -help: - @echo '=== Targets:' - @echo 'install [ prefix=path/to/usr ] # default: prefix=$(value prefix)' - @echo 'uninstall [ prefix=path/to/usr ]' - @echo - @echo 'clean' - -# DRY macros -truepath = $(shell echo $1 | sed -e 's/^debian\/$(progname)//') -libpath = $(call truepath,$(PATH_INSTALL_LIB))/$$(basename $1) -subcommand = $(progname)-$$(echo $1 | sed 's|.*/||; s/^cmd_//; s/_/-/g; s/.py$$//') -echo-do = echo $1; $1 - -# first argument: code we execute if there is just one executable module -# second argument: code we execute if there is more than on executable module -define with-py-executables - @modules=$$(find -maxdepth 1 -type f -name '*.py' -perm -100); \ - modules_len=$$(echo $$modules | wc -w); \ - if [ $$modules_len = 1 ]; then \ - module=$$modules; \ - $(call echo-do, $1); \ - elif [ $$modules_len -gt 1 ]; then \ - for module in $$modules; do \ - $(call echo-do, $2); \ - done; \ - fi; -endef - -# set path bin in udev rules -%.rules: %.rules.in - sed "s|@PATH_BIN@|$(call truepath,$(PATH_BIN))|g" $< > $@ - -# target: install -install: 85-ebsmount.rules - @echo - @echo \*\* CONFIG: prefix = $(prefix) \*\* - @echo - - install -d $(PATH_BIN) $(PATH_ETC) $(PATH_INSTALL_LIB) $(PATH_UDEV_RULES) - cp *.conf $(PATH_ETC) - cp *.py $(PATH_INSTALL_LIB) - cp *.rules $(PATH_UDEV_RULES) - - $(call with-py-executables, \ - ln -fs $(call libpath, $$module) $(PATH_BIN)/$(progname), \ - ln -fs $(call libpath, $$module) $(PATH_BIN)/$(call subcommand, $$module)) - -# target: uninstall -uninstall: - rm -rf $(PATH_INSTALL_LIB) - rm -f $(PATH_ETC)/ebsmount.conf - rm -f $(PATH_UDEV_RULES)/85-ebsmount.rules - - $(call with-py-executables, \ - rm -f $(PATH_BIN)/$(progname), \ - rm -f $(PATH_BIN)/$(call subcommand, $$module)) - -# target: clean -clean: - rm -f *.pyc *.pyo *.rules _$(progname) diff --git a/cmd_manual.py b/cmd_manual.py deleted file mode 100755 index 47b9a36..0000000 --- a/cmd_manual.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2010 Alon Swartz -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""EBS Mount - manually mount EBS device (simulates udev add trigger) - -Arguments: - - device EBS device to mount (e.g., /dev/xvdf, /dev/vda) - -Options: - - --format=FS Format device prior to mount (e.g., --format=ext3) -""" - -import re -import os -import sys -import getopt - -import ebsmount -import executil -from utils import config, is_mounted - -def usage(e=None): - if e: - print >> sys.stderr, "error: " + str(e) - - print >> sys.stderr, "Syntax: %s [-opts] " % sys.argv[0] - print >> sys.stderr, __doc__.strip() - sys.exit(1) - -def fatal(s): - print >> sys.stderr, "error: " + str(s) - sys.exit(1) - -def _expected_devpath(devname, devpaths): - """ugly hack to test expected structure of devpath""" - raw_output = executil.getoutput('udevadm info -a -n %s' % devname) - - for line in raw_output.splitlines(): - line = line.strip() - m = re.match("^looking at parent device '(.*)':", line) - if m: - devpath = m.group(1) - for pattern in devpaths: - if re.search(pattern, devpath): - return True - - return False - -def main(): - try: - opts, args = getopt.gnu_getopt(sys.argv[1:], 'h', ['format=']) - except getopt.GetoptError, e: - usage(e) - - filesystem = None - for opt, val in opts: - if opt == '-h': - usage() - - if opt == '--format': - filesystem = val - - if not len(args) == 1: - usage() - - devname = args[0] - if not os.path.exists(devname): - fatal("%s does not exist" % devname) - - if not _expected_devpath(devname, config.devpaths.split()): - fatal("devpath not of expected structure, or failed lookup") - - if filesystem: - if is_mounted(devname): - fatal("%s is mounted" % devname) - - if not filesystem in config.filesystems.split(): - fatal("%s is not supported in %s" % (filesystem, config.CONF_FILE)) - - executil.system("mkfs." + filesystem, "-q", devname) - - ebsmount.ebsmount_add(devname, config.mountdir) - -if __name__=="__main__": - main() - diff --git a/cmd_udev.py b/cmd_udev.py deleted file mode 100755 index 3680b03..0000000 --- a/cmd_udev.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2010 Alon Swartz -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""EBS Mount - triggered by udev on EBS attach and detach - -Arguments: - - action action trigger (add | remove) - -Environment variables (Amazon EC2): - - DEVNAME (required: e.g., /dev/xvdf) - PHYSDEVPATH (required: e.g., /devices/xen/vbd-2160) - -Environment variables (Eucalyptus): - - DEVNAME (required: e.g., /dev/vda) - DEVPATH (required: e.g., /devices/virtio-pci/virtio0/block/vda) - -""" - -import re -import os -import sys - -import ebsmount -from utils import log, config - -def usage(e=None): - if e: - print >> sys.stderr, "error: " + str(e) - - print >> sys.stderr, "Syntax: %s " % sys.argv[0] - print >> sys.stderr, __doc__.strip() - sys.exit(1) - -def fatal(s): - print >> sys.stderr, "error: " + str(s) - sys.exit(1) - -def _expected_devpath(devpath, devpaths): - for pattern in devpaths: - if re.search(pattern, devpath): - return True - - return False - -def main(): - if not len(sys.argv) == 2: - usage() - - if not config.enabled.lower() == "true": - fatal('ebsmount is not enabled (%s)' % config.CONF_FILE) - - action = sys.argv[1] - devname = os.getenv('DEVNAME', None) - devpath = os.getenv('PHYSDEVPATH', os.getenv('DEVPATH', None)) - - if not action in ('add', 'remove'): - usage('action must be one of: add, remove') - - if not devname: - usage('DEVNAME is required') - - if not devpath: - usage('PHYSDEVPATH or DEVPATH is required') - - if not _expected_devpath(devpath, config.devpaths.split()): - usage('PHYSDEVPATH/DEVPATH is not of the expected structure') - - # log trigger - log(devname, "received %s trigger" % action) - - func = getattr(ebsmount, 'ebsmount_' + action) - func(devname, config.mountdir) - -if __name__=="__main__": - main() - diff --git a/conf/85-ebsmount.rules b/conf/85-ebsmount.rules new file mode 100644 index 0000000..3a587d1 --- /dev/null +++ b/conf/85-ebsmount.rules @@ -0,0 +1,9 @@ +# udev rules to trigger the ebsmount systemd service. + +# Amazon EC2 +KERNEL=="xvd[a-z]|nvme[0-26]n[0-26]", SUBSYSTEM=="block", ACTION=="add", RUN+="/usr/bin/systemctl start ebsmount@%k" +KERNEL=="xvd[a-z]|nvme[0-26]n[0-26]", SUBSYSTEM=="block", ACTION=="remove", RUN+="/usr/bin/systemctl stop ebsmount@%k" + +# OpenStack +KERNEL=="vd[a-z]*", SUBSYSTEM=="block", ACTION=="add", RUN+="@PATH_BIN@/ebsmount-udev add" +KERNEL=="vd[a-z]*", SUBSYSTEM=="block", ACTION=="remove", RUN+="@PATH_BIN@/ebsmount-udev remove" diff --git a/ebsmount.conf b/conf/ebsmount.conf similarity index 100% rename from ebsmount.conf rename to conf/ebsmount.conf diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..d231762 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,22 @@ +ebsmount (0.96) artful; urgency=low + + * Change the device name detection pattern in `85-ebsmount.rules.in` to `KERNEL=="xvd[a-z]|nvme[0-26]n[0-26]"`. + * Upgrade to Ubuntu Artful. + + -- Roy Liu Sat, 10 Feb 2018 00:00:00 -0400 + +ebsmount (0.95) yakkety; urgency=low + + * Update the `ebsmount-udev` script to work with systemd mount namespaces. + * Upgrade to Ubuntu Yakkety. + + -- Roy Liu Tue, 14 Mar 2017 00:00:00 -0400 + +ebsmount (0.94+3-0ubuntu4) precise; urgency=low + + * Add the patch 0004: Change the range of virtual block devices detected by + udev from xv[f-p] to xv[a-z]. + * Import the work of Ubuntu maintainers Scott Moser (smoser@ubuntu.com) and + Logan Rosen (logatronico@gmail.com). + + -- Roy Liu Wed, 28 Nov 2012 00:00:00 -0500 diff --git a/debian/control b/debian/control index fd20984..6a08f70 100644 --- a/debian/control +++ b/debian/control @@ -1,22 +1,22 @@ Source: ebsmount Section: misc Priority: optional -Maintainer: Alon Swartz +Maintainer: Jeremy Davis Build-Depends: debhelper (>= 10), - python-all (>= 2.6.6-3~), -Standards-Version: 4.0.0 -X-Python-Version: >= 2.6 + python3-all (>= 3.6~), + dh-python, + turnkey-conffile +Standards-Version: 4.1.1 +Homepage: https://github.com/turnkeylinux/ebsmount Package: ebsmount -Architecture: any +Architecture: all Depends: - dh-python, ${misc:Depends}, - ${python:Depends}, - turnkey-pylib, udev, -Description: Automatically mount EC2/Eucalyptus EBS devices + turnkey-conffile +Description: Automatically mount EC2/OpenStack EBS devices Automatically mounts EBS (Elastic Block Storage) devices when they are attached, supports formatted devices as well as partitions, uniquely identifiable mount points, and hooking scripts execution upon mount. diff --git a/debian/ebsmount.config b/debian/ebsmount.config new file mode 100644 index 0000000..6028c79 --- /dev/null +++ b/debian/ebsmount.config @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +source -- /usr/share/debconf/confmodule + +if ( [[ "$1" == "configure" ]] && [[ -z "$2" ]] ) || [[ "$1" == "reconfigure" ]]; then + + db_input medium ebsmount/enable_hooks || true + db_go +fi diff --git a/debian/ebsmount.install b/debian/ebsmount.install new file mode 100644 index 0000000..1eb56c9 --- /dev/null +++ b/debian/ebsmount.install @@ -0,0 +1,2 @@ +conf/85-ebsmount.rules usr/lib/udev/rules.d/ +conf/*.conf etc/ diff --git a/debian/ebsmount.lintian-overrides b/debian/ebsmount.lintian-overrides new file mode 100644 index 0000000..20db0fc --- /dev/null +++ b/debian/ebsmount.lintian-overrides @@ -0,0 +1,4 @@ +config-does-not-load-confmodule +executable-not-elf-or-script +hyphen-used-as-minus-sign +postinst-does-not-load-confmodule diff --git a/debian/ebsmount.postinst b/debian/ebsmount.postinst new file mode 100644 index 0000000..f1d8a4f --- /dev/null +++ b/debian/ebsmount.postinst @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e + +source -- /usr/share/debconf/confmodule + +case "$1" in + + configure|reconfigure) + db_get ebsmount/enable_hooks + + case "$RET" in + + true) + runhooks="True" + ;; + + false) + runhooks="False" + ;; + + *) + echo "Invalid configuration value." >&2 + exit -- 1 + ;; + esac + + sed -i -E "s/^RUNHOOKS=.*\$/RUNHOOKS=${runhooks}/g" -- /etc/ebsmount.conf + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`${1}'." >&2 + exit -- 1 + ;; +esac + +#DEBHELPER# + +db_stop + +exit -- 0 diff --git a/debian/ebsmount.templates b/debian/ebsmount.templates new file mode 100644 index 0000000..e80c47c --- /dev/null +++ b/debian/ebsmount.templates @@ -0,0 +1,7 @@ +Template: ebsmount/enable_hooks +Type: boolean +Default: false +_Description: Enable hooks? Doing so may pose a security risk. + Hooks are the ebsmount package's way of having mounted volumes configure + themselves (see + `https://raw.github.com/turnkeylinux/ebsmount/master/docs/README'). diff --git a/debian/ebsmount@.service b/debian/ebsmount@.service new file mode 100644 index 0000000..3d8a8c9 --- /dev/null +++ b/debian/ebsmount@.service @@ -0,0 +1,14 @@ +[Unit] +Description=ebsmount for %i +Documentation=man:ebsmount-udev(8) +After=local-fs.target + +[Service] +Type=oneshot +RemainAfterExit=true +Environment="DEVNAME=%i" +ExecStart=/usr/bin/ebsmount-udev add +ExecStop=/usr/bin/ebsmount-udev remove +# Very important: Expose mounted volumes to other processes. +PrivateMounts=no +MountFlags=shared diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 0000000..3e31ec7 --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,5 @@ +[buildpackage] +builder = pdebuild --pbuilder cowbuilder --debbuildopts "-sa" -- --debbuildopts "" +debian-branch = master +upstream-branch = master +upstream-tree = branch diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in new file mode 100644 index 0000000..05d8c8a --- /dev/null +++ b/debian/po/POTFILES.in @@ -0,0 +1 @@ +[type: gettext/rfc822deb] ebsmount.templates diff --git a/debian/po/templates.pot b/debian/po/templates.pot new file mode 100644 index 0000000..2af9f0a --- /dev/null +++ b/debian/po/templates.pot @@ -0,0 +1,33 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: ebsmount\n" +"Report-Msgid-Bugs-To: ebsmount@packages.debian.org\n" +"POT-Creation-Date: 2012-11-28 00:00-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../ebsmount.templates:1001 +msgid "Enable hooks? Doing so may pose a security risk." +msgstr "" + +#. Type: boolean +#. Description +#: ../ebsmount.templates:1001 +msgid "" +"Hooks are the ebsmount package's way of having mounted volumes configure " +"themselves (see `https://raw.github.com/turnkeylinux/ebsmount/master/docs/" +"README')." +msgstr "" diff --git a/debian/rules b/debian/rules index aa44a7e..66883d6 100755 --- a/debian/rules +++ b/debian/rules @@ -2,8 +2,8 @@ include /usr/share/dpkg/pkg-info.mk -%: - dh $@ --with python2 +# disable test for now (import test fails as conf file doesn't exist) +export PYBUILD_DISABLE_python3=test -override_dh_auto_install: - dh_auto_install -- prefix=debian/$(DEB_SOURCE)/usr destdir=debian/$(DEB_SOURCE) +%: + dh $@ --buildsystem=pybuild diff --git a/debian/source.lintian-overrides b/debian/source.lintian-overrides new file mode 100644 index 0000000..ea1a461 --- /dev/null +++ b/debian/source.lintian-overrides @@ -0,0 +1 @@ +no-complete-debconf-translation diff --git a/docs/README b/docs/README index f5b2ea7..e872244 100644 --- a/docs/README +++ b/docs/README @@ -1,4 +1,4 @@ -EBSmount: Automatically mount EC2/Eucalyptus EBS devices +EBSmount: Automatically mount EC2/OpenStack EBS devices ======================================================== EBSmount has 2 commands: @@ -8,7 +8,7 @@ EBSmount has 2 commands: Features: - - Supports Amazon EC2 and Eucalyptus EBS devices + - Supports Amazon EC2 and OpenStack EBS devices - Automatically mounts EBS devices when they are attached (via udev) - Supports formatted devices, as well as partitions - Ignores unformatted devices and unsupported filesystems diff --git a/ebsmount-manual b/ebsmount-manual new file mode 100755 index 0000000..f30887a --- /dev/null +++ b/ebsmount-manual @@ -0,0 +1,95 @@ +#!/usr/bin/python3 +# Copyright (c) 2010-2021 Alon Swartz +# Copyright (c) 2022 TurnKey GNU/Linux +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""EBS Mount - manually mount EBS device (simulates udev add trigger)""" + +import re +import os +import sys +import argparse +import subprocess +from subprocess import PIPE, STDOUT + +import ebsmount_lib as ebsmount +from ebsmount_lib.utils import fatal, config, is_mounted + + +def _expected_devpath(devname, devpaths): + """ugly hack to test expected structure of devpath""" + raw_output = subprocess.run(['udevadm', 'info', '-a', '-n', devname], + stderr=STDOUT, stdout=PIPE, text=True + ).stdout + + for line in raw_output.splitlines(): + line = line.strip() + m = re.match("looking at parent device '(.*)':", line) + if m: + devpath = m.group(1) + for pattern in devpaths: + if re.search(pattern, devpath): + return True + + return False + + +def main(): + parser = argparse.ArgumentParser( + prog='ebsmount-manual', + description=("EBS Mount - manually mount EBS device" + " (simulates udev add trigger)") + ) + parser.add_argument( + 'devname', + help="EBS device to mount (e.g., /dev/xvdf, /dev/vda)" + ) + parser.add_argument( + '--format', + dest="filesystem", + nargs='?', + default=None, + choices=["ext2", "ext3", "ext4"], + const="ext4", + help="Format device prior to mount (defaults to ext4 unless specified)" + ) + args = parser.parse_args() + filesystem = args.filesystem + if not os.path.exists(args.devname): + raise parser.error(f"{devname} does not exist") + + if not _expected_devpath(devname, config.devpaths.split()): + raise parser.error( + "devpath not of expected structure, or failed lookup") + + if filesystem: + if is_mounted(args.devname): + raise parser.error(f"{args.devname} is mounted") + + if filesystem not in config.filesystems.split(): + raise parser.error( + # XXX TODO this message could be better/clearer + f"{filesystem} is not supported in {config.CONF_FILE}") + + format_dev = subprocess.run([f"mkfs.{filesystem}", "-q", args.devname], + stdout=PIPE, stderr=STDOUT, text=True) + if format_dev.returncode != 0: + fatal(f"formating {filesystem} failed: {format_dev.stdout}") + + ebsmount.ebsmount_add(args.devname, config.mountdir) + + +if __name__ == "__main__": + main() diff --git a/ebsmount-udev b/ebsmount-udev new file mode 100755 index 0000000..f8c8230 --- /dev/null +++ b/ebsmount-udev @@ -0,0 +1,87 @@ +#!/usr/bin/python3 +# Copyright (c) 2010-2021 Alon Swartz +# Copyright (c) 2022 TurnKey GNU/Linux +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""EBS Mount - triggered by udev on EBS attach and detach""" + +import re +import os +import sys +import argparse + +import ebsmount_lib as ebsmount +from ebsmount_lib.utils import fatal, log, config + +ENVVARS = """ +environment variables (required): + +platfrom env var example value +-------- ------- ------------- +AWS EC2 DEVNAME /dev/xvdf + PHYSDEVPATH /devices/vbd-51792/block/xvdf + +OpenStack DEVNAME /dev/vda + DEVPATH /devices/virtio-pci/virtio0/block/vda +""" + + +def _expected_devpath(devpath, devpaths): + for pattern in devpaths: + if re.search(pattern, devpath): + return True + + return False + + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + prog='ebsmount-udev', + description="EBS Mount - triggered by udev on EBS attach and detach", + epilog=ENVVARS + ) + parser.add_argument( + 'action', + choices=["add", "remove"], + help="action trigger" + ) + args = parser.parse_args() + + if not config.enabled.lower() == "true": + fatal(f'ebsmount is not enabled ({config.CONF_FILE})') + + devname = os.getenv('DEVNAME', None) + devpath = os.getenv('PHYSDEVPATH', os.getenv('DEVPATH', None)) + + if not devname: + raise parser.error('DEVNAME is required') + + # it seems that these may not be needed?! + #if not devpath: + # raise parser.error('PHYSDEVPATH or DEVPATH is required') + + #if not _expected_devpath(devpath, config.devpaths.split()): + # raise parser.error( + # 'PHYSDEVPATH/DEVPATH is not of the expected structure') + + # log trigger + log(devname, f"received {args.action} trigger") + func = getattr(ebsmount, 'ebsmount_' + args.action) + func(devname, config.mountdir) + + +if __name__ == "__main__": + main() diff --git a/ebsmount.py b/ebsmount.py deleted file mode 100644 index 6f19fa4..0000000 --- a/ebsmount.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2010 Alon Swartz -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -from os.path import * - -import pwd - -import udevdb -from executil import system -from utils import config, log, is_mounted, mount - -def ebsmount_add(devname, mountdir): - """ebs device attached""" - - matching_devices = [] - for device in udevdb.query(): - if device.name.startswith(basename(devname)): - matching_devices.append(device) - - for device in matching_devices: - devpath = join('/dev', device.name) - mountpath = join(mountdir, device.env.get('ID_FS_UUID', devpath[-1])[:6]) - mountoptions = ",".join(config.mountoptions.split()) - hookspath = join(mountpath, ".ebsmount") - - filesystem = device.env.get('ID_FS_TYPE', None) - if not filesystem: - log(devname, "could not identify filesystem: %s" % devpath) - continue - - if not filesystem in config.filesystems.split(): - log(devname, "filesystem (%s) not supported: %s" % (filesystem,devpath)) - continue - - if is_mounted(devpath): - log(devname, "already mounted: %s" % devpath) - continue - - mount(devpath, mountpath, mountoptions) - log(devname, "mounted %s %s (%s)" % (devpath, mountpath, mountoptions)) - - if exists(hookspath): - hooks = os.listdir(hookspath) - hooks.sort() - - if hooks and not config.runhooks.lower() == "true": - log(devname, "skipping hooks: RUNHOOKS not set to True") - continue - - for file in hooks: - fpath = join(hookspath, file) - if not os.access(fpath, os.X_OK): - log(devname, "skipping hook: '%s', not executable" % file) - continue - - if not os.stat(fpath).st_uid == 0 or not os.stat(fpath).st_gid == 0: - log(devname, "skipping hook: '%s', not owned root:root" % file) - continue - - log(devname, "executing hook: %s" % file) - os.environ['HOME'] = pwd.getpwuid(os.getuid()).pw_dir - os.environ['MOUNTPOINT'] = mountpath - system("/bin/bash --login -c '%s' 2>&1 | tee -a %s" % (fpath, config.logfile)) - -def ebsmount_remove(devname, mountdir): - """ebs device detached""" - - mounted = False - for d in os.listdir(mountdir): - path = join(mountdir, d) - if is_mounted(path): - mounted = True - continue - - os.rmdir(path) - - if not mounted: - os.rmdir(mountdir) - diff --git a/ebsmount_lib/__init__.py b/ebsmount_lib/__init__.py new file mode 100644 index 0000000..b7a9661 --- /dev/null +++ b/ebsmount_lib/__init__.py @@ -0,0 +1,108 @@ +# Copyright (c) 2010-2021 Alon Swartz +# Copyright (c) 2022 TurnKey GNU/Linux +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +# XXX TODO may need additional imports below (moving from importing *) +from os.path import join, basename, exists +import subprocess +from subprocess import STDOUT, PIPE + +import pwd + +from .udevdb import query +from .utils import config, log, is_mounted, mount + + +def ebsmount_add(devname, mountdir): + """ebs device attached""" + + matching_devices = [] + for device in query(): + if device.name.startswith(basename(devname)): + matching_devices.append(device) + + for device in matching_devices: + devpath = join('/dev', device.name) + mountpath = join(mountdir, device.env.get( + 'ID_FS_UUID', devpath[-1])[:6]) + mountoptions = " ".join(config.mountoptions.split()) + hookspath = join(mountpath, ".ebsmount") + + filesystem = device.env.get('ID_FS_TYPE', None) + if not filesystem: + log(devname, f"could not identify filesystem: {devpath}") + continue + + if filesystem not in config.filesystems.split(): + log(devname, + f"invalid filesystem: {filesystem}, encountered while" + f" adding {devpath}") + continue + + if is_mounted(devpath): + log(devname, f"already mounted: {devpath}") + continue + + mount(devpath, mountpath, mountoptions) + log(devname, f"mounted {devpath} {mountpath} ({mountoptions})") + + if exists(hookspath): + hooks = os.listdir(hookspath) + hooks.sort() + + if hooks and not config.runhooks.lower() == "true": + log(devname, "skipping hooks: RUNHOOKS not set to True") + continue + + for file in hooks: + fpath = join(hookspath, file) + if not os.access(fpath, os.X_OK): + log(devname, f"skipping hook: '{file}', not executable") + continue + + if (not os.stat(fpath).st_uid == 0 or + not os.stat(fpath).st_gid == 0): + log(devname, + f"skipping hook: '{file}', not owned root:root") + continue + + log(devname, f"executing hook: {file}") + os.environ['HOME'] = pwd.getpwuid(os.getuid()).pw_dir + os.environ['MOUNTPOINT'] = mountpath + proc = subprocess.run(['/bin/bash', '--login', '-c', fpath], + stderr=STDOUT, stdout=PIPE, check=True) + subprocess.run(['tee', '-a', config.logfile], + input=proc.stdout) + + +def ebsmount_remove(devname, mountdir): + """ebs device detached""" + + mounted = False + try: + for d in os.listdir(mountdir): + path = join(mountdir, d) + print(f'checking path: {path}') + if is_mounted(path): + print(f'path mounted: {path}') + mounted = True + continue + os.rmdir(path) + + if not mounted: + os.rmdir(mountdir) + except FileNotFoundError: + pass diff --git a/udevdb.py b/ebsmount_lib/udevdb.py similarity index 69% rename from udevdb.py rename to ebsmount_lib/udevdb.py index 987a670..4c4e4fa 100644 --- a/udevdb.py +++ b/ebsmount_lib/udevdb.py @@ -1,8 +1,9 @@ -# Copyright (c) 2010 Alon Swartz +# Copyright (c) 2010-2021 Alon Swartz +# Copyright (c) 2022 TurnKey GNU/Linux # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of +# published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, @@ -13,7 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from executil import ExecError, getoutput +import subprocess +from subprocess import PIPE, STDOUT + class Device: """class to hold device information enumerated from udev database""" @@ -50,15 +53,16 @@ def _parse_raw_data(self, s): self.env[name] = val.lstrip("=") def _get_volinfo(self): - if self.env.has_key('DEVTYPE') and self.env['DEVTYPE'] == 'disk': - try: - volume_info = getoutput('vol_id /dev/%s' % self.name) - except ExecError: + if 'DEVTYPE' in self.env and self.env['DEVTYPE'] == 'disk': + proc = subprocess.run(["blkid", '-o', 'udev', f"/dev/{self.name}"], + capture_output=True, text=True) + if proc.returncode != 0: return + volume_info = proc.stdout for value in volume_info.splitlines(): name, val = value.split("=") - if self.env.has_key(name): + if name in self.env: continue if not val: @@ -66,41 +70,36 @@ def _get_volinfo(self): self.env[name] = val + def query(device=None, volinfo=True): """query udev database and return device(s) information if no device is specified, all devices will be returned optionally query volume info (vol_id) on disk devices """ if device: - cmd = "udevadm info --query all --name %s" % device + cmd = ["udevadm", "info", "--query", "all", "--name", device] else: - cmd = "udevadm info --export-db" + cmd = ["udevadm", "info", "--export-db"] devices = [] - for s in getoutput(cmd + " 2>/dev/null").split('\n\n'): + output = subprocess.run(cmd, stdout=PIPE, stderr=STDOUT, text=True).stdout + for s in output.split('\n\n'): devices.append(Device(s, volinfo)) return devices - - + + def _disk_devices(): """debug/test method to print disk devices""" devices = query() for dev in devices: - if dev.env.has_key('DEVTYPE') and dev.env['DEVTYPE'] == 'disk': - print '/dev/' + dev.name + if 'DEVTYPE' in dev.env and dev.env['DEVTYPE'] == 'disk': + print('/dev/' + dev.name) - attrs = dev.env.keys() + attrs = list(dev.env.keys()) attrs.sort() - column_len = max([ len(attr) + 1 for attr in attrs ]) + column_len = max(len(attr) + 1 for attr in attrs) for attr in attrs: name = attr + ":" - print " %s %s" % (name.ljust(column_len), dev.env[attr]) - print - -def main(): - _disk_devices() #used in debugging/testing - -if __name__ == '__main__': - main() - + print(f" {name.ljust(column_len)} {dev.env[attr]}") + print() diff --git a/utils.py b/ebsmount_lib/utils.py similarity index 59% rename from utils.py rename to ebsmount_lib/utils.py index 8ec24c5..75a99b1 100644 --- a/utils.py +++ b/ebsmount_lib/utils.py @@ -1,8 +1,9 @@ -# Copyright (c) 2010 Alon Swartz +# Copyright (c) 2010-2021 Alon Swartz +# Copyright (c) 2022 TurnKey GNU/Linux # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of +# published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, @@ -14,22 +15,34 @@ # along with this program. If not, see . import os -import executil +import subprocess +from subprocess import PIPE, STDOUT + from conffile import ConfFile + class EBSMountConf(ConfFile): CONF_FILE = '/etc/ebsmount.conf' - REQUIRED = ['enabled', 'runhooks', 'mountdir', 'mountoptions', 'filesystems', 'logfile', 'devpaths'] + REQUIRED = ['enabled', 'runhooks', 'mountdir', 'mountoptions', + 'filesystems', 'logfile', 'devpaths'] + config = EBSMountConf() def log(devname, s): - entry = "%s: %s" % (devname, s) - file(config.logfile, 'a').write(entry + "\n") - print entry + entry = f"{devname}: {s}" + with open(config.logfile, 'a') as fob: + fob.write(entry + "\n") + print(entry) + + +def fatal(s): + print("error: " + str(s), file=sys.stderr) + sys.exit(1) -def mkdir_parents(path, mode=0777): + +def mkdir_parents(path, mode=0o777): """mkdir 'path' recursively (I.e., equivalent to mkdir -p)""" dirs = path.split("/") for i in range(2, len(dirs) + 1): @@ -39,20 +52,26 @@ def mkdir_parents(path, mode=0777): os.mkdir(dir, mode) + def is_mounted(path): """test if path is mounted""" - mounts = file("/proc/mounts").read() + with open("/proc/mounts") as fob: + mounts = fob.read() if mounts.find(path) != -1: return True return False -def mount(devpath, mountpath, options=None): + +def mount(devpath, mountpath, options=''): """mount devpath to mountpath with specified options (creates mountpath)""" if not os.path.exists(mountpath): mkdir_parents(mountpath) if options: - executil.system("mount", "-o", options, devpath, mountpath) + proc = subprocess.run(["systemd-mount", "-o", options, devpath, mountpath], + stderr=STDOUT, stdout=PIPE, text=True) else: - executil.system("mount", devpath, mountpath) - + proc = subprocess.run(["systemd-mount", devpath, mountpath], + stderr=STDOUT, stdout=PIPE, text=True) + if proc.returncode != 0: + print(f'An error occurred when mounting:\n{proc.stdout}') diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..69843d4 --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 + +from distutils.core import setup + +setup( + name="ebsmount", + version="0.96+", + author="Jeremy Davis", + author_email="jeremy@turnkeylinux.org", + url="https://github.com/turnkeylinux/ebsmount", + packages=["ebsmount_lib"], + scripts=["ebsmount-manual", "ebsmount-udev"] +)