project( 'kmod', 'c', version : '34', license : ['LGPL-2.1-or-later', 'GPL-2.0-or-later'], meson_version : '>=0.61.0', default_options : [ 'c_std=gnu11', 'b_pie=true', 'warning_level=2', 'prefix=/usr', 'sysconfdir=/etc', ] ) cdata = configuration_data() cdata.set_quoted('PACKAGE', meson.project_name()) cdata.set_quoted('VERSION', meson.project_version()) cdata.set10('ENABLE_LOGGING', get_option('logging')) cdata.set10('ENABLE_DEBUG', get_option('debug-messages')) cdata.set10('ENABLE_ELFDBG', false) pkg = import('pkgconfig') cc = meson.get_compiler('c') # We rely on the glibc variant of basename, et al. cdata.set10('_GNU_SOURCE', true) ################################################################################ # Function and structure checks ################################################################################ _funcs = [ 'open64', 'stat64', 'fopen64', '__stat64_time64', 'secure_getenv', ] foreach func : _funcs cdata.set10('HAVE_@0@'.format(func.to_upper()), cc.has_function(func, args : '-D_GNU_SOURCE')) endforeach # Meson has some amount of support for finding builtins by passing the symbol # name without the "__builtin_" prefix to cc.has_function(). In practice, it # doesn't seem to detect everything we need. _builtins = [ ['__builtin_clz', '0', true], ['__builtin_types_compatible_p', 'int, int', true], ['__builtin_uadd_overflow', '0U, 0U, (void*)0', false], ['__builtin_uaddl_overflow', '0UL, 0UL, (void*)0', false], ['__builtin_uaddll_overflow', '0ULL, 0ULL, (void*)0', false], ['__builtin_umul_overflow', '0U, 0U, (void*)0', false], ['__builtin_umull_overflow', '0UL, 0UL, (void*)0', false], ['__builtin_umulll_overflow', '0ULL, 0ULL, (void*)0', false], ] foreach tuple : _builtins builtin = tuple[0] args = tuple[1] required = tuple[2] # XXX: meson 1.5.0 has links(... required ) flag have = cc.links('int main(void){@0@(@1@);return 0;}'.format(builtin, args)) if required and not have error('required builtin function not found: @0@'.format(builtin)) endif cdata.set10('HAVE_@0@'.format(builtin.to_upper()), have) endforeach # basename may be only available in libgen.h with the POSIX behavior, # not desired here _decls = [ ['basename', 'string.h'], ['__xstat', 'sys/stat.h'], ] foreach tuple : _decls decl = tuple[0] header = tuple[1] have = cc.has_header_symbol(header, decl, args : '-D_GNU_SOURCE') cdata.set10('HAVE_DECL_@0@'.format(decl.to_upper()), have) endforeach cdata.set10('HAVE_STATIC_ASSERT', cc.compiles('_Static_assert(1, "Test");', name : '_Static_assert')) cdata.set10('HAVE_NORETURN', cc.compiles('#include ; _Noreturn int foo(void) { exit(0); }', name : '_Noreturn')) ################################################################################ # Default CFLAGS and LDFLAGS ################################################################################ add_project_arguments( cc.get_supported_arguments([ '-fdata-sections', '-fdiagnostics-show-option', '-ffunction-sections', '-fno-common', '-Wchar-subscripts', '-Wendif-labels', '-Wfloat-equal', '-Wformat=2', '-Wformat-nonliteral', '-Wformat-security', '-Winit-self', '-Wlogical-op', '-Wmissing-declarations', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wmissing-prototypes', '-Wnested-externs', '-Wno-attributes', '-Wno-declaration-after-statement', '-Wno-unused-parameter', '-Wold-style-definition', '-Wpointer-arith', '-Wredundant-decls', '-Wshadow', '-Wsign-compare', '-Wstrict-aliasing=3', '-Wstrict-prototypes', '-Wtype-limits', '-Wundef', '-Wuninitialized', '-Wvla', '-Wwrite-strings', ]), language : 'c' ) add_project_link_arguments( cc.get_supported_link_arguments([ '-Wl,--gc-sections', ]), language : 'c' ) # Clang as of v18, relies on statically linking the sanitizers. This causes two # distinct issues: # - the shared library is underlinked, so the build fails # - the modules (that we dlopen/ld_preload) are underlinked so the tests fail # # Force shared libasan (GCC defaults to shared and this toggle doesn't exist), # which combined with the LD_PRELOAD in our wrapper makes everything happy. if get_option('b_sanitize') != 'none' and cc.get_id() == 'clang' add_project_arguments('-shared-libasan', language : 'c') add_project_link_arguments('-shared-libasan', language : 'c') endif ################################################################################ # Options ################################################################################ module_compressions = '' module_signatures = '' features = [] dep_map = {} # keep in sync with meson_options.txt dlopen_all = get_option('dlopen').contains('all') #------------------------------------------------------------------------------- # Directories #------------------------------------------------------------------------------- prefixdir = get_option('prefix') sysconfdir = get_option('sysconfdir') bindir = prefixdir / get_option('bindir') sbindir = prefixdir / get_option('sbindir') includedir = prefixdir / get_option('includedir') libdir = prefixdir / get_option('libdir') datadir = prefixdir / get_option('datadir') distconfdir = get_option('distconfdir') moduledir = get_option('moduledir') bashcompletiondir = get_option('bashcompletiondir') fishcompletiondir = get_option('fishcompletiondir') zshcompletiondir = get_option('zshcompletiondir') cdata.set_quoted('SYSCONFDIR', sysconfdir) _customdirs = [ # The defaults are hard-coded due to historical reasons ['distconfdir', prefixdir / 'lib', 'DISTCONFDIR'], ['moduledir', '/lib/modules', 'MODULE_DIRECTORY'], ] foreach tuple : _customdirs dir_option = tuple[0] def_path = tuple[1] quoted = tuple[2] customdir = get_variable(dir_option) if customdir == '' customdir = def_path else if not customdir.startswith('/') error('User provided @0@, \'@1@\' is not an absolute path.' .format(dir_option, customdir)) endif # Strip all leading/trailing and re-add only the leading one. customdir = '/' / customdir.strip('/') endif cdata.set_quoted(quoted, customdir) set_variable(dir_option, customdir) endforeach foreach confdir : [sysconfdir, distconfdir] install_emptydir(confdir / 'depmod.d') install_emptydir(confdir / 'modprobe.d') endforeach _completiondirs = [ ['bash', 'bash-completion', 'bash-completion' / 'completions', '@0@'], ['fish', 'fish', 'fish' / 'vendor_functions.d', '@0@.fish'], ['zsh', '', 'zsh' / 'site-functions', '_@0@'], ] foreach tuple : _completiondirs dir_option = tuple[0] + 'completiondir' pkg_dep = tuple[1] def_path = tuple[2] ins_name = tuple[3] completiondir = get_variable(dir_option) if completiondir == '' completion = dependency(pkg_dep, required : false) if completion.found() completion_prefix = completion.get_variable(pkgconfig : 'prefix') if completion_prefix != prefixdir warning('User provided prefix \'@0@\' differs from @1@ one \'@2@\'.' .format(prefixdir, pkg_dep, completion_prefix)) warning('Not installing completion. To re-enable, manually set @0@.' .format(dir_option)) completiondir = 'no' else completiondir = completion.get_variable(pkgconfig : 'completionsdir') endif else completiondir = datadir / def_path endif endif _completions = [ 'insmod', 'lsmod', 'rmmod', ] if completiondir != 'no' foreach comp : _completions install_data( files('shell-completion' / tuple[0] / ins_name.format(comp)), install_dir : completiondir, ) endforeach endif set_variable(dir_option, completiondir) endforeach if bashcompletiondir != 'no' install_data( files('shell-completion/bash/kmod'), install_dir : bashcompletiondir, ) endif #------------------------------------------------------------------------------- # Compression support #------------------------------------------------------------------------------- _compression = [ ['zstd', 'libzstd', '>= 1.4.4'], ['xz', 'liblzma', '>= 4.99'], ['zlib', 'zlib', '>= 0'], ] foreach tuple : _compression opt = tuple[0] pkg_dep = tuple[1] pkg_dep_version = tuple[2] dlopen = dlopen_all or get_option('dlopen').contains(opt) if not dlopen_all and dlopen and get_option(opt).disabled() error('Incompatiable options: dlopen=@0@ for disabled @0@'.format(opt)) endif dep = dependency(pkg_dep, version : pkg_dep_version, required : get_option(opt)) have = dep.found() if have and dlopen dep = dep.partial_dependency(compile_args : true, includes : true) endif cdata.set10('ENABLE_' + opt.to_upper(), have) cdata.set10('ENABLE_' + opt.to_upper() + '_DLOPEN', have and dlopen) if have module_compressions += '@0@ '.format(opt) endif features += ['@0@@1@'.format(have ? '+' : '-', opt.to_upper())] dep_map += {opt : dep} endforeach #------------------------------------------------------------------------------- # Signed modules #------------------------------------------------------------------------------- opt = 'openssl' dep = dependency('libcrypto', version : '>= 1.1.0', required : get_option(opt)) have = dep.found() if have module_signatures = 'PKCS7 legacy' else module_signatures = 'legacy' endif cdata.set10('ENABLE_' + opt.to_upper(), have) features += ['@0@@1@'.format(have ? '+' : '-', opt.to_upper())] dep_map += {opt : dep} #------------------------------------------------------------------------------- # Config output #------------------------------------------------------------------------------- cdata.set_quoted('KMOD_FEATURES', ' '.join(features)) config_h = configure_file( output : 'config.h', configuration : cdata ) add_project_arguments('-include', 'config.h', language : 'c') ################################################################################ # libraries and binaries ################################################################################ libshared = static_library( 'shared', files( 'shared/array.c', 'shared/array.h', 'shared/elf-note.h', 'shared/hash.c', 'shared/hash.h', 'shared/macro.h', 'shared/missing.h', 'shared/strbuf.c', 'shared/strbuf.h', 'shared/util.c', 'shared/util.h', 'shared/tmpfile-util.c', 'shared/tmpfile-util.h', ), gnu_symbol_visibility : 'hidden', install : false, ) libkmod_files = files( 'libkmod/libkmod-builtin.c', 'libkmod/libkmod.c', 'libkmod/libkmod-config.c', 'libkmod/libkmod-elf.c', 'libkmod/libkmod-file.c', 'libkmod/libkmod.h', 'libkmod/libkmod-index.c', 'libkmod/libkmod-index.h', 'libkmod/libkmod-internal-file.h', 'libkmod/libkmod-internal.h', 'libkmod/libkmod-list.c', 'libkmod/libkmod-module.c', 'libkmod/libkmod-signature.c', ) libkmod_deps = [] cdeps = [] if not cc.has_function('dlopen') cdeps += cc.find_library('dl', required : true) endif if dep_map.get('zstd').found() libkmod_files += files('libkmod/libkmod-file-zstd.c') libkmod_deps += dep_map['zstd'] endif if dep_map.get('xz').found() libkmod_files += files('libkmod/libkmod-file-xz.c') libkmod_deps += dep_map['xz'] endif if dep_map.get('zlib').found() libkmod_files += files('libkmod/libkmod-file-zlib.c') libkmod_deps += dep_map['zlib'] endif if dep_map.get('openssl').found() libkmod_deps += dep_map['openssl'] endif install_headers('libkmod/libkmod.h') libkmod = shared_library( 'kmod', libkmod_files, dependencies : libkmod_deps + cdeps, link_with : libshared, link_args : ['-Wl,--version-script', meson.current_source_dir() / 'libkmod/libkmod.sym'], link_depends : files('libkmod/libkmod.sym'), gnu_symbol_visibility : 'hidden', version : '2.5.1', install : true, ) pkg.generate( name : 'libkmod', description : 'Library to deal with kernel modules', libraries : libkmod, requires_private : libkmod_deps, libraries_private : cdeps, ) libkmod_internal = static_library( 'kmod-internal', objects : libkmod.extract_all_objects(recursive : true), dependencies : libkmod_deps + cdeps, install : false, ) kmod_sources = files( 'tools/depmod.c', 'tools/insmod.c', 'tools/kmod.c', 'tools/kmod.h', 'tools/log.c', 'tools/log.h', 'tools/lsmod.c', 'tools/modinfo.c', 'tools/modprobe.c', 'tools/opt.c', 'tools/opt.h', 'tools/rmmod.c', 'tools/static-nodes.c', ) kmod = executable( 'kmod', kmod_sources, link_with : [libshared, libkmod_internal], gnu_symbol_visibility : 'hidden', build_by_default : get_option('tools'), install : get_option('tools'), ) _kmod_variables = [ 'sysconfdir=' + sysconfdir, 'distconfdir=' + distconfdir, 'module_directory=' + moduledir, ] # Don't (space) escape variables with space-separated lists, for consistency # with the autotools build. _kmod_unescaped_variables = [ 'module_signatures=' + module_signatures, ] # XXX: Support for empty variables was added in meson v1.4.0. # pkgconf behaves identically on missing and empty variable. if module_compressions != '' _kmod_unescaped_variables += ['module_compressions=' + module_compressions] endif pkg.generate( name : 'kmod', description : 'Tools to deal with kernel modules', install_dir : datadir / 'pkgconfig', unescaped_variables : _kmod_unescaped_variables, variables : _kmod_variables, ) _tools = [ 'depmod', 'insmod', 'lsmod', 'modinfo', 'modprobe', 'rmmod', ] if get_option('tools') mkdir_p = 'mkdir -p "$DESTDIR@0@"' meson.add_install_script('sh', '-c', mkdir_p.format(sbindir)) ln_s = 'ln -sfr "$DESTDIR@0@/kmod" "$DESTDIR@1@"' foreach tool : _tools meson.add_install_script('sh', '-c', ln_s.format(bindir, sbindir / tool)) endforeach endif internal_kmod_symlinks = [] foreach tool : _tools internal_kmod_symlinks += custom_target( tool, command : ['ln', '-sf', kmod, '@OUTPUT@'], output : tool, depends : kmod, build_by_default : true, ) endforeach # ------------------------------------------------------------------------------ # TESTSUITE # ------------------------------------------------------------------------------ if get_option('build-tests') bash = find_program('bash') sanitizer_env = find_program('scripts/sanitizer-env.sh') setup_modules = find_program('scripts/setup-modules.sh') setup_rootfs = find_program('scripts/setup-rootfs.sh') top_include = include_directories('.') subdir('testsuite') endif # ------------------------------------------------------------------ # documentation # ------------------------------------------------------------------ if get_option('manpages') build_scdoc = find_program('scripts/build-scdoc.sh') subdir('man') endif if get_option('docs') test_gtkdoc = find_program('scripts/test-gtkdoc.sh') subdir('libkmod/docs') endif # ------------------------------------------------------------------ # summary # ------------------------------------------------------------------ summary({ 'prefix' : prefixdir, 'sysconfdir' : sysconfdir, 'bindir' : bindir, 'sbindir' : sbindir, 'includedir' : includedir, 'libdir' : libdir, 'datadir' : datadir, }, section : 'Directories') summary({ 'distconfdir' : distconfdir, 'moduledir' : moduledir, }, section : 'Kmod specific') summary({ 'bashcompletiondir' : bashcompletiondir, 'fishcompletiondir' : fishcompletiondir, 'zshcompletiondir' : zshcompletiondir, }, section : 'Shell completions') summary({ 'tools' : get_option('tools'), 'logging' : get_option('logging'), 'debug-messages' : get_option('debug-messages'), 'build-tests' : get_option('build-tests'), 'manpages' : get_option('manpages'), 'docs' : get_option('docs'), 'dlopen' : get_option('dlopen'), }, section : 'Options') summary({ 'features' : ' '.join(features) }, section : '') summary({ 'Compiler' : cc.get_id() + ' (' + cc.version() + ')', 'Linker' : cc.get_linker_id(), }, section : 'Build tools')