You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
7.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# coding=utf-8
#
# Copyright 2011-2015 Splunk, 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.
from __future__ import absolute_import, division, print_function, unicode_literals
from logging import getLogger
import os
import sys
import traceback
from splunklib import six
if sys.platform == 'win32':
from signal import signal, CTRL_BREAK_EVENT, SIGBREAK, SIGINT, SIGTERM
from subprocess import Popen
import atexit
from . import splunklib_logger as logger
# P1 [ ] TODO: Add ExternalSearchCommand class documentation
class ExternalSearchCommand(object):
"""
"""
def __init__(self, path, argv=None, environ=None):
if not isinstance(path, (bytes, six.text_type)):
raise ValueError('Expected a string value for path, not {}'.format(repr(path)))
self._logger = getLogger(self.__class__.__name__)
self._path = six.text_type(path)
self._argv = None
self._environ = None
self.argv = argv
self.environ = environ
# region Properties
@property
def argv(self):
return getattr(self, '_argv')
@argv.setter
def argv(self, value):
if not (value is None or isinstance(value, (list, tuple))):
raise ValueError('Expected a list, tuple or value of None for argv, not {}'.format(repr(value)))
self._argv = value
@property
def environ(self):
return getattr(self, '_environ')
@environ.setter
def environ(self, value):
if not (value is None or isinstance(value, dict)):
raise ValueError('Expected a dictionary value for environ, not {}'.format(repr(value)))
self._environ = value
@property
def logger(self):
return self._logger
@property
def path(self):
return self._path
# endregion
# region Methods
def execute(self):
# noinspection PyBroadException
try:
if self._argv is None:
self._argv = os.path.splitext(os.path.basename(self._path))[0]
self._execute(self._path, self._argv, self._environ)
except:
error_type, error, tb = sys.exc_info()
message = 'Command execution failed: ' + six.text_type(error)
self._logger.error(message + '\nTraceback:\n' + ''.join(traceback.format_tb(tb)))
sys.exit(1)
if sys.platform == 'win32':
@staticmethod
def _execute(path, argv=None, environ=None):
""" Executes an external search command.
:param path: Path to the external search command.
:type path: unicode
:param argv: Argument list.
:type argv: list or tuple
The arguments to the child process should start with the name of the command being run, but this is not
enforced. A value of :const:`None` specifies that the base name of path name :param:`path` should be used.
:param environ: A mapping which is used to define the environment variables for the new process.
:type environ: dict or None.
This mapping is used instead of the current processs environment. A value of :const:`None` specifies that
the :data:`os.environ` mapping should be used.
:return: None
"""
search_path = os.getenv('PATH') if environ is None else environ.get('PATH')
found = ExternalSearchCommand._search_path(path, search_path)
if found is None:
raise ValueError('Cannot find command on path: {}'.format(path))
path = found
logger.debug('starting command="%s", arguments=%s', path, argv)
def terminate(signal_number, frame):
sys.exit('External search command is terminating on receipt of signal={}.'.format(signal_number))
def terminate_child():
if p.pid is not None and p.returncode is None:
logger.debug('terminating command="%s", arguments=%d, pid=%d', path, argv, p.pid)
os.kill(p.pid, CTRL_BREAK_EVENT)
p = Popen(argv, executable=path, env=environ, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
atexit.register(terminate_child)
signal(SIGBREAK, terminate)
signal(SIGINT, terminate)
signal(SIGTERM, terminate)
logger.debug('started command="%s", arguments=%s, pid=%d', path, argv, p.pid)
p.wait()
logger.debug('finished command="%s", arguments=%s, pid=%d, returncode=%d', path, argv, p.pid, p.returncode)
if p.returncode != 0:
sys.exit(p.returncode)
@staticmethod
def _search_path(executable, paths):
""" Locates an executable program file.
:param executable: The name of the executable program to locate.
:type executable: unicode
:param paths: A list of one or more directory paths where executable programs are located.
:type paths: unicode
:return:
:rtype: Path to the executable program located or :const:`None`.
"""
directory, filename = os.path.split(executable)
extension = os.path.splitext(filename)[1].upper()
executable_extensions = ExternalSearchCommand._executable_extensions
if directory:
if len(extension) and extension in executable_extensions:
return None
for extension in executable_extensions:
path = executable + extension
if os.path.isfile(path):
return path
return None
if not paths:
return None
directories = [directory for directory in paths.split(';') if len(directory)]
if len(directories) == 0:
return None
if len(extension) and extension in executable_extensions:
for directory in directories:
path = os.path.join(directory, executable)
if os.path.isfile(path):
return path
return None
for directory in directories:
path_without_extension = os.path.join(directory, executable)
for extension in executable_extensions:
path = path_without_extension + extension
if os.path.isfile(path):
return path
return None
_executable_extensions = ('.COM', '.EXE')
else:
@staticmethod
def _execute(path, argv, environ):
if environ is None:
os.execvp(path, argv)
else:
os.execvpe(path, argv, environ)
return
# endregion
def execute(path, argv=None, environ=None, command_class=ExternalSearchCommand):
"""
:param path:
:type path: basestring
:param argv:
:type: argv: list, tuple, or None
:param environ:
:type environ: dict
:param command_class: External search command class to instantiate and execute.
:type command_class: type
:return:
:rtype: None
"""
assert issubclass(command_class, ExternalSearchCommand)
command_class(path, argv, environ).execute()