@@ -71,3 +71,4 @@ depcomp
.deps/
.libs/
.stgit*
+py-compile
@@ -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
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
+"""
new file mode 100644
@@ -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()
new file mode 100644
@@ -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()
new file mode 100644
@@ -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__')
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 <chuck.lever@oracle.com> --- .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