From e7efce4f4334de910e61751053bac07bbaab3ed0 Mon Sep 17 00:00:00 2001 From: Pierre Tardy Date: Wed, 31 Mar 2021 15:44:33 +0200 Subject: [PATCH] failover to python-magic detection if vendored library not present This allows scancode to work on platforms not supported by typecode-libmagic e.g. M1 Macs or windows WSL Most of this code is from https://github.com/ahupp/python-magic/blob/e0ccc6d/magic/loader.py by Adam Hupp, MIT License, compatible with typecode's Apache Signed-off-by: Pierre Tardy --- src/typecode/magic2.py | 59 +++++++++++++++++++++++++++++++++---- tests/test_libmagic_load.py | 27 +++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 tests/test_libmagic_load.py diff --git a/src/typecode/magic2.py b/src/typecode/magic2.py index d23aef7..b9a16b7 100644 --- a/src/typecode/magic2.py +++ b/src/typecode/magic2.py @@ -43,6 +43,7 @@ import ctypes import os +import warnings from commoncode import command from plugincode.location_provider import get_location @@ -79,6 +80,43 @@ TYPECODE_LIBMAGIC_DATABASE = 'typecode.libmagic.db' +def load_lib_failover(): + # loader from python-magic + libmagic = None + # Let's try to find magic or magic1 + dll = (ctypes.util.find_library('magic') + or ctypes.util.find_library('magic1') + or ctypes.util.find_library('cygmagic-1') + or ctypes.util.find_library('libmagic-1') + or ctypes.util.find_library('msys-magic-1') # for MSYS2 + ) + # necessary because find_library returns None if it doesn't find the library + if dll: + libmagic = ctypes.CDLL(dll) + + if not libmagic or not libmagic._name: + windows_dlls = ['magic1.dll', 'cygmagic-1.dll', 'libmagic-1.dll', 'msys-magic-1.dll'] + platform_to_lib = {'darwin': ['/opt/local/lib/libmagic.dylib', + '/usr/local/lib/libmagic.dylib'] + + # Assumes there will only be one version installed + glob.glob('/usr/local/Cellar/libmagic/*/lib/libmagic.dylib'), # flake8:noqa + 'win32': windows_dlls, + 'cygwin': windows_dlls, + 'linux': ['libmagic.so.1'], + # fallback for some Linuxes (e.g. Alpine) where library search does not work # flake8:noqa + } + platform = 'linux' if sys.platform.startswith('linux') else sys.platform + for dll in platform_to_lib.get(platform, []): + try: + libmagic = ctypes.CDLL(dll) + break + except OSError: + pass + + if not libmagic or not libmagic._name: + return None + return libmagic + def load_lib(): """ Return the loaded libmagic shared library object from plugin-provided path. @@ -86,11 +124,15 @@ def load_lib(): dll = get_location(TYPECODE_LIBMAGIC_DLL) libdir = get_location(TYPECODE_LIBMAGIC_LIBDIR) if not (dll and libdir) or not os.path.isfile(dll) or not os.path.isdir(libdir): - raise Exception( - 'CRITICAL: libmagic DLL and is magic database are not installed. ' - 'Unable to continue: you need to install a valid typecode-libmagic ' - 'plugin with a valid and proper libmagic and magic DB available.' - ) + ret = load_lib_failover() + if ret is None: + raise ImportError( + 'CRITICAL: libmagic DLL and is magic database are not installed. ' + 'Unable to continue: you need to install a valid typecode-libmagic ' + 'plugin with a valid and proper libmagic and magic DB available.' + ) + warnings.warn("System libmagic is used. Install typecode-libmagic for best consitency") + return ret return command.load_shared_library(dll, libdir) @@ -164,6 +206,7 @@ def __init__(self, flags, magic_db_location=None): self.flags = flags self.cookie = _magic_open(self.flags) if not magic_db_location: + # if no plugin, None is returned, and libmagic will load the default db magic_db_location = get_location(TYPECODE_LIBMAGIC_DATABASE) # Note: this location must always be bytes on Python2 and 3, all OSes @@ -257,3 +300,9 @@ def check_error(result, func, args): # NOQA _magic_load.restype = ctypes.c_int _magic_load.argtypes = [ctypes.c_void_p, ctypes.c_char_p] _magic_load.errcheck = check_error + +_magic_version = libmagic.magic_version +_magic_version.restype = ctypes.c_int +_magic_version.argtypes = [] + +libmagic_version = _magic_version() diff --git a/tests/test_libmagic_load.py b/tests/test_libmagic_load.py new file mode 100644 index 0000000..84b6995 --- /dev/null +++ b/tests/test_libmagic_load.py @@ -0,0 +1,27 @@ +# +# Copyright (c) nexB Inc. and others. +# SPDX-License-Identifier: Apache-2.0 +# +# Visit https://aboutcode.org and https://github.com/nexB/ for support and download. +# ScanCode is a trademark of nexB 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. +# + +import os + +from typecode.magic2 import libmagic_version + + +def test_load_lib(): + assert libmagic_version > 0 \ No newline at end of file