Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 47 additions & 15 deletions newrelic/admin/run_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,50 @@
from newrelic.admin import command, usage


def _resolve_program_path(program):
import os
import sys
from pathlib import Path

# If the program path contains a parent directory, then we never have to search PATH.
# Don't use pathlib.Path.parent to check this, as it can't distinguish between ./ and no parent.
if os.path.dirname(program): # noqa: PTH120
return program

# Split PATH into a list of directories to search for the program.
program = Path(program)
program_search_path = os.environ.get("PATH", "").split(os.pathsep)

if sys.platform != "win32":
# POSIX systems simply search each entry in the PATH in order.
for path_entry in program_search_path:
path = Path(path_entry) / program
if path.exists() and os.access(path, os.X_OK):
return path
else:
# Windows systems search the current directory, followed by each entry in the PATH in order.
# In each directory, it will search for a program name with each of the
# executable extensions in PATHEXT in order. If the program name was specified with
# one of the executable extensions, it will search for that first before trying to append
# extensions to what was specified by the user.

# Split PATHEXT into a list of file extensions to append to the program when searching. (eg. [".exe", ".bat"])
program_ext_search_list = [ext.lower() for ext in os.environ.get("PATHEXT", "").split(os.pathsep)]

# If the program already has a valid executable extension, then we should first search for it as is.
if program.suffix.lower() in program_ext_search_list:
program_ext_search_list.insert(0, "")

for path_entry in program_search_path:
for ext in program_ext_search_list:
# Must be careful not to delete existing suffix
path = Path(path_entry) / program.with_suffix(program.suffix + ext)
if path.exists() and os.access(path, os.X_OK):
return path

return program # Failsafe, if not found let os.execl() handle the error


@command(
"run-program",
"...",
Expand Down Expand Up @@ -94,23 +138,11 @@ def log_message(text, *args):
os.environ["NEW_RELIC_PYTHON_PREFIX"] = sys_prefix
os.environ["NEW_RELIC_PYTHON_VERSION"] = ".".join(map(str, sys.version_info[:2]))

# If not an absolute or relative path, then we need to
# see if program can be found in PATH. Note that can
# be found in current working directory even though '.'
# not in PATH.

program_exe_path = args[0]

# Don't use path.parent, as it can't distinguish between ./ and no parent.
if not os.path.dirname(program_exe_path): # noqa: PTH120
program_search_path = os.environ.get("PATH", "").split(os.pathsep)
for path in program_search_path:
path = Path(path) / program_exe_path
if path.exists() and os.access(path, os.X_OK):
program_exe_path = str(path) # Convert to str to match other code path
break
# Convert output to str for cleaner logging
program_exe_path = str(_resolve_program_path(args[0]))

log_message("program_exe_path = %r", program_exe_path)
log_message("execl_arguments = %r", [program_exe_path, *args])

# args already contains program_exe_path as first element, no need to repeat it again
os.execl(program_exe_path, *args) # noqa: S606