To support upcoming tools based on Python, start building a module of utility functions that can be imported to keep the new tools small.
Eventually this could become a FedFS implementation in Python. Signed-off-by: Chuck Lever <[email protected]> --- .gitignore | 1 configure.ac | 2 src/Makefile.am | 2 src/PyFedfs/Makefile.am | 30 +++++ src/PyFedfs/__init__.py | 25 ++++ src/PyFedfs/run.py | 299 ++++++++++++++++++++++++++++++++++++++++++++++ src/PyFedfs/userinput.py | 105 ++++++++++++++++ src/PyFedfs/utilities.py | 134 +++++++++++++++++++++ 8 files changed, 597 insertions(+), 1 deletion(-) create mode 100644 src/PyFedfs/Makefile.am create mode 100644 src/PyFedfs/__init__.py create mode 100644 src/PyFedfs/run.py create mode 100644 src/PyFedfs/userinput.py create mode 100644 src/PyFedfs/utilities.py diff --git a/.gitignore b/.gitignore index 379a95e..ab759f1 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,4 @@ depcomp .deps/ .libs/ .stgit* +py-compile diff --git a/configure.ac b/configure.ac index e07f108..fd70d92 100644 --- a/configure.ac +++ b/configure.ac @@ -36,6 +36,7 @@ AC_CONFIG_MACRO_DIR([m4]) m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) LT_INIT AM_INIT_AUTOMAKE([-Wall -Werror silent-rules]) +AM_PATH_PYTHON([2.7]) # configure command line options AC_ARG_WITH([fedfsuser], @@ -195,5 +196,6 @@ AC_CONFIG_FILES([Makefile src/nfsref/Makefile src/nsdbc/Makefile src/nsdbparams/Makefile + src/PyFedfs/Makefile src/plug-ins/Makefile]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 003ff8e..c7dff86 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,7 +26,7 @@ SUBDIRS = include libxlog libadmin libnsdb libjunction \ libparser libsi \ fedfsc fedfsd mount nfsref nsdbc nsdbparams \ - plug-ins + plug-ins PyFedfs CLEANFILES = cscope.in.out cscope.out cscope.po.out *~ DISTCLEANFILES = Makefile.in diff --git a/src/PyFedfs/Makefile.am b/src/PyFedfs/Makefile.am new file mode 100644 index 0000000..be67d2a --- /dev/null +++ b/src/PyFedfs/Makefile.am @@ -0,0 +1,30 @@ +## +## @file src/PyFedfs/Makefile.am +## @brief Process this file with automake to produce src/domainroot/Makefile.in +## + +## +## Copyright 2013 Oracle. All rights reserved. +## +## This file is part of fedfs-utils. +## +## fedfs-utils is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License version 2.0 as +## published by the Free Software Foundation. +## +## fedfs-utils 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 version 2.0 for more details. +## +## You should have received a copy of the GNU General Public License +## version 2.0 along with fedfs-utils. If not, see: +## +## http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +## + +pyfedfs_PYTHON = __init__.py run.py userinput.py utilities.py +pyfedfsdir = $(pythondir)/PyFedfs + +CLEANFILES = cscope.in.out cscope.out cscope.po.out *~ +DISTCLEANFILES = Makefile.in diff --git a/src/PyFedfs/__init__.py b/src/PyFedfs/__init__.py new file mode 100644 index 0000000..5f76b5a --- /dev/null +++ b/src/PyFedfs/__init__.py @@ -0,0 +1,25 @@ +""" +PyFedfs + +This module contains an evolving implementation of FedFS +in Python. + +""" + +__copyright__ = """ +Copyright 2013 Oracle. All rights reserved. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2.0 +as published by the Free Software Foundation. + +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 version 2.0 for more details. + +A copy of the GNU General Public License version 2.0 is +available here: + + http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +""" diff --git a/src/PyFedfs/run.py b/src/PyFedfs/run.py new file mode 100644 index 0000000..dc04b92 --- /dev/null +++ b/src/PyFedfs/run.py @@ -0,0 +1,299 @@ +""" +run - utilities for running commands and managing daemons + +Part of the PyFedfs module +""" + +__copyright__ = """ +Copyright 2013 Oracle. All rights reserved. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2.0 +as published by the Free Software Foundation. + +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 version 2.0 for more details. + +A copy of the GNU General Public License version 2.0 is +available here: + + http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +""" + +# Standard Unix shell exit values +EXIT_SUCCESS = 0 +EXIT_FAILURE = 1 + +import os +import pwd +import logging as log + +from subprocess import Popen, PIPE + + +def __run(command): + """ + Run a command, ignore all command output, but return exit status + + Returns a shell exit status value + """ + try: + process = Popen(command, stdout=PIPE, stderr=PIPE, shell=False) + except OSError: + log.error('"%s" command did not execute', ' '.join(command)) + return None + except ValueError: + log.error('"%s": bad arguments to Popen', ' '.join(command)) + return None + return process + + +def run_command(command, force=False): + """ + Run a command, ignore all command output, but return exit status + + Returns a shell exit status value + """ + log.debug('Running the "%s" command...', command[0]) + + process = __run(command) + if process is None: + return EXIT_FAILURE + + # pylint: disable-msg=E1101 + process.wait() + # pylint: disable-msg=E1101 + if process.returncode != 0: + if not force: + log.error('"%s" returned %d', command[0], process.returncode) + return EXIT_FAILURE + return EXIT_SUCCESS + + +def __ut_run_command(): + """ + Unit tests for run_command + """ + result = run_command(['ls', '-l']) + if result == EXIT_SUCCESS: + print('run_command("ls -l") succeeded') + elif result == EXIT_FAILURE: + print('run_command("ls -l") failed') + else: + print('run_command("ls -l"): %d' % result) + + +def demote(user_uid, user_gid): + """ + Returns a function that changes the UID and GID of a process + """ + def result(): + """ + Change the UID and GID of a process + """ + os.setgid(user_gid) + os.setuid(user_uid) + + return result + + +def run_as_user(username, command): + """ + Run a command as a different user + + Returns a shell exit status value + """ + log.debug('Running the "%s" command as %s...', command[0], username) + + try: + user_uid = pwd.getpwnam(username).pw_uid + user_gid = pwd.getpwnam(username).pw_gid + except KeyError: + log.error('"%s" is not a valid user', username) + return EXIT_FAILURE + + try: + process = Popen(command, preexec_fn=demote(user_uid, user_gid), + stdout=PIPE, stderr=PIPE, shell=False) + except OSError: + log.error('"%s" command did not execute', ' '.join(command)) + return EXIT_FAILURE + except ValueError: + log.error('"%s": bad arguments to Popen', ' '.join(command)) + return EXIT_FAILURE + + # pylint: disable-msg=E1101 + process.wait() + # pylint: disable-msg=E1101 + if process.returncode != 0: + log.error('"%s" returned %d', command[0], process.returncode) + return EXIT_FAILURE + return EXIT_SUCCESS + + +def __ut_run_as_user(): + """ + Unit tests for run_as_user() + """ + result = run_as_user('ldap', ['echo', 'this is a test']) + if result == EXIT_SUCCESS: + print('run_as_user("ldap", "echo this is a test") succeeded') + elif result == EXIT_FAILURE: + print('run_as_user("ldap", "echo this is a test") failed') + else: + print('run_as_user("ldap", "echo this is a test"): %d' % result) + + result = run_as_user('bogus', ['id']) + if result == EXIT_SUCCESS: + print('run_as_user("bogus", "id") succeeded') + elif result == EXIT_FAILURE: + print('run_as_user("bogus", "id") failed') + else: + print('run_as_user("bogus", "id"): %d' % result) + + +def check_for_daemon(name): + """ + Predicate: is a process named "name" running on the system? + + Returns True if "name" is running, otherwise returns False + """ + process = __run(['pgrep', name]) + if process is None: + return False + + # pylint: disable-msg=E1101 + process.wait() + # pylint: disable-msg=E1101 + if process.returncode != 0: + return False + return True + + +def __ut_check_for_daemon(): + """ + Unit tests for check_for_daemon() + """ + if check_for_daemon('dbus'): + print('dbus is running') + else: + print('dbus is not running') + + if check_for_daemon('bogus'): + print('bogus is running') + else: + print('bogus is not running') + + +def systemctl(command, service): + """ + Try a systemctl command, report failure + + Returns a shell exit status value + """ + log.debug('Trying to %s the %s service...', command, service) + + process = __run(['systemctl', command, service + '.service']) + if process is None: + return EXIT_FAILURE + + # pylint: disable-msg=E1101 + process.wait() + # pylint: disable-msg=E1101 + if process.returncode != 0: + log.error('systemctl %s %s.service failed: %d', + command, service, process.returncode) + return EXIT_FAILURE + return EXIT_SUCCESS + + +def __ut_systemctl(): + """ + Unit tests for run.py module + """ + result = systemctl('status', 'network') + if result == EXIT_SUCCESS: + print('systemctl result: succeeded') + elif result == EXIT_FAILURE: + print('systemctl result: failed') + else: + print('systemctl result: %d' % result) + + result = systemctl('status', 'bogus') + if result == EXIT_SUCCESS: + print('systemctl result: succeeded') + elif result == EXIT_FAILURE: + print('systemctl result: failed') + else: + print('systemctl result: %d' % result) + + +def stop_service(service): + """ + Stop a server + + Returns a shell exit status value + """ + if systemctl('status', service) != 0: + log.debug('"%s" is not running', service) + return EXIT_SUCCESS + + ret = systemctl('stop', service) + if ret != EXIT_SUCCESS: + return ret + + log.debug('The "%s" service was stopped successfully', service) + return EXIT_SUCCESS + + +def start_service(service): + """ + Start a server + + Returns a shell exit status value + """ + ret = systemctl('start', service) + if ret != EXIT_SUCCESS: + return ret + + log.debug('The "%s" service was started successfully', service) + return EXIT_SUCCESS + + +def enable_and_start_service(service): + """ + Enable and start a server + + Returns a shell exit status value + """ + ret = systemctl('enable', service) + if ret != EXIT_SUCCESS: + return ret + + return start_service(service) + + +def restart_service(service): + """ + Restart a server + + Returns a shell exit status value + """ + return systemctl('restart', service) + + +__all__ = ['EXIT_SUCCESS', 'EXIT_FAILURE', + 'run_command', 'run_as_user', + 'check_for_daemon', + 'stop_service', 'start_service', + 'restart_service', 'enable_and_start_service'] + +if __name__ == '__main__': + log.basicConfig(format='%(levelname)s: %(message)s', level=log.DEBUG) + + __ut_check_for_daemon() + __ut_run_command() + __ut_run_as_user() + __ut_systemctl() diff --git a/src/PyFedfs/userinput.py b/src/PyFedfs/userinput.py new file mode 100644 index 0000000..285e903 --- /dev/null +++ b/src/PyFedfs/userinput.py @@ -0,0 +1,105 @@ +""" +userinput - utilities to get user input + +Part of the PyFedfs module +""" + +__copyright__ = """ +Copyright 2013 Oracle. All rights reserved. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2.0 +as published by the Free Software Foundation. + +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 version 2.0 for more details. + +A copy of the GNU General Public License version 2.0 is +available here: + + http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +""" + + +import sys +from getpass import getpass +from subprocess import check_output, CalledProcessError + + +def confirm(question): + """ + Confirm user would like to proceed with some operation + + Returns True if user gives a "yes" answer, False if user + gives a "no" answer or hits return. + """ + valid = {'yes': True, 'ye': True, 'y': True, 'no': False, 'n': False} + + while True: + sys.stdout.write(question + ' [y/N] ') + choice = raw_input().lower() + if choice == '': + return False + elif choice in valid: + return valid[choice] + else: + sys.stdout.write('Respond "yes" or "no"\n') + + +def __ut_confirm(): + """ + Unit tests for confirm() + """ + if confirm('Answer this question: '): + print('You answered "yes"') + else: + print('You answered "no"') + + +def get_password(prompt): + """ + Ask user for a password; use input blanking + + Returns a string + """ + print(prompt) + while True: + try: + password1 = getpass('New password: ') + password2 = getpass('Re-enter new password: ') + except KeyboardInterrupt: + return '' + + if password1 == '': + print('Empty password, try again') + elif password1 != password2: + print('Password values do not match, try again') + else: + break + + try: + result = check_output(['slappasswd', '-n', '-s', password1]) + except CalledProcessError: + return '' + return result + + +def __ut_get_password(): + """ + Unit tests for get_password() + """ + password = get_password('Enter a strong password: ') + if password == '': + print('An empty password was returned') + else: + print('The password you entered was "%s"' % password) + + +__all__ = ['confirm', 'get_password'] + +# unit tests +if __name__ == '__main__': + __ut_confirm() + __ut_get_password() diff --git a/src/PyFedfs/utilities.py b/src/PyFedfs/utilities.py new file mode 100644 index 0000000..cf36f4f --- /dev/null +++ b/src/PyFedfs/utilities.py @@ -0,0 +1,134 @@ +""" +Utility functions for PyFedfs +""" + +__copyright__ = """ +Copyright 2013 Oracle. All rights reserved. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2.0 +as published by the Free Software Foundation. + +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 version 2.0 for more details. + +A copy of the GNU General Public License version 2.0 is +available here: + + http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +""" + +try: + import sys + import os + import logging as log + + from PyFedfs.run import EXIT_SUCCESS, EXIT_FAILURE +except ImportError: + print >> sys.stderr, \ + 'Could not import a required Python module:', sys.exc_value + sys.exit(1) + + +def change_mode(pathname, mode): + """ + Change permission on a local file + + Returns a shell exit status value + """ + log.debug('Changing mode bits on "%s"...', pathname) + + ret = EXIT_FAILURE + try: + os.chmod(pathname, mode) + ret = EXIT_SUCCESS + except OSError: + log.error('Failed to chmod "%s"', pathname) + + return ret + + +def list_directory(pathname): + """ + List all entries but 'lost+found' + + Returns a list containing one entry for each item in "pathname" + """ + log.debug('Listing directory "' + pathname + '"...') + + try: + output = os.listdir(pathname) + except OSError as inst: + log.error('Failed to list "%s": %s', pathname, inst) + return [] + + return [x for x in output if x != 'lost+found'] + + +def remove_directory(pathname, force=False): + """ + Remove a local directory + + Returns a shell exit status value + """ + log.debug('Removing directory "%s"...', pathname) + + ret = EXIT_FAILURE + try: + os.rmdir(pathname) + ret = EXIT_SUCCESS + except OSError: + if not force: + log.error('Failed to remove "%s"', pathname) + + if force: + return EXIT_SUCCESS + return ret + + +def make_directory(pathname, mode): + """ + Create a local directory + + Returns a shell exit status value + """ + if os.path.isdir(pathname): + log.debug('Directory "%s" exists', pathname) + return EXIT_SUCCESS + + log.debug('Creating directory "%s"...', pathname) + + try: + os.mkdir(pathname) + except OSError: + log.error('Failed to create "%s"', pathname) + return EXIT_FAILURE + + ret = change_mode(pathname, mode) + if ret != EXIT_SUCCESS: + log.error('Failed to chmod "%s"', pathname) + os.rmdir(pathname) + return EXIT_FAILURE + + return EXIT_SUCCESS + + +__all__ = ['make_directory', 'list_directory', 'remove_directory', + 'change_mode'] + +if __name__ == '__main__': + log.basicConfig(format='%(levelname)s: %(message)s', level=log.DEBUG) + + list_directory('/tmp') + list_directory('/bogus') + + make_directory('/tmp/__ut__', 0755) + make_directory('/tmp/__ut__', 0755) + remove_directory('/tmp/__ut__') + remove_directory('/tmp/__ut__') + + make_directory('/tmp/__ut_chmod__', 0700) + change_mode('/tmp/__ut_chmod__', 0755) + remove_directory('/tmp/__ut_chmod__') _______________________________________________ fedfs-utils-devel mailing list [email protected] https://oss.oracle.com/mailman/listinfo/fedfs-utils-devel
