Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-26439 Fix ctypes.util.find_library failure on AIX #4507

Merged
merged 19 commits into from
Dec 19, 2017

Conversation

aixtools
Copy link
Contributor

@aixtools aixtools commented Nov 22, 2017

This patch implements the function find_library() for AIX.
By default it searches in AIX style archives first, and if None is found, repeats the search looking for a .so file instead. For example:
find_library("c") returns libc.a(shr.o)
while
find_library("rpm") returns librpm.so

If python is a 64-bit build, the routine knows to find the 64-bit members, e.g.,
find_library("c") returns libc.a(shr_64.o)

https://bugs.python.org/issue26439

@the-knights-who-say-ni
Copy link

Hello, and thanks for your contribution!

I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA).

Unfortunately we couldn't find an account corresponding to your GitHub username on bugs.python.org (b.p.o) to verify you have signed the CLA (this might be simply due to a missing "GitHub Name" entry in your b.p.o account settings). This is necessary for legal reasons before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue.

Thanks again to your contribution and we look forward to looking at it!

@merwok merwok changed the title Issue 26439 bpo-26439 ... Nov 22, 2017
@merwok merwok changed the title bpo-26439 ... bpo-26439 Fix ctypes.util.find_library failure on AIX Nov 22, 2017
@aixtools
Copy link
Contributor Author

where do I add news and/or say "skip news".

@Mariatta
Copy link
Member

This will need a news entry, and you can add it using blurb

@aixtools
Copy link
Contributor Author

thx.

Too bad the file cannot just be added by using an editor. While packaging I do not have any python installed - so definitely in a chicken and egg circle here...

You can install blurb from PyPI using pip. Alternatively, simply add blurb to a directory on your path. blurb’s only dependency is Python 3.5+.

@aixtools
Copy link
Contributor Author

p.s. how many days before CLA is reviewed again?

@Mariatta
Copy link
Member

I rechecked CLA :)

If you don't have blurb, the News entry can be added manually:

  • Create a new file at Misc/NEWS.d/next/ .... containing the news entry. You'll need to read blurb readme about the file name requirement.
  • commit it

@aixtools
Copy link
Contributor Author

aixtools commented Nov 24, 2017

thx for the CLA update.

I had already found the directory and the readme. will work on that today.

Update: installed python3-3.5.2 on AIX, pip installed blurb - seemed to be working - but blurb insists on calling git - so that failed (my git depends on python2 and I have not tested having python2 and python3 installed in parallel. Been working on that, but not tested it yet.

Looked at: PRETTY_NAME="Debian GNU/Linux stretch/sid" (on power)

python3.5.1 - but no pip, not even via apt install pip
centos1611 - no python3 - and the latest python2 is 2.7.5 (really sad!)

So, when I am back home I'll start testing parallel python installs - as I do not see how I can fake a random number, or is it paired to a git commit number, etc..

Here is the text I would like to add - in case someone can tell me what the file name should be.

fyi: I also have an update to Modules/_ctypes/_ctypes.c (so that import RTLD_MEMBER from _ctypes would work) - and configure.ac so that a "HAVE_RTLD_MEMBER" gets defined (not perfect define name, but close enough I hope for discussion).

Shall I push those updates now - or wait until the blurb is also ready?

@aixtools
Copy link
Contributor Author

Little bit amazing to me that removing two words from the NEWS does not complete after two hours - while with the error (finding it took <3 minutes) the 3 other tests took about 30 minutes.

@aixtools
Copy link
Contributor Author

I just remembered - something I had originally worked in, but had to remove, was looking at LD_LIBRARY_PATH and LIBPATH. Need to add that back in. Update to come asap.

@aixtools
Copy link
Contributor Author

travis ended with this error: oci runtime error: exec failed: container_linux.go:265: starting container process caused "could not create session key: disk quota exceeded - so I hope someone will cause a repeat for the pass (expected).

I have tested localally in both 32-bit and 64-bit mode. The 64-bit mode returns. as expected:
michael@x071:[/data/prj/python/git/python-3.7.0.1]./python Lib/ctypes/util.py
None
libc.a(shr_64.o)
libbz2.a(libbz2.so.1)
<CDLL 'libc.a(shr_64.o)', handle d at 0x7000000001e7a20>
<CDLL 'libc.a(shr_64.o)', handle e at 0x7000000001e7a20>

@aixtools
Copy link
Contributor Author

Could someone please get the tests to re-run. The failure was established in one second - due to lack of disk space - nothing related to code.

image

@Mariatta
Copy link
Member

I'll close and re-open the PR, it'll trigger the CI.

@Mariatta Mariatta closed this Nov 29, 2017
@Mariatta Mariatta reopened this Nov 29, 2017
@aixtools
Copy link
Contributor Author

aixtools commented Nov 29, 2017

thx. so, learning to wait for queues :)

(update: and now that the tests are passed - wait for someone to accept assignment and review!)

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enjoy a first round of review ;-)

The overall change LGTM, but I have sent a long list of requests ;-)

"""
return sizeof(c_void_p) * 8

def get_dumpH(file):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of "H", would you mind to replace it with the meaning of the option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

from ctypes._util import _last_version
from subprocess import Popen, PIPE

def aixABI():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP 8 suggests the name: aix_abi()


def aixABI():
"""Internal support function:
return executable size - 32-bit, or 64-bit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"return executable size - 32-bit, or 64-bit"

I propose: "Get the executable bits: return 32 or 64"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adjusted

def aixABI():
"""Internal support function:
return executable size - 32-bit, or 64-bit
This is vital to the search for suitable member in an archive
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"This is vital to the search for suitable member in an archive"

I don't think that this sentence is useful. At soon as a function is used, it's useful ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adjusted

objects.append((object, get_info(p)))

p.stdout.close()
p.wait()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use "with p: ..." to automatically close stdout on error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may raise an exception if p.returncode is non-zero.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this was based on recommendation from someone else. I'll reread the documentation. I hope
with Popen(....) as p:

is what you mean

If p.returncode is non-zero then there is 'nothing to find' and a return value of None is still ok.

# issue-26439 - fix broken test call for AIX
elif sys.platform.startswith("aix"):
from ctypes import CDLL
RTLD_MEMBER = 0x00040000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use os.RTDL_MEMBER instead of an hardcoded constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -0,0 +1,10 @@
Fix ctypes.util.find_library() for AIX
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure of this summary. In short, I understand that you implemented find_library() on AIX, not just "fixed".

Copy link
Contributor Author

@aixtools aixtools Dec 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it just needs to be implemented - I take that to be the "reviews" position.

However, ctypes.util.find_library() does exist for AIX - asis. And, this is broken.
The documentation makes clear that the behavior of find_library is platform-dependent.

So if you object to saying that this patch 'fixes' the published (aka existing feature of ctypes.util) because this patch 'implements' (as ctypes._aix.find_library(), just as "darwin" implements it as ctypes.macholib.dyld.dyld_find() (this was actually what I tried to emulate, just without a new sub-directory, and per much earlier reviews renamed it ctypes._aix to make clear it is not something to be imported automagically. Again - it is a platform dependency (and your suggestion to just import it into the namespace is excellent. Should have thought of that long ago.)

In closing - I see ctypes.util.find_library() as the API definition, and the current implementation is broken. This patch replaces that implementation with a new one - and this 'fixes' the API for AIX.
If the reviewers feel that the word 'fixed' is misplaced - I'll delete the line. Or, could/should I say I fixed the AIX implementation of find_library().

while legacy names e.g., find_library("c") returns libc.a(shr.o)
or libc.a(shr_64.o) - depending on 32 or 64-bit operations.
Include RTLD_MEMBER to mode to support AIX legacy library(member) names
(Modules/_ctype/_ctype.c), ctypes/__init__.py and configure.ac)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please credit yourself: add "Patch by ..." (your full name).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok.

#endif
#if HAVE_DECL_RTLD_NOW
PyModule_AddObject(m, "RTLD_NOW", PyLong_FromLong(RTLD_NOW));
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to Modules/posixmodule.c. We already have many os.RTLD_* constants there... like os.RTLD_NOW ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. as above. In my previous convos about this - they were being put into _ctypes - so I just used that convention.

@@ -338,6 +338,16 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None,
flags |= _FUNCFLAG_USE_ERRNO
if use_last_error:
flags |= _FUNCFLAG_USE_LASTERROR
if _sys.platform.startswith("aix"):
"""When the name contains ".a(" and ends with ")",
asin "libFOO.a(libFOO.so)" this is taken to be an
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asin: typo for as in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I frequently type "asin" as a single world in presentations I give. I'll change it to two words here.

 and call the routines by name, e.g., re.search, re.match, re.escape
if (sys.maxsize < 2**32):
libc_name = "libc.a(shr.o)"
else:
libc_name = "libc.a(shr_64.o)"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really needed? Won’t your find_library("c") implementation find these?

universal_newlines=True, stdout=PIPE, stderr=DEVNULL) as p:
ld_header = get_ld_header(p)
if ld_header:
ldr_headers.append((ld_header, get_ld_header_info(p)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you now only return one item, would it be better to just return the tuple or None, without building a list?

if ld_header:
    return (ld_header, get_ld_header_info(p))
else:
    return None


def get_shared(ld_headers):
"""
extract a the shareable objects from ld_headers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract the . . . [drop a]

return libpaths

def find_library(name):
"""AIX implemantation of ctypes.util.find_library()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: implementation

@@ -80,6 +81,14 @@ def find_library(name):
continue
return None

if sys.platform.startswith("aix"):
# find .so members in .a files
# using dump loader header information + sys.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the comment “using dump loader header information + sys”, I did not realize that you are referring to the sys Python module, nor the sys.platform string. I don’t understand how you can use sys.platform “to find members in files”. You are only using it to determine if the code is called on AIX.

from ctypes import CDLL
RTLD_MEMBER = 0x00040000
# print("crypto\t:: %s" % cdll.LoadLibrary(find_library("crypto")))
if (sys.maxsize < 2**32):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be easier to read (IMO) without any kind of brackets. I removed them when I modified your patch (https://bugs.python.org/review/26439/diff2/18236:18714/Lib/ctypes/util.py), and they stayed removed in your updated aix-library.161004.patch. See also test_loading.py.

rather than as separate, detailed if: sections in utils.py
"""
Lib/ctypes.util.find_library() support for AIX
Similar approach as done for Darwin support by using seperate files
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate

2.9 SVR4 linking affinity
Nowadays, there are two major object file formats used by the operating systems:
XCOFF: The COFF enhanced by IBM and others. The original COFF (Common
Object FIle Format) was the base of SVR3 and BSD 4.2 systems.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lowercase i in File?

library object is referred to as the "member".

For dlopen() on AIX (read initAndLoad()) the calls are similiar.
Default activity is achived by not providing any path information. When path
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: achieved (or archived?)


For dlopen() on AIX (read initAndLoad()) the calls are similiar.
Default activity is achived by not providing any path information. When path
information is provided dlopen() does not search any alturnate directories.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: alternate (or to avoid the Americanism, alternative or other)

@aixtools
Copy link
Contributor Author

aixtools commented Dec 6, 2017 via email

@vadmium
Copy link
Member

vadmium commented Dec 6, 2017 via email

@aixtools
Copy link
Contributor Author

aixtools commented Dec 7, 2017 via email

@aixtools
Copy link
Contributor Author

aixtools commented Dec 7, 2017 via email

@aixtools
Copy link
Contributor Author

aixtools commented Dec 7, 2017 via email

@aixtools
Copy link
Contributor Author

aixtools commented Dec 7, 2017 via email

 Restored a "while True: loop to be sure file was being read to the end
  otherwise, only the first entry was being returned.
 Corrected spelling errors
 Clarified some comments
 Modified some (temporary) tests in util.py
@aixtools
Copy link
Contributor Author

aixtools commented Dec 7, 2017 via email

@aixtools
Copy link
Contributor Author

My thanks to the reviewers.

@aixtools
Copy link
Contributor Author

The appveyor test failed in an area where this patch has not changed anything...

test_pipe_overlapped (test.test_asyncio.test_windows_utils.PipeTests) ... ok
test_popen (test.test_asyncio.test_windows_utils.PopenTests) ... test test_asyncio failed
C:\projects\cpython\lib\asyncio\base_events.py:499: ResourceWarning: unclosed event loop <_WindowsSelectorEventLoop running=False closed=False debug=False>
  source=self)
ok
======================================================================
ERROR: test_get_event_loop_returns_running_loop (test.test_asyncio.test_events.TestCGetEventLoop)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\projects\cpython\lib\test\test_asyncio\test_events.py", line 2738, in setUp
    watcher = asyncio.SafeChildWatcher()
AttributeError: module 'asyncio' has no attribute 'SafeChildWatcher'
======================================================================
ERROR: test_get_event_loop_returns_running_loop (test.test_asyncio.test_events.TestPyGetEventLoop)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\projects\cpython\lib\test\test_asyncio\test_events.py", line 2738, in setUp
    watcher = asyncio.SafeChildWatcher()
AttributeError: module 'asyncio' has no attribute 'SafeChildWatcher'
----------------------------------------------------------------------
Ran 1273 tests in 19.004s
FAILED (errors=2, skipped=56)
2 tests failed again:
    test_asyncio test_distutils
Total duration: 6 min 31 sec
Tests result: FAILURE
C:\projects\cpython>set lastexitcode=2 
C:\projects\cpython>set  1>C:\Users\appveyor\AppData\Local\Temp\1\tmp4D38.tmp 
C:\projects\cpython>echo C:\projects\cpython  1>C:\Users\appveyor\AppData\Local\Temp\1\tmp4D39.tmp 
C:\projects\cpython>exit /b 2 

  • Nothing I can examine
michael@x071:[/data/prj/python/git/python-3.7.0.1]find . -name test_windows_utils\*
./Lib/test/test_asyncio/test_windows_utils.py
michael@x071:[/data/prj/python/git/python-3.7.0.1]-name-name
michael@x071:[/data/prj/python/git/python-3.7.0.1]./python ./Lib/test/test_asyncio/test_windows_utils.py
Traceback (most recent call last):
  File "./Lib/test/test_asyncio/test_windows_utils.py", line 10, in <module>
    raise unittest.SkipTest('Windows only')
unittest.case.SkipTest: Windows only

  • How does this move forward now?

@vstinner
Copy link
Member

As Martin mentioned (and I guess I can add later), one of my goals was to have one version for both python2 and python3.

I suggest write code for Python 3.7, so using latest Python features like f-string.

If later we decide to backport the code Python 3.6 and 2.7, you can adapt the code for older version.

But I dislike having code written for Python 2 in the Python 3 stdlib.

@aixtools
Copy link
Contributor Author

aixtools commented Dec 17, 2017 via email

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f-strings are not mandatory. I propose to go without f-string, and if @Mariatta really want to get f-string in ctypes, maybe @Mariatta can write a more global f-string PR for the whole ctypes module, once the AIX code landed?

@aixtools: I rewiewed the latest version of your PR, and it's almost good to be merged. IMHO the remaining blocker issue the the creation of the _util.py file (I would prefer to not create such file for a single function, I proposed solutions).

@aixtools: It would be sad to lose this work just because of coding style. As I wrote, I'm ok to take your PR without f-string. It was more a proposal than a requirement.

Thank your for your work, and come on, it's almost done! It would be great to finally fix this very old AIX issue in Python 3.7!

naming style.
"""
__author__ = "Michael Felt <aixtools@felt.demon.nl>"
__version__ = "1.0.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike having versions in stdlib modules, especially in submodules. IMHO the Python version is enough, since the only ship the stdlib with Python. I suggest to remove __version.

# 2. If "INDEX" in occurs in a following line - return ld_header
# 3. get info (lines starting with [0-9])

def get_ld_header(p):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested functions are slow, since they are created each time you call get_ld_headers(). Would it be possible to put them at the module scope?


ldr_headers = []
p = Popen(["/usr/bin/dump", "-X%s" % aix_abi(), "-H", file],
universal_newlines=True, stdout=PIPE, stderr=DEVNULL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind to use "with p:" here to close the subprocess in case of error?

universal_newlines=True, stdout=PIPE, stderr=DEVNULL)
# be sure to read to the end-of-file - getting all entries
while True:
ld_header = get_ld_header(p)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation should be fixed.

Return executable bit size - 32 or 64
Used to filter the search in an archive by size, e.g., -X64
"""
return sizeof(c_void_p) * 8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not putting directly the result into a constant? AIX_ABI = sizeof(c_void_p) * 8. sizeof() is fast and cheap, and the result is not going to change :-)

If no archive+member pair is found, look for a .so file.
"""

def find_shared(paths, name):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, would it be possible to put this function at the module scope for performance?

@@ -0,0 +1,15 @@
from sys import maxsize
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not putting this code in util.py directly? If it's really specific to AIX, maybe put it in _aix.py instead?

@Mariatta
Copy link
Member

I don't mind preparing another PR to convert %s to f-strings, once this is merged :)

@aixtools
Copy link
Contributor Author

aixtools commented Dec 19, 2017 via email

@aixtools
Copy link
Contributor Author

aixtools commented Dec 19, 2017 via email

@aixtools
Copy link
Contributor Author

aixtools commented Dec 19, 2017 via email

@vstinner vstinner merged commit c5ae169 into python:master Dec 19, 2017
@vstinner
Copy link
Member

I merged @aixtools PR. I still had minor comments, but this PR has 116 comments, 19 commits, and @aixtools wrote that he was close to give up. Perfect is the enemy of good.

@vadmium, @Mariatta and me are free to write a new PR on top of the merged change to enhance the code even more ;-)

@aixtools
Copy link
Contributor Author

aixtools commented Dec 19, 2017 via email

@Mariatta
Copy link
Member

Thanks @aixtools for your contribution to CPython, and @vstinner and @vadmium for the reviews and getting this merged 🎉

@aixtools aixtools deleted the issue-26439 branch January 8, 2018 15:28
@aixtools aixtools restored the issue-26439 branch January 11, 2018 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants