Skip to content

Commit 02915b3

Browse files
committed
slapdtest: Set and check command paths in __init__
Make SlapdObject.PATH_* instance attributes, rather than class ones. Set them in __init__. Move checking them from start() to __init__(), so the check becomes simply error handling. Put a straight-up copy of Python's shutil.which() in ldap.compat -- it is a temporary backport, not a modified fork.
1 parent f01b66e commit 02915b3

File tree

2 files changed

+113
-66
lines changed

2 files changed

+113
-66
lines changed

Lib/ldap/compat.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Compatibility wrappers for Py2/Py3."""
22

33
import sys
4+
import os
45

56
if sys.version_info[0] < 3:
67
from UserDict import UserDict, IterableUserDict
@@ -41,3 +42,72 @@ def reraise(exc_type, exc_value, exc_traceback):
4142
"""
4243
# In Python 3, all exception info is contained in one object.
4344
raise exc_value
45+
46+
try:
47+
from shutil import which
48+
except ImportError:
49+
# shutil.which() from Python 3.6
50+
# "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
51+
# 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation;
52+
# All Rights Reserved"
53+
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
54+
"""Given a command, mode, and a PATH string, return the path which
55+
conforms to the given mode on the PATH, or None if there is no such
56+
file.
57+
58+
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
59+
of os.environ.get("PATH"), or can be overridden with a custom search
60+
path.
61+
62+
"""
63+
# Check that a given file can be accessed with the correct mode.
64+
# Additionally check that `file` is not a directory, as on Windows
65+
# directories pass the os.access check.
66+
def _access_check(fn, mode):
67+
return (os.path.exists(fn) and os.access(fn, mode)
68+
and not os.path.isdir(fn))
69+
70+
# If we're given a path with a directory part, look it up directly rather
71+
# than referring to PATH directories. This includes checking relative to the
72+
# current directory, e.g. ./script
73+
if os.path.dirname(cmd):
74+
if _access_check(cmd, mode):
75+
return cmd
76+
return None
77+
78+
if path is None:
79+
path = os.environ.get("PATH", os.defpath)
80+
if not path:
81+
return None
82+
path = path.split(os.pathsep)
83+
84+
if sys.platform == "win32":
85+
# The current directory takes precedence on Windows.
86+
if not os.curdir in path:
87+
path.insert(0, os.curdir)
88+
89+
# PATHEXT is necessary to check on Windows.
90+
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
91+
# See if the given file matches any of the expected path extensions.
92+
# This will allow us to short circuit when given "python.exe".
93+
# If it does match, only test that one, otherwise we have to try
94+
# others.
95+
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
96+
files = [cmd]
97+
else:
98+
files = [cmd + ext for ext in pathext]
99+
else:
100+
# On other platforms you don't have things like PATHEXT to tell you
101+
# what file suffixes are executable, so just pass on cmd as-is.
102+
files = [cmd]
103+
104+
seen = set()
105+
for dir in path:
106+
normdir = os.path.normcase(dir)
107+
if not normdir in seen:
108+
seen.add(normdir)
109+
for thefile in files:
110+
name = os.path.join(dir, thefile)
111+
if _access_check(name, mode):
112+
return name
113+
return None

Lib/slapdtest/_slapdtest.py

Lines changed: 43 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
os.environ['LDAPNOINIT'] = '1'
2222

2323
import ldap
24-
from ldap.compat import quote_plus
24+
from ldap.compat import quote_plus, which
2525

2626
HERE = os.path.abspath(os.path.dirname(__file__))
2727

@@ -109,46 +109,14 @@ def requires_ldapi():
109109
else:
110110
return identity
111111

112-
113-
def _which(cmd):
114-
"""Specialized which command based on shutil.which() from Python 3.6.
115-
116-
* simplified
117-
* always adds /sbin directories to path
118-
"""
119-
120-
def _access_check(fn):
121-
return (os.path.exists(fn) and os.access(fn, os.F_OK | os.X_OK)
122-
and not os.path.isdir(fn))
123-
124-
# Path with directory part skips PATH lookup.
125-
if os.path.dirname(cmd):
126-
if _access_check(cmd):
127-
return cmd
128-
return None
129-
130-
path = os.environ.get("PATH", os.defpath).split(os.pathsep)
131-
132-
if sys.platform == 'win32':
133-
if os.curdir not in path:
134-
path.insert(0, os.curdir)
135-
# include path extension (.exe)
136-
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
137-
files = [cmd + ext for ext in pathext]
138-
else:
139-
# always include sbin for slapd binary
140-
for sbin in ['/usr/local/sbin', '/sbin', '/usr/sbin']:
141-
if sbin not in path:
142-
path.append(sbin)
143-
files = [cmd]
144-
145-
for directory in path:
146-
for name in files:
147-
name = os.path.join(directory, name)
148-
if _access_check(name):
149-
return name
150-
return None
151-
112+
def _add_sbin(path):
113+
"""Add /sbin and related directories to a command search path"""
114+
directories = path.split(os.pathsep)
115+
if sys.platform != 'win32':
116+
for sbin in '/usr/local/sbin', '/sbin', '/usr/sbin':
117+
if sbin not in directories:
118+
directories.append(sbin)
119+
return os.pathsep.join(directories)
152120

153121
def combined_logger(
154122
log_name,
@@ -221,14 +189,9 @@ class SlapdObject(object):
221189
SCHEMADIR = "/etc/ldap/schema"
222190
else:
223191
SCHEMADIR = None
224-
# _check_requirements turns paths into absolute paths
225-
PATH_LDAPADD = 'ldapadd'
226-
PATH_LDAPDELETE = 'ldapdelete'
227-
PATH_LDAPMODIFY = 'ldapmodify'
228-
PATH_LDAPWHOAMI = 'ldapwhoami'
229-
# The following two binaries are usually in /usr/sbin.
230-
PATH_SLAPD = os.environ.get('SLAPD', 'slapd')
231-
PATH_SLAPTEST = 'slaptest'
192+
193+
BIN_PATH = os.environ.get('BIN', os.environ.get('PATH', os.defpath))
194+
SBIN_PATH = os.environ.get('SBIN', _add_sbin(BIN_PATH))
232195

233196
# time in secs to wait before trying to access slapd via LDAP (again)
234197
_start_sleep = 1.5
@@ -256,29 +219,44 @@ def __init__(self):
256219
self.default_ldap_uri = self.ldap_uri
257220
# Use simple bind via LDAP uri
258221
self.cli_sasl_external = False
222+
223+
self._find_commands()
224+
225+
if self.SCHEMADIR is None:
226+
raise ValueError('SCHEMADIR is None, ldap schemas are missing.')
227+
259228
# TLS certs
260229
self.cafile = os.path.join(HERE, 'certs/ca.pem')
261230
self.servercert = os.path.join(HERE, 'certs/server.pem')
262231
self.serverkey = os.path.join(HERE, 'certs/server.key')
263232
self.clientcert = os.path.join(HERE, 'certs/client.pem')
264233
self.clientkey = os.path.join(HERE, 'certs/client.key')
265234

266-
def _check_requirements(self):
267-
names = [
268-
"PATH_LDAPADD", "PATH_LDAPMODIFY", "PATH_LDAPDELETE",
269-
"PATH_LDAPWHOAMI", "PATH_SLAPD", "PATH_SLAPTEST",
270-
]
271-
for name in names:
272-
value = getattr(self, name)
273-
binary = _which(value)
274-
if binary is None:
275-
raise ValueError(
276-
"Command '{}' not found in PATH".format(value)
277-
)
278-
else:
279-
setattr(self, name, binary)
280-
if self.SCHEMADIR is None:
281-
raise ValueError('SCHEMADIR is None, ldap schemas are missing.')
235+
def _find_commands(self):
236+
self.PATH_LDAPADD = self._find_command('ldapadd')
237+
self.PATH_LDAPDELETE = self._find_command('ldapdelete')
238+
self.PATH_LDAPMODIFY = self._find_command('ldapmodify')
239+
self.PATH_LDAPWHOAMI = self._find_command('ldapwhoami')
240+
241+
self.PATH_SLAPD = os.environ.get('SLAPD', None)
242+
if not self.PATH_SLAPD:
243+
self.PATH_SLAPD = self._find_command('slapd', in_sbin=True)
244+
self.PATH_SLAPTEST = self._find_command('slaptest', in_sbin=True)
245+
246+
def _find_command(self, cmd, in_sbin=False):
247+
if in_sbin:
248+
path = self.SBIN_PATH
249+
var_name = 'SBIN'
250+
else:
251+
path = self.BIN_PATH
252+
var_name = 'BIN'
253+
command = which(cmd, path=path)
254+
if command is None:
255+
raise ValueError(
256+
"Command '{}' not found. Set the {} environment variable to "
257+
"override slapdtest's search path.".format(value, var_name)
258+
)
259+
return command
282260

283261
def setup_rundir(self):
284262
"""
@@ -439,7 +417,6 @@ def start(self):
439417
"""
440418

441419
if self._proc is None:
442-
self._check_requirements()
443420
# prepare directory structure
444421
atexit.register(self.stop)
445422
self._cleanup_rundir()

0 commit comments

Comments
 (0)