-
Notifications
You must be signed in to change notification settings - Fork 144
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
Add support for musllinux #315
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
class AuditwheelException(Exception): | ||
pass | ||
|
||
|
||
class InvalidLibc(AuditwheelException): | ||
pass |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -17,9 +17,12 @@ | |||||
import errno | ||||||
import logging | ||||||
import functools | ||||||
from pathlib import Path | ||||||
from typing import List, Dict, Optional, Any, Tuple | ||||||
|
||||||
from elftools.elf.elffile import ELFFile | ||||||
from .libc import get_libc, Libc | ||||||
|
||||||
|
||||||
log = logging.getLogger(__name__) | ||||||
__all__ = ['lddtree'] | ||||||
|
@@ -195,11 +198,32 @@ def load_ld_paths(root: str = '/', prefix: str = '') -> Dict[str, List[str]]: | |||||
# on a per-ELF basis so it can get turned into the right thing. | ||||||
ldpaths['env'] = parse_ld_paths(env_ldpath, path='') | ||||||
|
||||||
# Load up /etc/ld.so.conf. | ||||||
ldpaths['conf'] = parse_ld_so_conf(root + prefix + '/etc/ld.so.conf', | ||||||
root=root) | ||||||
# the trusted directories are not necessarily in ld.so.conf | ||||||
ldpaths['conf'].extend(['/lib', '/lib64/', '/usr/lib', '/usr/lib64']) | ||||||
libc = get_libc() | ||||||
if libc == Libc.MUSL: | ||||||
# from https://git.musl-libc.org/cgit/musl/tree/ldso | ||||||
# /dynlink.c?id=3f701faace7addc75d16dea8a6cd769fa5b3f260#n1063 | ||||||
root_prefix = Path(root) / prefix | ||||||
ld_musl = list((root_prefix / 'etc').glob("ld-musl-*.path")) | ||||||
assert len(ld_musl) <= 1 | ||||||
if len(ld_musl) == 0: | ||||||
ldpaths['conf'] = [ | ||||||
root + '/lib', | ||||||
root + '/usr/local/lib', | ||||||
root + '/usr/lib' | ||||||
] | ||||||
else: | ||||||
ldpaths['conf'] = [] | ||||||
for ldpath in ld_musl[0].read_text().split(':'): | ||||||
ldpath_stripped = ldpath.strip() | ||||||
if ldpath_stripped == "": | ||||||
continue | ||||||
ldpaths['conf'].append(root + ldpath_stripped) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefix is not taken into account here either but I'm not actually sure how the prefix logic is supposed to work in this function so maybe it's fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as earlier, it won't matter until cross-repairing is a thing. |
||||||
else: | ||||||
# Load up /etc/ld.so.conf. | ||||||
ldpaths['conf'] = parse_ld_so_conf(root + prefix + '/etc/ld.so.conf', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather not touch the glibc code path for now. We can get this cleaned up later. |
||||||
root=root) | ||||||
# the trusted directories are not necessarily in ld.so.conf | ||||||
ldpaths['conf'].extend(['/lib', '/lib64/', '/usr/lib', '/usr/lib64']) | ||||||
log.debug('linker ldpaths: %s', ldpaths) | ||||||
return ldpaths | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import logging | ||
from enum import IntEnum | ||
|
||
from .error import InvalidLibc | ||
from .musllinux import find_musl_libc | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class Libc(IntEnum): | ||
GLIBC = 1, | ||
MUSL = 2, | ||
|
||
|
||
def get_libc() -> Libc: | ||
try: | ||
find_musl_libc() | ||
logger.debug("Detected musl libc") | ||
return Libc.MUSL | ||
except InvalidLibc: | ||
logger.debug("Falling back to GNU libc") | ||
return Libc.GLIBC |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import logging | ||
import pathlib | ||
import subprocess | ||
import re | ||
from typing import NamedTuple | ||
|
||
from auditwheel.error import InvalidLibc | ||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
|
||
class MuslVersion(NamedTuple): | ||
major: int | ||
minor: int | ||
patch: int | ||
|
||
|
||
def find_musl_libc() -> pathlib.Path: | ||
try: | ||
ldd = subprocess.check_output(["ldd", "/bin/ls"], errors='strict') | ||
except (subprocess.CalledProcessError, FileNotFoundError): | ||
LOG.error("Failed to determine libc version", exc_info=True) | ||
raise InvalidLibc | ||
|
||
match = re.search( | ||
r"libc\.musl-(?P<platform>\w+)\.so.1 " # TODO drop the platform | ||
r"=> (?P<path>[/\-\w.]+)", | ||
ldd) | ||
|
||
if not match: | ||
raise InvalidLibc | ||
|
||
return pathlib.Path(match.group("path")) | ||
|
||
|
||
def get_musl_version(ld_path: pathlib.Path) -> MuslVersion: | ||
try: | ||
ld = subprocess.run( | ||
[ld_path], | ||
check=False, | ||
errors='strict', | ||
stderr=subprocess.PIPE | ||
).stderr | ||
except FileNotFoundError: | ||
LOG.error("Failed to determine musl version", exc_info=True) | ||
raise InvalidLibc | ||
|
||
match = re.search( | ||
r"Version " | ||
r"(?P<major>\d+)." | ||
r"(?P<minor>\d+)." | ||
r"(?P<patch>\d+)", | ||
ld) | ||
if not match: | ||
raise InvalidLibc | ||
|
||
return MuslVersion( | ||
int(match.group("major")), | ||
int(match.group("minor")), | ||
int(match.group("patch"))) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
[ | ||
{"name": "linux", | ||
"aliases": [], | ||
"priority": 0, | ||
"symbol_versions": {}, | ||
"lib_whitelist": [] | ||
}, | ||
{"name": "musllinux_1_1", | ||
"aliases": [], | ||
"priority": 100, | ||
"symbol_versions": { | ||
"i686": { | ||
}, | ||
"x86_64": { | ||
}, | ||
"aarch64": { | ||
}, | ||
"ppc64le": { | ||
}, | ||
"s390x": { | ||
}, | ||
"armv7l": { | ||
} | ||
mayeut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
"lib_whitelist": [ | ||
"libc.so" | ||
]}, | ||
{"name": "musllinux_1_2", | ||
"aliases": [], | ||
"priority": 90, | ||
"symbol_versions": { | ||
"i686": { | ||
}, | ||
"x86_64": { | ||
}, | ||
"aarch64": { | ||
}, | ||
"ppc64le": { | ||
}, | ||
"s390x": { | ||
}, | ||
"armv7l": { | ||
} | ||
mayeut marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
"lib_whitelist": [ | ||
"libc.so" | ||
]} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import subprocess | ||
from unittest.mock import patch | ||
|
||
import pytest | ||
|
||
from auditwheel.musllinux import find_musl_libc, get_musl_version | ||
from auditwheel.error import InvalidLibc | ||
|
||
|
||
@patch("auditwheel.musllinux.subprocess.check_output") | ||
def test_find_musllinux_no_ldd(check_output_mock): | ||
check_output_mock.side_effect = FileNotFoundError() | ||
with pytest.raises(InvalidLibc): | ||
find_musl_libc() | ||
|
||
|
||
@patch("auditwheel.musllinux.subprocess.check_output") | ||
def test_find_musllinux_ldd_error(check_output_mock): | ||
check_output_mock.side_effect = subprocess.CalledProcessError(1, "ldd") | ||
with pytest.raises(InvalidLibc): | ||
find_musl_libc() | ||
|
||
|
||
@patch("auditwheel.musllinux.subprocess.check_output") | ||
def test_find_musllinux_not_found(check_output_mock): | ||
check_output_mock.return_value = "" | ||
with pytest.raises(InvalidLibc): | ||
find_musl_libc() | ||
|
||
|
||
def test_get_musl_version_invalid_path(): | ||
with pytest.raises(InvalidLibc): | ||
get_musl_version("/tmp/no/executable/here") | ||
|
||
|
||
@patch("auditwheel.musllinux.subprocess.run") | ||
def test_get_musl_version_invalid_version(run_mock): | ||
run_mock.return_value = subprocess.CompletedProcess([], 1, None, "Version 1.1") | ||
with pytest.raises(InvalidLibc): | ||
get_musl_version("anything") | ||
|
||
|
||
@patch("auditwheel.musllinux.subprocess.run") | ||
def test_get_musl_version_valid_version(run_mock): | ||
run_mock.return_value = subprocess.CompletedProcess([], 1, None, "Version 5.6.7") | ||
version = get_musl_version("anything") | ||
assert version.major == 5 | ||
assert version.minor == 6 | ||
assert version.patch == 7 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if the prefix should be taken into account here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. I guess it won't matter until cross-repairing is a thing.
PS: for glibc, some folders are appended without either root or prefix.